Compare commits

...
9 Commits
Author SHA1 Message Date
steve-chavez 4197d2f739 bump version to 11.0.1 2023-04-27 21:30:40 -05:00
steve-chavez c10ba8e214 fix: skew of 30 seconds for JWT validation 2023-04-27 21:06:06 -05:00
steve-chavez 887948d259 fix: missing=default error msg on generated column 2023-04-27 19:27:09 -05:00
steve-chavez c63786733a fix: no schema privilege for schema cache
This was due to the usage of pg_get_serial_sequence
2023-04-27 19:27:09 -05:00
steve-chavez 4fe696dd96 nix: PGRST_DB_ANON_ROLE default for postgrest-run
Makes manually testing a feature easier
2023-04-27 19:27:09 -05:00
steve-chavez 67936b343f test: prove that authenticator is not a superuser 2023-04-27 19:27:09 -05:00
steve-chavez b0e395f495 nix: no SUPERUSER for connection role
Change :USER to :PGUSER in SQL scripts
2023-04-27 19:27:09 -05:00
dependabot[bot]andGitHub 3b55a27ef3 build(deps): bump codecov/codecov-action from 3.1.2 to 3.1.3 (#2765) 2023-04-24 11:21:49 -05:00
dependabot[bot]andGitHub 43da81c30c build(deps): bump codecov/codecov-action from 3.1.1 to 3.1.2 (#2757) 2023-04-18 12:25:50 -05:00
15 changed files with 129 additions and 29 deletions
+1 -1
View File
@@ -46,7 +46,7 @@ jobs:
- name: Run coverage (IO tests and Spec tests against PostgreSQL 15)
run: postgrest-coverage
- name: Upload coverage to codecov
uses: codecov/codecov-action@v3.1.1
uses: codecov/codecov-action@v3.1.3
with:
files: ./coverage/codecov.json
+9
View File
@@ -5,6 +5,15 @@ This project adheres to [Semantic Versioning](http://semver.org/).
## Unreleased
## [11.0.1] - 2023-04-27
### Fixed
- #2762, Fixes "permission denied for schema" error during schema cache load - @steve-chavez
- #2756, Fix bad error message on generated columns when using `Prefer: missing=default` - @steve-chavez
- #1139, Allow a 30 second skew for JWT validation - @steve-chavez
+ It used to be 1 second, which was too strict
## [11.0.0] - 2023-04-16
### Added
+7 -1
View File
@@ -38,11 +38,17 @@ let
{
name = "postgrest-run";
docs = "Run PostgREST after building it interactively with cabal-install";
args = [ "ARG_LEFTOVERS([PostgREST arguments])" ];
args =
[
"ARG_USE_ENV([PGRST_DB_ANON_ROLE], [postgrest_test_anonymous], [PostgREST anonymous role])"
"ARG_LEFTOVERS([PostgREST arguments])"
];
inRootDir = true;
withEnv = postgrest.env;
}
''
export PGRST_DB_ANON_ROLE
exec ${cabal-install}/bin/cabal v2-run ${devCabalOptions} --verbose=0 -- \
postgrest "''${_arg_leftovers[@]}"
'';
+12 -5
View File
@@ -15,11 +15,14 @@
let
withTmpDb =
{ name, postgresql }:
let commandName = "postgrest-with-${name}"; in
let
commandName = "postgrest-with-${name}";
superuserRole = "postgres";
in
checkedShellScript
{
name = commandName;
docs = "Run the given command in a temporary database with ${name}";
docs = "Run the given command in a temporary database with ${name}. If you wish to mutate the database, login with the '${superuserRole}' role.";
args =
[
"ARG_OPTIONAL_SINGLE([fixtures], [f], [SQL file to load fixtures from], [test/spec/fixtures/load.sql])"
@@ -66,7 +69,8 @@ let
log "Initializing database cluster..."
# We try to make the database cluster as independent as possible from the host
# by specifying the timezone, locale and encoding.
PGTZ=UTC initdb --no-locale --encoding=UTF8 --nosync -U "$PGUSER" --auth=trust \
# initdb -U creates a superuser(man initdb)
PGTZ=UTC initdb --no-locale --encoding=UTF8 --nosync -U "${superuserRole}" --auth=trust \
>> "$setuplog"
log "Starting the database cluster..."
@@ -82,8 +86,11 @@ let
}
trap stop EXIT
log "Loading fixtures..."
psql -v ON_ERROR_STOP=1 -f "$_arg_fixtures" >> "$setuplog"
log "Creating a minimally privileged $PGUSER connection role..."
createuser "$PGUSER" -U "${superuserRole}" --host="$tmpdir/socket" --no-createdb --no-inherit --no-superuser --no-createrole --no-replication --login
log "Loading fixtures under the ${superuserRole} role..."
psql -U "${superuserRole}" -v PGUSER="$PGUSER" -v ON_ERROR_STOP=1 -f "$_arg_fixtures" >> "$setuplog"
log "Done. Running command..."
+2 -1
View File
@@ -1,5 +1,5 @@
name: postgrest
version: 11.0.0
version: 11.0.1
synopsis: REST API for any Postgres database
description: Reads the schema of a PostgreSQL database and creates RESTful routes
for tables, views, and functions, supporting all HTTP methods that security
@@ -187,6 +187,7 @@ test-suite spec
Feature.CorsSpec
Feature.ExtraSearchPathSpec
Feature.LegacyGucsSpec
Feature.NoSuperuserSpec
Feature.ObservabilitySpec
Feature.OpenApi.DisabledOpenApiSpec
Feature.OpenApi.IgnorePrivOpenApiSpec
+1 -1
View File
@@ -63,7 +63,7 @@ parseToken AppConfig{..} token time = do
liftEither . mapLeft jwtClaimsError $ JSON.toJSON <$> eitherClaims
where
validation =
JWT.defaultJWTValidationSettings audienceCheck & set JWT.allowedSkew 1
JWT.defaultJWTValidationSettings audienceCheck & set JWT.allowedSkew 30
audienceCheck :: JWT.StringOrURI -> Bool
audienceCheck = maybe (const True) (==) configJwtAudience
+22 -12
View File
@@ -40,7 +40,7 @@ import Text.InterpolatedString.Perl6 (q)
import PostgREST.Config.Database (pgVersionStatement)
import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
pgVersion110)
pgVersion110, pgVersion120)
import PostgREST.SchemaCache.Identifiers (AccessSet, FieldName,
QualifiedIdentifier (..),
Schema)
@@ -506,6 +506,8 @@ tablesSqlQuery pgVer =
-- the tbl_constraints/key_col_usage CTEs are based on the standard "information_schema.table_constraints"/"information_schema.key_column_usage" views,
-- we cannot use those directly as they include the following privilege filter:
-- (pg_has_role(ss.relowner, 'USAGE'::text) OR has_column_privilege(ss.roid, a.attnum, 'SELECT, INSERT, UPDATE, REFERENCES'::text));
-- on the "columns" CTE, left joining on pg_depend and pg_class is used to obtain the sequence name as a column default in case there are GENERATED .. AS IDENTITY,
-- generated columns are only available from pg >= 10 but the query is agnostic to versions. dep.deptype = 'i' is done because there are other 'a' dependencies on PKs
[q|
WITH
columns AS (
@@ -549,6 +551,12 @@ tablesSqlQuery pgVer =
ON t.typtype = 'd' AND t.typbasetype = bt.oid
LEFT JOIN (pg_collation co JOIN pg_namespace nco ON co.collnamespace = nco.oid)
ON a.attcollation = co.oid AND (nco.nspname <> 'pg_catalog'::name OR co.collname <> 'default'::name)
LEFT JOIN pg_depend dep
ON dep.refobjid = a.attrelid and dep.refobjsubid = a.attnum and dep.deptype = 'i'
LEFT JOIN pg_class seqclass
ON seqclass.oid = dep.objid
LEFT JOIN pg_namespace seqsch
ON seqsch.oid = seqclass.relnamespace
WHERE
NOT pg_is_other_temp_schema(nc.oid)
AND a.attnum > 0
@@ -697,17 +705,19 @@ tablesSqlQuery pgVer =
"ORDER BY table_schema, table_name"
where
relIsPartition = if pgVer >= pgVersion100 then " AND not c.relispartition " else mempty
-- detect default values on columns that have GENERATED .. AS IDENTITY
columnDefault =
if pgVer >= pgVersion100
then [q|
CASE
WHEN nullif(a.attidentity, '') is null
THEN pg_get_expr(ad.adbin, ad.adrelid)::text
ELSE format('nextval(%s)', quote_literal(pg_get_serial_sequence(a.attrelid::regclass::text, a.attname::text)))
END AS column_default,|]
else "pg_get_expr(ad.adbin, ad.adrelid)::text AS column_default,"
columnDefault
| pgVer >= pgVersion120 = [q|
CASE
WHEN a.attidentity = 'd' THEN format('nextval(%s)', quote_literal(seqsch.nspname || '.' || seqclass.relname))
WHEN a.attgenerated = 's' THEN null
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
END AS column_default,|]
| pgVer >= pgVersion100 = [q|
CASE
WHEN a.attidentity = 'd' THEN format('nextval(%s)', quote_literal(seqsch.nspname || '.' || seqclass.relname))
ELSE pg_get_expr(ad.adbin, ad.adrelid)::text
END AS column_default,|]
| otherwise = "pg_get_expr(ad.adbin, ad.adrelid)::text as column_default,"
-- | Gets many-to-one relationships and one-to-one(O2O) relationships, which are a refinement of the many-to-one's
allM2OandO2ORels :: PgVersion -> Bool -> SQL.Statement () [Relationship]
+2 -2
View File
@@ -4,7 +4,7 @@
set search_path to public;
CREATE ROLE postgrest_test_anonymous;
ALTER ROLE :USER SET pgrst.db_anon_role = 'postgrest_test_anonymous';
ALTER ROLE :PGUSER SET pgrst.db_anon_role = 'postgrest_test_anonymous';
CREATE ROLE postgrest_test_author;
@@ -14,7 +14,7 @@ alter role postgrest_test_serializable set default_transaction_isolation = 'seri
CREATE ROLE postgrest_test_repeatable_read;
alter role postgrest_test_repeatable_read set default_transaction_isolation = 'REPEATABLE READ';
GRANT postgrest_test_anonymous, postgrest_test_author, postgrest_test_serializable, postgrest_test_repeatable_read TO :USER;
GRANT postgrest_test_anonymous, postgrest_test_author, postgrest_test_serializable, postgrest_test_repeatable_read TO :PGUSER;
CREATE SCHEMA v1;
GRANT USAGE ON SCHEMA v1 TO postgrest_test_anonymous;
+1 -1
View File
@@ -1,5 +1,5 @@
CREATE ROLE postgrest_test_anonymous;
GRANT postgrest_test_anonymous TO :USER;
GRANT postgrest_test_anonymous TO :PGUSER;
CREATE SCHEMA test;
-- PUT+PATCH target needs one record and column to modify
+20
View File
@@ -0,0 +1,20 @@
module Feature.NoSuperuserSpec where
import Network.Wai (Application)
import Network.HTTP.Types
import Test.Hspec
import Test.Hspec.Wai
import Protolude
spec :: SpecWith ((), Application)
spec =
describe "No Superuser" $ do
it "proves that the authenticator role is not a superuser" $ do
request methodGet "/rpc/is_superuser"
mempty
""
`shouldRespondWith`
"false"
{ matchStatus = 200 }
+26 -2
View File
@@ -13,7 +13,8 @@ import Text.Heredoc
import PostgREST.Config.PgVersion (PgVersion, pgVersion100,
pgVersion110, pgVersion112,
pgVersion130)
pgVersion120, pgVersion130,
pgVersion140)
import Protolude hiding (get)
import SpecHelper
@@ -496,7 +497,7 @@ spec actualPgVersion = do
}
when (actualPgVersion >= pgVersion100) $
it "inserts a default on a generated by default as identity column" $ do
it "inserts a default on a generated by default as identity column" $
request methodPost "/channels?columns=id,data,slug&select=data,slug" [("Prefer", "return=representation"), ("Prefer", "missing=default")]
[json| { "slug": "foo" } |]
`shouldRespondWith`
@@ -505,6 +506,29 @@ spec actualPgVersion = do
, matchHeaders = ["Preference-Applied" <:> "missing=default"]
}
when (actualPgVersion >= pgVersion120) $
it "fails with a good error message on generated always columns" $
request methodPost "/foo?columns=a,b" [("Prefer", "return=representation"), ("Prefer", "missing=default")]
[json| [
{"a": "val"},
{"a": "val", "b": "val"}
]|]
`shouldRespondWith`
(if actualPgVersion < pgVersion140
then [json| {
"code": "42601",
"details": "Column \"b\" is a generated column.",
"hint": null,
"message": "cannot insert into column \"b\""
}|]
else [json| {
"code": "428C9",
"details": "Column \"b\" is a generated column.",
"hint": null,
"message": "cannot insert a non-DEFAULT value into column \"b\""
}|])
{ matchStatus = 400 }
it "inserts json that has duplicate keys" $ do
request methodPost "/tbl_w_json" [("Prefer", "return=representation")]
[json| { "data": { "a": 1, "a": 2 }, "id": 3 } |]
+2
View File
@@ -27,6 +27,7 @@ import qualified Feature.ConcurrentSpec
import qualified Feature.CorsSpec
import qualified Feature.ExtraSearchPathSpec
import qualified Feature.LegacyGucsSpec
import qualified Feature.NoSuperuserSpec
import qualified Feature.ObservabilitySpec
import qualified Feature.OpenApi.DisabledOpenApiSpec
import qualified Feature.OpenApi.IgnorePrivOpenApiSpec
@@ -149,6 +150,7 @@ main = do
, ("Feature.Query.ComputedRelsSpec" , Feature.Query.ComputedRelsSpec.spec)
, ("Feature.Query.RelatedQueriesSpec" , Feature.Query.RelatedQueriesSpec.spec)
, ("Feature.Query.SpreadQueriesSpec" , Feature.Query.SpreadQueriesSpec.spec)
, ("Feature.NoSuperuserSpec" , Feature.NoSuperuserSpec.spec)
]
hspec $ do
+1 -1
View File
@@ -211,7 +211,7 @@ testObservabilityCfg = baseCfg { configServerTraceHeader = Just $ mk "X-Request-
analyzeTable :: Text -> IO ()
analyzeTable tableName =
void $ readProcess "psql" ["--set", "ON_ERROR_STOP=1", "-a", "-c", toS $ "ANALYZE test.\"" <> tableName <> "\""] []
void $ readProcess "psql" ["-U", "postgres", "--set", "ON_ERROR_STOP=1", "-a", "-c", toS $ "ANALYZE test.\"" <> tableName <> "\""] []
rangeHdrs :: ByteRange -> [Header]
rangeHdrs r = [rangeUnit, (hRange, renderByteRange r)]
+1 -2
View File
@@ -1,7 +1,6 @@
\set AUTHENTICATOR current_user
DROP ROLE IF EXISTS postgrest_test_anonymous, postgrest_test_default_role, postgrest_test_author;
CREATE ROLE postgrest_test_anonymous;
CREATE ROLE postgrest_test_default_role;
CREATE ROLE postgrest_test_author;
GRANT postgrest_test_anonymous, postgrest_test_default_role, postgrest_test_author TO :USER;
GRANT postgrest_test_anonymous, postgrest_test_default_role, postgrest_test_author TO :PGUSER;
+22
View File
@@ -3122,3 +3122,25 @@ BEGIN
END IF;
END
$do$;
CREATE FUNCTION test.is_superuser() RETURNS boolean
LANGUAGE sql
AS $$
select current_setting('is_superuser')::boolean;
$$;
DO $do$
BEGIN
IF current_setting('server_version_num')::INT >= 120000 THEN
CREATE TABLE test.foo (
a text,
b text GENERATED ALWAYS AS (
case WHEN a = 'telegram' THEN 'im'
WHEN a = 'proton' THEN 'email'
WHEN a = 'infinity' THEN 'idea'
ELSE 'bad idea'
end) stored
);
END IF;
END
$do$;