Compare commits

...
44 Commits
Author SHA1 Message Date
Joe Nelson 200e5a26cc Merge pull request #536 from begriffs/build-0.3.1.1
Bump version
2016-03-28 15:09:04 -07:00
Joe Nelson b8bbaa7764 Bump version 2016-03-28 13:26:06 -07:00
Joe Nelson 1470091f1c Merge pull request #534 from begriffs/unicode-schema
Regression test for read/write unicode table names
2016-03-27 00:04:30 -07:00
Joe Nelson 31738d745f Regression test for read/write unicode table names 2016-03-25 15:14:00 -07:00
Joe Nelson f19d4300bc Merge pull request #533 from begriffs/no-count-singular
Do not do table count when plurality=singular
2016-03-23 20:43:05 -07:00
Joe Nelson cd81e9346f Do not do table count when plurality=singular
Rebasing commits by @ruslantalpa
2016-03-23 20:30:51 -07:00
Joe Nelson 01355f39a1 Merge pull request #524 from begriffs/unicode-inserts
Preserve unicode in requests and responses
2016-03-18 11:59:36 -07:00
Joe Nelson 87298f580a Merge pull request #528 from rowdypixel/patch-1
Fix typo-d flag in the installation docs.
2016-03-18 09:47:46 -07:00
Joe Nelson 3bfe64dd06 Create monomorphic statement function to force use of Text 2016-03-16 21:04:18 -07:00
Dan Walker b9d3eedb9d Fix typo-d flag in the installation docs. 2016-03-14 21:28:46 -04:00
Joe Nelson bb4126bf3a Merge pull request #526 from daurnimator/no-uuid-ossp
Remove remaining uuid-ossp references
2016-03-14 09:18:45 -07:00
daurnimator 2e440822cb remove unnessecary create extension "uuid-ossp" 2016-03-14 20:52:51 +11:00
daurnimator 13eed84f57 Use gen_random_uuid instead of uuid_generate_v4 2016-03-14 20:51:35 +11:00
Joe Nelson e5fed86965 Changelog 2016-03-13 14:33:00 -07:00
Joe Nelson 3c5fab009b Remove ancient test comments 2016-03-13 14:22:36 -07:00
Joe Nelson b858626e17 For correctness include charset=utf-8 in responses 2016-03-13 14:22:17 -07:00
Joe Nelson 330cc91645 Protect unicode values in requests 2016-03-13 14:20:54 -07:00
Joe Nelson 1037824e11 Merge pull request #523 from begriffs/single-proc-call
Prevent duplicate call to stored procs
2016-03-12 23:34:25 -08:00
Joe Nelson 4cc08a11e7 Prevent duplicate call to stored procs
Reuse a CTE for results of call
2016-03-12 18:28:36 -08:00
Joe Nelson 358254639a Merge @ruslantalpa's fk improved detection 2016-03-12 12:42:57 -08:00
Joe Nelson 43bc9bfa83 Merge pull request #522 from begriffs/full-jwt
Allow SQL functions to generate registered JWT claims
2016-03-12 12:26:36 -08:00
Joe Nelson a779e9eb8b Batch the sql commands to set local vars 2016-03-11 23:45:05 -08:00
Joe Nelson f67e195f76 Expose all claims via sql postgrest.claims 2016-03-11 20:51:22 -08:00
Joe Nelson 508d722fb2 Allow SQL functions to generate registered JWT claims 2016-03-10 21:58:43 -08:00
Joe Nelson 14d7364f4b Merge pull request #521 from dex-ethics/spelling
Spelling fixes in documentation
2016-03-09 12:15:28 -08:00
Remco Bloemen bfbce27a65 Spelling fixes in documentation 2016-03-09 15:47:12 +01:00
Joe Nelson 00a23058c8 Merge pull request #511 from dex-ethics/docker-exec
Use `CMD exec` in Dockerfile
2016-03-07 22:34:16 -08:00
Remco Bloemen 82c74ed21f Use CMD exec in Dockerfile
Without exec the `postgrest` process is not run with PID 1 (it
is a child process of the shell that starts it). This means
signals send to the docker (like `docker stop` or ^C) will
not be handled correctly.

However, Linux treats PID 1 as special and sets the SIGTERM
handler to ignore by default. It is also necessary to install
a SIGTERM handler.

This commit adds `exec` to resolve this problem, as per the
recommendation in the Dockerfile documentation:

https://docs.docker.com/engine/reference/builder/#shell-form-entrypoint-example
2016-03-07 18:30:40 +01:00
Joe Nelson 5f0b4977da Merge pull request #514 from dex-ethics/docs
Minor changes in documentation
2016-03-07 09:15:16 -08:00
Remco Bloemen 82214856b6 Split build and install in build from source instructions.
Stack refuses to build when run under sudo.
2016-03-07 17:54:37 +01:00
Remco Bloemen c09adb967a Use gen_random_uuid() in user management example.
The function uuid_generate_v4() is not available
without extensions.
2016-03-07 17:53:55 +01:00
Remco Bloemen e5d420b2db Gracefull exit on sigTERM
Like the sigINT that was already handled, postgrest
should gracefuly shut down on a sigTERM. This is a
common way of stopping processes, used amongst
others by docker.

See: https://stackoverflow.com/questions/4042201/how-does-sigint-relate-to-the-other-termination-signals
2016-03-07 17:45:53 +01:00
Joe Nelson ef021056c9 Merge pull request #497 from bobcolner/bobcolner-dockerfile
PostgREST Dockerfile
2016-03-05 13:20:05 -08:00
Bob Colner b7b082cd8e updated Dockerfile to use postgrest 3.1.0 2016-03-05 13:07:11 -08:00
Bob Colner a02632f18c Update Dockerfile 2016-03-05 12:53:33 -08:00
Ruslan Talpa e43ad54dbf Merge branch 'master' of https://github.com/begriffs/postgrest 2016-03-01 17:54:55 +02:00
Ruslan Talpa 7b94fb608d suggestions by @diogob 2016-03-01 08:08:57 +02:00
Ruslan Talpa 40eec0b2ff code beautify using stylish-haskell 2016-02-29 14:53:41 +02:00
Ruslan Talpa 77bec52be7 Fix compile notice 2016-02-29 14:11:48 +02:00
Ruslan Talpa 155d1dee6b changelog entry 2016-02-29 13:59:04 +02:00
Ruslan Talpa 0548d65911 main module of the executable needs to be Main, with PostgREST.Main build fails 2016-02-29 13:57:34 +02:00
Ruslan Talpa 40a30d7b02 Fix view column source detection 2016-02-29 13:10:33 +02:00
Ruslan Talpa 62af792add Add failing test to test correct view column detection 2016-02-29 11:37:23 +02:00
Bob Colner ef3e2511fe PostgRest Dockerfile
PostgRest Dockerfile with ENV parameter passthrough.
2016-02-17 10:41:34 -08:00
30 changed files with 510 additions and 233 deletions
+10
View File
@@ -7,6 +7,16 @@ This project adheres to [Semantic Versioning](http://semver.org/).
### Fixed ### Fixed
## [0.3.1.1] - 2016-03-28
### Fixed
- Preserve unicode values in insert,update,rpc (regression) - @begriffs
- Prevent duplicate call to stored procs (regression) - @begriffs
- Allow SQL functions to generate registered JWT claims - @begriffs
- Terminate gracefully on SIGTERM (for use in Docker) - @recmo
- Relation detection fix for views that depend on multiple tables - @ruslantalpa
- Avoid count on plurality=singular and allow multiple Prefer values - @ruslantalpa
## [0.3.1.0] - 2016-02-28 ## [0.3.1.0] - 2016-02-28
### Fixed ### Fixed
+27
View File
@@ -0,0 +1,27 @@
FROM debian:jessie
ENV POSTGREST_VERSION 0.3.1.0
ENV POSTGREST_SCHEMA public
ENV POSTGREST_ANONYMOUS postgres
ENV POSTGREST_JWT_SECRET thisisnotarealsecret
ENV POSTGREST_MAX_ROWS 1000000
ENV POSTGREST_POOL 200
RUN apt-get update && \
apt-get install -y tar xz-utils wget libpq-dev && \
apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
RUN wget http://github.com/begriffs/postgrest/releases/download/v${POSTGREST_VERSION}/postgrest-${POSTGREST_VERSION}-ubuntu.tar.xz && \
tar --xz -xvf postgrest-${POSTGREST_VERSION}-ubuntu.tar.xz && \
mv postgrest /usr/local/bin/postgrest && \
rm postgrest-${POSTGREST_VERSION}-ubuntu.tar.xz
CMD exec postgrest postgres://${PG_ENV_POSTGRES_USER}:${PG_ENV_POSTGRES_PASSWORD}@${PG_PORT_5432_TCP_ADDR}:${PG_PORT_5432_TCP_PORT}/${PG_ENV_POSTGRES_DB} \
--port 3000 \
--schema ${POSTGREST_SCHEMA} \
--anonymous ${POSTGREST_ANONYMOUS} \
--pool ${POSTGREST_POOL} \
--jwt-secret ${POSTGREST_JWT_SECRET} \
--max-rows ${POSTGREST_MAX_ROWS}
EXPOSE 3000
+1 -1
View File
@@ -10,7 +10,7 @@
}, },
"POSTGREST_VER": { "POSTGREST_VER": {
"description": "Version of PostgREST to deploy", "description": "Version of PostgREST to deploy",
"value": "0.3.1.0" "value": "0.3.1.1"
}, },
"DB_NAME": { "DB_NAME": {
"description": "Database name", "description": "Database name",
+2 -2
View File
@@ -153,7 +153,7 @@ similar way to our ```POST``` example.
<p>It's advisable to create a separate trigger for <code>UPDATE</code> and <code>INSERT</code> <p>It's advisable to create a separate trigger for <code>UPDATE</code> and <code>INSERT</code>
avoiding conditionals that decide which is the trigger current operation. avoiding conditionals that decide which is the trigger current operation.
This makes it easier to change code for (or even disable) one operation without intefering with others while This makes it easier to change code for (or even disable) one operation without interfering with others while
improving readability. improving readability.
</p> </p>
</div> </div>
@@ -186,7 +186,7 @@ basic field replacements, and not at all "incorrect."
* ❌ Cannot be cached or prefetched * ❌ Cannot be cached or prefetched
* ✅ Idempotent * ✅ Idempotent
Simply use the `DELETE` verb. All recors that match your filter Simply use the `DELETE` verb. All records that match your filter
will be removed. For instance deleting inactive users: will be removed. For instance deleting inactive users:
```HTTP ```HTTP
+1 -1
View File
@@ -43,7 +43,7 @@ ALTER TABLE users ADD role text NOT NULL DEFAULT 'customer';
``` ```
Besides the main user that PostgREST uses to connect to PostgreSQL Besides the main user that PostgREST uses to connect to PostgreSQL
and the anonymous user, we will need two aditional roles for our example: and the anonymous user, we will need two additional roles for our example:
* admin - to be used by users that access all the system rows. * admin - to be used by users that access all the system rows.
* customer - to be used when user has restricted access to database rows. * customer - to be used when user has restricted access to database rows.
+5 -5
View File
@@ -8,12 +8,12 @@ a username and password system on top of JWT using only plpgsql.
Future examples such as the multi-tenant blogging platform will use Future examples such as the multi-tenant blogging platform will use
the results from this example for their auth. We will build a system the results from this example for their auth. We will build a system
for users to sign up, log in, manage their accounts, and for admins for users to sign up, log in, manage their accounts, and for admins
to manange other people's accounts. We will also see how to trigger to manage other people's accounts. We will also see how to trigger
outside events like sending password reset emails. outside events like sending password reset emails.
Before jumping into the code, a little more about how the tokens Before jumping into the code, a little more about how the tokens
work. Every JWT contains cryptographically signed *claims*. PostgREST work. Every JWT contains cryptographically signed *claims*. PostgREST
cares specificaly about a claim called `role`. When a client includes cares specifically about a claim called `role`. When a client includes
a `role` claim PostgREST executes their request using that database a `role` claim PostgREST executes their request using that database
role. role.
@@ -224,7 +224,7 @@ begin
where token_type = 'reset' where token_type = 'reset'
and tokens.email = reset_password.email; and tokens.email = reset_password.email;
select uuid_generate_v4() into tok; select gen_random_uuid() into tok;
insert into basic_auth.tokens (token, token_type, email) insert into basic_auth.tokens (token, token_type, email)
values (tok, 'reset', reset_password.email); values (tok, 'reset', reset_password.email);
perform pg_notify('reset', perform pg_notify('reset',
@@ -251,7 +251,7 @@ basic_auth.send_validation() returns trigger
declare declare
tok uuid; tok uuid;
begin begin
select uuid_generate_v4() into tok; select gen_random_uuid() into tok;
insert into basic_auth.tokens (token, token_type, email) insert into basic_auth.tokens (token, token_type, email)
values (tok, 'validation', new.email); values (tok, 'validation', new.email);
perform pg_notify('validate', perform pg_notify('validate',
@@ -294,7 +294,7 @@ where actual.role = member_of.rolname;
-- is equal to email so that user can only see themselves -- is equal to email so that user can only see themselves
``` ```
Using this view clients can see themeslves and any other users with Using this view clients can see themselves and any other users with
the right db roles. This view does not yet support inserts or updates the right db roles. This view does not yet support inserts or updates
because not all the columns refer directly to underlying columns. because not all the columns refer directly to underlying columns.
Nor do we want it to be auto-updatable because it would allow an escalation Nor do we want it to be auto-updatable because it would allow an escalation
+3 -2
View File
@@ -55,7 +55,8 @@ sudo apt-get install -y libpq-dev
```bash ```bash
git clone https://github.com/begriffs/postgrest.git git clone https://github.com/begriffs/postgrest.git
cd postgrest cd postgrest
sudo stack install --install-ghc --local-bin-path /usr/local/bin stack build --install-ghc
sudo stack install --allow-different-user --local-bin-path /usr/local/bin
``` ```
* Run the server * Run the server
@@ -94,7 +95,7 @@ The complete list of options:
<code>secret</code> but do not use the default in production! <code>secret</code> but do not use the default in production!
Load-balanced PostgREST servers should share the same secret.</dd> Load-balanced PostgREST servers should share the same secret.</dd>
<dt>-p, --pool</dt> <dt>-o, --pool</dt>
<dd>Max connections to use in db pool. Defaults to to 10, but you <dd>Max connections to use in db pool. Defaults to to 10, but you
should find an optimal value for your db by running the SQL should find an optimal value for your db by running the SQL
command <code>show max_connections;</code></dd> command <code>show max_connections;</code></dd>
+13 -5
View File
@@ -2,7 +2,7 @@ name: postgrest
description: Reads the schema of a PostgreSQL database and creates RESTful routes description: Reads the schema of a PostgreSQL database and creates RESTful routes
for the tables and views, supporting all HTTP verbs that security for the tables and views, supporting all HTTP verbs that security
permits. permits.
version: 0.3.1.0 version: 0.3.1.1
synopsis: REST API for any Postgres database synopsis: REST API for any Postgres database
license: MIT license: MIT
license-file: LICENSE license-file: LICENSE
@@ -40,6 +40,8 @@ executable postgrest
, http-types , http-types
, interpolatedstring-perl6 , interpolatedstring-perl6
, jwt , jwt
, lens >=3.8 && < 5.0
, lens-aeson >= 1.0.0.0 && < 1.1.0.0
, mtl , mtl
, optparse-applicative >= 0.11 && < 0.13 , optparse-applicative >= 0.11 && < 0.13
, parsec , parsec
@@ -93,6 +95,8 @@ library
, http-types , http-types
, interpolatedstring-perl6 , interpolatedstring-perl6
, jwt , jwt
, lens
, lens-aeson
, mtl , mtl
, optparse-applicative , optparse-applicative
, parsec , parsec
@@ -104,12 +108,13 @@ library
, time , time
, unordered-containers , unordered-containers
, vector , vector
, wai
, wai-cors
, wai-extra
, wai-middleware-static
, HTTP , HTTP
, Ranged-sets , Ranged-sets
, wai >= 3.0.1
, wai-cors
, wai-extra
, wai-middleware-static >= 0.6.0
, warp >= 3.1.0
Other-Modules: Paths_postgrest Other-Modules: Paths_postgrest
Exposed-Modules: PostgREST.App Exposed-Modules: PostgREST.App
@@ -141,6 +146,7 @@ Test-Suite spec
, Feature.QueryLimitedSpec , Feature.QueryLimitedSpec
, Feature.RangeSpec , Feature.RangeSpec
, Feature.StructureSpec , Feature.StructureSpec
, Feature.UnicodeSpec
, Paths_postgrest , Paths_postgrest
, PostgREST.App , PostgREST.App
, PostgREST.Auth , PostgREST.Auth
@@ -175,6 +181,8 @@ Test-Suite spec
, http-types , http-types
, interpolatedstring-perl6 , interpolatedstring-perl6
, jwt , jwt
, lens
, lens-aeson
, monad-control , monad-control
, mtl , mtl
, optparse-applicative , optparse-applicative
+3 -4
View File
@@ -11,7 +11,6 @@ create role authenticator noinherit;
grant anon, author to authenticator; grant anon, author to authenticator;
create extension if not exists pgcrypto; create extension if not exists pgcrypto;
create extension if not exists "uuid-ossp";
-- We put things inside the basic_auth schema to hide -- We put things inside the basic_auth schema to hide
-- them from public view. Certain public procs/views will -- them from public view. Certain public procs/views will
@@ -97,7 +96,7 @@ basic_auth.send_validation() returns trigger
declare declare
tok uuid; tok uuid;
begin begin
select uuid_generate_v4() into tok; select gen_random_uuid() into tok;
insert into basic_auth.tokens (token, token_type, email) insert into basic_auth.tokens (token, token_type, email)
values (tok, 'validation', new.email); values (tok, 'validation', new.email);
perform pg_notify('validate', perform pg_notify('validate',
@@ -175,7 +174,7 @@ begin
where token_type = 'reset' where token_type = 'reset'
and tokens.email = request_password_reset.email; and tokens.email = request_password_reset.email;
select uuid_generate_v4() into tok; select gen_random_uuid() into tok;
insert into basic_auth.tokens (token, token_type, email) insert into basic_auth.tokens (token, token_type, email)
values (tok, 'reset', request_password_reset.email); values (tok, 'reset', request_password_reset.email);
perform pg_notify('reset', perform pg_notify('reset',
@@ -215,7 +214,7 @@ begin
where token_type = 'reset' where token_type = 'reset'
and tokens.email = reset_password.email; and tokens.email = reset_password.email;
select uuid_generate_v4() into tok; select gen_random_uuid() into tok;
insert into basic_auth.tokens (token, token_type, email) insert into basic_auth.tokens (token, token_type, email)
values (tok, 'reset', reset_password.email); values (tok, 'reset', reset_password.email);
perform pg_notify('reset', perform pg_notify('reset',
+8 -4
View File
@@ -41,8 +41,8 @@ data PreferRepresentation = Full | HeadersOnly | None deriving Eq
-- route responses and upload payloads -- route responses and upload payloads
data ContentType = ApplicationJSON | TextCSV deriving Eq data ContentType = ApplicationJSON | TextCSV deriving Eq
instance Show ContentType where instance Show ContentType where
show ApplicationJSON = "application/json" show ApplicationJSON = "application/json; charset=utf-8"
show TextCSV = "text/csv" show TextCSV = "text/csv; charset=utf-8"
{-| {-|
Describes what the user wants to do. This data type is a Describes what the user wants to do. This data type is a
@@ -130,7 +130,7 @@ userApiRequest schema req reqBody =
, iPayload = relevantPayload , iPayload = relevantPayload
, iPreferRepresentation = representation , iPreferRepresentation = representation
, iPreferSingular = singular , iPreferSingular = singular
, iPreferCount = not $ hasPrefer "count=none" , iPreferCount = not $ singular || hasPrefer "count=none"
, iFilters = [ (k, fromJust v) | (k,v) <- qParams, k `notElem` ["select", "order"], isJust v ] , iFilters = [ (k, fromJust v) | (k,v) <- qParams, k `notElem` ["select", "order"], isJust v ]
, iSelect = if method == "DELETE" , iSelect = if method == "DELETE"
then "*" then "*"
@@ -145,7 +145,11 @@ userApiRequest schema req reqBody =
hdrs = requestHeaders req hdrs = requestHeaders req
qParams = [(cs k, cs <$> v)|(k,v) <- queryString req] qParams = [(cs k, cs <$> v)|(k,v) <- queryString req]
lookupHeader = flip lookup hdrs lookupHeader = flip lookup hdrs
hasPrefer val = any (\(h,v) -> h == "Prefer" && v == val) hdrs hasPrefer :: T.Text -> Bool
hasPrefer val = any (\(h,v) -> h == "Prefer" && val `elem` split v) hdrs
where
split :: BS.ByteString -> [T.Text]
split = map T.strip . T.split (==';') . cs
singular = hasPrefer "plurality=singular" singular = hasPrefer "plurality=singular"
representation representation
| hasPrefer "return=representation" = Full | hasPrefer "return=representation" = Full
+1 -1
View File
@@ -249,7 +249,7 @@ contentRangeH frm to total =
fromInRange = frm <= to fromInRange = frm <= to
jsonH :: Header jsonH :: Header
jsonH = (hContentType, "application/json") jsonH = (hContentType, "application/json; charset=utf-8")
formatRelationError :: Text -> Text formatRelationError :: Text -> Text
formatRelationError = formatGeneralError formatRelationError = formatGeneralError
+32 -30
View File
@@ -18,19 +18,20 @@ module PostgREST.Auth (
, tokenJWT , tokenJWT
) where ) where
import Control.Monad (join) import Control.Lens
import Data.Aeson (Value (..), Object) import Data.Aeson (Value (..), parseJSON, toJSON)
import Data.Aeson.Types (emptyObject, emptyArray) import Data.Aeson.Lens
import Data.Aeson.Types (parseMaybe, emptyObject, emptyArray)
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import Data.Vector as V (null, head) import qualified Data.Vector as V
import Data.Map as M (fromList, toList) import qualified Data.HashMap.Strict as M
import Data.Maybe (fromMaybe)
import Data.Monoid ((<>)) import Data.Monoid ((<>))
import Data.String.Conversions (cs) import Data.String.Conversions (cs)
import Data.Text (Text) import Data.Text (Text)
import Data.Time.Clock (NominalDiffTime) import Data.Time.Clock (NominalDiffTime)
import PostgREST.QueryBuilder (pgFmtLit, pgFmtIdent, unquoted) import PostgREST.QueryBuilder (pgFmtIdent, pgFmtLit, unquoted)
import qualified Web.JWT as JWT import qualified Web.JWT as JWT
import qualified Data.HashMap.Lazy as H
{-| {-|
Receives a map of JWT claims and returns a list Receives a map of JWT claims and returns a list
@@ -39,12 +40,12 @@ import qualified Data.HashMap.Lazy as H
this one is mapped to a SET ROLE statement. this one is mapped to a SET ROLE statement.
In case there is any problem decoding the JWT it returns Nothing. In case there is any problem decoding the JWT it returns Nothing.
-} -}
claimsToSQL :: JWT.ClaimsMap -> [BS.ByteString] claimsToSQL :: M.HashMap Text Value -> [BS.ByteString]
claimsToSQL = map setVar . toList claimsToSQL = map setVar . M.toList
where where
setVar ("role", String val) = setRole val setVar ("role", String val) = setRole val
setVar (k, val) = "set local postgrest.claims." <> cs (pgFmtIdent k) <> setVar (k, val) = "set local " <> cs (pgFmtIdent $ "postgrest.claims." <> k)
" = " <> cs (valueToVariable val) <> ";" <> " = " <> cs (valueToVariable val) <> ";"
valueToVariable = pgFmtLit . unquoted valueToVariable = pgFmtLit . unquoted
{-| {-|
@@ -52,19 +53,22 @@ claimsToSQL = map setVar . toList
returns a map of JWT claims returns a map of JWT claims
In case there is any problem decoding the JWT it returns Nothing. In case there is any problem decoding the JWT it returns Nothing.
-} -}
jwtClaims :: JWT.Secret -> Text -> NominalDiffTime -> Maybe JWT.ClaimsMap
jwtClaims :: JWT.Secret -> Text -> NominalDiffTime -> Either Text (M.HashMap Text Value)
jwtClaims secret input time = jwtClaims secret input time =
case join $ claim JWT.exp of case mClaims of
Just expires -> Nothing -> Right M.empty
if JWT.secondsSinceEpoch expires > time Just claims -> do
then customClaims let mExp = claims ^? key "exp" . _Integer
else Nothing expired = fromMaybe False $ (<= time) . fromInteger <$> mExp
_ -> customClaims if expired
where then Left "JWT expired"
decoded = JWT.decodeAndVerifySignature secret input else Right (value2map claims)
claim :: (JWT.JWTClaimsSet -> a) -> Maybe a where
claim prop = prop . JWT.claims <$> decoded mClaims = toJSON . JWT.claims <$> JWT.decodeAndVerifySignature secret input
customClaims = claim JWT.unregisteredClaims value2map (Object o) = o
value2map _ = M.empty
{-| Receives the name of a role and returns a SET ROLE statement -} {-| Receives the name of a role and returns a SET ROLE statement -}
setRole :: Text -> BS.ByteString setRole :: Text -> BS.ByteString
@@ -76,10 +80,8 @@ setRole r = "set local role " <> cs (pgFmtLit r) <> ";"
and returns a signed JWT. and returns a signed JWT.
-} -}
tokenJWT :: JWT.Secret -> Value -> Text tokenJWT :: JWT.Secret -> Value -> Text
tokenJWT secret (Array a) = JWT.encodeSigned JWT.HS256 secret tokenJWT secret (Array arr) =
JWT.def { JWT.unregisteredClaims = fromHashMap o } let obj = if V.null arr then emptyObject else V.head arr
where jcs = parseMaybe parseJSON obj :: Maybe JWT.JWTClaimsSet in
Object o = if V.null a then emptyObject else V.head a JWT.encodeSigned JWT.HS256 secret $ fromMaybe JWT.def jcs
fromHashMap :: Object -> JWT.ClaimsMap tokenJWT secret _ = tokenJWT secret emptyArray
fromHashMap = M.fromList . H.toList
tokenJWT secret _ = tokenJWT secret emptyArray
+1 -1
View File
@@ -30,9 +30,9 @@ import Network.Wai
import Network.Wai.Middleware.Cors (CorsResourcePolicy (..)) import Network.Wai.Middleware.Cors (CorsResourcePolicy (..))
import Options.Applicative import Options.Applicative
import Paths_postgrest (version) import Paths_postgrest (version)
import Prelude
import Safe (readMay) import Safe (readMay)
import Web.JWT (Secret, secret) import Web.JWT (Secret, secret)
import Prelude
-- | Data type to store all command line options -- | Data type to store all command line options
data AppConfig = AppConfig { data AppConfig = AppConfig {
+81 -72
View File
@@ -10,23 +10,25 @@ module PostgREST.DbStructure (
, doesProcReturnJWT , doesProcReturnJWT
) where ) where
import qualified Hasql.Query as H import qualified Hasql.Decoders as HD
import qualified Hasql.Encoders as HE import qualified Hasql.Encoders as HE
import qualified Hasql.Decoders as HD import qualified Hasql.Query as H
import Control.Applicative import Control.Applicative
import Control.Monad (join, replicateM) import Control.Monad (join, replicateM)
import Data.Functor.Contravariant (contramap) import Data.Functor.Contravariant (contramap)
import Text.InterpolatedString.Perl6 (q) import Data.List (elemIndex, find, sort,
import Data.List (elemIndex, find, subsequences, sort, transpose) subsequences, transpose)
import Data.Maybe (fromMaybe, fromJust, isJust, mapMaybe, listToMaybe) import Data.Maybe (fromJust, fromMaybe, isJust,
listToMaybe, mapMaybe)
import Data.Monoid import Data.Monoid
import Data.Text (Text, split) import Data.Text (Text, split)
import qualified Hasql.Session as H import qualified Hasql.Session as H
import PostgREST.Types import PostgREST.Types
import Text.InterpolatedString.Perl6 (q)
import GHC.Exts (groupWith) import Data.Int (Int32)
import Data.Int (Int32) import GHC.Exts (groupWith)
import Prelude import Prelude
getDbStructure :: Schema -> H.Session DbStructure getDbStructure :: Schema -> H.Session DbStructure
@@ -556,69 +558,76 @@ allSynonyms :: [Column] -> H.Query () [(Column,Column)]
allSynonyms cols = allSynonyms cols =
H.statement sql HE.unit (decodeSynonyms cols) True H.statement sql HE.unit (decodeSynonyms cols) True
where where
-- query explanation at https://gist.github.com/ruslantalpa/2eab8c930a65e8043d8f
sql = [q| sql = [q|
WITH synonyms AS ( WITH view_columns AS (
/* SELECT
-- CTE to replace the view from information_schema because the information in it depended on the logged in role c.oid AS view_oid,
-- notice the commented line a.attname::information_schema.sql_identifier AS column_name
*/ FROM pg_attribute a
WITH view_column_usage AS ( JOIN pg_class c ON a.attrelid = c.oid
SELECT DISTINCT JOIN pg_namespace nc ON c.relnamespace = nc.oid
CAST(current_database() AS character varying) AS view_catalog, WHERE
CAST(nv.nspname AS character varying) AS view_schema, NOT pg_is_other_temp_schema(nc.oid)
CAST(v.relname AS character varying) AS view_name, AND a.attnum > 0
CAST(current_database() AS character varying) AS table_catalog, AND NOT a.attisdropped
CAST(nt.nspname AS character varying) AS table_schema, AND (c.relkind = 'v'::"char")
CAST(t.relname AS character varying) AS table_name, AND nc.nspname NOT IN ('information_schema', 'pg_catalog')
CAST(a.attname AS character varying) AS column_name ),
FROM pg_namespace nv, pg_class v, pg_depend dv, view_column_usage AS (
pg_depend dt, pg_class t, pg_namespace nt, SELECT DISTINCT
pg_attribute a v.oid as view_oid,
WHERE nv.oid = v.relnamespace nv.nspname::information_schema.sql_identifier AS view_schema,
AND v.relkind = 'v' v.relname::information_schema.sql_identifier AS view_name,
AND v.oid = dv.refobjid nt.nspname::information_schema.sql_identifier AS table_schema,
AND dv.refclassid = 'pg_catalog.pg_class'::regclass t.relname::information_schema.sql_identifier AS table_name,
AND dv.classid = 'pg_catalog.pg_rewrite'::regclass a.attname::information_schema.sql_identifier AS column_name,
AND dv.deptype = 'i' pg_get_viewdef(v.oid)::information_schema.character_data AS view_definition
AND dv.objid = dt.objid FROM pg_namespace nv
AND dv.refobjid <> dt.refobjid JOIN pg_class v ON nv.oid = v.relnamespace
AND dt.classid = 'pg_catalog.pg_rewrite'::regclass JOIN pg_depend dv ON v.oid = dv.refobjid
AND dt.refclassid = 'pg_catalog.pg_class'::regclass JOIN pg_depend dt ON dv.objid = dt.objid
AND dt.refobjid = t.oid JOIN pg_class t ON dt.refobjid = t.oid
AND t.relnamespace = nt.oid JOIN pg_namespace nt ON t.relnamespace = nt.oid
AND t.relkind IN ('r', 'v', 'f') JOIN pg_attribute a ON t.oid = a.attrelid AND dt.refobjsubid = a.attnum
AND t.oid = a.attrelid
AND dt.refobjsubid = a.attnum WHERE
/*--AND pg_has_role(t.relowner, 'USAGE')*/ nv.nspname not in ('information_schema', 'pg_catalog')
) AND v.relkind = 'v'::"char"
SELECT AND dv.refclassid = 'pg_class'::regclass::oid
vcu.table_schema AS src_table_schema, AND dv.classid = 'pg_rewrite'::regclass::oid
vcu.table_name AS src_table_name, AND dv.deptype = 'i'::"char"
vcu.column_name AS src_column_name, AND dv.refobjid <> dt.refobjid
view.schemaname AS syn_table_schema, AND dt.classid = 'pg_rewrite'::regclass::oid
view.viewname AS syn_table_name, AND dt.refclassid = 'pg_class'::regclass::oid
view.definition AS view_definition AND (t.relkind = ANY (ARRAY['r'::"char", 'v'::"char", 'f'::"char"]))
FROM ),
pg_catalog.pg_views AS view, candidates AS (
view_column_usage AS vcu SELECT
WHERE vcu.*,
view.schemaname = vcu.view_schema AND (
view.viewname = vcu.view_name AND SELECT CASE WHEN match IS NOT NULL THEN coalesce(match[7], match[4]) END
view.schemaname NOT IN ('pg_catalog', 'information_schema') FROM REGEXP_MATCHES(
/*--AND (SELECT COUNT(*) FROM information_schema.view_table_usage WHERE view_schema = view.schemaname AND view_name = view.viewname) = 1*/ CONCAT('SELECT ', SPLIT_PART(vcu.view_definition, 'SELECT', 2)),
CONCAT('SELECT.*?((',vcu.table_name,')|(\w+))\.(', vcu.column_name, ')(\sAS\s(")?([^"]+)\6)?.*?FROM.*?',vcu.table_schema,'\.(\2|',vcu.table_name,'\s+(AS\s)?\3)'),
'ns'
) match
) AS view_column_name
FROM view_column_usage AS vcu
) )
SELECT SELECT
src_table_schema, src_table_name, src_column_name, c.table_schema,
syn_table_schema, syn_table_name, c.table_name,
(regexp_matches(view_definition, CONCAT('\.(', src_column_name, ')(?=,|$)'), 'gn'))[1] AS syn_column_name c.column_name AS table_column_name,
FROM synonyms c.view_schema,
UNION ( c.view_name,
SELECT c.view_column_name
src_table_schema, src_table_name, src_column_name, FROM view_columns AS vc, candidates AS c
syn_table_schema, syn_table_name, WHERE
(regexp_matches(view_definition, CONCAT('\.', src_column_name, '\sAS\s("?)(.+?)\1(,|$)'), 'gn'))[2] AS syn_column_name /* " <- for syntax highlighting */ vc.view_oid = c.view_oid AND
FROM synonyms vc.column_name = c.view_column_name
) |] ORDER BY c.view_schema, c.view_name, c.table_name, c.view_column_name
|]
synonymFromRow :: [Column] -> (Text,Text,Text,Text,Text,Text) -> Maybe (Column,Column) synonymFromRow :: [Column] -> (Text,Text,Text,Text,Text,Text) -> Maybe (Column,Column)
synonymFromRow allCols (s1,t1,c1,s2,t2,c2) = (,) <$> col1 <*> col2 synonymFromRow allCols (s1,t1,c1,s2,t2,c2) = (,) <$> col1 <*> col2
+1 -1
View File
@@ -12,8 +12,8 @@ import Data.Monoid ((<>))
import Data.String.Conversions (cs) import Data.String.Conversions (cs)
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import qualified Hasql.Session as H
import qualified Hasql.Pool as P import qualified Hasql.Pool as P
import qualified Hasql.Session as H
import Network.HTTP.Types.Header import Network.HTTP.Types.Header
import qualified Network.HTTP.Types.Status as HT import qualified Network.HTTP.Types.Status as HT
import Network.Wai (Response, responseLBS) import Network.Wai (Response, responseLBS)
+5 -4
View File
@@ -60,10 +60,11 @@ main = do
#ifndef mingw32_HOST_OS #ifndef mingw32_HOST_OS
tid <- myThreadId tid <- myThreadId
void $ installHandler keyboardSignal (Catch $ do forM_ [sigINT, sigTERM] $ \sig ->
P.release pool void $ installHandler sig (Catch $ do
throwTo tid UserInterrupt P.release pool
) Nothing throwTo tid UserInterrupt
) Nothing
#endif #endif
result <- P.use pool $ do result <- P.use pool $ do
+23 -20
View File
@@ -3,52 +3,55 @@
module PostgREST.Middleware where module PostgREST.Middleware where
import Control.Monad (unless)
import qualified Data.ByteString as BS
import qualified Data.HashMap.Strict as M
import Data.Maybe (fromMaybe) import Data.Maybe (fromMaybe)
import Data.Text
import Data.String.Conversions (cs) import Data.String.Conversions (cs)
import Data.Text
import Data.Time.Clock (NominalDiffTime) import Data.Time.Clock (NominalDiffTime)
import qualified Hasql.Transaction as H import qualified Hasql.Transaction as H
import Network.HTTP.Types.Header (hAccept, hAuthorization) import Network.HTTP.Types.Header (hAccept, hAuthorization)
import Network.HTTP.Types.Status (status415, status400) import Network.HTTP.Types.Status (status400, status415)
import Network.Wai (Application, Request (..), Response, import Network.Wai (Application, Request (..),
requestHeaders) Response, requestHeaders)
import Network.Wai.Middleware.Cors (cors) import Network.Wai.Middleware.Cors (cors)
import Network.Wai.Middleware.Gzip (def, gzip) 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 (setRole, jwtClaims, claimsToSQL)
import PostgREST.Config (AppConfig (..), corsPolicy) import PostgREST.Config (AppConfig (..), corsPolicy)
import PostgREST.Error (errResponse) import PostgREST.Error (errResponse)
import Prelude hiding(concat) import Prelude hiding (concat, null)
import qualified Data.Map.Lazy as M
runWithClaims :: AppConfig -> NominalDiffTime -> 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 H.sql setAnon
case split (== ' ') (cs auth) of let tokenStr = case split (== ' ') (cs auth) of
("Bearer" : tokenStr : _) -> ("Bearer" : t : _) -> t
case jwtClaims jwtSecret tokenStr time of _ -> ""
Just claims -> eClaims = jwtClaims jwtSecret tokenStr time
if M.member "role" claims case eClaims of
then do Left e -> clientErr e
mapM_ H.sql $ claimsToSQL claims Right claims ->
app req if M.null claims && not (null tokenStr)
else invalidJWT then clientErr "Invalid JWT"
_ -> invalidJWT else do
_ -> app req let cmdBatch = mconcat $ claimsToSQL claims
unless (BS.null cmdBatch) (H.sql cmdBatch)
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 = cs $ configAnonRole conf
setAnon = setRole anon setAnon = setRole anon
invalidJWT = return $ errResponse status400 "Invalid JWT" clientErr = return . errResponse status400
unsupportedAccept :: Application -> Application unsupportedAccept :: Application -> Application
unsupportedAccept app req respond = unsupportedAccept app req respond =
+2 -2
View File
@@ -3,14 +3,14 @@ module PostgREST.Parsers
-- ) -- )
where where
import Control.Applicative hiding ((<$>)) import Control.Applicative hiding ((<$>))
import Data.Monoid import Data.Monoid
import Data.String.Conversions (cs) import Data.String.Conversions (cs)
import Data.Text (Text) import Data.Text (Text)
import Data.Tree import Data.Tree
import PostgREST.QueryBuilder (operators)
import PostgREST.Types import PostgREST.Types
import Text.ParserCombinators.Parsec hiding (many, (<|>)) import Text.ParserCombinators.Parsec hiding (many, (<|>))
import PostgREST.QueryBuilder (operators)
pRequestSelect :: Text -> Parser ReadRequest pRequestSelect :: Text -> Parser ReadRequest
pRequestSelect rootNodeName = do pRequestSelect rootNodeName = do
+21 -13
View File
@@ -43,6 +43,7 @@ import Data.List (find, (\\))
import Data.Monoid ((<>)) import Data.Monoid ((<>))
import Data.Text (Text, intercalate, unwords, replace, isInfixOf, toLower, split) import Data.Text (Text, intercalate, unwords, replace, isInfixOf, toLower, split)
import qualified Data.Text as T (map, takeWhile) import qualified Data.Text as T (map, takeWhile)
import qualified Data.Text.Encoding as T
import Data.String.Conversions (cs) import Data.String.Conversions (cs)
import Control.Applicative ((<|>)) import Control.Applicative ((<|>))
import Control.Monad (join) import Control.Monad (join)
@@ -93,7 +94,7 @@ encodeUniformObjs =
createReadStatement :: SqlQuery -> SqlQuery -> NonnegRange -> Bool -> Bool -> Bool -> createReadStatement :: SqlQuery -> SqlQuery -> NonnegRange -> Bool -> Bool -> Bool ->
H.Query () ResultsWithCount H.Query () ResultsWithCount
createReadStatement selectQuery countQuery range isSingle countTotal asCsv = createReadStatement selectQuery countQuery range isSingle countTotal asCsv =
H.statement sql HE.unit decodeStandard True unicodeStatement sql HE.unit decodeStandard True
where where
sql = [qc| sql = [qc|
WITH {sourceCTEName} AS ({selectQuery}) SELECT {cols} WITH {sourceCTEName} AS ({selectQuery}) SELECT {cols}
@@ -116,7 +117,7 @@ createWriteStatement :: QualifiedIdentifier -> SqlQuery -> SqlQuery -> Bool ->
createWriteStatement _ _ _ _ _ _ _ (PayloadParseError _) = undefined createWriteStatement _ _ _ _ _ _ _ (PayloadParseError _) = undefined
createWriteStatement _ _ mutateQuery _ None createWriteStatement _ _ mutateQuery _ None
_ _ (PayloadJSON (UniformObjects _)) = _ _ (PayloadJSON (UniformObjects _)) =
H.statement sql encodeUniformObjs decodeStandardMay True unicodeStatement sql encodeUniformObjs decodeStandardMay True
where where
sql = [qc| sql = [qc|
WITH {sourceCTEName} AS ({mutateQuery}) WITH {sourceCTEName} AS ({mutateQuery})
@@ -124,7 +125,7 @@ createWriteStatement _ _ mutateQuery _ None
createWriteStatement qi _ mutateQuery isSingle HeadersOnly createWriteStatement qi _ mutateQuery isSingle HeadersOnly
pKeys _ (PayloadJSON (UniformObjects _)) = pKeys _ (PayloadJSON (UniformObjects _)) =
H.statement sql encodeUniformObjs decodeStandardMay True unicodeStatement sql encodeUniformObjs decodeStandardMay True
where where
sql = [qc| sql = [qc|
WITH {sourceCTEName} AS ({mutateQuery} RETURNING {fromQi qi}.*) WITH {sourceCTEName} AS ({mutateQuery} RETURNING {fromQi qi}.*)
@@ -139,7 +140,7 @@ createWriteStatement qi _ mutateQuery isSingle HeadersOnly
createWriteStatement qi selectQuery mutateQuery isSingle Full createWriteStatement qi selectQuery mutateQuery isSingle Full
pKeys asCsv (PayloadJSON (UniformObjects _)) = pKeys asCsv (PayloadJSON (UniformObjects _)) =
H.statement sql encodeUniformObjs decodeStandardMay True unicodeStatement sql encodeUniformObjs decodeStandardMay True
where where
sql = [qc| sql = [qc|
WITH {sourceCTEName} AS ({mutateQuery} RETURNING {fromQi qi}.*) WITH {sourceCTEName} AS ({mutateQuery} RETURNING {fromQi qi}.*)
@@ -206,20 +207,24 @@ addJoinConditions schema (Node (query, (n, r)) forest) =
type ProcResults = (Maybe Int64, Int64, JSON.Value) type ProcResults = (Maybe Int64, Int64, JSON.Value)
callProc :: QualifiedIdentifier -> JSON.Object -> NonnegRange -> Bool -> H.Query () (Maybe ProcResults) callProc :: QualifiedIdentifier -> JSON.Object -> NonnegRange -> Bool -> H.Query () (Maybe ProcResults)
callProc qi params range countTotal = callProc qi params range countTotal =
H.statement sql HE.unit decodeProc True unicodeStatement sql HE.unit decodeProc True
where where
sql = [qc| SELECT sql = [qc|
{countQuery} as countTotal, WITH t AS (select * {_callSql})
{countResult} as countResult, SELECT
{_countExpr} as countTotal,
pg_catalog.count(1) as countResult,
array_to_json( array_to_json(
coalesce(array_agg(row_to_json(t)), '\{}') coalesce(array_agg(row_to_json(r)), '\{}')
)::character varying )::character varying
from (select * {_callSql} {limitF range}) t |] FROM (select * from t {limitF range}) r;
|]
_args = intercalate "," $ map _assignment (HM.toList params) _args = intercalate "," $ map _assignment (HM.toList params)
_assignment (n,v) = pgFmtIdent n <> ":=" <> insertableValue v _assignment (n,v) = pgFmtIdent n <> ":=" <> insertableValue v
_callSql = [qc| from {fromQi qi}({_args}) |] :: BS.ByteString _callSql = [qc| from {fromQi qi}({_args}) |] :: Text
countQuery = if countTotal then [qc| (select pg_catalog.count(1) {_callSql} c) |] else "null::bigint" :: BS.ByteString _countExpr = if countTotal
countResult = "pg_catalog.count(t)" :: BS.ByteString then "(select pg_catalog.count(1) from t)"
else "null::bigint" :: Text
decodeProc = HD.maybeRow procRow decodeProc = HD.maybeRow procRow
procRow = (,,) <$> HD.nullableValue HD.int8 <*> HD.value HD.int8 procRow = (,,) <$> HD.nullableValue HD.int8 <*> HD.value HD.int8
<*> HD.value HD.json <*> HD.value HD.json
@@ -435,6 +440,9 @@ getJoinConditions (Relation t cols ft fcs typ lt lc1 lc2) =
toFilter :: Text -> Text -> Column -> Column -> Filter toFilter :: Text -> Text -> Column -> Column -> Filter
toFilter tb ftb c fc = Filter (colName c, Nothing) "=" (VForeignKey (QualifiedIdentifier s tb) (ForeignKey fc{colTable=(colTable fc){tableName=ftb}})) toFilter tb ftb c fc = Filter (colName c, Nothing) "=" (VForeignKey (QualifiedIdentifier s tb) (ForeignKey fc{colTable=(colTable fc){tableName=ftb}}))
unicodeStatement :: Text -> HE.Params a -> HD.Result b -> Bool -> H.Query a b
unicodeStatement = H.statement . T.encodeUtf8
emptyOnNull :: Text -> [a] -> Text emptyOnNull :: Text -> [a] -> Text
emptyOnNull val x = if null x then "" else val emptyOnNull val x = if null x then "" else val
+8 -8
View File
@@ -1,16 +1,16 @@
module PostgREST.Types where module PostgREST.Types where
import Data.Text import Data.Aeson
import Data.Tree
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString as BS import qualified Data.ByteString as BS
import qualified Data.ByteString.Lazy as BL
import Data.Int (Int32)
import Data.Text
import Data.Tree
import qualified Data.Vector as V import qualified Data.Vector as V
import Data.Aeson
import Data.Int (Int32)
data DbStructure = DbStructure { data DbStructure = DbStructure {
dbTables :: [Table] dbTables :: [Table]
, dbColumns :: [Column] , dbColumns :: [Column]
, dbRelations :: [Relation] , dbRelations :: [Relation]
, dbPrimaryKeys :: [PrimaryKey] , dbPrimaryKeys :: [PrimaryKey]
} deriving (Show, Eq) } deriving (Show, Eq)
+1 -1
View File
@@ -9,7 +9,7 @@ extra-deps:
- postgresql-error-codes-1 - postgresql-error-codes-1
- postgresql-binary-0.8.1 - postgresql-binary-0.8.1
ghc-options: ghc-options:
postgrest: -O1 -Werror -Wall -fwarn-monomorphism-restriction -fwarn-missing-exported-sigs -fwarn-identities postgrest: -O2 -Werror -Wall -fwarn-monomorphism-restriction -fwarn-missing-exported-sigs -fwarn-identities
packages: packages:
- '.' - '.'
+17 -1
View File
@@ -21,9 +21,25 @@ spec = describe "authorization" $ do
`shouldRespondWith` ResponseMatcher { `shouldRespondWith` ResponseMatcher {
matchBody = Just [json| {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqZG9lIn0.y4vZuu1dDdwAl0-S00MCRWRYMlJ5YAMSir6Es6WtWx0"} |] matchBody = Just [json| {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqZG9lIn0.y4vZuu1dDdwAl0-S00MCRWRYMlJ5YAMSir6Es6WtWx0"} |]
, matchStatus = 200 , matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json"] , matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"]
} }
it "sql functions can encode custom and standard claims" $
post "/rpc/jwt_test" "{}"
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| {"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmdW4iLCJqdGkiOiJmb28iLCJuYmYiOjEzMDA4MTkzODAsImV4cCI6MTMwMDgxOTM4MCwiaHR0cDovL3Bvc3RncmVzdC5jb20vZm9vIjp0cnVlLCJpc3MiOiJqb2UiLCJyb2xlIjoicG9zdGdyZXN0X3Rlc3QiLCJpYXQiOjEzMDA4MTkzODAsImF1ZCI6ImV2ZXJ5b25lIn0._tQCF79-ZZGMlLktd3csM_bVaiMg7A8YvIb6K2hcu5w"} |]
, matchStatus = 200
, matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"]
}
it "sql functions can read custom and standard claims variables" $ do
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmdW4iLCJqdGkiOiJmb28iLCJuYmYiOjEzMDA4MTkzODAsImV4cCI6OTk5OTk5OTk5OSwiaHR0cDovL3Bvc3RncmVzdC5jb20vZm9vIjp0cnVlLCJpc3MiOiJqb2UiLCJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWF0IjoxMzAwODE5MzgwLCJhdWQiOiJldmVyeW9uZSJ9.AQmCA7CMScvfaDRMqRPeUY6eNf--69gpW-kxaWfq9X0"
request methodPost "/rpc/reveal_big_jwt" [auth] "{}"
`shouldRespondWith` [json| [
{"sub":"fun", "jti":"foo", "nbf":1300819380, "exp":9999999999,
"http://postgrest.com/foo":true, "iss":"joe", "iat":1300819380,
"aud":"everyone"}] |]
it "allows users with permissions to see their tables" $ do it "allows users with permissions to see their tables" $ do
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqZG9lIn0.y4vZuu1dDdwAl0-S00MCRWRYMlJ5YAMSir6Es6WtWx0" let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqZG9lIn0.y4vZuu1dDdwAl0-S00MCRWRYMlJ5YAMSir6Es6WtWx0"
request methodGet "/authors_only" [auth] "" request methodGet "/authors_only" [auth] ""
+33 -42
View File
@@ -9,10 +9,11 @@ import SpecHelper
import qualified Data.Aeson as JSON import qualified Data.Aeson as JSON
import Data.Maybe (fromJust) import Data.Maybe (fromJust)
import Data.Monoid ((<>))
import Text.Heredoc import Text.Heredoc
import Network.HTTP.Types.Header import Network.HTTP.Types.Header
import Network.HTTP.Types import Network.HTTP.Types
import Control.Monad (replicateM_) import Control.Monad (replicateM_, void)
import TestTypes(IncPK(..), CompoundPK(..)) import TestTypes(IncPK(..), CompoundPK(..))
import Network.Wai (Application) import Network.Wai (Application)
@@ -41,7 +42,7 @@ spec = do
} |] `shouldRespondWith` ResponseMatcher { } |] `shouldRespondWith` ResponseMatcher {
matchBody = Just [str|{"integer":14,"varchar":"testing!"}|] matchBody = Just [str|{"integer":14,"varchar":"testing!"}|]
, matchStatus = 201 , matchStatus = 201
, matchHeaders = ["Content-Type" <:> "application/json"] , matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8"]
} }
it "includes related data after insert" $ it "includes related data after insert" $
@@ -49,7 +50,7 @@ spec = do
[str|{"id":6,"name":"New Project","client_id":2}|] `shouldRespondWith` ResponseMatcher { [str|{"id":6,"name":"New Project","client_id":2}|] `shouldRespondWith` ResponseMatcher {
matchBody = Just [str|{"id":6,"name":"New Project","clients":{"id":2,"name":"Apple"}}|] matchBody = Just [str|{"id":6,"name":"New Project","clients":{"id":2,"name":"Apple"}}|]
, matchStatus = 201 , matchStatus = 201
, matchHeaders = ["Content-Type" <:> "application/json", "Location" <:> "/projects?id=eq.6"] , matchHeaders = ["Content-Type" <:> "application/json; charset=utf-8", "Location" <:> "/projects?id=eq.6"]
} }
@@ -145,13 +146,6 @@ spec = do
, matchHeaders = ["Location" <:> [str|/json?data=eq.{"foo":"bar"}|]] , matchHeaders = ["Location" <:> [str|/json?data=eq.{"foo":"bar"}|]]
} }
-- TODO! the test above seems right, why was the one below working before and not now
-- p <- request methodPost "/json" [("Prefer", "return=representation")] inserted
-- liftIO $ do
-- simpleBody p `shouldBe` inserted
-- simpleHeaders p `shouldSatisfy` matchHeader hLocation "/json\\?data=eq\\.%7B%22foo%22%3A%22bar%22%7D"
-- simpleStatus p `shouldBe` created201
it "serializes nested array" $ do it "serializes nested array" $ do
let inserted = [json| { "data": [1,2,3] } |] let inserted = [json| { "data": [1,2,3] } |]
request methodPost "/json" request methodPost "/json"
@@ -162,12 +156,6 @@ spec = do
, matchStatus = 201 , matchStatus = 201
, matchHeaders = ["Location" <:> [str|/json?data=eq.[1,2,3]|]] , matchHeaders = ["Location" <:> [str|/json?data=eq.[1,2,3]|]]
} }
-- TODO! the test above seems right, why was the one below working before and not now
-- p <- request methodPost "/json" [("Prefer", "return=representation")] inserted
-- liftIO $ do
-- simpleBody p `shouldBe` inserted
-- simpleHeaders p `shouldSatisfy` matchHeader hLocation "/json\\?data=eq\\.%5B1%2C2%2C3%5D"
-- simpleStatus p `shouldBe` created201
describe "CSV insert" $ do describe "CSV insert" $ do
@@ -183,16 +171,8 @@ spec = do
`shouldRespondWith` ResponseMatcher { `shouldRespondWith` ResponseMatcher {
matchBody = Just inserted matchBody = Just inserted
, matchStatus = 201 , matchStatus = 201
, matchHeaders = ["Content-Type" <:> "text/csv"] , matchHeaders = ["Content-Type" <:> "text/csv; charset=utf-8"]
} }
-- p <- request methodPost "/menagerie" [("Content-Type", "text/csv")]
-- [str|integer,double,varchar,boolean,date,money,enum
-- |13,3.14159,testing!,false,1900-01-01,$3.99,foo
-- |12,0.1,a string,true,1929-10-01,12,bar
-- |]
-- liftIO $ do
-- simpleBody p `shouldBe` "Content-Type: application/json\nLocation: /menagerie?integer=eq.13\n\n\n--postgrest_boundary\nContent-Type: application/json\nLocation: /menagerie?integer=eq.12\n\n"
-- simpleStatus p `shouldBe` created201
context "requesting full representation" $ do context "requesting full representation" $ do
it "returns full details of inserted record" $ it "returns full details of inserted record" $
@@ -202,21 +182,10 @@ spec = do
`shouldRespondWith` ResponseMatcher { `shouldRespondWith` ResponseMatcher {
matchBody = Just "a,b\nbar,baz" matchBody = Just "a,b\nbar,baz"
, matchStatus = 201 , matchStatus = 201
, matchHeaders = ["Content-Type" <:> "text/csv", , matchHeaders = ["Content-Type" <:> "text/csv; charset=utf-8",
"Location" <:> "/no_pk?a=eq.bar&b=eq.baz"] "Location" <:> "/no_pk?a=eq.bar&b=eq.baz"]
} }
-- it "can post nulls (old way)" $ do
-- pendingWith "changed the response when in csv mode"
-- request methodPost "/no_pk"
-- [("Content-Type", "text/csv"), ("Prefer", "return=representation")]
-- "a,b\nNULL,foo"
-- `shouldRespondWith` ResponseMatcher {
-- matchBody = Just [json| { "a":null, "b":"foo" } |]
-- , matchStatus = 201
-- , matchHeaders = ["Content-Type" <:> "application/json",
-- "Location" <:> "/no_pk?a=is.null&b=eq.foo"]
-- }
it "can post nulls" $ it "can post nulls" $
request methodPost "/no_pk" request methodPost "/no_pk"
[("Content-Type", "text/csv"), ("Accept", "text/csv"), ("Prefer", "return=representation")] [("Content-Type", "text/csv"), ("Accept", "text/csv"), ("Prefer", "return=representation")]
@@ -224,7 +193,7 @@ spec = do
`shouldRespondWith` ResponseMatcher { `shouldRespondWith` ResponseMatcher {
matchBody = Just "a,b\n,foo" matchBody = Just "a,b\n,foo"
, matchStatus = 201 , matchStatus = 201
, matchHeaders = ["Content-Type" <:> "text/csv", , matchHeaders = ["Content-Type" <:> "text/csv; charset=utf-8",
"Location" <:> "/no_pk?a=is.null&b=eq.foo"] "Location" <:> "/no_pk?a=is.null&b=eq.foo"]
} }
@@ -233,10 +202,21 @@ spec = do
it "fails for too few" $ do it "fails for too few" $ do
p <- request methodPost "/no_pk" [("Content-Type", "text/csv")] "a,b\nfoo,bar\nbaz" p <- request methodPost "/no_pk" [("Content-Type", "text/csv")] "a,b\nfoo,bar\nbaz"
liftIO $ simpleStatus p `shouldBe` badRequest400 liftIO $ simpleStatus p `shouldBe` badRequest400
-- it does not fail because the extra columns are ignored
-- it "fails for too many" $ do context "with unicode values" $
-- p <- request methodPost "/no_pk" [("Content-Type", "text/csv")] "a,b\nfoo,bar\nbaz,bat,bad" it "succeeds and returns usable location header" $ do
-- liftIO $ simpleStatus p `shouldBe` badRequest400 let payload = [json| { "a":"圍棋", "b":"" } |]
p <- request methodPost "/no_pk"
[("Prefer", "return=representation")]
payload
liftIO $ do
simpleBody p `shouldBe` payload
simpleStatus p `shouldBe` created201
let Just location = lookup hLocation $ simpleHeaders p
r <- get location
liftIO $ simpleBody r `shouldBe` "["<>payload<>"]"
describe "Putting record" $ do describe "Putting record" $ do
@@ -387,6 +367,17 @@ spec = do
, matchHeaders = [] , matchHeaders = []
} }
context "with unicode values" $
it "succeeds and returns values intact" $ do
void $ request methodPost "/no_pk" []
[json| { "a":"patchme", "b":"patchme" } |]
let payload = [json| { "a":"圍棋", "b":"" } |]
p <- request methodPatch "/no_pk?a=eq.patchme&b=eq.patchme"
[("Prefer", "return=representation")] payload
liftIO $ do
simpleBody p `shouldBe` "["<>payload<>"]"
simpleStatus p `shouldBe` ok200
describe "Row level permission" $ describe "Row level permission" $
it "set user_id when inserting rows" $ do it "set user_id when inserting rows" $ do
let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqZG9lIn0.y4vZuu1dDdwAl0-S00MCRWRYMlJ5YAMSir6Es6WtWx0" let auth = authHeaderJWT "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoicG9zdGdyZXN0X3Rlc3RfYXV0aG9yIiwiaWQiOiJqZG9lIn0.y4vZuu1dDdwAl0-S00MCRWRYMlJ5YAMSir6Es6WtWx0"
+20 -2
View File
@@ -246,6 +246,14 @@ spec = do
, matchHeaders = [] , matchHeaders = []
} }
it "can combine multiple prefer values" $
request methodGet "/items?id=eq.5" [("Prefer","plurality=singular ; future=new; count=none")] ""
`shouldRespondWith` ResponseMatcher {
matchBody = Just [json| {"id":5} |]
, matchStatus = 200
, matchHeaders = []
}
it "works in the presence of a range header" $ it "works in the presence of a range header" $
let headers = ("Prefer","plurality=singular") : let headers = ("Prefer","plurality=singular") :
rangeHdrs (ByteRangeFromTo 0 9) in rangeHdrs (ByteRangeFromTo 0 9) in
@@ -337,7 +345,7 @@ spec = do
`shouldRespondWith` ResponseMatcher { `shouldRespondWith` ResponseMatcher {
matchBody = Just "k,extra\nxyyx,u\nxYYx,v" matchBody = Just "k,extra\nxyyx,u\nxYYx,v"
, matchStatus = 200 , matchStatus = 200
, matchHeaders = ["Content-Type" <:> "text/csv"] , matchHeaders = ["Content-Type" <:> "text/csv; charset=utf-8"]
} }
describe "Canonical location" $ do describe "Canonical location" $ do
@@ -390,11 +398,15 @@ spec = do
post "/rpc/test_empty_rowset" [json| {} |] `shouldRespondWith` post "/rpc/test_empty_rowset" [json| {} |] `shouldRespondWith`
[json| [] |] [json| [] |]
context "a proc that returns plain text" $ context "a proc that returns plain text" $ do
it "returns proper json" $ it "returns proper json" $
post "/rpc/sayhello" [json| { "name": "world" } |] `shouldRespondWith` post "/rpc/sayhello" [json| { "name": "world" } |] `shouldRespondWith`
[json| [{"sayhello":"Hello, world"}] |] [json| [{"sayhello":"Hello, world"}] |]
it "can handle unicode" $
post "/rpc/sayhello" [json| { "name": "" } |] `shouldRespondWith`
[json| [{"sayhello":"Hello, ¥"}] |]
context "improper input" $ do context "improper input" $ do
it "rejects unknown content type even if payload is good" $ it "rejects unknown content type even if payload is good" $
request methodPost "/rpc/sayhello" request methodPost "/rpc/sayhello"
@@ -422,6 +434,12 @@ spec = do
it "GET with 405 on known procs" $ it "GET with 405 on known procs" $
get "/rpc/sayhello" `shouldRespondWith` 405 get "/rpc/sayhello" `shouldRespondWith` 405
it "executes the proc exactly once per request" $ do
post "/rpc/callcounter" [json| {} |] `shouldRespondWith`
[json| [{"callcounter":1}] |]
post "/rpc/callcounter" [json| {} |] `shouldRespondWith`
[json| [{"callcounter":2}] |]
describe "weird requests" $ do describe "weird requests" $ do
it "can query as normal" $ do it "can query as normal" $ do
get "/Escap3e;" `shouldRespondWith` get "/Escap3e;" `shouldRespondWith`
+56
View File
@@ -24,6 +24,7 @@ spec = do
, {"schema":"test","name":"comments","insertable":true} , {"schema":"test","name":"comments","insertable":true}
, {"schema":"test","name":"complex_items","insertable":true} , {"schema":"test","name":"complex_items","insertable":true}
, {"schema":"test","name":"compound_pk","insertable":true} , {"schema":"test","name":"compound_pk","insertable":true}
, {"schema":"test","name":"filtered_tasks","insertable":true}
, {"schema":"test","name":"ghostBusters","insertable":true} , {"schema":"test","name":"ghostBusters","insertable":true}
, {"schema":"test","name":"has_count_column","insertable":false} , {"schema":"test","name":"has_count_column","insertable":false}
, {"schema":"test","name":"has_fk","insertable":true} , {"schema":"test","name":"has_fk","insertable":true}
@@ -57,6 +58,61 @@ spec = do
{matchStatus = 200} {matchStatus = 200}
describe "Table info" $ do describe "Table info" $ do
it "The structure of complex views is correctly detected" $
request methodOptions "/filtered_tasks" [] "" `shouldRespondWith`
[json|
{
"pkey": [
"myId"
],
"columns": [
{
"references": null,
"default": null,
"precision": 32,
"updatable": true,
"schema": "test",
"name": "myId",
"type": "integer",
"maxLen": null,
"enum": [],
"nullable": true,
"position": 1
},
{
"references": null,
"default": null,
"precision": null,
"updatable": true,
"schema": "test",
"name": "name",
"type": "text",
"maxLen": null,
"enum": [],
"nullable": true,
"position": 2
},
{
"references": {
"schema": "test",
"column": "id",
"table": "projects"
},
"default": null,
"precision": 32,
"updatable": true,
"schema": "test",
"name": "projectID",
"type": "integer",
"maxLen": null,
"enum": [],
"nullable": true,
"position": 3
}
]
}
|]
it "is available with OPTIONS verb" $ it "is available with OPTIONS verb" $
request methodOptions "/menagerie" [] "" `shouldRespondWith` request methodOptions "/menagerie" [] "" `shouldRespondWith`
[json| [json|
+20
View File
@@ -0,0 +1,20 @@
module Feature.UnicodeSpec where
import Test.Hspec
import Test.Hspec.Wai
import Test.Hspec.Wai.JSON
import Network.Wai (Application)
import Control.Monad (void)
spec :: SpecWith Application
spec =
describe "Reading and writing to unicode schema and table names" $
it "Can read and write values" $ do
get "/%D9%85%D9%88%D8%A7%D8%B1%D8%AF"
`shouldRespondWith` "[]"
void $ post "/%D9%85%D9%88%D8%A7%D8%B1%D8%AF"
[json| { "هویت": 1 } |]
get "/%D9%85%D9%88%D8%A7%D8%B1%D8%AF"
`shouldRespondWith` [json| [{ "هویت": 1 }] |]
+6
View File
@@ -18,6 +18,7 @@ import qualified Feature.QueryLimitedSpec
import qualified Feature.QuerySpec import qualified Feature.QuerySpec
import qualified Feature.RangeSpec import qualified Feature.RangeSpec
import qualified Feature.StructureSpec import qualified Feature.StructureSpec
import qualified Feature.UnicodeSpec
main :: IO () main :: IO ()
main = do main = do
@@ -29,6 +30,7 @@ main = do
let dbStructure = either (error.show) id result let dbStructure = either (error.show) id result
withApp = return $ postgrest testCfg dbStructure pool withApp = return $ postgrest testCfg dbStructure pool
ltdApp = return $ postgrest testLtdRowsCfg dbStructure pool ltdApp = return $ postgrest testLtdRowsCfg dbStructure pool
unicodeApp = return $ postgrest testUnicodeCfg dbStructure pool
hspec $ do hspec $ do
mapM_ (beforeAll_ resetDb . before withApp) specs mapM_ (beforeAll_ resetDb . before withApp) specs
@@ -37,6 +39,10 @@ main = do
beforeAll_ resetDb . before ltdApp $ beforeAll_ resetDb . before ltdApp $
describe "Feature.QueryLimitedSpec" Feature.QueryLimitedSpec.spec describe "Feature.QueryLimitedSpec" Feature.QueryLimitedSpec.spec
-- this test runs with a different schema
beforeAll_ resetDb . before unicodeApp $
describe "Feature.UnicodeSpec" Feature.UnicodeSpec.spec
where where
specs = map (uncurry describe) [ specs = map (uncurry describe) [
("Feature.AuthSpec" , Feature.AuthSpec.spec) ("Feature.AuthSpec" , Feature.AuthSpec.spec)
+4
View File
@@ -21,6 +21,10 @@ testCfg :: AppConfig
testCfg = testCfg =
AppConfig testDbConn "postgrest_test_anonymous" "test" 3000 (secret "safe") 10 Nothing True AppConfig testDbConn "postgrest_test_anonymous" "test" 3000 (secret "safe") 10 Nothing True
testUnicodeCfg :: AppConfig
testUnicodeCfg =
AppConfig testDbConn "postgrest_test_anonymous" "تست" 3000 (secret "safe") 10 Nothing True
testLtdRowsCfg :: AppConfig testLtdRowsCfg :: AppConfig
testLtdRowsCfg = testLtdRowsCfg =
AppConfig testDbConn "postgrest_test_anonymous" "test" 3000 (secret "safe") 10 (Just 3) True AppConfig testDbConn "postgrest_test_anonymous" "test" 3000 (secret "safe") 10 (Just 3) True
+5 -1
View File
@@ -2,10 +2,11 @@
GRANT USAGE ON SCHEMA GRANT USAGE ON SCHEMA
postgrest postgrest
, test , test
, "تست"
TO postgrest_test_anonymous; TO postgrest_test_anonymous;
-- Schema test objects -- Schema test objects
SET search_path = test, pg_catalog; SET search_path = test, "تست", pg_catalog;
GRANT ALL ON TABLE GRANT ALL ON TABLE
items items
@@ -28,6 +29,7 @@ GRANT ALL ON TABLE
, projects_view , projects_view
, simple_pk , simple_pk
, tasks , tasks
, filtered_tasks
, tsearch , tsearch
, users , users
, users_projects , users_projects
@@ -35,6 +37,7 @@ GRANT ALL ON TABLE
, "Escap3e;" , "Escap3e;"
, "ghostBusters" , "ghostBusters"
, "withUnique" , "withUnique"
, "موارد"
TO postgrest_test_anonymous; TO postgrest_test_anonymous;
GRANT INSERT ON TABLE insertonly TO postgrest_test_anonymous; GRANT INSERT ON TABLE insertonly TO postgrest_test_anonymous;
@@ -42,6 +45,7 @@ GRANT INSERT ON TABLE insertonly TO postgrest_test_anonymous;
GRANT USAGE ON SEQUENCE GRANT USAGE ON SEQUENCE
auto_incrementing_pk_id_seq auto_incrementing_pk_id_seq
, items_id_seq , items_id_seq
, callcounter_count
TO postgrest_test_anonymous; TO postgrest_test_anonymous;
-- Privileges for non anonymous users -- Privileges for non anonymous users
+100 -10
View File
@@ -33,6 +33,13 @@ CREATE SCHEMA private;
CREATE SCHEMA test; CREATE SCHEMA test;
--
-- Name: تست; Type: SCHEMA; Schema: -; Owner: -
--
CREATE SCHEMA تست;
-- --
-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: - -- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: -
-- --
@@ -50,6 +57,23 @@ CREATE TYPE jwt_claims AS (
id text id text
); );
--
-- Name: big_jwt_claims; Type: TYPE; Schema: public; Owner: -
--
CREATE TYPE big_jwt_claims AS (
iss text,
sub text,
aud text,
exp integer,
nbf integer,
iat integer,
jti text,
role text,
"http://postgrest.com/foo" boolean
);
SET search_path = test, pg_catalog; SET search_path = test, pg_catalog;
@@ -145,6 +169,14 @@ CREATE FUNCTION anti_id(test.items) RETURNS bigint
AS $_$ SELECT $1.id * -1 $_$; AS $_$ SELECT $1.id * -1 $_$;
SET search_path = تست, pg_catalog;
CREATE TABLE موارد (
هویت bigint NOT NULL
);
SET search_path = test, pg_catalog; SET search_path = test, pg_catalog;
-- --
@@ -183,6 +215,43 @@ SELECT rolname::text, id::text FROM postgrest.auth WHERE id = id AND pass = pass
$$; $$;
--
-- Name: jwt_test(); Type: FUNCTION; Schema: test; Owner: -
--
CREATE FUNCTION jwt_test() RETURNS public.big_jwt_claims
LANGUAGE sql SECURITY DEFINER
AS $$
SELECT 'joe'::text as iss, 'fun'::text as sub, 'everyone'::text as aud,
1300819380 as exp, 1300819380 as nbf, 1300819380 as iat,
'foo'::text as jti, 'postgrest_test'::text as role,
true as "http://postgrest.com/foo";
$$;
--
-- Name: reveal_big_jwt(); Type: FUNCTION; Schema: test; Owner: -
--
CREATE FUNCTION reveal_big_jwt() RETURNS TABLE (
iss text, sub text, aud text, exp bigint,
nbf bigint, iat bigint, jti text, "http://postgrest.com/foo" boolean
)
LANGUAGE sql SECURITY DEFINER
AS $$
SELECT current_setting('postgrest.claims.iss') as iss,
current_setting('postgrest.claims.sub') as sub,
current_setting('postgrest.claims.aud') as aud,
current_setting('postgrest.claims.exp')::bigint as exp,
current_setting('postgrest.claims.nbf')::bigint as nbf,
current_setting('postgrest.claims.iat')::bigint as iat,
current_setting('postgrest.claims.jti') as jti,
-- role is not included in the claims list
current_setting('postgrest.claims.http://postgrest.com/foo')::boolean
as "http://postgrest.com/foo";
$$;
-- --
-- Name: problem(); Type: FUNCTION; Schema: test; Owner: - -- Name: problem(); Type: FUNCTION; Schema: test; Owner: -
-- --
@@ -207,6 +276,18 @@ CREATE FUNCTION sayhello(name text) RETURNS text
$_$; $_$;
--
-- Name: callcounter(); Type: FUNCTION; Schema: test; Owner: -
--
CREATE SEQUENCE callcounter_count START 1;
CREATE FUNCTION callcounter() RETURNS bigint
LANGUAGE sql
AS $_$
SELECT nextval('test.callcounter_count');
$_$;
-- --
-- Name: test_empty_rowset(); Type: FUNCTION; Schema: test; Owner: - -- Name: test_empty_rowset(); Type: FUNCTION; Schema: test; Owner: -
-- --
@@ -540,6 +621,15 @@ CREATE TABLE simple_pk (
extra character varying NOT NULL extra character varying NOT NULL
); );
--
-- Name: users_projects; Type: TABLE; Schema: test; Owner: -
--
CREATE TABLE users_projects (
user_id integer NOT NULL,
project_id integer NOT NULL
);
-- --
-- Name: tasks; Type: TABLE; Schema: test; Owner: - -- Name: tasks; Type: TABLE; Schema: test; Owner: -
@@ -551,6 +641,16 @@ CREATE TABLE tasks (
project_id integer project_id integer
); );
CREATE OR REPLACE VIEW filtered_tasks AS
SELECT id AS "myId", name, project_id AS "projectID"
FROM tasks
WHERE project_id IN (
SELECT id FROM projects WHERE id = 1
) AND
project_id IN (
SELECT project_id FROM users_projects WHERE user_id = 1
);
-- --
-- Name: tsearch; Type: TABLE; Schema: test; Owner: - -- Name: tsearch; Type: TABLE; Schema: test; Owner: -
@@ -571,15 +671,6 @@ CREATE TABLE users (
); );
--
-- Name: users_projects; Type: TABLE; Schema: test; Owner: -
--
CREATE TABLE users_projects (
user_id integer NOT NULL,
project_id integer NOT NULL
);
-- --
-- Name: users_tasks; Type: TABLE; Schema: test; Owner: - -- Name: users_tasks; Type: TABLE; Schema: test; Owner: -
@@ -909,7 +1000,6 @@ ALTER TABLE ONLY users_tasks
ALTER TABLE ONLY users_tasks ALTER TABLE ONLY users_tasks
ADD CONSTRAINT users_tasks_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id); ADD CONSTRAINT users_tasks_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id);
-- --
-- PostgreSQL database dump complete -- PostgreSQL database dump complete
-- --