Allow PATCH/DELETE w/o Prefer when no SELECT privs

PATCH/DELETE can now be done without adding Prefer return=minimal when
the user doesn't have SELECT privileges.

* Also fix PATCH wrong HTTP status code
This commit is contained in:
steve-chavez
2019-10-08 12:41:39 -05:00
committed by Steve Chávez
parent 337f821e00
commit ed2bfc09a6
13 changed files with 156 additions and 126 deletions
+4 -2
View File
@@ -9,15 +9,17 @@ This project adheres to [Semantic Versioning](http://semver.org/).
- #1383, Add support for HEAD request - @steve-chavez - #1383, Add support for HEAD request - @steve-chavez
- #1378, Add support for `Prefer: count=planned` and `Prefer: count=estimated` on GET /table - @steve-chavez - #1378, Add support for `Prefer: count=planned` and `Prefer: count=estimated` on GET /table - @steve-chavez
### Fixed
- #1301, Fix self join resource embedding on PATCH - @herulume, @steve-chavez - #1301, Fix self join resource embedding on PATCH - @herulume, @steve-chavez
- #1389, Fix many to many resource embedding on RPC/PATCH - @steve-chavez - #1389, Fix many to many resource embedding on RPC/PATCH - @steve-chavez
- #1355, Allow PATCH/DELETE without `return=minimal` on tables with no select privileges - @steve-chavez
### Changed ### Changed
- #1385, bulk RPC call now should be done by specifying a `Prefer: params=multiple-objects` header - @steve-chavez - #1385, bulk RPC call now should be done by specifying a `Prefer: params=multiple-objects` header - @steve-chavez
### Fixed
## [6.0.2] - 2019-08-22 ## [6.0.2] - 2019-08-22
### Fixed ### Fixed
+24 -46
View File
@@ -11,7 +11,6 @@ module PostgREST.ApiRequest (
, ContentType(..) , ContentType(..)
, Action(..) , Action(..)
, Target(..) , Target(..)
, PreferRepresentation (..)
, mutuallyAgreeable , mutuallyAgreeable
, userApiRequest , userApiRequest
) where ) where
@@ -67,10 +66,6 @@ data Target = TargetIdent QualifiedIdentifier
| TargetUnknown [Text] | TargetUnknown [Text]
deriving Eq deriving Eq
-- | How to return the inserted data
data PreferRepresentation = Full | HeadersOnly | None deriving Eq
{-| {-|
Describes what the user wants to do. This data type is a Describes what the user wants to do. This data type is a
translation of the raw elements of an HTTP request into domain translation of the raw elements of an HTTP request into domain
@@ -79,44 +74,25 @@ data PreferRepresentation = Full | HeadersOnly | None deriving Eq
if it is an action we are able to perform. if it is an action we are able to perform.
-} -}
data ApiRequest = ApiRequest { data ApiRequest = ApiRequest {
-- | Similar but not identical to HTTP verb, e.g. Create/Invoke both POST iAction :: Action -- ^ Similar but not identical to HTTP verb, e.g. Create/Invoke both POST
iAction :: Action , iRange :: M.HashMap ByteString NonnegRange -- ^ Requested range of rows within response
-- | Requested range of rows within response , iTopLevelRange :: NonnegRange -- ^ Requested range of rows from the top level
, iRange :: M.HashMap ByteString NonnegRange , iTarget :: Target -- ^ The target, be it calling a proc or accessing a table
-- | Requested range of rows from the top level , iAccepts :: [ContentType] -- ^ Content types the client will accept, [CTAny] if no Accept header
, iTopLevelRange :: NonnegRange , iPayload :: Maybe PayloadJSON -- ^ Data sent by client and used for mutation actions
-- | The target, be it calling a proc or accessing a table , iPreferRepresentation :: PreferRepresentation -- ^ If client wants created items echoed back
, iTarget :: Target , iPreferParameters :: Maybe PreferParameters -- ^ How to pass parameters to a stored procedure
-- | Content types the client will accept, [CTAny] if no Accept header , iPreferCount :: Maybe PreferCount -- ^ Whether the client wants a result count
, iAccepts :: [ContentType] , iPreferResolution :: Maybe PreferResolution -- ^ Whether the client wants to UPSERT or ignore records on PK conflict
-- | Data sent by client and used for mutation actions , iFilters :: [(Text, Text)] -- ^ Filters on the result ("id", "eq.10")
, iPayload :: Maybe PayloadJSON , iLogic :: [(Text, Text)] -- ^ &and and &or parameters used for complex boolean logic
-- | If client wants created items echoed back , iSelect :: Maybe Text -- ^ &select parameter used to shape the response
, iPreferRepresentation :: PreferRepresentation , iColumns :: Maybe Text -- ^ &columns parameter used to shape the payload
-- | How to pass parameters to a stored procedure , iOrder :: [(Text, Text)] -- ^ &order parameters for each level
, iPreferParameters :: Maybe PreferParameters , iCanonicalQS :: ByteString -- ^ Alphabetized (canonical) request query string for response URLs
-- | Whether the client wants a result count , iJWT :: Text -- ^ JSON Web Token
, iPreferCount :: Maybe PreferCount , iHeaders :: [(Text, Text)] -- ^ HTTP request headers
-- | Whether the client wants to UPSERT or ignore records on PK conflict , iCookies :: [(Text, Text)] -- ^ Request Cookies
, iPreferResolution :: Maybe PreferResolution
-- | Filters on the result ("id", "eq.10")
, iFilters :: [(Text, Text)]
-- | &and and &or parameters used for complex boolean logic
, iLogic :: [(Text, Text)]
-- | &select parameter used to shape the response
, iSelect :: Maybe Text
-- | &columns parameter used to shape the payload
, iColumns :: Maybe Text
-- | &order parameters for each level
, iOrder :: [(Text, Text)]
-- | Alphabetized (canonical) request query string for response URLs
, iCanonicalQS :: ByteString
-- | JSON Web Token
, iJWT :: Text
-- | HTTP request headers
, iHeaders :: [(Text, Text)]
-- | Request Cookies
, iCookies :: [(Text, Text)]
} }
-- | Examines HTTP request and translates it into user intent. -- | Examines HTTP request and translates it into user intent.
@@ -255,9 +231,11 @@ userApiRequest schema rootSpec req reqBody
split :: BS.ByteString -> [Text] split :: BS.ByteString -> [Text]
split = map T.strip . T.split (==',') . toS split = map T.strip . T.split (==',') . toS
representation representation
| hasPrefer "return=representation" = Full | hasPrefer (show Full) = Full
| hasPrefer "return=minimal" = None | hasPrefer (show None) = None
| otherwise = HeadersOnly | otherwise = if action == ActionCreate
then HeadersOnly -- Assume the user wants the Location header(for POST) by default
else None
auth = fromMaybe "" $ lookupHeader hAuthorization auth = fromMaybe "" $ lookupHeader hAuthorization
tokenStr = case T.split (== ' ') (toS auth) of tokenStr = case T.split (== ' ') (toS auth) of
("Bearer" : t : _) -> t ("Bearer" : t : _) -> t
+2 -4
View File
@@ -41,10 +41,8 @@ import Network.Wai
import PostgREST.ApiRequest (Action (..), ApiRequest (..), import PostgREST.ApiRequest (Action (..), ApiRequest (..),
ContentType (..), ContentType (..),
InvokeMethod (..), InvokeMethod (..), Target (..),
PreferRepresentation (..), mutuallyAgreeable, userApiRequest)
Target (..), mutuallyAgreeable,
userApiRequest)
import PostgREST.Auth (containsRole, jwtClaims, import PostgREST.Auth (containsRole, jwtClaims,
parseSecret) parseSecret)
import PostgREST.Config (AppConfig (..)) import PostgREST.Config (AppConfig (..))
+1 -2
View File
@@ -33,8 +33,7 @@ import Control.Applicative
import Data.Tree import Data.Tree
import Network.Wai import Network.Wai
import PostgREST.ApiRequest (Action (..), ApiRequest (..), import PostgREST.ApiRequest (Action (..), ApiRequest (..))
PreferRepresentation (..))
import PostgREST.Error (ApiRequestError (..), errorResponseFor) import PostgREST.Error (ApiRequestError (..), errorResponseFor)
import PostgREST.Parsers import PostgREST.Parsers
import PostgREST.RangeQuery (NonnegRange, allRange, restrictRange) import PostgREST.RangeQuery (NonnegRange, allRange, restrictRange)
+6
View File
@@ -191,3 +191,9 @@ countF countQuery shouldCount =
else ( else (
mempty mempty
, "null::bigint") , "null::bigint")
returningF :: QualifiedIdentifier -> [FieldName] -> SqlFragment
returningF qi returnings =
if null returnings
then "RETURNING 1" -- For mutation cases where there's no ?select, we return 1 to know how many rows were modified
else "RETURNING " <> intercalate ", " (pgFmtColumn qi <$> returnings)
+4 -3
View File
@@ -96,7 +96,8 @@ mutateRequestToQuery (Insert mainQi iCols onConflct putConditions returnings) =
then "DO NOTHING" then "DO NOTHING"
else "DO UPDATE SET " <> intercalate ", " (pgFmtIdent <> const " = EXCLUDED." <> pgFmtIdent <$> S.toList iCols) else "DO UPDATE SET " <> intercalate ", " (pgFmtIdent <> const " = EXCLUDED." <> pgFmtIdent <$> S.toList iCols)
) `emptyOnFalse` null oncCols) onConflct, ) `emptyOnFalse` null oncCols) onConflct,
("RETURNING " <> intercalate ", " (map (pgFmtColumn mainQi) returnings)) `emptyOnFalse` null returnings] returningF mainQi returnings
]
where where
cols = intercalate ", " $ pgFmtIdent <$> S.toList iCols cols = intercalate ", " $ pgFmtIdent <$> S.toList iCols
mutateRequestToQuery (Update mainQi uCols logicForest returnings) = mutateRequestToQuery (Update mainQi uCols logicForest returnings) =
@@ -108,7 +109,7 @@ mutateRequestToQuery (Update mainQi uCols logicForest returnings) =
"UPDATE " <> fromQi mainQi <> " SET " <> cols, "UPDATE " <> fromQi mainQi <> " SET " <> cols,
"FROM (SELECT * FROM json_populate_recordset", "(null::", fromQi mainQi, ", " <> selectBody <> ")) _ ", "FROM (SELECT * FROM json_populate_recordset", "(null::", fromQi mainQi, ", " <> selectBody <> ")) _ ",
("WHERE " <> intercalate " AND " (pgFmtLogicTree mainQi <$> logicForest)) `emptyOnFalse` null logicForest, ("WHERE " <> intercalate " AND " (pgFmtLogicTree mainQi <$> logicForest)) `emptyOnFalse` null logicForest,
("RETURNING " <> intercalate ", " (pgFmtColumn mainQi <$> returnings)) `emptyOnFalse` null returnings returningF mainQi returnings
] ]
where where
cols = intercalate ", " (pgFmtIdent <> const " = _." <> pgFmtIdent <$> S.toList uCols) cols = intercalate ", " (pgFmtIdent <> const " = _." <> pgFmtIdent <$> S.toList uCols)
@@ -117,7 +118,7 @@ mutateRequestToQuery (Delete mainQi logicForest returnings) =
"WITH " <> ignoredBody, "WITH " <> ignoredBody,
"DELETE FROM ", fromQi mainQi, "DELETE FROM ", fromQi mainQi,
("WHERE " <> intercalate " AND " (map (pgFmtLogicTree mainQi) logicForest)) `emptyOnFalse` null logicForest, ("WHERE " <> intercalate " AND " (map (pgFmtLogicTree mainQi) logicForest)) `emptyOnFalse` null logicForest,
("RETURNING " <> intercalate ", " (map (pgFmtColumn mainQi) returnings)) `emptyOnFalse` null returnings returningF mainQi returnings
] ]
requestToCallProcQuery :: QualifiedIdentifier -> [PgArg] -> Bool -> Maybe PreferParameters -> SqlQuery requestToCallProcQuery :: QualifiedIdentifier -> [PgArg] -> Bool -> Maybe PreferParameters -> SqlQuery
+18 -30
View File
@@ -22,18 +22,15 @@ import Data.Aeson as JSON
import qualified Data.Aeson.Lens as L import qualified Data.Aeson.Lens as L
import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Char8 as BS
import Data.Maybe import Data.Maybe
import Data.Text (intercalate, import Data.Text (unwords)
unwords)
import Data.Text.Encoding (encodeUtf8) import Data.Text.Encoding (encodeUtf8)
import qualified Hasql.Decoders as HD import qualified Hasql.Decoders as HD
import qualified Hasql.Encoders as HE import qualified Hasql.Encoders as HE
import qualified Hasql.Statement as H import qualified Hasql.Statement as H
import PostgREST.ApiRequest (PreferRepresentation (..))
import PostgREST.Private.Common import PostgREST.Private.Common
import PostgREST.Private.QueryFragment import PostgREST.Private.QueryFragment
import PostgREST.Types import PostgREST.Types
import Protolude hiding (cast, import Protolude hiding (cast,
intercalate,
replace) replace)
import Text.InterpolatedString.Perl6 (qc) import Text.InterpolatedString.Perl6 (qc)
@@ -49,36 +46,27 @@ createWriteStatement :: SqlQuery -> SqlQuery -> Bool -> Bool -> Bool ->
createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys = createWriteStatement selectQuery mutateQuery wantSingle isInsert asCsv rep pKeys =
unicodeStatement sql (param HE.unknown) decodeStandard True unicodeStatement sql (param HE.unknown) decodeStandard True
where where
sql = case rep of sql = [qc|
None -> [qc| WITH
WITH {sourceCTEName} AS ({mutateQuery}) {sourceCTEName} AS ({mutateQuery})
SELECT '', 0, {noLocationF}, '' |] SELECT
HeadersOnly -> [qc| '' AS total_result_set,
WITH {sourceCTEName} AS ({mutateQuery}) pg_catalog.count(_postgrest_t) AS page_total,
SELECT {cols} {locF} AS header,
FROM (SELECT 1 FROM {sourceCTEName}) _postgrest_t |] {bodyF} AS body
Full -> [qc|
WITH {sourceCTEName} AS ({mutateQuery})
SELECT {cols}
FROM ({selectQuery}) _postgrest_t |] FROM ({selectQuery}) _postgrest_t |]
cols = intercalate ", " [ locF =
"'' AS total_result_set", -- when updateing it does not make sense if isInsert && rep `elem` [Full, HeadersOnly]
"pg_catalog.count(_postgrest_t) AS page_total", then unwords [
if isInsert "CASE WHEN pg_catalog.count(_postgrest_t) = 1",
then unwords [ "THEN coalesce(" <> locationF pKeys <> ", " <> noLocationF <> ")",
"CASE", "ELSE " <> noLocationF,
"WHEN pg_catalog.count(_postgrest_t) = 1 THEN", "END"]
"coalesce(" <> locationF pKeys <> ", " <> noLocationF <> ")", else noLocationF
"ELSE " <> noLocationF,
"END AS header"]
else noLocationF <> "AS header",
if rep == Full
then bodyF <> " AS body"
else "''"
]
bodyF bodyF
| rep `elem` [None, HeadersOnly] = "''"
| asCsv = asCsvF | asCsv = asCsvF
| wantSingle = asJsonSingleF | wantSingle = asJsonSingleF
| otherwise = asJsonF | otherwise = asJsonF
+10
View File
@@ -70,6 +70,16 @@ instance Show PreferResolution where
show MergeDuplicates = "resolution=merge-duplicates" show MergeDuplicates = "resolution=merge-duplicates"
show IgnoreDuplicates = "resolution=ignore-duplicates" show IgnoreDuplicates = "resolution=ignore-duplicates"
-- | How to return the mutated data. From https://tools.ietf.org/html/rfc7240#section-4.2
data PreferRepresentation = Full -- ^ Return the body plus the Location header(in case of POST).
| HeadersOnly -- ^ Return the Location header(in case of POST). This needs a SELECT privilege on the pk.
| None -- ^ Return nothing from the mutated data.
deriving Eq
instance Show PreferRepresentation where
show Full = "return=representation"
show None = "return=minimal"
show HeadersOnly = mempty
data PreferParameters data PreferParameters
= SingleObject -- ^ Pass all parameters as a single json object to a stored procedure = SingleObject -- ^ Pass all parameters as a single json object to a stored procedure
| MultipleObjects -- ^ Pass an array of json objects as params to a stored procedure | MultipleObjects -- ^ Pass an array of json objects as params to a stored procedure
+22
View File
@@ -5,6 +5,7 @@ import Network.Wai (Application)
import Network.HTTP.Types import Network.HTTP.Types
import Test.Hspec import Test.Hspec
import Test.Hspec.Wai import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import Text.Heredoc import Text.Heredoc
import Protolude hiding (get) import Protolude hiding (get)
@@ -62,3 +63,24 @@ spec =
context "totally unknown route" $ context "totally unknown route" $
it "fails with 404" $ it "fails with 404" $
request methodDelete "/foozle?id=eq.101" [] "" `shouldRespondWith` 404 request methodDelete "/foozle?id=eq.101" [] "" `shouldRespondWith` 404
context "table with limited privileges" $ do
it "fails deleting the row when return=representation and selecting all the columns" $
request methodDelete "/app_users?id=eq.1" [("Prefer", "return=representation")] mempty
`shouldRespondWith` 401
it "succeeds deleting the row when return=representation and selecting only the privileged columns" $
request methodDelete "/app_users?id=eq.1&select=id,email" [("Prefer", "return=representation")]
[json| { "password": "passxyz" } |]
`shouldRespondWith` [json|[ { "id": 1, "email": "test@123.com" } ]|]
{ matchStatus = 200
, matchHeaders = ["Content-Range" <:> "*/*"]
}
it "suceeds deleting the row with no explicit select when using return=minimal" $
request methodDelete "/app_users?id=eq.2" [("Prefer", "return=minimal")] mempty
`shouldRespondWith` 204
it "suceeds deleting the row with no explicit select by default" $
request methodDelete "/app_users?id=eq.3" [] mempty
`shouldRespondWith` 204
+50 -39
View File
@@ -148,15 +148,6 @@ spec actualPgVersion = do
simpleBody p `shouldBe` [json| [] |] simpleBody p `shouldBe` [json| [] |]
simpleStatus p `shouldBe` created201 simpleStatus p `shouldBe` created201
it "can insert in tables with no select privileges" $ do
p <- request methodPost "/insertonly"
[("Prefer", "return=minimal")]
[json| { "v":"some value" } |]
liftIO $ do
simpleBody p `shouldBe` ""
simpleStatus p `shouldBe` created201
it "can post nulls" $ do it "can post nulls" $ do
p <- request methodPost "/no_pk" p <- request methodPost "/no_pk"
[("Prefer", "return=representation")] [("Prefer", "return=representation")]
@@ -260,36 +251,6 @@ spec actualPgVersion = do
, matchHeaders = [] , matchHeaders = []
} }
context "table with limited privileges" $ do
it "succeeds if correct select is applied" $
request methodPost "/limited_article_stars?select=article_id,user_id" [("Prefer", "return=representation")]
[json| {"article_id": 2, "user_id": 1} |] `shouldRespondWith` [str|[{"article_id":2,"user_id":1}]|]
{ matchStatus = 201
, matchHeaders = []
}
it "fails if more columns are selected" $
request methodPost "/limited_article_stars?select=article_id,user_id,created_at" [("Prefer", "return=representation")]
[json| {"article_id": 2, "user_id": 2} |] `shouldRespondWith` (
if actualPgVersion >= pgVersion112 then
[str|{"hint":null,"details":null,"code":"42501","message":"permission denied for view limited_article_stars"}|]
else
[str|{"hint":null,"details":null,"code":"42501","message":"permission denied for relation limited_article_stars"}|]
)
{ matchStatus = 401
, matchHeaders = []
}
it "fails if select is not specified" $
request methodPost "/limited_article_stars" [("Prefer", "return=representation")]
[json| {"article_id": 3, "user_id": 1} |] `shouldRespondWith` (
if actualPgVersion >= pgVersion112 then
[str|{"hint":null,"details":null,"code":"42501","message":"permission denied for view limited_article_stars"}|]
else
[str|{"hint":null,"details":null,"code":"42501","message":"permission denied for relation limited_article_stars"}|]
)
{ matchStatus = 401
, matchHeaders = []
}
context "POST with ?columns parameter" $ do context "POST with ?columns parameter" $ do
it "ignores json keys not included in ?columns" $ do it "ignores json keys not included in ?columns" $ do
request methodPost "/articles?columns=id,body" [("Prefer", "return=representation")] request methodPost "/articles?columns=id,body" [("Prefer", "return=representation")]
@@ -664,3 +625,53 @@ spec actualPgVersion = do
{ matchStatus = 200, { matchStatus = 200,
matchHeaders = [matchContentTypeJson] matchHeaders = [matchContentTypeJson]
} }
context "table with limited privileges" $ do
it "succeeds inserting if correct select is applied" $
request methodPost "/limited_article_stars?select=article_id,user_id" [("Prefer", "return=representation")]
[json| {"article_id": 2, "user_id": 1} |] `shouldRespondWith` [str|[{"article_id":2,"user_id":1}]|]
{ matchStatus = 201
, matchHeaders = []
}
it "fails inserting if more columns are selected" $
request methodPost "/limited_article_stars?select=article_id,user_id,created_at" [("Prefer", "return=representation")]
[json| {"article_id": 2, "user_id": 2} |] `shouldRespondWith` (
if actualPgVersion >= pgVersion112 then
[str|{"hint":null,"details":null,"code":"42501","message":"permission denied for view limited_article_stars"}|]
else
[str|{"hint":null,"details":null,"code":"42501","message":"permission denied for relation limited_article_stars"}|]
)
{ matchStatus = 401
, matchHeaders = []
}
it "fails inserting if select is not specified" $
request methodPost "/limited_article_stars" [("Prefer", "return=representation")]
[json| {"article_id": 3, "user_id": 1} |] `shouldRespondWith` (
if actualPgVersion >= pgVersion112 then
[str|{"hint":null,"details":null,"code":"42501","message":"permission denied for view limited_article_stars"}|]
else
[str|{"hint":null,"details":null,"code":"42501","message":"permission denied for relation limited_article_stars"}|]
)
{ matchStatus = 401
, matchHeaders = []
}
it "can insert in a table with no select and return=minimal" $ do
p <- request methodPost "/insertonly"
[("Prefer", "return=minimal")]
[json| { "v":"some value" } |]
liftIO $ do
simpleBody p `shouldBe` ""
simpleStatus p `shouldBe` created201
it "succeeds updating row and gives a 204 when using return=minimal" $
request methodPatch "/app_users?id=eq.1" [("Prefer", "return=minimal")]
[json| { "password": "passxyz" } |]
`shouldRespondWith` 204
it "can update without return=minimal and no explicit select" $
request methodPatch "/app_users?id=eq.1" []
[json| { "password": "passabc" } |]
`shouldRespondWith` 204
+5
View File
@@ -507,3 +507,8 @@ INSERT INTO web_content VALUES (1, 'fezz', 0);
INSERT INTO web_content VALUES (2, 'foo', 0); INSERT INTO web_content VALUES (2, 'foo', 0);
INSERT INTO web_content VALUES (3, 'bar', 0); INSERT INTO web_content VALUES (3, 'bar', 0);
INSERT INTO web_content VALUES (4, 'wut', 1); INSERT INTO web_content VALUES (4, 'wut', 1);
TRUNCATE TABLE app_users CASCADE;
INSERT INTO app_users (id, email, "password") VALUES (1, 'test@123.com','pass');
INSERT INTO app_users (id, email, "password") VALUES (2, 'abc@123.com','pass');
INSERT INTO app_users (id, email, "password") VALUES (3, 'def@123.com','pass');
+4
View File
@@ -123,6 +123,10 @@ GRANT SELECT (article_id, user_id) ON TABLE limited_article_stars TO postgrest_t
GRANT INSERT (article_id, user_id) ON TABLE limited_article_stars TO postgrest_test_anonymous; GRANT INSERT (article_id, user_id) ON TABLE limited_article_stars TO postgrest_test_anonymous;
GRANT UPDATE (article_id, user_id) ON TABLE limited_article_stars TO postgrest_test_anonymous; GRANT UPDATE (article_id, user_id) ON TABLE limited_article_stars TO postgrest_test_anonymous;
GRANT SELECT(id, email) ON TABLE app_users TO postgrest_test_anonymous;
GRANT INSERT, UPDATE ON TABLE app_users TO postgrest_test_anonymous;
GRANT DELETE ON TABLE app_users TO postgrest_test_anonymous;
REVOKE EXECUTE ON FUNCTION privileged_hello(text) FROM PUBLIC; -- All functions are available to every role(PUBLIC) by default REVOKE EXECUTE ON FUNCTION privileged_hello(text) FROM PUBLIC; -- All functions are available to every role(PUBLIC) by default
GRANT EXECUTE ON FUNCTION privileged_hello(text) TO postgrest_test_author; GRANT EXECUTE ON FUNCTION privileged_hello(text) TO postgrest_test_author;
+6
View File
@@ -1753,3 +1753,9 @@ CREATE TABLE web_content (
CREATE FUNCTION getallusers() RETURNS SETOF users AS $$ CREATE FUNCTION getallusers() RETURNS SETOF users AS $$
SELECT * FROM test.users; SELECT * FROM test.users;
$$ LANGUAGE sql; $$ LANGUAGE sql;
create table app_users (
id integer primary key,
email text unique not null,
password text not null
);