diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5749b4f93..37605b85c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. From versio
## Unreleased
+### Fixed
+
+- The OpenAPI output now reflects table privileges: only the granted HTTP methods are exposed (e.g. `SELECT` grants `GET`, `INSERT` grants `POST`) and column-level grants filter the columns shown on table definitions and row filters.
+
## [16.1] - 2026-08-10
### Fixed
diff --git a/docs/references/api/openapi.rst b/docs/references/api/openapi.rst
index ec546eb72..c32855837 100644
--- a/docs/references/api/openapi.rst
+++ b/docs/references/api/openapi.rst
@@ -9,6 +9,8 @@ PostgREST automatically serves a full `OpenAPI `_ des
By default, this output depends on the permissions of the role that is contained in the JWT role claim (or the :ref:`db-anon-role` if no JWT is sent). If you need to show all the endpoints disregarding the role's permissions, set the :ref:`openapi-mode` config to :code:`ignore-privileges`.
+ When following privileges, the output reflects both the granted HTTP methods and columns: a relation with only ``SELECT`` will only expose ``GET``, a relation with only ``INSERT`` will only expose ``POST``, and column-level grants limit the columns shown on the table definitions and row filters.
+
For extra customization, the OpenAPI output contains a "description" field for every `SQL comment `_ on any database object. For instance,
.. code-block:: postgres
diff --git a/postgrest.cabal b/postgrest.cabal
index 835a812bc..4368d62f3 100644
--- a/postgrest.cabal
+++ b/postgrest.cabal
@@ -78,6 +78,7 @@ library
PostgREST.Network
PostgREST.Observation
PostgREST.Query
+ PostgREST.Query.OpenApi
PostgREST.Query.PreQuery
PostgREST.Query.QueryBuilder
PostgREST.Query.SqlFragment
diff --git a/src/library/PostgREST/MainTx.hs b/src/library/PostgREST/MainTx.hs
index 30f02d441..0a3f6efbb 100644
--- a/src/library/PostgREST/MainTx.hs
+++ b/src/library/PostgREST/MainTx.hs
@@ -19,7 +19,6 @@ import qualified Data.Aeson.Lens as L
import qualified Data.ByteString as BS hiding (break)
import qualified Data.ByteString.Char8 as BS
import qualified Data.HashMap.Strict as HM
-import qualified Data.Set as S
import qualified Hasql.Decoders as HD
import qualified Hasql.DynamicStatements.Statement as SQL
import qualified Hasql.Session as SQL (Session)
@@ -44,6 +43,7 @@ import PostgREST.Plan (ActionPlan (..), CrudPlan (..),
DbActionPlan (..), InfoPlan (..),
InspectPlan (..))
import PostgREST.Query (MainQuery (..))
+import PostgREST.Query.OpenApi (TableAccess (..), TablesAccess)
import PostgREST.SchemaCache (SchemaCache (..))
import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
import PostgREST.SchemaCache.Routine (Routine (..), RoutineMap)
@@ -60,7 +60,7 @@ data MainTx
data DbResult
= DbCrudResult CrudPlan ResultSet
| DbPlanResult MediaType BS.ByteString
- | MaybeDbResult InspectPlan (Maybe (TablesMap, RoutineMap, Maybe Text))
+ | MaybeDbResult InspectPlan (Maybe (TablesMap, TablesAccess, RoutineMap, Maybe Text))
| NoDbResult InfoPlan
-- | Standard result set format used for the mqMain query
@@ -174,33 +174,36 @@ actionResult MainQuery{mqOpenAPI=(tblsQ, funcsQ, schQ)} (MayUseDb plan@InspectPl
mainActionQuery = lift $
case configOpenApiMode of
OAFollowPriv -> do
- tableAccess <- SQL.statement mempty $ SQL.dynamicallyParameterized tblsQ decodeAccessibleIdentifiers configDbPreparedStatements
+ tableAccess <- SQL.statement mempty $ SQL.dynamicallyParameterized tblsQ decodeTablesAccess configDbPreparedStatements
accFuncs <- SQL.statement mempty $ SQL.dynamicallyParameterized funcsQ SchemaCache.decodeFuncs configDbPreparedStatements
schDesc <- SQL.statement mempty $ SQL.dynamicallyParameterized schQ decodeSchemaDesc configDbPreparedStatements
- let tbls = HM.filterWithKey (\qi _ -> S.member qi tableAccess) $ SchemaCache.dbTables sCache
+ let tbls = HM.filterWithKey (\qi _ -> HM.member qi tableAccess) $ SchemaCache.dbTables sCache
- pure $ MaybeDbResult plan (Just (tbls, accFuncs, schDesc))
+ pure $ MaybeDbResult plan (Just (tbls, tableAccess, accFuncs, schDesc))
OAIgnorePriv -> do
schDesc <- SQL.statement mempty (SQL.dynamicallyParameterized schQ decodeSchemaDesc configDbPreparedStatements)
let tbls = HM.filterWithKey (\(QualifiedIdentifier sch _) _ -> sch == tSchema) (SchemaCache.dbTables sCache)
routs = HM.filterWithKey (\(QualifiedIdentifier sch _) _ -> sch == tSchema) (SchemaCache.dbRoutines sCache)
- pure $ MaybeDbResult plan (Just (tbls, routs, schDesc))
+ pure $ MaybeDbResult plan (Just (tbls, mempty, routs, schDesc))
OADisabled ->
pure $ MaybeDbResult plan Nothing
decodeSchemaDesc :: HD.Result (Maybe Text)
decodeSchemaDesc = join <$> HD.rowMaybe (nullableColumn HD.text)
- decodeAccessibleIdentifiers :: HD.Result (S.Set QualifiedIdentifier)
- decodeAccessibleIdentifiers =
+ decodeTablesAccess :: HD.Result TablesAccess
+ decodeTablesAccess =
let
- row = QualifiedIdentifier
- <$> column HD.text
- <*> column HD.text
+ row = (,) <$> (QualifiedIdentifier <$> column HD.text <*> column HD.text)
+ <*> (TableAccess
+ <$> arrayColumn HD.text
+ <*> arrayColumn HD.text
+ <*> arrayColumn HD.text
+ <*> column HD.bool)
in
- S.fromList <$> HD.rowList row
+ HM.fromList <$> HD.rowList row
-- Makes sure the querystring pk matches the payload pk
-- e.g. PUT /items?id=eq.1 { "id" : 1, .. } is accepted,
diff --git a/src/library/PostgREST/Query/OpenApi.hs b/src/library/PostgREST/Query/OpenApi.hs
new file mode 100644
index 000000000..3ee046aa8
--- /dev/null
+++ b/src/library/PostgREST/Query/OpenApi.hs
@@ -0,0 +1,29 @@
+{-|
+Module : PostgREST.Query.OpenApi
+Description : Types for reflecting the role privileges on the OpenAPI output.
+-}
+module PostgREST.Query.OpenApi
+ ( TableAccess (..)
+ , TablesAccess
+ ) where
+
+import qualified Data.HashMap.Strict as HM
+
+import PostgREST.SchemaCache.Identifiers (FieldName, QualifiedIdentifier)
+
+import Protolude
+
+-- | Privileges that a role has on a relation, used to reflect them on the OpenAPI output.
+data TableAccess = TableAccess
+ { taSelectCols :: [FieldName]
+ -- ^ columns the role can SELECT
+ , taInsertCols :: [FieldName]
+ -- ^ columns the role can INSERT into
+ , taUpdateCols :: [FieldName]
+ -- ^ columns the role can UPDATE
+ , taDelete :: Bool
+ -- ^ whether the role can DELETE rows
+ }
+ deriving (Show, Eq)
+
+type TablesAccess = HM.HashMap QualifiedIdentifier TableAccess
diff --git a/src/library/PostgREST/Query/SqlFragment.hs b/src/library/PostgREST/Query/SqlFragment.hs
index 973dabab5..842ae7189 100644
--- a/src/library/PostgREST/Query/SqlFragment.hs
+++ b/src/library/PostgREST/Query/SqlFragment.hs
@@ -598,7 +598,32 @@ accessibleTables :: Text -> SQL.Snippet
accessibleTables schema = SQL.sql (encodeUtf8 [trimming|
SELECT
n.nspname AS table_schema,
- c.relname AS table_name
+ c.relname AS table_name,
+ COALESCE((
+ SELECT array_agg(a.attname ORDER BY a.attnum)
+ FROM pg_attribute a
+ WHERE a.attrelid = c.oid
+ AND a.attnum > 0
+ AND NOT a.attisdropped
+ AND has_column_privilege(c.oid, a.attnum, 'SELECT')
+ ), '{}') AS select_cols,
+ COALESCE((
+ SELECT array_agg(a.attname ORDER BY a.attnum)
+ FROM pg_attribute a
+ WHERE a.attrelid = c.oid
+ AND a.attnum > 0
+ AND NOT a.attisdropped
+ AND has_column_privilege(c.oid, a.attnum, 'INSERT')
+ ), '{}') AS insert_cols,
+ COALESCE((
+ SELECT array_agg(a.attname ORDER BY a.attnum)
+ FROM pg_attribute a
+ WHERE a.attrelid = c.oid
+ AND a.attnum > 0
+ AND NOT a.attisdropped
+ AND has_column_privilege(c.oid, a.attnum, 'UPDATE')
+ ), '{}') AS update_cols,
+ has_table_privilege(c.oid, 'DELETE') AS has_delete
FROM pg_class c
JOIN pg_namespace n ON n.oid = c.relnamespace
WHERE c.relkind IN ('v','r','m','f','p')
diff --git a/src/library/PostgREST/Response.hs b/src/library/PostgREST/Response.hs
index 89f431a98..a02d5caaa 100644
--- a/src/library/PostgREST/Response.hs
+++ b/src/library/PostgREST/Response.hs
@@ -202,7 +202,7 @@ actionResponse (DbPlanResult media plan) ctxApiRequest _ _ _ =
actionResponse (MaybeDbResult InspectPlan{ipHdrsOnly=headersOnly} body) ApiRequest{..} versions conf sCache =
let
- rsBody = maybe mempty (\(x, y, z) -> if headersOnly then mempty else OpenAPI.encode versions conf sCache x y z) body
+ rsBody = maybe mempty (\(tbls, tblAccess, procs, schDesc) -> if headersOnly then mempty else OpenAPI.encode versions conf sCache tbls tblAccess procs schDesc) body
cLHeader = if headersOnly then mempty else [contentLengthHeader rsBody]
in
Right $ PgrstResponse HTTP.status200 (MediaType.toContentType MTOpenAPI : cLHeader ++ maybeToList (profileHeader iSchema iNegotiatedByProfile)) rsBody
diff --git a/src/library/PostgREST/Response/OpenAPI.hs b/src/library/PostgREST/Response/OpenAPI.hs
index c7cb2748b..9276b7d50 100644
--- a/src/library/PostgREST/Response/OpenAPI.hs
+++ b/src/library/PostgREST/Response/OpenAPI.hs
@@ -27,8 +27,9 @@ import PostgREST.Config (AppConfig (..), Proxy (..),
isMalformedProxyUri, toURI)
import PostgREST.MediaType
import PostgREST.Network (escapeHostName)
+import PostgREST.Query.OpenApi (TableAccess (..), TablesAccess)
import PostgREST.SchemaCache (SchemaCache (..))
-import PostgREST.SchemaCache.Identifiers (QualifiedIdentifier (..))
+import PostgREST.SchemaCache.Identifiers (FieldName, QualifiedIdentifier (..))
import PostgREST.SchemaCache.Relationship (Cardinality (..), Relationship (..),
RelationshipsMap)
import PostgREST.SchemaCache.Routine (FuncVolatility (..), Routine (..),
@@ -38,18 +39,27 @@ import PostgREST.SchemaCache.Table (Column (..), Table (..), TablesMap,
import Protolude hiding (Proxy, get)
-encode :: (Text, Text) -> AppConfig -> SchemaCache -> TablesMap -> HM.HashMap k [Routine] -> Maybe Text -> LBS.ByteString
-encode versions conf sCache tables procs schemaDescription =
+encode :: (Text, Text) -> AppConfig -> SchemaCache -> TablesMap -> TablesAccess -> HM.HashMap k [Routine] -> Maybe Text -> LBS.ByteString
+encode versions conf sCache tables access procs schemaDescription =
JSON.encode $
postgrestSpec
versions
(dbRelationships sCache)
(concat $ HM.elems procs)
- (snd <$> HM.toList tables)
+ (fmap (\(_, t) -> (t, accessFor access t)) (HM.toList tables))
(proxyUri conf)
schemaDescription
(configOpenApiSecurityActive conf)
+-- | Get the access privileges for a table. When the table is not present in the
+-- map(ignore-privileges mode), assume the role has full access to it.
+accessFor :: TablesAccess -> Table -> TableAccess
+accessFor access t =
+ fromMaybe fullAccess (HM.lookup (QualifiedIdentifier (tableSchema t) (tableName t)) access)
+ where
+ fullAccess = TableAccess allCols allCols allCols True
+ allCols = colName <$> tableColumnsList t
+
makeMimeList :: [MediaType] -> MimeList
makeMimeList cs = MimeList $ fmap (fromString . BS.unpack . toMime) cs
@@ -97,14 +107,19 @@ parseDefault colType colDefault =
where
wrapInQuotations text = "\"" <> text <> "\""
-makeTableDef :: RelationshipsMap -> Table -> (Text, Schema)
-makeTableDef rels t =
- let tn = tableName t in
- (tn, (mempty :: Schema)
- & description .~ tableDescription t
- & type_ ?~ SwaggerObject
- & properties .~ fromList (makeProperty t rels <$> tableColumnsList t)
- & required .~ fmap colName (filter (not . colNullable) $ tableColumnsList t))
+makeTableDef :: RelationshipsMap -> (Table, TableAccess) -> (Text, Schema)
+makeTableDef rels (t, access) =
+ (tn, (mempty :: Schema)
+ & description .~ tableDescription t
+ & type_ ?~ SwaggerObject
+ & properties .~ fromList (makeProperty t rels <$> cols)
+ & required .~ fmap colName (filter (not . colNullable) cols))
+ where
+ tn = tableName t
+ cols = accessibleCols t (taSelectCols access)
+
+accessibleCols :: Table -> [FieldName] -> [Column]
+accessibleCols t cols = filter ((`elem` cols) . colName) (tableColumnsList t)
makeProperty :: Table -> RelationshipsMap -> Column -> (Text, Referenced Schema)
makeProperty tbl rels col = (colName col, Inline s)
@@ -222,8 +237,8 @@ makeProcPostParams pd =
, Ref $ Reference "preferParams"
]
-makeParamDefs :: [Table] -> [(Text, Param)]
-makeParamDefs ti =
+makeParamDefs :: RelationshipsMap -> [(Table, TableAccess)] -> [(Text, Param)]
+makeParamDefs rels tis =
-- TODO: create Prefer for each method (GET, PATCH, etc.)
[ ("preferParams", makePreferParam ["params"])
, ("preferReturn", makePreferParam ["return"])
@@ -280,17 +295,27 @@ makeParamDefs ti =
& in_ .~ ParamQuery
& type_ ?~ SwaggerString))
]
- <> concat [ makeObjectBody (tableName t) : makeRowFilters (tableName t) (tableColumnsList t)
- | t <- ti
+ <> concat [ makeObjectBody rels t access <> makeRowFilters (tableName t) (accessibleCols t (taSelectCols access))
+ | (t, access) <- tis
]
-makeObjectBody :: Text -> (Text, Param)
-makeObjectBody tn =
- ("body." <> tn, (mempty :: Param)
- & name .~ tn
- & description ?~ tn
- & required ?~ False
- & schema .~ ParamBody (Ref (Reference tn)))
+makeObjectBody :: RelationshipsMap -> Table -> TableAccess -> [(Text, Param)]
+makeObjectBody rels t access =
+ [ ("body." <> tn, makeBodyParam (taInsertCols access))
+ , ("body." <> tn <> ".patch", makeBodyParam (taUpdateCols access))
+ ]
+ where
+ tn = tableName t
+ makeBodyParam cols = (mempty :: Param)
+ & name .~ tn
+ & description ?~ tn
+ & required ?~ False
+ & schema .~ ParamBody (Inline bodySchema)
+ where
+ bodySchema = (mempty :: Schema)
+ & type_ ?~ SwaggerObject
+ & properties .~ fromList (makeProperty t rels <$> accessibleCols t cols)
+ & required .~ fmap colName (filter (not . colNullable) (accessibleCols t cols))
makeRowFilter :: Text -> Column -> (Text, Param)
makeRowFilter tn c =
@@ -305,8 +330,8 @@ makeRowFilter tn c =
makeRowFilters :: Text -> [Column] -> [(Text, Param)]
makeRowFilters tn = fmap (makeRowFilter tn)
-makePathItem :: Table -> (FilePath, PathItem)
-makePathItem t = ("/" ++ T.unpack tn, p $ tableInsertable t || tableUpdatable t || tableDeletable t)
+makePathItem :: (Table, TableAccess) -> (FilePath, PathItem)
+makePathItem (t, access) = ("/" ++ T.unpack tn, p)
where
-- Use first line of table description as summary; rest as description (if present)
-- We strip leading newlines from description so that users can include a blank line between summary and description
@@ -327,20 +352,26 @@ makePathItem t = ("/" ++ T.unpack tn, p $ tableInsertable t || tableUpdatable t
)
)
postOp = tOp
- & parameters .~ fmap ref ["body." <> tn, "select", "preferPost"]
+ & parameters .~ fmap ref [bodyParam, "select", "preferPost"]
& at 201 ?~ "Created"
patchOp = tOp
- & parameters .~ fmap ref (rs <> ["body." <> tn, "preferReturn"])
+ & parameters .~ fmap ref (rs <> [patchBodyParam, "preferReturn"])
& at 204 ?~ "No Content"
deletOp = tOp
& parameters .~ fmap ref (rs <> ["preferReturn"])
& at 204 ?~ "No Content"
- pr = (mempty :: PathItem) & get ?~ getOp
- pw = pr & post ?~ postOp & patch ?~ patchOp & delete ?~ deletOp
- p False = pr
- p True = pw
+ p = (mempty :: PathItem)
+ & get .~ (if not (null selCols) then Just getOp else Nothing)
+ & post .~ (if tableInsertable t && not (null insCols) then Just postOp else Nothing)
+ & patch .~ (if tableUpdatable t && not (null updCols) then Just patchOp else Nothing)
+ & delete .~ (if tableDeletable t && taDelete access then Just deletOp else Nothing)
tn = tableName t
- rs = [ T.intercalate "." ["rowFilter", tn, colName c ] | c <- tableColumnsList t ]
+ selCols = accessibleCols t (taSelectCols access)
+ insCols = accessibleCols t (taInsertCols access)
+ updCols = accessibleCols t (taUpdateCols access)
+ rs = [ T.intercalate "." ["rowFilter", tn, colName c ] | c <- selCols ]
+ bodyParam = "body." <> tn
+ patchBodyParam = "body." <> tn <> ".patch"
ref = Ref . Reference
makeProcPathItem :: Routine -> (FilePath, PathItem)
@@ -375,9 +406,9 @@ makeRootPathItem = ("/", p)
pr = (mempty :: PathItem) & get ?~ getOp
p = pr
-makePathItems :: [Routine] -> [Table] -> InsOrdHashMap FilePath PathItem
-makePathItems pds ti = fromList $ makeRootPathItem :
- fmap makePathItem ti ++ fmap makeProcPathItem pds
+makePathItems :: [Routine] -> [(Table, TableAccess)] -> InsOrdHashMap FilePath PathItem
+makePathItems pds tis = fromList $ makeRootPathItem :
+ fmap makePathItem tis ++ fmap makeProcPathItem pds
makeSecurityDefinitions :: Text -> Bool -> SecurityDefinitions
makeSecurityDefinitions secName allow
@@ -387,8 +418,8 @@ makeSecurityDefinitions secName allow
secSchType = SecuritySchemeApiKey (ApiKeyParams "Authorization" ApiKeyHeader)
secSchDescription = Just "Add the token prepending \"Bearer \" (without quotes) to it"
-postgrestSpec :: (Text, Text) -> RelationshipsMap -> [Routine] -> [Table] -> (Text, Text, Integer, Text) -> Maybe Text -> Bool -> Swagger
-postgrestSpec (prettyVersion, docsVersion) rels pds ti (s, h, p, b) sd allowSecurityDef = (mempty :: Swagger)
+postgrestSpec :: (Text, Text) -> RelationshipsMap -> [Routine] -> [(Table, TableAccess)] -> (Text, Text, Integer, Text) -> Maybe Text -> Bool -> Swagger
+postgrestSpec (prettyVersion, docsVersion) rels pds tis (s, h, p, b) sd allowSecurityDef = (mempty :: Swagger)
& basePath ?~ T.unpack b
& schemes ?~ [s']
& info .~ ((mempty :: Info)
@@ -399,9 +430,9 @@ postgrestSpec (prettyVersion, docsVersion) rels pds ti (s, h, p, b) sd allowSecu
& description ?~ "PostgREST Documentation"
& url .~ URL ("https://postgrest.org/en/" <> docsVersion <> "/references/api.html"))
& host .~ h'
- & definitions .~ fromList (makeTableDef rels <$> ti)
- & parameters .~ fromList (makeParamDefs ti)
- & paths .~ makePathItems pds ti
+ & definitions .~ fromList (makeTableDef rels <$> tis)
+ & parameters .~ fromList (makeParamDefs rels tis)
+ & paths .~ makePathItems pds tis
& produces .~ makeMimeList [MTApplicationJSON, MTVndSingularJSON True, MTVndSingularJSON False, MTTextCSV]
& consumes .~ makeMimeList [MTApplicationJSON, MTVndSingularJSON True, MTVndSingularJSON False, MTTextCSV]
& securityDefinitions .~ makeSecurityDefinitions securityDefName allowSecurityDef
diff --git a/test/spec/Feature/OpenApi/OpenApiSpec.hs b/test/spec/Feature/OpenApi/OpenApiSpec.hs
index cd98090e1..694a30e96 100644
--- a/test/spec/Feature/OpenApi/OpenApiSpec.hs
+++ b/test/spec/Feature/OpenApi/OpenApiSpec.hs
@@ -222,6 +222,58 @@ spec withConfig = withConfig baseCfg $ describe "OpenAPI" $ do
. nth 0
liftIO $ tableTag `shouldBe` Just [aesonQQ|"authors_only"|]
+ it "reflects table privileges in the HTTP methods" $ do
+ r <- simpleBody <$> get "/"
+
+ let selectonlyGet = r ^? key "paths" . key "/selectonly" . key "get"
+ selectonlyPost = r ^? key "paths" . key "/selectonly" . key "post"
+ insertonlyGet = r ^? key "paths" . key "/insertonly" . key "get"
+ insertonlyPost = r ^? key "paths" . key "/insertonly" . key "post"
+ insertonlyDelete = r ^? key "paths" . key "/insertonly" . key "delete"
+ limitedStarsGet = r ^? key "paths" . key "/limited_article_stars" . key "get"
+ limitedStarsPost = r ^? key "paths" . key "/limited_article_stars" . key "post"
+ limitedStarsPatch = r ^? key "paths" . key "/limited_article_stars" . key "patch"
+ limitedStarsDelete = r ^? key "paths" . key "/limited_article_stars" . key "delete"
+
+ liftIO $ do
+ selectonlyGet `shouldNotBe` Nothing
+ selectonlyPost `shouldBe` Nothing
+
+ insertonlyGet `shouldBe` Nothing
+ insertonlyPost `shouldNotBe` Nothing
+ insertonlyDelete `shouldBe` Nothing
+
+ limitedStarsGet `shouldNotBe` Nothing
+ limitedStarsPost `shouldNotBe` Nothing
+ limitedStarsPatch `shouldNotBe` Nothing
+ limitedStarsDelete `shouldBe` Nothing
+
+ it "reflects column privileges in the table definition" $ do
+ r <- simpleBody <$> get "/"
+
+ let appUsersId = r ^? key "definitions" . key "app_users" . key "properties" . key "id"
+ appUsersEmail = r ^? key "definitions" . key "app_users" . key "properties" . key "email"
+ appUsersPassword = r ^? key "definitions" . key "app_users" . key "properties" . key "password"
+ appUsersRequired = r ^? key "definitions" . key "app_users" . key "required"
+
+ liftIO $ do
+ appUsersId `shouldNotBe` Nothing
+ appUsersEmail `shouldNotBe` Nothing
+ appUsersPassword `shouldBe` Nothing
+ appUsersRequired `shouldBe` Just [aesonQQ|["id", "email"]|]
+
+ it "reflects column privileges in the rowFilter parameters" $ do
+ r <- simpleBody <$> get "/"
+
+ let filterId = r ^? key "parameters" . key "rowFilter.app_users.id"
+ filterEmail = r ^? key "parameters" . key "rowFilter.app_users.email"
+ filterPassword = r ^? key "parameters" . key "rowFilter.app_users.password"
+
+ liftIO $ do
+ filterId `shouldNotBe` Nothing
+ filterEmail `shouldNotBe` Nothing
+ filterPassword `shouldBe` Nothing
+
it "includes a fk description for a O2O relationship" $ do
r <- simpleBody <$> get "/"
diff --git a/test/spec/fixtures/privileges.sql b/test/spec/fixtures/privileges.sql
index 93e05ea49..e90b85a57 100644
--- a/test/spec/fixtures/privileges.sql
+++ b/test/spec/fixtures/privileges.sql
@@ -28,10 +28,13 @@ REVOKE ALL PRIVILEGES ON TABLE
, authors_only
, insertonly
, limited_article_stars
+ , selectonly
FROM postgrest_test_anonymous;
GRANT INSERT ON TABLE insertonly TO postgrest_test_anonymous;
+GRANT SELECT ON TABLE selectonly TO postgrest_test_anonymous;
+
GRANT USAGE ON SEQUENCE
auto_incrementing_pk_id_seq
, items_id_seq
diff --git a/test/spec/fixtures/schema.sql b/test/spec/fixtures/schema.sql
index cce6136e8..f60491b14 100644
--- a/test/spec/fixtures/schema.sql
+++ b/test/spec/fixtures/schema.sql
@@ -1926,6 +1926,11 @@ create table app_users (
password text not null
);
+create table selectonly (
+ id integer primary key,
+ name text
+);
+
create table private.pages (
link int not null unique
, url text