server: new function permissions layer

Co-authored-by: Rikin Kachhia <54616969+rikinsk@users.noreply.github.com>
Co-authored-by: Rakesh Emmadi <12475069+rakeshkky@users.noreply.github.com>
GitOrigin-RevId: 35645121242294cb6bb500ea598e9a1f2ca67fa1
This commit is contained in:
Karthikeyan Chinnakonda 2021-01-29 11:18:17 +05:30 committed by hasura-bot
parent 0767333597
commit 10a3f9960d
57 changed files with 1271 additions and 298 deletions

View File

@ -6,7 +6,7 @@ echo "Running tests on node $CIRCLE_NODE_INDEX of $CIRCLE_NODE_TOTAL"
if [ -z "$SERVER_TEST_TO_RUN" ]; then if [ -z "$SERVER_TEST_TO_RUN" ]; then
echo 'Please specify $SERVER_TEST_TO_RUN' echo 'Please specify $SERVER_TEST_TO_RUN'
exit 1 exit 1
else else
echo "Running test $SERVER_TEST_TO_RUN" echo "Running test $SERVER_TEST_TO_RUN"
fi fi
@ -522,7 +522,7 @@ case "$SERVER_TEST_TO_RUN" in
unset HASURA_GRAPHQL_CORS_DOMAIN unset HASURA_GRAPHQL_CORS_DOMAIN
;; ;;
ws-init-cookie-read-cors-enabled) ws-init-cookie-read-cors-enabled)
# test websocket transport with initial cookie header # test websocket transport with initial cookie header
@ -660,6 +660,22 @@ case "$SERVER_TEST_TO_RUN" in
kill_hge_servers kill_hge_servers
;; ;;
function-permissions)
echo -e "\n$(time_elapsed): <########## TEST GRAPHQL-ENGINE WITH FUNCTION PERMISSIONS ENABLED ########>\n"
TEST_TYPE="remote-schema-permissions"
export HASURA_GRAPHQL_INFER_FUNCTION_PERMISSIONS=false
run_hge_with_args serve
wait_for_port 8080
pytest -n 1 -vv --hge-urls "$HGE_URL" --pg-urls "$HASURA_GRAPHQL_DATABASE_URL" --hge-key="$HASURA_GRAPHQL_ADMIN_SECRET" --test_function_permissions test_graphql_queries.py::TestGraphQLQueryFunctionPermissions
pytest -n 1 -vv --hge-urls "$HGE_URL" --pg-urls "$HASURA_GRAPHQL_DATABASE_URL" --hge-key="$HASURA_GRAPHQL_ADMIN_SECRET" --test_function_permissions test_graphql_mutations.py::TestGraphQLMutationFunctions
unset HASURA_GRAPHQL_INFER_FUNCTION_PERMISSIONS
kill_hge_servers
;;
query-caching) query-caching)
echo -e "\n$(time_elapsed): <########## TEST GRAPHQL-ENGINE QUERY CACHING #####################################>\n" echo -e "\n$(time_elapsed): <########## TEST GRAPHQL-ENGINE QUERY CACHING #####################################>\n"
TEST_TYPE="query-caching" TEST_TYPE="query-caching"
@ -705,18 +721,18 @@ case "$SERVER_TEST_TO_RUN" in
if [ "$RUN_WEBHOOK_TESTS" == "true" ] ; then if [ "$RUN_WEBHOOK_TESTS" == "true" ] ; then
TEST_TYPE="post-webhook" TEST_TYPE="post-webhook"
echo -e "\n$(time_elapsed): <########## TEST GRAPHQL-ENGINE WITH ADMIN SECRET & WEBHOOK (POST) #########################>\n" echo -e "\n$(time_elapsed): <########## TEST GRAPHQL-ENGINE WITH ADMIN SECRET & WEBHOOK (POST) #########################>\n"
export HASURA_GRAPHQL_AUTH_HOOK="https://localhost:9090/" export HASURA_GRAPHQL_AUTH_HOOK="https://localhost:9090/"
export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM"
init_ssl init_ssl
start_multiple_hge_servers start_multiple_hge_servers
python3 webhook.py 9090 "$OUTPUT_FOLDER/ssl/webhook-key.pem" "$OUTPUT_FOLDER/ssl/webhook.pem" > "$OUTPUT_FOLDER/webhook.log" 2>&1 & WH_PID=$! python3 webhook.py 9090 "$OUTPUT_FOLDER/ssl/webhook-key.pem" "$OUTPUT_FOLDER/ssl/webhook.pem" > "$OUTPUT_FOLDER/webhook.log" 2>&1 & WH_PID=$!
wait_for_port 9090 wait_for_port 9090
run_pytest_parallel --hge-key="$HASURA_GRAPHQL_ADMIN_SECRET" --hge-webhook="$HASURA_GRAPHQL_AUTH_HOOK" run_pytest_parallel --hge-key="$HASURA_GRAPHQL_ADMIN_SECRET" --hge-webhook="$HASURA_GRAPHQL_AUTH_HOOK"
kill_hge_servers kill_hge_servers
fi fi
;; ;;

View File

@ -60,6 +60,14 @@ access over it.
either with the server flag ``--enable-remote-schema-permissions`` or the environment either with the server flag ``--enable-remote-schema-permissions`` or the environment
variable ``HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS`` set to ``true``. variable ``HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS`` set to ``true``.
### Function Permissions
Before volatile functions were supported, the permissions for functions were automatically inferred
from the select permission of the target table. Now, since volatile functions are supported we can't
do this anymore, so function permissions are introduced which will explicitly grant permission to
a function for a given role. A pre-requisite to adding a function permission is that the role should
have select permissions to the target table of the function.
### Breaking changes ### Breaking changes
- This release contains the [PDV refactor (#4111)](https://github.com/hasura/graphql-engine/pull/4111), a significant rewrite of the internals of the server, which did include some breaking changes: - This release contains the [PDV refactor (#4111)](https://github.com/hasura/graphql-engine/pull/4111), a significant rewrite of the internals of the server, which did include some breaking changes:
@ -69,12 +77,12 @@ variable ``HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS`` set to ``true``.
- if a query selects table `bar` through table `foo` via a relationship, the required permissions headers will be the union of the required headers of table `foo` and table `bar` (we used to only check the headers of the root table); - if a query selects table `bar` through table `foo` via a relationship, the required permissions headers will be the union of the required headers of table `foo` and table `bar` (we used to only check the headers of the root table);
- if an insert does not have an `on_conflict` clause, it will not require the update permissions headers. - if an insert does not have an `on_conflict` clause, it will not require the update permissions headers.
This release contains the remote schema permissions feature, which introduces a breaking change: - This release contains the remote schema permissions feature, which introduces a breaking change:
Earlier, remote schemas were considered to be a public entity and all the roles had unrestricted Earlier, remote schemas were considered to be a public entity and all the roles had unrestricted
access to the remote schema. If remote schema permissions are enabled in the graphql-engine, a given access to the remote schema. If remote schema permissions are enabled in the graphql-engine, a given
remote schema will only be accessible to a role ,if the role has permissions configured for the said remote schema remote schema will only be accessible to a role ,if the role has permissions configured for the said remote schema
and be accessible according to the permissions that were configured for the role. and be accessible according to the permissions that were configured for the role.
### Bug fixes and improvements ### Bug fixes and improvements

View File

@ -15,29 +15,33 @@ API Reference
Available APIs Available APIs
-------------- --------------
+-----------------+-----------------------------------------+------------------+ +----------------------------------+---------------------------------------------------+------------------+
| API | Endpoint | Access | | API | Endpoint | Access |
+=================+=========================================+==================+ +==================================+===================================================+==================+
| GraphQL | :ref:`/v1/graphql <graphql_api>` | Permission rules | | GraphQL | :ref:`/v1/graphql <graphql_api>` | Permission rules |
+-----------------+-----------------------------------------+------------------+ +----------------------------------+---------------------------------------------------+------------------+
| Relay | :ref:`/v1beta1/relay <relay_api>` | Permission rules | | Relay | :ref:`/v1beta1/relay <relay_api>` | Permission rules |
+-----------------+-----------------------------------------+------------------+ +----------------------------------+---------------------------------------------------+------------------+
| Legacy GraphQL | :ref:`/v1alpha1/graphql <graphql_api>` | Permission rules | | Legacy GraphQL | :ref:`/v1alpha1/graphql <graphql_api>` | Permission rules |
+-----------------+-----------------------------------------+------------------+ +----------------------------------+---------------------------------------------------+------------------+
| Schema/Metadata | :ref:`/v1/query <schema_metadata_api>` | Admin only | | Schema/Metadata *(< v1.3)* | :ref:`/v1/query <schema_metadata_api>` | Admin only |
+-----------------+-----------------------------------------+------------------+ +----------------------------------+---------------------------------------------------+------------------+
| RESTified GQL | :ref:`/api/rest <restified_api>` | GQL REST Routes | | Schema *(> v1.4)* | :ref:`/v2/query <schema_api>` | Admin only |
+-----------------+-----------------------------------------+------------------+ +----------------------------------+---------------------------------------------------+------------------+
| Version | :ref:`/v1/version <version_api>` | Public | | Metadata *(> v1.4)* | :ref:`/v1/metadata <metadata_api>` | Admin only |
+-----------------+-----------------------------------------+------------------+ +----------------------------------+---------------------------------------------------+------------------+
| Health | :ref:`/healthz <health_api>` | Public | | Restified GQL | :ref:`/api/rest <restified_api>` | GQL REST Routes |
+-----------------+-----------------------------------------+------------------+ +----------------------------------+---------------------------------------------------+------------------+
| PG Dump | :ref:`/v1alpha1/pg_dump <pg_dump_api>` | Admin only | | Version | :ref:`/v1/version <version_api>` | Public |
+-----------------+-----------------------------------------+------------------+ +----------------------------------+---------------------------------------------------+------------------+
| Config | :ref:`/v1alpha1/config <config_api>` | Admin only | | Health | :ref:`/healthz <health_api>` | Public |
+-----------------+-----------------------------------------+------------------+ +----------------------------------+---------------------------------------------------+------------------+
| Explain | :ref:`/v1/graphql/explain <explain_api>`| Admin only | | PG Dump | :ref:`/v1alpha1/pg_dump <pg_dump_api>` | Admin only |
+-----------------+-----------------------------------------+------------------+ +----------------------------------+---------------------------------------------------+------------------+
| Config | :ref:`/v1alpha1/config <config_api>` | Admin only |
+----------------------------------+---------------------------------------------------+------------------+
| Explain | :ref:`/v1/graphql/explain <explain_api>` | Admin only |
+----------------------------------+---------------------------------------------------+------------------+
.. _graphql_api: .. _graphql_api:
@ -61,14 +65,36 @@ See details at :ref:`api_reference_relay_graphql`.
.. _schema_metadata_api: .. _schema_metadata_api:
Schema / metadata API Schema / metadata API V1 (v1.3 and below)
^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Hasura exposes a schema / metadata API for managing metadata for permissions/relationships or for directly Hasura exposes a schema / metadata API for managing metadata for permissions/relationships or for directly
executing SQL on the underlying Postgres. executing SQL on the underlying Postgres.
This is primarily intended to be used as an ``admin`` API to manage the Hasura schema and metadata. This is primarily intended to be used as an ``admin`` API to manage the Hasura schema and metadata.
See details at :ref:`schema_metadata_apis` .
.. _schema_api:
Schema API (v1.4 and above)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
Hasura exposes a schema API for directly executing SQL on the underlying Postgres.
This is primarily intended to be used as an ``admin`` API to manage the Hasura schema.
See details at :ref:`schema_apis`.
.. _metadata_api:
Metadata API (v1.4 and above)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Hasura exposes a metadata API for managing metadata.
This is primarily intended to be used as an ``admin`` API to manage the Hasura metadata.
See details at :ref:`metadata_apis`. See details at :ref:`metadata_apis`.
.. _version_api: .. _version_api:
@ -142,7 +168,9 @@ You can refer to the following to know about all PostgreSQL types supported by t
GraphQL API <graphql-api/index> GraphQL API <graphql-api/index>
Relay GraphQL API <relay-graphql-api/index> Relay GraphQL API <relay-graphql-api/index>
Schema / Metadata APIs <schema-metadata-api/index> Schema / Metadata APIs V1 <schema-metadata-api/index>
Schema APIs <schema-api/index>
Metadata APIs <metadata-api/index>
RESTified GraphQL Endpoints <restified> RESTified GraphQL Endpoints <restified>
Version API <version> Version API <version>
Health check API <health> Health check API <health>

View File

@ -0,0 +1,62 @@
Status Code,Code,Error
400,postgres-error,Not-NULL violation. null value in column <column-name> violates not-null constraint
400,permission-denied,select on <column/table> for role <role-name> is not allowed.
400,not-exists,table <table-name> does not exist
400,not-exists,no such table/view exists in postgres : <table-name>
400,not-exists,<field-name> does not exist
400,already-tracked,view/table already tracked : <table-name>
400,access-denied,restricted access : admin only
400,not-supported,table renames are not yet supported : <table-name>
400,not-exists,<column-name> does not exist
400,already-exists,cannot add column <column-name> in table <table-name> as a relationship with the name already exists
400,invalid-json,invalid json
400,not-supported,column renames are not yet supported : <table-name>.<column-name>
400,invalid-headers,missing header : <header-name>
400,dependency-error,cannot change type of column <column-name> in table <table-name> because of the following dependencies : <dependencies>
400,invalid-headers,X-Hasura-User-Id should be an integer
400,dependency-error,cannot drop due to the following dependent objects : <dependencies>
400,access-denied,You have to be admin to access this endpoint
400,parse-failed,parsing dotted table failed : <table-name>
400,access-denied,not authorised to access this tx
400,already-exists,multiple declarations exist for the following <table-name> : <duplicates>
400,not-exists,tx does not exists
400,already-exists,column/relationship of table <table-name> already exists
400,already-initialised,the state seems to be initialised already. \ \ you may need to migrate from this version: <catalog-version>
400,constraint-error,no foreign constraint exists on the given column
400,not-supported,unsupported version : <catalog-version>
400,constraint-error,more than one foreign key constraint exists on the given column
400,already-exists,the query template already exists <template-name>
400,permission-error,<permission-type>' permission on <table-name> for role <role-name> already exists
400,permission-error,<permission-type>' permission on <table-name> for role <role-name> does not exist
400,unexpected-payload,Unknown operator : <operator-type>
400,unexpected-payload,expecting a string for column operator
400,unexpected-payload,"incompatible column types : '<column-name>', '<column-name>' "
400,unexpected-payload,Expecting 'constraint' or 'constraint_on' when the 'action' is 'update'
400,unexpected-payload,constraint' and 'constraint_on' cannot be set at a time
400,unexpected-payload,upsert is not allowed for role '<role-name>'
400,unexpected-payload,objects should not be empty
400,invalid-params,missing parameter : <param-name>
400,unexpected-payload,can't be empty
400,,<col-name>' is a relationship and should be expanded
400,unexpected-payload,<column-name>' should be included in 'columns'
400,unexpected-payload,<column-name>' is an array relationship and can't be used in 'order_by'
400,,<column-name>' is a Postgres column and cannot be chained further
400,unexpected-payload,order_by array should not be empty
400,unexpected-payload,"when selecting an 'obj_relationship' 'where', 'order_by', 'limit' and 'offset' can't be used"
400,unexpected-payload,"atleast one of $set, $inc, $mul has to be present"
400,permission-denied,<permission-type> on <table-name> for role <role-name> is not allowed
400,not-exists,no such column exists : <column-name>
400,permission-denied,role <role-name> does not have permission to <permission-type> column <column-name>
400,,"expecting a postgres column; but, <name> is relationship"
400,unexpected-payload,JSON column can not be part of where clause
400,unexpected-payload,is of type <type-name>; this operator works only on column of types <[types]>
400,postgres-error,query execution failed
500,unexpected,unexpected dependency of relationship : <dependency>
500,unexpected,unexpected dependent object : <dependency>
500,unexpected,field already exists
500,unexpected,field does not exist
500,unexpected,permission does not exist
500,postgres-error,postgres transaction error
500,postgres-error,connection error
500,postgres-error,postgres query error
404,not-found,No such resource exists
1 Status Code Code Error
2 400 postgres-error Not-NULL violation. null value in column <column-name> violates not-null constraint
3 400 permission-denied select on <column/table> for role <role-name> is not allowed.
4 400 not-exists table <table-name> does not exist
5 400 not-exists no such table/view exists in postgres : <table-name>
6 400 not-exists <field-name> does not exist
7 400 already-tracked view/table already tracked : <table-name>
8 400 access-denied restricted access : admin only
9 400 not-supported table renames are not yet supported : <table-name>
10 400 not-exists <column-name> does not exist
11 400 already-exists cannot add column <column-name> in table <table-name> as a relationship with the name already exists
12 400 invalid-json invalid json
13 400 not-supported column renames are not yet supported : <table-name>.<column-name>
14 400 invalid-headers missing header : <header-name>
15 400 dependency-error cannot change type of column <column-name> in table <table-name> because of the following dependencies : <dependencies>
16 400 invalid-headers X-Hasura-User-Id should be an integer
17 400 dependency-error cannot drop due to the following dependent objects : <dependencies>
18 400 access-denied You have to be admin to access this endpoint
19 400 parse-failed parsing dotted table failed : <table-name>
20 400 access-denied not authorised to access this tx
21 400 already-exists multiple declarations exist for the following <table-name> : <duplicates>
22 400 not-exists tx does not exists
23 400 already-exists column/relationship of table <table-name> already exists
24 400 already-initialised the state seems to be initialised already. \ \ you may need to migrate from this version: <catalog-version>
25 400 constraint-error no foreign constraint exists on the given column
26 400 not-supported unsupported version : <catalog-version>
27 400 constraint-error more than one foreign key constraint exists on the given column
28 400 already-exists the query template already exists <template-name>
29 400 permission-error <permission-type>' permission on <table-name> for role <role-name> already exists
30 400 permission-error <permission-type>' permission on <table-name> for role <role-name> does not exist
31 400 unexpected-payload Unknown operator : <operator-type>
32 400 unexpected-payload expecting a string for column operator
33 400 unexpected-payload incompatible column types : '<column-name>', '<column-name>'
34 400 unexpected-payload Expecting 'constraint' or 'constraint_on' when the 'action' is 'update'
35 400 unexpected-payload constraint' and 'constraint_on' cannot be set at a time
36 400 unexpected-payload upsert is not allowed for role '<role-name>'
37 400 unexpected-payload objects should not be empty
38 400 invalid-params missing parameter : <param-name>
39 400 unexpected-payload can't be empty
40 400 <col-name>' is a relationship and should be expanded
41 400 unexpected-payload <column-name>' should be included in 'columns'
42 400 unexpected-payload <column-name>' is an array relationship and can't be used in 'order_by'
43 400 <column-name>' is a Postgres column and cannot be chained further
44 400 unexpected-payload order_by array should not be empty
45 400 unexpected-payload when selecting an 'obj_relationship' 'where', 'order_by', 'limit' and 'offset' can't be used
46 400 unexpected-payload atleast one of $set, $inc, $mul has to be present
47 400 permission-denied <permission-type> on <table-name> for role <role-name> is not allowed
48 400 not-exists no such column exists : <column-name>
49 400 permission-denied role <role-name> does not have permission to <permission-type> column <column-name>
50 400 expecting a postgres column; but, <name> is relationship
51 400 unexpected-payload JSON column can not be part of where clause
52 400 unexpected-payload is of type <type-name>; this operator works only on column of types <[types]>
53 400 postgres-error query execution failed
54 500 unexpected unexpected dependency of relationship : <dependency>
55 500 unexpected unexpected dependent object : <dependency>
56 500 unexpected field already exists
57 500 unexpected field does not exist
58 500 unexpected permission does not exist
59 500 postgres-error postgres transaction error
60 500 postgres-error connection error
61 500 postgres-error postgres query error
62 404 not-found No such resource exists

View File

@ -0,0 +1,164 @@
.. meta::
:description: Hasura metadata API reference
:keywords: hasura, docs, metadata API, API reference
.. _metadata_apis:
Metadata API Reference (v1.4 and above)
=======================================
.. contents:: Table of contents
:backlinks: none
:depth: 1
:local:
Introduction
------------
This is primarily intended to be used as an ``admin`` API to manage the Hasura metadata.
Endpoint
--------
All requests are ``POST`` requests to the ``/v1/metadata`` endpoint.
Request structure
-----------------
.. code-block:: http
POST /v1/metadata HTTP/1.1
{
"type": "<query-type>",
"args": <args-object>
}
Request body
^^^^^^^^^^^^
.. parsed-literal::
Query_
.. _Query:
Query
*****
.. list-table::
:header-rows: 1
* - Key
- Required
- Schema
- Description
* - type
- true
- String
- Type of the query
* - args
- true
- JSON Value
- The arguments to the query
* - version
- false
- Integer
- Version of the API (default: 1)
Request types
-------------
The various types of queries are listed in the following table:
.. list-table::
:header-rows: 1
* - ``type``
- ``args``
- ``version``
- Synopsis
* - **bulk**
- :ref:`Query <Query>` array
- 1
- Execute multiple operations in a single query
* - :ref:`pg_create_function_permission`
- :ref:`pg_create_function_permission_args <pg_create_function_permission_args_syntax>`
- 1
- Create a function permission
* - :ref:`pg_drop_function_permission`
- :ref:`pg_drop_function_permission_args <pg_drop_function_permission_args_syntax>`
- 1
- Drop an existing function permission
Response structure
------------------
.. list-table::
:widths: 10 10 30
:header-rows: 1
* - Status code
- Description
- Response structure
* - ``200``
- Success
- .. parsed-literal::
Request specific
* - ``400``
- Bad request
- .. code-block:: haskell
{
"path" : String,
"error" : String
}
* - ``401``
- Unauthorized
- .. code-block:: haskell
{
"error" : String
}
* - ``500``
- Internal server error
- .. code-block:: haskell
{
"error" : String
}
Error codes
-----------
.. csv-table::
:file: dataerrors.csv
:widths: 10, 20, 70
:header-rows: 1
Disabling metadata API
----------------------
Since this API can be used to make changes to the GraphQL schema, it can be
disabled, especially in production deployments.
The ``enabled-apis`` flag or the ``HASURA_GRAPHQL_ENABLED_APIS`` env var can be used to
enable/disable this API. By default, the schema/metadata API is enabled. To disable it, you need
to explicitly state that this API is not enabled i.e. remove it from the list of enabled APIs.
.. code-block:: bash
# enable only graphql api, disable metadata and pgdump
--enabled-apis="graphql"
HASURA_GRAPHQL_ENABLED_APIS="graphql"
See :ref:`server_flag_reference` for info on setting the above flag/env var.

View File

@ -0,0 +1,154 @@
.. meta::
:description: Hasura schema API reference
:keywords: hasura, docs, schema API, API reference
.. _schema_apis:
Schema API Reference (v1.4 and above)
=====================================
.. contents:: Table of contents
:backlinks: none
:depth: 1
:local:
Introduction
------------
The schema API provides the following features:
1. Execute SQL on the underlying Postgres database, supports schema modifying actions.
This is primarily intended to be used as an ``admin`` API to manage the Hasura schema.
Endpoint
--------
All requests are ``POST`` requests to the ``/v2/query`` endpoint.
Request structure
-----------------
.. code-block:: http
POST /v1/query HTTP/1.1
{
"type": "<query-type>",
"args": <args-object>
}
Request body
^^^^^^^^^^^^
.. parsed-literal::
Query_
.. _Query_:
Query
*****
.. list-table::
:header-rows: 1
* - Key
- Required
- Schema
- Description
* - type
- true
- String
- Type of the query
* - args
- true
- JSON Value
- The arguments to the query
* - version
- false
- Integer
- Version of the API (default: 1)
Request types
-------------
The various types of queries are listed in the following table:
.. list-table::
:header-rows: 1
* - ``type``
- ``args``
- ``version``
- Synopsis
* - **bulk**
- :ref:`Query <Query>` array
- 1
- Execute multiple operations in a single query
* - :ref:`run_sql`
- :ref:`run_sql_args <run_sql_syntax>`
- 1
- Run SQL directly on Postgres
Response structure
------------------
.. list-table::
:widths: 10 10 30
:header-rows: 1
* - Status code
- Description
- Response structure
* - ``200``
- Success
- .. parsed-literal::
Request specific
* - ``400``
- Bad request
- .. code-block:: haskell
{
"path" : String,
"error" : String
}
* - ``401``
- Unauthorized
- .. code-block:: haskell
{
"error" : String
}
* - ``500``
- Internal server error
- .. code-block:: haskell
{
"error" : String
}
Disabling schema API
--------------------
Since this API can be used to make changes to the GraphQL schema, it can be
disabled, especially in production deployments.
The ``enabled-apis`` flag or the ``HASURA_GRAPHQL_ENABLED_APIS`` env var can be used to
enable/disable this API. By default, the schema/metadata API is enabled. To disable it, you need
to explicitly state that this API is not enabled i.e. remove it from the list of enabled APIs.
.. code-block:: bash
# enable only graphql api, disable metadata and pgdump
--enabled-apis="graphql"
HASURA_GRAPHQL_ENABLED_APIS="graphql"
See :ref:`server_flag_reference` for info on setting the above flag/env var.

View File

@ -107,7 +107,7 @@ In most cases you will want ``VOLATILE`` functions to only be exposed as
mutations, and only ``STABLE`` and ``IMMUTABLE`` functions to be queries. mutations, and only ``STABLE`` and ``IMMUTABLE`` functions to be queries.
When tracking ``VOLATILE`` functions under the ``query`` root, the user needs When tracking ``VOLATILE`` functions under the ``query`` root, the user needs
to guarantee that the field is idempotent and side-effect free, in the context to guarantee that the field is idempotent and side-effect free, in the context
of the resulting GraphQL API. of the resulting GraphQL API.
One such use case might be a function that wraps a simple query and performs One such use case might be a function that wraps a simple query and performs
some logging visible only to administrators. some logging visible only to administrators.
@ -170,6 +170,102 @@ Function Configuration
- **Return type**: MUST be ``SETOF <table-name>`` where ``<table-name>`` is already tracked - **Return type**: MUST be ``SETOF <table-name>`` where ``<table-name>`` is already tracked
- **Argument modes**: ONLY ``IN`` - **Argument modes**: ONLY ``IN``
.. _pg_create_function_permission:
pg_create_function_permission
-----------------------------
``pg_create_function_permission`` is used to add permission to an existing custom function.
To add a function permission, the graphql-engine should have disabled inferring of
function permissions and the provided role should have select permissions to the
target table of the function.
.. code-block:: http
POST /v1/metadata HTTP/1.1
Content-Type: application/json
X-Hasura-Role: admin
{
"type": "pg_create_function_permission",
"args": {
"function": "get_articles",
"role": "user"
}
}
.. _pg_create_function_permission_args_syntax:
Args syntax
^^^^^^^^^^^
.. list-table::
:header-rows: 1
* - Key
- Required
- Schema
- Description
* - function
- true
- :ref:`FunctionName <FunctionName>`
- Name of the SQL function
* - role
- true
- :ref:`RoleName <RoleName>`
- Name of the role
* - source
- false
- Text
- Name of the source of the SQL function
.. _pg_drop_function_permission:
pg_drop_function_permission
---------------------------
``pg_drop_function_permission`` is used to drop an existing function permission.
.. code-block:: http
POST /v1/metadata HTTP/1.1
Content-Type: application/json
X-Hasura-Role: admin
{
"type": "pg_drop_function_permission",
"args": {
"function": "get_articles",
"role": "user"
}
}
.. _pg_drop_function_permission_args_syntax:
Args syntax
^^^^^^^^^^^
.. list-table::
:header-rows: 1
* - Key
- Required
- Schema
- Description
* - function
- true
- :ref:`FunctionName <FunctionName>`
- Name of the SQL function
* - role
- true
- :ref:`RoleName <RoleName>`
- Name of the role
* - source
- false
- Text
- Name of the source of the SQL function
.. _untrack_function: .. _untrack_function:
untrack_function untrack_function

View File

@ -2,10 +2,10 @@
:description: Hasura schema/metadata API reference :description: Hasura schema/metadata API reference
:keywords: hasura, docs, schema/metadata API, API reference :keywords: hasura, docs, schema/metadata API, API reference
.. _metadata_apis: .. _schema_metadata_apis:
Schema / Metadata API Reference Schema / Metadata API Reference (v1.3 and below)
=============================== ================================================
.. contents:: Table of contents .. contents:: Table of contents
:backlinks: none :backlinks: none

View File

@ -177,7 +177,7 @@ API should be called with the schema document.
} }
Argument Presets Argument Presets
^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
Argument presets can be used to automatically inject input values for fields Argument presets can be used to automatically inject input values for fields
during execution. This way the field is executed with limited input values. Argument during execution. This way the field is executed with limited input values. Argument
@ -344,7 +344,7 @@ Args syntax
.. _RemoteSchemaPermission: .. _RemoteSchemaPermission:
RemoteSchemaPermission RemoteSchemaPermission
&&&&&&&&&&&&&&&&&&&&&& """"""""""""""""""""""
.. list-table:: .. list-table::
:header-rows: 1 :header-rows: 1

View File

@ -227,6 +227,16 @@ For the ``serve`` sub-command these are the available flags and ENV variables:
- ``HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS`` - ``HASURA_GRAPHQL_ENABLE_REMOTE_SCHEMA_PERMISSIONS``
- Enable remote schema permissions (default: ``false``) - Enable remote schema permissions (default: ``false``)
* - ``--infer-function-permissions``
- ``HASURA_GRAPHQL_INFER_FUNCTION_PERMISSIONS``
- When the ``--infer-function-permissions`` flag is set to ``false``, a function ``f``, stable, immutable or volatile is
only exposed for a role ``r`` if there is a permission defined on the function ``f`` for the role ``r``, creating a
function permission will only be allowed if there is a select permission on the table type.
When the ``--infer-function-permissions`` flag is set to ``true`` or the flag is omitted (defaults to ``true``), the
permission of the function is inferred from the select permissions from the target table of the function, only for
stable/immutable functions. Volatile functions are not exposed to any of the roles in this case.
.. note:: .. note::
When the equivalent flags for environment variables are used, the flags will take precedence. When the equivalent flags for environment variables are used, the flags will take precedence.

View File

@ -583,3 +583,8 @@ Permissions for custom function queries
**For example**, in our text-search example above, if the role ``user`` doesn't have the requisite permissions to view **For example**, in our text-search example above, if the role ``user`` doesn't have the requisite permissions to view
the table ``article``, a validation error will be thrown if the ``search_articles`` query is run using the ``user`` the table ``article``, a validation error will be thrown if the ``search_articles`` query is run using the ``user``
role. role.
.. note::
When inferring of function permissions is disabled, then there should be a function permission configured for the function to
be accessible to a role, otherwise the function is not exposed to the role.

View File

@ -101,12 +101,14 @@ runApp env (HGEOptionsG rci metadataDbUrl hgeCmd) = do
remoteSchemaPermsCtx = RemoteSchemaPermsDisabled remoteSchemaPermsCtx = RemoteSchemaPermsDisabled
pgLogger = print pgLogger = print
pgSourceResolver = mkPgSourceResolver pgLogger pgSourceResolver = mkPgSourceResolver pgLogger
cacheBuildParams = CacheBuildParams _gcHttpManager sqlGenCtx remoteSchemaPermsCtx pgSourceResolver functionPermsCtx = FunctionPermissionsInferred
serverConfigCtx = ServerConfigCtx functionPermsCtx remoteSchemaPermsCtx sqlGenCtx
cacheBuildParams = CacheBuildParams _gcHttpManager pgSourceResolver serverConfigCtx
runManagedT (mkMinimalPool _gcMetadataDbConnInfo) $ \metadataDbPool -> do runManagedT (mkMinimalPool _gcMetadataDbConnInfo) $ \metadataDbPool -> do
res <- flip runPGMetadataStorageApp (metadataDbPool, pgLogger) $ res <- flip runPGMetadataStorageApp (metadataDbPool, pgLogger) $
runMetadataStorageT $ liftEitherM do runMetadataStorageT $ liftEitherM do
metadata <- fetchMetadata metadata <- fetchMetadata
runAsAdmin sqlGenCtx _gcHttpManager remoteSchemaPermsCtx $ do runAsAdmin sqlGenCtx _gcHttpManager remoteSchemaPermsCtx functionPermsCtx $ do
schemaCache <- runCacheBuild cacheBuildParams $ schemaCache <- runCacheBuild cacheBuildParams $
buildRebuildableSchemaCache env metadata buildRebuildableSchemaCache env metadata
execQuery env queryBs execQuery env queryBs

View File

@ -275,7 +275,7 @@ initialiseServeCtx env GlobalCtx{..} so@ServeOptions{..} = do
(rebuildableSchemaCache, cacheInitStartTime) <- (rebuildableSchemaCache, cacheInitStartTime) <-
lift . flip onException (flushLogger loggerCtx) $ lift . flip onException (flushLogger loggerCtx) $
migrateCatalogSchema env logger metadataDbPool maybeDefaultSourceConfig _gcHttpManager migrateCatalogSchema env logger metadataDbPool maybeDefaultSourceConfig _gcHttpManager
sqlGenCtx soEnableRemoteSchemaPermissions (mkPgSourceResolver pgLogger) sqlGenCtx soEnableRemoteSchemaPermissions soInferFunctionPermissions (mkPgSourceResolver pgLogger)
let schemaSyncCtx = SchemaSyncCtx schemaSyncListenerThread schemaSyncEventRef cacheInitStartTime let schemaSyncCtx = SchemaSyncCtx schemaSyncListenerThread schemaSyncEventRef cacheInitStartTime
pure $ ServeCtx _gcHttpManager instanceId loggers metadataDbPool latch pure $ ServeCtx _gcHttpManager instanceId loggers metadataDbPool latch
@ -297,14 +297,19 @@ mkLoggers enabledLogs logLevel = do
migrateCatalogSchema migrateCatalogSchema
:: (HasVersion, MonadIO m, MonadBaseControl IO m) :: (HasVersion, MonadIO m, MonadBaseControl IO m)
=> Env.Environment -> Logger Hasura -> Q.PGPool -> Maybe SourceConfiguration => Env.Environment -> Logger Hasura -> Q.PGPool -> Maybe SourceConfiguration
-> HTTP.Manager -> SQLGenCtx -> RemoteSchemaPermsCtx -> SourceResolver -> HTTP.Manager -> SQLGenCtx -> RemoteSchemaPermsCtx -> FunctionPermissionsCtx
-> SourceResolver
-> m (RebuildableSchemaCache, UTCTime) -> m (RebuildableSchemaCache, UTCTime)
migrateCatalogSchema env logger pool defaultSourceConfig httpManager sqlGenCtx remoteSchemaPermsCtx sourceResolver = do migrateCatalogSchema env logger pool defaultSourceConfig
httpManager sqlGenCtx remoteSchemaPermsCtx functionPermsCtx
sourceResolver = do
currentTime <- liftIO Clock.getCurrentTime currentTime <- liftIO Clock.getCurrentTime
initialiseResult <- runExceptT $ do initialiseResult <- runExceptT $ do
(migrationResult, metadata) <- Q.runTx pool (Q.Serializable, Just Q.ReadWrite) $ (migrationResult, metadata) <- Q.runTx pool (Q.Serializable, Just Q.ReadWrite) $
migrateCatalog defaultSourceConfig currentTime migrateCatalog defaultSourceConfig currentTime
let cacheBuildParams = CacheBuildParams httpManager sqlGenCtx remoteSchemaPermsCtx sourceResolver let serverConfigCtx = ServerConfigCtx functionPermsCtx remoteSchemaPermsCtx sqlGenCtx
cacheBuildParams =
CacheBuildParams httpManager sourceResolver serverConfigCtx
buildReason = case getMigratedFrom migrationResult of buildReason = case getMigratedFrom migrationResult of
Nothing -> CatalogSync Nothing -> CatalogSync
Just version -> Just version ->
@ -451,6 +456,7 @@ runHGEServer env ServeOptions{..} ServeCtx{..} initTime postPollHook serverMetri
_scSchemaCache _scSchemaCache
ekgStore ekgStore
soEnableRemoteSchemaPermissions soEnableRemoteSchemaPermissions
soInferFunctionPermissions
soConnectionOptions soConnectionOptions
soWebsocketKeepAlive soWebsocketKeepAlive
@ -462,6 +468,7 @@ runHGEServer env ServeOptions{..} ServeCtx{..} initTime postPollHook serverMetri
_ <- startSchemaSyncProcessorThread sqlGenCtx _ <- startSchemaSyncProcessorThread sqlGenCtx
logger _scHttpManager _sscSyncEventRef logger _scHttpManager _sscSyncEventRef
cacheRef _scInstanceId _sscCacheInitStartTime soEnableRemoteSchemaPermissions cacheRef _scInstanceId _sscCacheInitStartTime soEnableRemoteSchemaPermissions
soInferFunctionPermissions
let let
maxEvThrds = fromMaybe defaultMaxEventThreads soEventsHttpPoolSize maxEvThrds = fromMaybe defaultMaxEventThreads soEventsHttpPoolSize
@ -613,10 +620,12 @@ runAsAdmin
:: SQLGenCtx :: SQLGenCtx
-> HTTP.Manager -> HTTP.Manager
-> RemoteSchemaPermsCtx -> RemoteSchemaPermsCtx
-> FunctionPermissionsCtx
-> RunT m a -> RunT m a
-> m (Either QErr a) -> m (Either QErr a)
runAsAdmin sqlGenCtx httpManager remoteSchemaPermsCtx m = do runAsAdmin sqlGenCtx httpManager remoteSchemaPermsCtx functionPermsCtx m = do
let runCtx = RunCtx adminUserInfo httpManager sqlGenCtx remoteSchemaPermsCtx let serverConfigCtx = ServerConfigCtx functionPermsCtx remoteSchemaPermsCtx sqlGenCtx
runCtx = RunCtx adminUserInfo httpManager serverConfigCtx
runExceptT $ peelRun runCtx m runExceptT $ peelRun runCtx m
execQuery execQuery
@ -626,10 +635,9 @@ execQuery
, MonadBaseControl IO m , MonadBaseControl IO m
, MonadUnique m , MonadUnique m
, HasHttpManagerM m , HasHttpManagerM m
, HasSQLGenCtx m
, UserInfoM m , UserInfoM m
, Tracing.MonadTrace m , Tracing.MonadTrace m
, HasRemoteSchemaPermsCtx m , HasServerConfigCtx m
, MetadataM m , MetadataM m
, MonadMetadataStorageQueryAPI m , MonadMetadataStorageQueryAPI m
) )

View File

@ -77,8 +77,7 @@ buildGQLContext
, MonadError QErr m , MonadError QErr m
, MonadIO m , MonadIO m
, MonadUnique m , MonadUnique m
, HasSQLGenCtx m , HasServerConfigCtx m
, HasRemoteSchemaPermsCtx m
) )
=> ( GraphQLQueryType => ( GraphQLQueryType
, SourceCache , SourceCache
@ -92,9 +91,8 @@ buildGQLContext
) )
buildGQLContext = buildGQLContext =
proc (queryType, pgSources, allRemoteSchemas, allActions, nonObjectCustomTypes) -> do proc (queryType, pgSources, allRemoteSchemas, allActions, nonObjectCustomTypes) -> do
ServerConfigCtx functionPermsCtx remoteSchemaPermsCtx sqlGenCtx@(SQLGenCtx stringifyNum) <-
sqlGenCtx@(SQLGenCtx{ stringifyNum }) <- bindA -< askSQLGenCtx bindA -< askServerConfigCtx
remoteSchemaPermsCtx <- bindA -< askRemoteSchemaPermsCtx
let remoteSchemasRoles = concatMap (Map.keys . _rscPermissions . fst . snd) $ Map.toList allRemoteSchemas let remoteSchemasRoles = concatMap (Map.keys . _rscPermissions . fst . snd) $ Map.toList allRemoteSchemas
@ -108,7 +106,9 @@ buildGQLContext =
allRemoteSchemas allRemoteSchemas
<&> (\(remoteSchemaCtx, _metadataObj) -> <&> (\(remoteSchemaCtx, _metadataObj) ->
(_rscIntro remoteSchemaCtx, _rscParsed remoteSchemaCtx)) (_rscIntro remoteSchemaCtx, _rscParsed remoteSchemaCtx))
adminQueryContext = QueryContext stringifyNum queryType adminRemoteRelationshipQueryCtx -- The function permissions context doesn't actually matter because the
-- admin will have access to the function anyway
adminQueryContext = QueryContext stringifyNum queryType adminRemoteRelationshipQueryCtx FunctionPermissionsInferred
-- build the admin DB-only context so that we can check against name clashes with remotes -- build the admin DB-only context so that we can check against name clashes with remotes
-- TODO: Is there a better way to check for conflicts without actually building the admin schema? -- TODO: Is there a better way to check for conflicts without actually building the admin schema?
@ -142,10 +142,10 @@ buildGQLContext =
( Set.toMap allRoles & Map.traverseWithKey \roleName () -> ( Set.toMap allRoles & Map.traverseWithKey \roleName () ->
case queryType of case queryType of
QueryHasura -> QueryHasura ->
buildRoleContext (sqlGenCtx, queryType) pgSources allRemoteSchemas allActionInfos buildRoleContext (sqlGenCtx, queryType, functionPermsCtx) pgSources allRemoteSchemas allActionInfos
nonObjectCustomTypes remotes roleName remoteSchemaPermsCtx nonObjectCustomTypes remotes roleName remoteSchemaPermsCtx
QueryRelay -> QueryRelay ->
buildRelayRoleContext (sqlGenCtx, queryType) pgSources allActionInfos buildRelayRoleContext (sqlGenCtx, queryType, functionPermsCtx) pgSources allActionInfos
nonObjectCustomTypes adminMutationRemotes roleName nonObjectCustomTypes adminMutationRemotes roleName
) )
unauthenticated <- bindA -< unauthenticatedContext adminQueryRemotes adminMutationRemotes remoteSchemaPermsCtx unauthenticated <- bindA -< unauthenticatedContext adminQueryRemotes adminMutationRemotes remoteSchemaPermsCtx
@ -187,13 +187,13 @@ buildRoleBasedRemoteSchemaParser role remoteSchemaCache = do
-- TODO: Integrate relay schema -- TODO: Integrate relay schema
buildRoleContext buildRoleContext
:: (MonadError QErr m, MonadIO m, MonadUnique m) :: (MonadError QErr m, MonadIO m, MonadUnique m)
=> (SQLGenCtx, GraphQLQueryType) -> SourceCache -> RemoteSchemaCache => (SQLGenCtx, GraphQLQueryType, FunctionPermissionsCtx) -> SourceCache -> RemoteSchemaCache
-> [ActionInfo 'Postgres] -> NonObjectTypeMap -> [ActionInfo 'Postgres] -> NonObjectTypeMap
-> [( RemoteSchemaName , (IntrospectionResult, ParsedIntrospection))] -> [( RemoteSchemaName , (IntrospectionResult, ParsedIntrospection))]
-> RoleName -> RoleName
-> RemoteSchemaPermsCtx -> RemoteSchemaPermsCtx
-> m (RoleContext GQLContext) -> m (RoleContext GQLContext)
buildRoleContext (SQLGenCtx stringifyNum, queryType) sources buildRoleContext (SQLGenCtx stringifyNum, queryType, functionPermsCtx) sources
allRemoteSchemas allActionInfos nonObjectCustomTypes remotes roleName remoteSchemaPermsCtx = do allRemoteSchemas allActionInfos nonObjectCustomTypes remotes roleName remoteSchemaPermsCtx = do
roleBasedRemoteSchemas <- roleBasedRemoteSchemas <-
@ -206,7 +206,7 @@ buildRoleContext (SQLGenCtx stringifyNum, queryType) sources
let queryRemotes = getQueryRemotes $ snd . snd <$> roleBasedRemoteSchemas let queryRemotes = getQueryRemotes $ snd . snd <$> roleBasedRemoteSchemas
mutationRemotes = getMutationRemotes $ snd . snd <$> roleBasedRemoteSchemas mutationRemotes = getMutationRemotes $ snd . snd <$> roleBasedRemoteSchemas
remoteRelationshipQueryContext = Map.fromList roleBasedRemoteSchemas remoteRelationshipQueryContext = Map.fromList roleBasedRemoteSchemas
roleQueryContext = QueryContext stringifyNum queryType remoteRelationshipQueryContext roleQueryContext = QueryContext stringifyNum queryType remoteRelationshipQueryContext functionPermsCtx
runMonadSchema roleName roleQueryContext sources $ do runMonadSchema roleName roleQueryContext sources $ do
let pgSources = mapMaybe unsafeSourceInfo $ toList sources let pgSources = mapMaybe unsafeSourceInfo $ toList sources
@ -252,7 +252,6 @@ buildRoleContext (SQLGenCtx stringifyNum, queryType) sources
-> [P.FieldParser (P.ParseT Identity) RemoteField] -> [P.FieldParser (P.ParseT Identity) RemoteField]
getMutationRemotes = concatMap (concat . piMutation) getMutationRemotes = concatMap (concat . piMutation)
buildFullestDBSchema buildFullestDBSchema
:: (MonadError QErr m, MonadIO m, MonadUnique m) :: (MonadError QErr m, MonadIO m, MonadUnique m)
=> QueryContext -> SourceCache -> [ActionInfo 'Postgres] -> NonObjectTypeMap => QueryContext -> SourceCache -> [ActionInfo 'Postgres] -> NonObjectTypeMap
@ -287,16 +286,16 @@ buildFullestDBSchema queryContext sources allActionInfos nonObjectCustomTypes =
buildRelayRoleContext buildRelayRoleContext
:: (MonadError QErr m, MonadIO m, MonadUnique m) :: (MonadError QErr m, MonadIO m, MonadUnique m)
=> (SQLGenCtx, GraphQLQueryType) -> SourceCache -> [ActionInfo 'Postgres] -> NonObjectTypeMap => (SQLGenCtx, GraphQLQueryType, FunctionPermissionsCtx) -> SourceCache -> [ActionInfo 'Postgres] -> NonObjectTypeMap
-> [P.FieldParser (P.ParseT Identity) RemoteField] -> [P.FieldParser (P.ParseT Identity) RemoteField]
-> RoleName -> RoleName
-> m (RoleContext GQLContext) -> m (RoleContext GQLContext)
buildRelayRoleContext (SQLGenCtx stringifyNum, queryType) sources buildRelayRoleContext (SQLGenCtx stringifyNum, queryType, functionPermsCtx) sources
allActionInfos nonObjectCustomTypes mutationRemotes roleName = allActionInfos nonObjectCustomTypes mutationRemotes roleName =
-- TODO: At the time of writing this, remote schema queries are not supported in relay. -- TODO: At the time of writing this, remote schema queries are not supported in relay.
-- When they are supported, we should get do what `buildRoleContext` does. Since, they -- When they are supported, we should get do what `buildRoleContext` does. Since, they
-- are not supported yet, we use `mempty` below for `RemoteRelationshipQueryContext`. -- are not supported yet, we use `mempty` below for `RemoteRelationshipQueryContext`.
let roleQueryContext = QueryContext stringifyNum queryType mempty let roleQueryContext = QueryContext stringifyNum queryType mempty functionPermsCtx
in in
runMonadSchema roleName roleQueryContext sources $ do runMonadSchema roleName roleQueryContext sources $ do
@ -432,6 +431,8 @@ buildQueryFields
-> [FunctionInfo b] -> [FunctionInfo b]
-> m [P.FieldParser n (QueryRootField (UnpreparedValue b))] -> m [P.FieldParser n (QueryRootField (UnpreparedValue b))]
buildQueryFields sourceName sourceConfig tables (takeExposedAs FEAQuery id -> functions) = do buildQueryFields sourceName sourceConfig tables (takeExposedAs FEAQuery id -> functions) = do
functionPermsCtx <- asks $ qcFunctionPermsContext . getter
roleName <- askRoleName
tableSelectExpParsers <- for tables \(table, _tableInfo) -> do tableSelectExpParsers <- for tables \(table, _tableInfo) -> do
selectPerms <- tableSelectPermissions table selectPerms <- tableSelectPermissions table
customRootFields <- _tcCustomRootFields . _tciCustomConfig . _tiCoreInfo <$> askTableInfo @'Postgres table customRootFields <- _tcCustomRootFields . _tciCustomConfig . _tiCoreInfo <$> askTableInfo @'Postgres table
@ -447,32 +448,38 @@ buildQueryFields sourceName sourceConfig tables (takeExposedAs FEAQuery id -> fu
, mapMaybeFieldParser (asDbRootField . QDBPrimaryKey) $ selectTableByPk table (fromMaybe pkName $ _tcrfSelectByPk customRootFields) (Just pkDesc) perms , mapMaybeFieldParser (asDbRootField . QDBPrimaryKey) $ selectTableByPk table (fromMaybe pkName $ _tcrfSelectByPk customRootFields) (Just pkDesc) perms
, mapMaybeFieldParser (asDbRootField . QDBAggregation) $ selectTableAggregate table (fromMaybe aggName $ _tcrfSelectAggregate customRootFields) (Just aggDesc) perms , mapMaybeFieldParser (asDbRootField . QDBAggregation) $ selectTableAggregate table (fromMaybe aggName $ _tcrfSelectAggregate customRootFields) (Just aggDesc) perms
] ]
functionSelectExpParsers <- for functions \function -> do functionSelectExpParsers <- for functions \function -> runMaybeT $ do
let targetTable = fiReturnType function let targetTable = _fiReturnType function
functionName = fiName function functionName = _fiName function
selectPerms <- tableSelectPermissions targetTable selectPerms <- lift $ tableSelectPermissions targetTable
for selectPerms \perms -> do perms <- hoistMaybe selectPerms
displayName <- functionGraphQLName @b functionName `onLeft` throwError when (functionPermsCtx == FunctionPermissionsManual) $
let functionDesc = G.Description $ "execute function " <> functionName <<> " which returns " <>> targetTable -- see Note [Function Permissions]
aggName = displayName <> $$(G.litName "_aggregate") guard $ roleName == adminRoleName || roleName `elem` (_fiPermissions function)
aggDesc = G.Description $ "execute function " <> functionName <<> " and query aggregates on result of table type " <>> targetTable displayName <- functionGraphQLName @b functionName `onLeft` throwError
catMaybes <$> sequenceA let functionDesc = G.Description $ "execute function " <> functionName <<> " which returns " <>> targetTable
[ requiredFieldParser (asDbRootField . QDBSimple) $ selectFunction function displayName (Just functionDesc) perms aggName = displayName <> $$(G.litName "_aggregate")
, mapMaybeFieldParser (asDbRootField . QDBAggregation) $ selectFunctionAggregate function aggName (Just aggDesc) perms aggDesc = G.Description $ "execute function " <> functionName <<> " and query aggregates on result of table type " <>> targetTable
] catMaybes <$> sequenceA
[ requiredFieldParser (asDbRootField . QDBSimple) $ lift $ selectFunction function displayName (Just functionDesc) perms
, mapMaybeFieldParser (asDbRootField . QDBAggregation) $ lift $ selectFunctionAggregate function aggName (Just aggDesc) perms
]
pure $ (concat . catMaybes) (tableSelectExpParsers <> functionSelectExpParsers) pure $ (concat . catMaybes) (tableSelectExpParsers <> functionSelectExpParsers)
where where
asDbRootField = asDbRootField =
let pgExecCtx = PG._pscExecCtx sourceConfig let pgExecCtx = PG._pscExecCtx sourceConfig
in RFDB sourceName pgExecCtx in RFDB sourceName pgExecCtx
mapMaybeFieldParser :: (a -> a') -> m (Maybe (P.FieldParser n a)) -> m (Maybe (P.FieldParser n a'))
mapMaybeFieldParser f = fmap $ fmap $ fmap f
requiredFieldParser requiredFieldParser
:: (Functor n, Functor m)=> (a -> b) -> m (P.FieldParser n a) -> m (Maybe (P.FieldParser n b)) :: (Functor n, Functor m)=> (a -> b) -> m (P.FieldParser n a) -> m (Maybe (P.FieldParser n b))
requiredFieldParser f = fmap $ Just . fmap f requiredFieldParser f = fmap $ Just . fmap f
mapMaybeFieldParser
:: (Functor n, Functor m)
=> (a -> b)
-> m (Maybe (P.FieldParser n a))
-> m (Maybe (P.FieldParser n b))
mapMaybeFieldParser f = fmap $ fmap $ fmap f
-- | Includes remote schema fields and actions -- | Includes remote schema fields and actions
buildActionQueryFields buildActionQueryFields
@ -536,8 +543,8 @@ buildRelayPostgresQueryFields sourceName sourceConfig allTables (takeExposedAs F
lift $ selectTableConnection table fieldName fieldDesc pkeyColumns selectPerms lift $ selectTableConnection table fieldName fieldDesc pkeyColumns selectPerms
functionConnectionFields <- for queryFunctions $ \function -> runMaybeT do functionConnectionFields <- for queryFunctions $ \function -> runMaybeT do
let returnTable = fiReturnType function let returnTable = _fiReturnType function
functionName = fiName function functionName = _fiName function
pkeyColumns <- MaybeT $ (^? tiCoreInfo.tciPrimaryKey._Just.pkColumns) pkeyColumns <- MaybeT $ (^? tiCoreInfo.tciPrimaryKey._Just.pkColumns)
<$> askTableInfo returnTable <$> askTableInfo returnTable
selectPerms <- MaybeT $ tableSelectPermissions returnTable selectPerms <- MaybeT $ tableSelectPermissions returnTable
@ -762,24 +769,34 @@ buildMutationParser
-> m (Maybe (Parser 'Output n (OMap.InsOrdHashMap G.Name (MutationRootField (UnpreparedValue 'Postgres))))) -> m (Maybe (Parser 'Output n (OMap.InsOrdHashMap G.Name (MutationRootField (UnpreparedValue 'Postgres)))))
buildMutationParser allRemotes allActions nonObjectCustomTypes buildMutationParser allRemotes allActions nonObjectCustomTypes
(takeExposedAs FEAMutation fst -> mutationFunctions) pgMutationFields = do (takeExposedAs FEAMutation fst -> mutationFunctions) pgMutationFields = do
roleName <- askRoleName
functionPermsCtx <- asks $ qcFunctionPermsContext . getter
-- NOTE: this is basically copied from functionSelectExpParsers body -- NOTE: this is basically copied from functionSelectExpParsers body
functionMutationExpParsers <- for mutationFunctions \(function@FunctionInfo{..}, (sourceName, sourceConfig)) -> do functionMutationExpParsers <-
selectPerms <- tableSelectPermissions fiReturnType case functionPermsCtx of
for selectPerms \perms -> do -- when function permissions are inferred, we don't expose the
displayName <- PG.qualifiedObjectToName fiName -- mutation functions. See Note [Function Permissions]
let functionDesc = G.Description $ FunctionPermissionsInferred -> pure []
"execute VOLATILE function " <> fiName <<> " which returns " <>> fiReturnType FunctionPermissionsManual ->
asDbRootField = for mutationFunctions \(function@FunctionInfo{..}, (sourceName, sourceConfig)) -> runMaybeT do
let pgExecCtx = PG._pscExecCtx sourceConfig selectPerms <- lift $ tableSelectPermissions _fiReturnType
in RFDB sourceName pgExecCtx -- A function exposed as mutation must have a function permission
-- configured for the role. See Note [Function Permissions]
guard $ roleName == adminRoleName || roleName `elem` _fiPermissions
perms <- hoistMaybe selectPerms
displayName <- PG.qualifiedObjectToName _fiName
let functionDesc = G.Description $
"execute VOLATILE function " <> _fiName <<> " which returns " <>> _fiReturnType
asDbRootField =
let pgExecCtx = PG._pscExecCtx sourceConfig
in RFDB sourceName pgExecCtx
catMaybes <$> sequenceA catMaybes <$> sequenceA
[ requiredFieldParser (asDbRootField . MDBFunction) $ [ requiredFieldParser (asDbRootField . MDBFunction) $
selectFunction function displayName (Just functionDesc) perms lift $ selectFunction function displayName (Just functionDesc) perms
-- FWIW: The equivalent of this is possible for mutations; do we want that?: -- FWIW: The equivalent of this is possible for mutations; do we want that?:
-- , mapMaybeFieldParser (asDbRootField . QDBAggregation) $ selectFunctionAggregate function aggName (Just aggDesc) perms -- , mapMaybeFieldParser (asDbRootField . QDBAggregation) $ selectFunctionAggregate function aggName (Just aggDesc) perms
] ]
actionParsers <- for allActions $ \actionInfo -> actionParsers <- for allActions $ \actionInfo ->
case _adType (_aiDefinition actionInfo) of case _adType (_aiDefinition actionInfo) of
@ -804,7 +821,7 @@ buildMutationParser allRemotes allActions nonObjectCustomTypes
-- local helpers -- local helpers
takeExposedAs :: FunctionExposedAs -> (a -> FunctionInfo b) -> [a] -> [a] takeExposedAs :: FunctionExposedAs -> (a -> FunctionInfo b) -> [a] -> [a]
takeExposedAs x f = filter ((== x) . fiExposedAs . f) takeExposedAs x f = filter ((== x) . _fiExposedAs . f)
subscriptionRoot :: G.Name subscriptionRoot :: G.Name
subscriptionRoot = $$(G.litName "subscription_root") subscriptionRoot = $$(G.litName "subscription_root")

View File

@ -33,6 +33,7 @@ data QueryContext =
{ qcStringifyNum :: !Bool { qcStringifyNum :: !Bool
, qcQueryType :: !ET.GraphQLQueryType , qcQueryType :: !ET.GraphQLQueryType
, qcRemoteRelationshipContext :: !(HashMap RemoteSchemaName (IntrospectionResult, ParsedIntrospection)) , qcRemoteRelationshipContext :: !(HashMap RemoteSchemaName (IntrospectionResult, ParsedIntrospection))
, qcFunctionPermsContext :: !FunctionPermissionsCtx
} }
textToName :: MonadError QErr m => Text -> m G.Name textToName :: MonadError QErr m => Text -> m G.Name
@ -129,4 +130,4 @@ takeValidTables = Map.filterWithKey graphQLTableFilter . Map.filter tableFilter
takeValidFunctions :: forall b. FunctionCache b -> [FunctionInfo b] takeValidFunctions :: forall b. FunctionCache b -> [FunctionInfo b]
takeValidFunctions = Map.elems . Map.filter functionFilter takeValidFunctions = Map.elems . Map.filter functionFilter
where where
functionFilter = not . isSystemDefined . fiSystemDefined functionFilter = not . isSystemDefined . _fiSystemDefined

View File

@ -464,7 +464,7 @@ selectFunction
-> m (FieldParser n (SelectExp b)) -> m (FieldParser n (SelectExp b))
selectFunction function fieldName description selectPermissions = do selectFunction function fieldName description selectPermissions = do
stringifyNum <- asks $ qcStringifyNum . getter stringifyNum <- asks $ qcStringifyNum . getter
let table = fiReturnType function let table = _fiReturnType function
tableArgsParser <- tableArgs table selectPermissions tableArgsParser <- tableArgs table selectPermissions
functionArgsParser <- customSQLFunctionArgs function functionArgsParser <- customSQLFunctionArgs function
selectionSetParser <- tableSelectionList table selectPermissions selectionSetParser <- tableSelectionList table selectPermissions
@ -472,7 +472,7 @@ selectFunction function fieldName description selectPermissions = do
pure $ P.subselection fieldName description argsParser selectionSetParser pure $ P.subselection fieldName description argsParser selectionSetParser
<&> \((funcArgs, tableArgs'), fields) -> IR.AnnSelectG <&> \((funcArgs, tableArgs'), fields) -> IR.AnnSelectG
{ IR._asnFields = fields { IR._asnFields = fields
, IR._asnFrom = IR.FromFunction (fiName function) funcArgs Nothing , IR._asnFrom = IR.FromFunction (_fiName function) funcArgs Nothing
, IR._asnPerm = tablePermissionsInfo selectPermissions , IR._asnPerm = tablePermissionsInfo selectPermissions
, IR._asnArgs = tableArgs' , IR._asnArgs = tableArgs'
, IR._asnStrfyNum = stringifyNum , IR._asnStrfyNum = stringifyNum
@ -492,7 +492,7 @@ selectFunctionAggregate
-> SelPermInfo b -- ^ select permissions of the target table -> SelPermInfo b -- ^ select permissions of the target table
-> m (Maybe (FieldParser n (AggSelectExp b))) -> m (Maybe (FieldParser n (AggSelectExp b)))
selectFunctionAggregate function fieldName description selectPermissions = runMaybeT do selectFunctionAggregate function fieldName description selectPermissions = runMaybeT do
let table = fiReturnType function let table = _fiReturnType function
stringifyNum <- asks $ qcStringifyNum . getter stringifyNum <- asks $ qcStringifyNum . getter
guard $ spiAllowAgg selectPermissions guard $ spiAllowAgg selectPermissions
tableGQLName <- getTableGQLName @b table tableGQLName <- getTableGQLName @b table
@ -511,7 +511,7 @@ selectFunctionAggregate function fieldName description selectPermissions = runMa
pure $ P.subselection fieldName description argsParser aggregationParser pure $ P.subselection fieldName description argsParser aggregationParser
<&> \((funcArgs, tableArgs'), fields) -> IR.AnnSelectG <&> \((funcArgs, tableArgs'), fields) -> IR.AnnSelectG
{ IR._asnFields = fields { IR._asnFields = fields
, IR._asnFrom = IR.FromFunction (fiName function) funcArgs Nothing , IR._asnFrom = IR.FromFunction (_fiName function) funcArgs Nothing
, IR._asnPerm = tablePermissionsInfo selectPermissions , IR._asnPerm = tablePermissionsInfo selectPermissions
, IR._asnArgs = tableArgs' , IR._asnArgs = tableArgs'
, IR._asnStrfyNum = stringifyNum , IR._asnStrfyNum = stringifyNum
@ -532,7 +532,7 @@ selectFunctionConnection
-> m (FieldParser n (ConnectionSelectExp 'Postgres)) -> m (FieldParser n (ConnectionSelectExp 'Postgres))
selectFunctionConnection function fieldName description pkeyColumns selectPermissions = do selectFunctionConnection function fieldName description pkeyColumns selectPermissions = do
stringifyNum <- asks $ qcStringifyNum . getter stringifyNum <- asks $ qcStringifyNum . getter
let table = fiReturnType function let table = _fiReturnType function
tableConnectionArgsParser <- tableConnectionArgs pkeyColumns table selectPermissions tableConnectionArgsParser <- tableConnectionArgs pkeyColumns table selectPermissions
functionArgsParser <- customSQLFunctionArgs function functionArgsParser <- customSQLFunctionArgs function
selectionSetParser <- tableConnectionSelectionSet table selectPermissions selectionSetParser <- tableConnectionSelectionSet table selectPermissions
@ -544,7 +544,7 @@ selectFunctionConnection function fieldName description pkeyColumns selectPermis
, IR._csSlice = slice , IR._csSlice = slice
, IR._csSelect = IR.AnnSelectG , IR._csSelect = IR.AnnSelectG
{ IR._asnFields = fields { IR._asnFields = fields
, IR._asnFrom = IR.FromFunction (fiName function) funcArgs Nothing , IR._asnFrom = IR.FromFunction (_fiName function) funcArgs Nothing
, IR._asnPerm = tablePermissionsInfo selectPermissions , IR._asnPerm = tablePermissionsInfo selectPermissions
, IR._asnArgs = args , IR._asnArgs = args
, IR._asnStrfyNum = stringifyNum , IR._asnStrfyNum = stringifyNum
@ -1130,7 +1130,7 @@ customSQLFunctionArgs
:: (BackendSchema b, MonadSchema n m, MonadTableInfo r m) :: (BackendSchema b, MonadSchema n m, MonadTableInfo r m)
=> FunctionInfo b => FunctionInfo b
-> m (InputFieldsParser n (IR.FunctionArgsExpTableRow b (UnpreparedValue b))) -> m (InputFieldsParser n (IR.FunctionArgsExpTableRow b (UnpreparedValue b)))
customSQLFunctionArgs FunctionInfo{..} = functionArgs fiName fiInputArgs customSQLFunctionArgs FunctionInfo{..} = functionArgs _fiName _fiInputArgs
-- | Parses the arguments to the underlying sql function of a computed field or -- | Parses the arguments to the underlying sql function of a computed field or
-- a custom function. All arguments to the underlying sql function are parsed -- a custom function. All arguments to the underlying sql function are parsed

View File

@ -55,7 +55,7 @@ pgIdenTrigger op trn = pgFmtIdentifier . qualifyTriggerName op $ triggerNameToTx
qualifyTriggerName op' trn' = "notify_hasura_" <> trn' <> "_" <> tshow op' qualifyTriggerName op' trn' = "notify_hasura_" <> trn' <> "_" <> tshow op'
mkAllTriggersQ mkAllTriggersQ
:: (MonadTx m, HasSQLGenCtx m) :: (MonadTx m, HasServerConfigCtx m)
=> TriggerName => TriggerName
-> QualifiedTable -> QualifiedTable
-> [ColumnInfo 'Postgres] -> [ColumnInfo 'Postgres]
@ -67,7 +67,7 @@ mkAllTriggersQ trn qt allCols fullspec = do
onJust (tdDelete fullspec) (mkTriggerQ trn qt allCols DELETE) onJust (tdDelete fullspec) (mkTriggerQ trn qt allCols DELETE)
mkTriggerQ mkTriggerQ
:: (MonadTx m, HasSQLGenCtx m) :: (MonadTx m, HasServerConfigCtx m)
=> TriggerName => TriggerName
-> QualifiedTable -> QualifiedTable
-> [ColumnInfo 'Postgres] -> [ColumnInfo 'Postgres]
@ -75,7 +75,7 @@ mkTriggerQ
-> SubscribeOpSpec -> SubscribeOpSpec
-> m () -> m ()
mkTriggerQ trn qt@(QualifiedObject schema table) allCols op (SubscribeOpSpec columns payload) = do mkTriggerQ trn qt@(QualifiedObject schema table) allCols op (SubscribeOpSpec columns payload) = do
strfyNum <- stringifyNum <$> askSQLGenCtx strfyNum <- stringifyNum . _sccSQLGenCtx <$> askServerConfigCtx
liftTx $ Q.multiQE defaultTxErrorHandler $ Q.fromText . TL.toStrict $ liftTx $ Q.multiQE defaultTxErrorHandler $ Q.fromText . TL.toStrict $
let payloadColumns = fromMaybe SubCStar payload let payloadColumns = fromMaybe SubCStar payload
mkQId opVar colInfo = toJSONableExp strfyNum (pgiType colInfo) False $ mkQId opVar colInfo = toJSONableExp strfyNum (pgiType colInfo) False $
@ -255,7 +255,7 @@ runCreateEventTriggerQuery q = do
-- transaction as soon as after @'runCreateEventTriggerQuery' is called and -- transaction as soon as after @'runCreateEventTriggerQuery' is called and
-- in building schema cache. -- in building schema cache.
createPostgresTableEventTrigger createPostgresTableEventTrigger
:: (MonadTx m, HasSQLGenCtx m) :: (MonadTx m, HasServerConfigCtx m)
=> QualifiedTable => QualifiedTable
-> [ColumnInfo 'Postgres] -> [ColumnInfo 'Postgres]
-> TriggerName -> TriggerName

View File

@ -20,7 +20,7 @@ import qualified Data.HashMap.Strict.InsOrd as OMap
import qualified Data.HashSet as HS import qualified Data.HashSet as HS
import qualified Data.List as L import qualified Data.List as L
import Control.Lens ((^?)) import Control.Lens ((.~), (^?))
import Data.Aeson import Data.Aeson
import Hasura.Metadata.Class import Hasura.Metadata.Class
@ -108,11 +108,38 @@ runReplaceMetadata replaceMetadata = do
pure successMsg pure successMsg
runExportMetadata runExportMetadata
:: (MetadataM m) :: forall m . ( QErrM m, MetadataM m, HasServerConfigCtx m)
=> ExportMetadata -> m EncJSON => ExportMetadata -> m EncJSON
runExportMetadata _ = runExportMetadata _ = do
AO.toEncJSON . metadataToOrdJSON <$> getMetadata functionPermsCtx <- _sccFunctionPermsCtx <$> askServerConfigCtx
metadata <- getMetadata
exportMetadata <- processFunctionPermissions functionPermsCtx metadata
pure $ AO.toEncJSON . metadataToOrdJSON $ exportMetadata
where
-- | when FunctionPermissionsCtx is set to `FunctionPermissionsInferred`
-- we don't export the function permissions to the exported metadata
-- Note: Please **do not** make this function public as this is only meant
-- to be used while exporting metadata i.e. we don't intend on deleting
-- any function permissions that may exist in the DB, we simply hide it
-- from the user.
processFunctionPermissions :: FunctionPermissionsCtx -> Metadata -> m Metadata
processFunctionPermissions FunctionPermissionsManual metadata = pure metadata
processFunctionPermissions FunctionPermissionsInferred metadata =
let sources = OMap.keys $ _metaSources metadata
in foldrM clearFunctionPermission metadata sources
where
clearFunctionPermission sourceName accumulatedMetadata = do
let sourceFunctions =
OMap.keys . _smFunctions <$> OMap.lookup sourceName (_metaSources accumulatedMetadata)
functions <- onNothing sourceFunctions (throw500 "unexpected: runExportMetadata - source not found")
pure $ foldr
(\functionName md ->
((metaSources.ix sourceName.smFunctions.ix functionName.fmPermissions .~ mempty) md))
accumulatedMetadata
functions
runReloadMetadata :: (QErrM m, CacheRWM m, MetadataM m) => ReloadMetadata -> m EncJSON runReloadMetadata :: (QErrM m, CacheRWM m, MetadataM m) => ReloadMetadata -> m EncJSON
runReloadMetadata (ReloadMetadata reloadRemoteSchemas reloadSources) = do runReloadMetadata (ReloadMetadata reloadRemoteSchemas reloadSources) = do
@ -177,7 +204,8 @@ purgeMetadataObj = \case
MTOTrigger trn -> dropEventTriggerInMetadata trn MTOTrigger trn -> dropEventTriggerInMetadata trn
MTOComputedField ccn -> dropComputedFieldInMetadata ccn MTOComputedField ccn -> dropComputedFieldInMetadata ccn
MTORemoteRelationship rn -> dropRemoteRelationshipInMetadata rn MTORemoteRelationship rn -> dropRemoteRelationshipInMetadata rn
SMOFunction qf -> dropFunctionInMetadata source qf SMOFunction qf -> dropFunctionInMetadata source qf
SMOFunctionPermission qf rn -> dropFunctionPermissionInMetadata source qf rn
MORemoteSchema rsn -> dropRemoteSchemaInMetadata rsn MORemoteSchema rsn -> dropRemoteSchemaInMetadata rsn
MORemoteSchemaPermissions rsName role -> dropRemoteSchemaPermissionInMetadata rsName role MORemoteSchemaPermissions rsName role -> dropRemoteSchemaPermissionInMetadata rsName role
MOCustomTypes -> clearCustomTypesInMetadata MOCustomTypes -> clearCustomTypesInMetadata

View File

@ -64,6 +64,9 @@ instance Arbitrary MetadataVersion where
instance Arbitrary FunctionMetadata where instance Arbitrary FunctionMetadata where
arbitrary = genericArbitrary arbitrary = genericArbitrary
instance Arbitrary FunctionPermissionMetadata where
arbitrary = genericArbitrary
instance Arbitrary PostgresPoolSettings where instance Arbitrary PostgresPoolSettings where
arbitrary = genericArbitrary arbitrary = genericArbitrary

View File

@ -56,13 +56,13 @@ runAddRemoteSchema env q@(AddRemoteSchemaQuery name defn comment) = do
runAddRemoteSchemaPermissions runAddRemoteSchemaPermissions
:: ( QErrM m :: ( QErrM m
, CacheRWM m , CacheRWM m
, HasRemoteSchemaPermsCtx m , HasServerConfigCtx m
, MetadataM m , MetadataM m
) )
=> AddRemoteSchemaPermissions => AddRemoteSchemaPermissions
-> m EncJSON -> m EncJSON
runAddRemoteSchemaPermissions q = do runAddRemoteSchemaPermissions q = do
remoteSchemaPermsCtx <- askRemoteSchemaPermsCtx remoteSchemaPermsCtx <- _sccRemoteSchemaPermsCtx <$> askServerConfigCtx
unless (remoteSchemaPermsCtx == RemoteSchemaPermsEnabled) $ do unless (remoteSchemaPermsCtx == RemoteSchemaPermsEnabled) $ do
throw400 ConstraintViolation throw400 ConstraintViolation
$ "remote schema permissions can only be added when " $ "remote schema permissions can only be added when "

View File

@ -105,7 +105,7 @@ isSchemaCacheBuildRequiredRunSQL RunSQL {..} =
{ TDFA.captureGroups = False } { TDFA.captureGroups = False }
"\\balter\\b|\\bdrop\\b|\\breplace\\b|\\bcreate function\\b|\\bcomment on\\b") "\\balter\\b|\\bdrop\\b|\\breplace\\b|\\bcreate function\\b|\\bcomment on\\b")
runRunSQL :: (MonadIO m, MonadBaseControl IO m, MonadError QErr m, CacheRWM m, HasSQLGenCtx m, MetadataM m) runRunSQL :: (MonadIO m, MonadBaseControl IO m, MonadError QErr m, CacheRWM m, HasServerConfigCtx m, MetadataM m)
=> RunSQL -> m EncJSON => RunSQL -> m EncJSON
runRunSQL q@RunSQL {..} runRunSQL q@RunSQL {..}
-- see Note [Checking metadata consistency in run_sql] -- see Note [Checking metadata consistency in run_sql]

View File

@ -92,8 +92,8 @@ newtype CacheRWT m a
= CacheRWT (StateT (RebuildableSchemaCache, CacheInvalidations) m a) = CacheRWT (StateT (RebuildableSchemaCache, CacheInvalidations) m a)
deriving deriving
( Functor, Applicative, Monad, MonadIO, MonadUnique, MonadReader r, MonadError e, MonadTx ( Functor, Applicative, Monad, MonadIO, MonadUnique, MonadReader r, MonadError e, MonadTx
, UserInfoM, HasHttpManagerM, HasSQLGenCtx, HasSystemDefined, MonadMetadataStorage , UserInfoM, HasHttpManagerM, HasSystemDefined, MonadMetadataStorage
, MonadMetadataStorageQueryAPI, HasRemoteSchemaPermsCtx, Tracing.MonadTrace) , MonadMetadataStorageQueryAPI, Tracing.MonadTrace, HasServerConfigCtx)
deriving instance (MonadBase IO m) => MonadBase IO (CacheRWT m) deriving instance (MonadBase IO m) => MonadBase IO (CacheRWT m)
deriving instance (MonadBaseControl IO m) => MonadBaseControl IO (CacheRWT m) deriving instance (MonadBaseControl IO m) => MonadBaseControl IO (CacheRWT m)
@ -110,8 +110,8 @@ instance MonadTrans CacheRWT where
instance (Monad m) => CacheRM (CacheRWT m) where instance (Monad m) => CacheRM (CacheRWT m) where
askSchemaCache = CacheRWT $ gets (lastBuiltSchemaCache . (^. _1)) askSchemaCache = CacheRWT $ gets (lastBuiltSchemaCache . (^. _1))
instance (MonadIO m, MonadError QErr m, HasHttpManagerM m, HasSQLGenCtx m instance (MonadIO m, MonadError QErr m, HasHttpManagerM m
, HasRemoteSchemaPermsCtx m, MonadResolveSource m) => CacheRWM (CacheRWT m) where , MonadResolveSource m, HasServerConfigCtx m) => CacheRWM (CacheRWT m) where
buildSchemaCacheWithOptions buildReason invalidations metadata = CacheRWT do buildSchemaCacheWithOptions buildReason invalidations metadata = CacheRWT do
(RebuildableSchemaCache _ invalidationKeys rule, oldInvalidations) <- get (RebuildableSchemaCache _ invalidationKeys rule, oldInvalidations) <- get
let newInvalidationKeys = invalidateKeys invalidations invalidationKeys let newInvalidationKeys = invalidateKeys invalidations invalidationKeys
@ -134,7 +134,8 @@ buildSchemaCacheRule
-- what we want! -- what we want!
:: ( HasVersion, ArrowChoice arr, Inc.ArrowDistribute arr, Inc.ArrowCache m arr :: ( HasVersion, ArrowChoice arr, Inc.ArrowDistribute arr, Inc.ArrowCache m arr
, MonadIO m, MonadUnique m, MonadBaseControl IO m, MonadError QErr m , MonadIO m, MonadUnique m, MonadBaseControl IO m, MonadError QErr m
, MonadReader BuildReason m, HasHttpManagerM m, HasSQLGenCtx m , HasRemoteSchemaPermsCtx m, MonadResolveSource m) , MonadReader BuildReason m, HasHttpManagerM m, MonadResolveSource m
, HasServerConfigCtx m)
=> Env.Environment => Env.Environment
-> (Metadata, InvalidationKeys) `arr` SchemaCache -> (Metadata, InvalidationKeys) `arr` SchemaCache
buildSchemaCacheRule env = proc (metadata, invalidationKeys) -> do buildSchemaCacheRule env = proc (metadata, invalidationKeys) -> do
@ -216,7 +217,7 @@ buildSchemaCacheRule env = proc (metadata, invalidationKeys) -> do
buildSource buildSource
:: ( ArrowChoice arr, Inc.ArrowDistribute arr, Inc.ArrowCache m arr :: ( ArrowChoice arr, Inc.ArrowDistribute arr, Inc.ArrowCache m arr
, ArrowWriter (Seq CollectedInfo) arr, MonadBaseControl IO m , ArrowWriter (Seq CollectedInfo) arr, MonadBaseControl IO m
, HasSQLGenCtx m, MonadIO m, MonadError QErr m, MonadReader BuildReason m) , HasServerConfigCtx m, MonadIO m, MonadError QErr m, MonadReader BuildReason m)
=> ( SourceMetadata => ( SourceMetadata
, SourceConfig 'Postgres , SourceConfig 'Postgres
, DBTablesMetadata 'Postgres , DBTablesMetadata 'Postgres
@ -259,7 +260,7 @@ buildSchemaCacheRule env = proc (metadata, invalidationKeys) -> do
-- sql functions -- sql functions
functionCache <- (mapFromL _fmFunction (OMap.elems functions) >- returnA) functionCache <- (mapFromL _fmFunction (OMap.elems functions) >- returnA)
>-> (| Inc.keyed (\_ (FunctionMetadata qf config) -> do >-> (| Inc.keyed (\_ (FunctionMetadata qf config funcPermissions) -> do
let systemDefined = SystemDefined False let systemDefined = SystemDefined False
definition = toJSON $ TrackFunction qf definition = toJSON $ TrackFunction qf
metadataObject = MetadataObject (MOSourceObjId source $ SMOFunction qf) definition metadataObject = MetadataObject (MOSourceObjId source $ SMOFunction qf) definition
@ -269,7 +270,7 @@ buildSchemaCacheRule env = proc (metadata, invalidationKeys) -> do
(| modifyErrA (do (| modifyErrA (do
let funcDefs = fromMaybe [] $ M.lookup qf pgFunctions let funcDefs = fromMaybe [] $ M.lookup qf pgFunctions
rawfi <- bindErrorA -< handleMultipleFunctions qf funcDefs rawfi <- bindErrorA -< handleMultipleFunctions qf funcDefs
(fi, dep) <- bindErrorA -< mkFunctionInfo source qf systemDefined config rawfi (fi, dep) <- bindErrorA -< mkFunctionInfo source qf systemDefined config funcPermissions rawfi
recordDependencies -< (metadataObject, schemaObject, [dep]) recordDependencies -< (metadataObject, schemaObject, [dep])
returnA -< fi) returnA -< fi)
|) addFunctionContext) |) addFunctionContext)
@ -282,7 +283,7 @@ buildSchemaCacheRule env = proc (metadata, invalidationKeys) -> do
:: ( ArrowChoice arr, Inc.ArrowDistribute arr, Inc.ArrowCache m arr :: ( ArrowChoice arr, Inc.ArrowDistribute arr, Inc.ArrowCache m arr
, ArrowWriter (Seq CollectedInfo) arr, MonadIO m, MonadUnique m, MonadError QErr m , ArrowWriter (Seq CollectedInfo) arr, MonadIO m, MonadUnique m, MonadError QErr m
, MonadReader BuildReason m, MonadBaseControl IO m , MonadReader BuildReason m, MonadBaseControl IO m
, HasHttpManagerM m, HasSQLGenCtx m, MonadResolveSource m) , HasHttpManagerM m, HasServerConfigCtx m, MonadResolveSource m)
=> (Metadata, Inc.Dependency InvalidationKeys) `arr` BuildOutputs 'Postgres => (Metadata, Inc.Dependency InvalidationKeys) `arr` BuildOutputs 'Postgres
buildAndCollectInfo = proc (metadata, invalidationKeys) -> do buildAndCollectInfo = proc (metadata, invalidationKeys) -> do
let Metadata sources remoteSchemas collections allowlists let Metadata sources remoteSchemas collections allowlists
@ -466,7 +467,7 @@ buildSchemaCacheRule env = proc (metadata, invalidationKeys) -> do
buildTableEventTriggers buildTableEventTriggers
:: ( ArrowChoice arr, Inc.ArrowDistribute arr, ArrowWriter (Seq CollectedInfo) arr :: ( ArrowChoice arr, Inc.ArrowDistribute arr, ArrowWriter (Seq CollectedInfo) arr
, Inc.ArrowCache m arr, MonadIO m, MonadError QErr m, MonadBaseControl IO m , Inc.ArrowCache m arr, MonadIO m, MonadError QErr m, MonadBaseControl IO m
, MonadReader BuildReason m, HasSQLGenCtx m) , MonadReader BuildReason m, HasServerConfigCtx m)
=> ( SourceName, SourceConfig 'Postgres, TableCoreInfo 'Postgres => ( SourceName, SourceConfig 'Postgres, TableCoreInfo 'Postgres
, [EventTriggerConf], Inc.Dependency Inc.InvalidationKey , [EventTriggerConf], Inc.Dependency Inc.InvalidationKey
) `arr` EventTriggerInfoMap ) `arr` EventTriggerInfoMap
@ -562,7 +563,7 @@ buildSchemaCacheRule env = proc (metadata, invalidationKeys) -> do
-- result. If it did, it checks to ensure the changes do not violate any integrity constraints, and -- result. If it did, it checks to ensure the changes do not violate any integrity constraints, and
-- if not, incorporates them into the schema cache. -- if not, incorporates them into the schema cache.
withMetadataCheck withMetadataCheck
:: (MonadIO m, MonadBaseControl IO m, MonadError QErr m, CacheRWM m, HasSQLGenCtx m, MetadataM m) :: (MonadIO m, MonadBaseControl IO m, MonadError QErr m, CacheRWM m, HasServerConfigCtx m, MetadataM m)
=> SourceName -> Bool -> Q.TxAccess -> LazyTxT QErr m a -> m a => SourceName -> Bool -> Q.TxAccess -> LazyTxT QErr m a -> m a
withMetadataCheck source cascade txAccess action = do withMetadataCheck source cascade txAccess action = do
SourceInfo _ preActionTables preActionFunctions sourceConfig <- askSourceInfo source SourceInfo _ preActionTables preActionFunctions sourceConfig <- askSourceInfo source

View File

@ -117,10 +117,9 @@ $(makeLenses ''BuildOutputs)
-- | Parameters required for schema cache build -- | Parameters required for schema cache build
data CacheBuildParams data CacheBuildParams
= CacheBuildParams = CacheBuildParams
{ _cbpManager :: !HTTP.Manager { _cbpManager :: !HTTP.Manager
, _cbpSqlGenCtx :: !SQLGenCtx , _cbpSourceResolver :: !SourceResolver
, _cbpRemoteSchemaPermsCtx :: !RemoteSchemaPermsCtx , _cbpServerConfigCtx :: !ServerConfigCtx
, _cbpSourceResolver :: !SourceResolver
} }
-- | The monad in which @'RebuildableSchemaCache' is being run -- | The monad in which @'RebuildableSchemaCache' is being run
@ -138,11 +137,8 @@ newtype CacheBuild a
instance HTTP.HasHttpManagerM CacheBuild where instance HTTP.HasHttpManagerM CacheBuild where
askHttpManager = asks _cbpManager askHttpManager = asks _cbpManager
instance HasSQLGenCtx CacheBuild where instance HasServerConfigCtx CacheBuild where
askSQLGenCtx = asks _cbpSqlGenCtx askServerConfigCtx = asks _cbpServerConfigCtx
instance HasRemoteSchemaPermsCtx CacheBuild where
askRemoteSchemaPermsCtx = asks _cbpRemoteSchemaPermsCtx
instance MonadResolveSource CacheBuild where instance MonadResolveSource CacheBuild where
getSourceResolver = asks _cbpSourceResolver getSourceResolver = asks _cbpSourceResolver
@ -160,17 +156,15 @@ runCacheBuildM
:: ( MonadIO m :: ( MonadIO m
, MonadError QErr m , MonadError QErr m
, HTTP.HasHttpManagerM m , HTTP.HasHttpManagerM m
, HasSQLGenCtx m , HasServerConfigCtx m
, HasRemoteSchemaPermsCtx m
, MonadResolveSource m , MonadResolveSource m
) )
=> CacheBuild a -> m a => CacheBuild a -> m a
runCacheBuildM m = do runCacheBuildM m = do
params <- CacheBuildParams params <- CacheBuildParams
<$> HTTP.askHttpManager <$> HTTP.askHttpManager
<*> askSQLGenCtx
<*> askRemoteSchemaPermsCtx
<*> getSourceResolver <*> getSourceResolver
<*> askServerConfigCtx
runCacheBuild params m runCacheBuild params m
data RebuildableSchemaCache data RebuildableSchemaCache

View File

@ -156,6 +156,8 @@ deleteMetadataObject = \case
deleteObjFn = \case deleteObjFn = \case
SMOTable name -> siTables %~ M.delete name SMOTable name -> siTables %~ M.delete name
SMOFunction name -> siFunctions %~ M.delete name SMOFunction name -> siFunctions %~ M.delete name
SMOFunctionPermission functionName role ->
siFunctions.ix functionName.fiPermissions %~ HS.delete role
SMOTableObj tableName tableObjectId -> siTables.ix tableName %~ case tableObjectId of SMOTableObj tableName tableObjectId -> siTables.ix tableName %~ case tableObjectId of
MTORel name _ -> tiCoreInfo.tciFieldInfoMap %~ M.delete (fromRel name) MTORel name _ -> tiCoreInfo.tciFieldInfoMap %~ M.delete (fromRel name)
MTOComputedField name -> tiCoreInfo.tciFieldInfoMap %~ M.delete (fromComputedField name) MTOComputedField name -> tiCoreInfo.tciFieldInfoMap %~ M.delete (fromComputedField name)

View File

@ -7,7 +7,9 @@ module Hasura.RQL.DDL.Schema.Function where
import Hasura.Prelude import Hasura.Prelude
import qualified Control.Monad.Validate as MV import qualified Control.Monad.Validate as MV
import qualified Data.HashMap.Strict as Map
import qualified Data.HashMap.Strict.InsOrd as OMap import qualified Data.HashMap.Strict.InsOrd as OMap
import qualified Data.HashSet as Set
import qualified Data.Sequence as Seq import qualified Data.Sequence as Seq
import qualified Data.Text as T import qualified Data.Text as T
import qualified Database.PG.Query as Q import qualified Database.PG.Query as Q
@ -24,6 +26,7 @@ import Hasura.EncJSON
import Hasura.RQL.Types import Hasura.RQL.Types
import Hasura.Server.Utils (englishList, makeReasonMessage) import Hasura.Server.Utils (englishList, makeReasonMessage)
import Hasura.Session
mkFunctionArgs :: Int -> [QualifiedPGType] -> [FunctionArgName] -> [FunctionArg 'Postgres] mkFunctionArgs :: Int -> [QualifiedPGType] -> [FunctionArgName] -> [FunctionArg 'Postgres]
mkFunctionArgs defArgsNo tys argNames = mkFunctionArgs defArgsNo tys argNames =
@ -69,9 +72,10 @@ mkFunctionInfo
-> QualifiedFunction -> QualifiedFunction
-> SystemDefined -> SystemDefined
-> FunctionConfig -> FunctionConfig
-> [FunctionPermissionMetadata]
-> RawFunctionInfo -> RawFunctionInfo
-> m (FunctionInfo 'Postgres, SchemaDependency) -> m (FunctionInfo 'Postgres, SchemaDependency)
mkFunctionInfo source qf systemDefined FunctionConfig{..} rawFuncInfo = mkFunctionInfo source qf systemDefined FunctionConfig{..} permissions rawFuncInfo =
either (throw400 NotSupported . showErrors) pure either (throw400 NotSupported . showErrors) pure
=<< MV.runValidateT validateFunction =<< MV.runValidateT validateFunction
where where
@ -113,8 +117,10 @@ mkFunctionInfo source qf systemDefined FunctionConfig{..} rawFuncInfo =
inputArguments <- makeInputArguments inputArguments <- makeInputArguments
let retTable = typeToTable returnType let retTable = typeToTable returnType
functionInfo =
FunctionInfo qf systemDefined funVol exposeAs inputArguments retTable descM (Set.fromList $ _fpmRole <$> permissions)
pure ( FunctionInfo qf systemDefined funVol exposeAs inputArguments retTable descM pure ( functionInfo
, SchemaDependency (SOSourceObj source $ SOITable retTable) DRTable , SchemaDependency (SOSourceObj source $ SOITable retTable) DRTable
) )
@ -183,7 +189,7 @@ trackFunctionP2 sourceName qf config = do
buildSchemaCacheFor (MOSourceObjId sourceName $ SMOFunction qf) buildSchemaCacheFor (MOSourceObjId sourceName $ SMOFunction qf)
$ MetadataModifier $ MetadataModifier
$ metaSources.ix sourceName.smFunctions $ metaSources.ix sourceName.smFunctions
%~ OMap.insert qf (FunctionMetadata qf config) %~ OMap.insert qf (FunctionMetadata qf config mempty)
pure successMsg pure successMsg
handleMultipleFunctions :: (QErrM m) => QualifiedFunction -> [a] -> m a handleMultipleFunctions :: (QErrM m) => QualifiedFunction -> [a] -> m a
@ -240,14 +246,19 @@ instance FromJSON UnTrackFunction where
UnTrackFunction <$> o .: "table" UnTrackFunction <$> o .: "table"
<*> o .:? "source" .!= defaultSource <*> o .:? "source" .!= defaultSource
askPGFunctionInfo
:: (CacheRM m, MonadError QErr m)
=> SourceName -> QualifiedFunction -> m (FunctionInfo 'Postgres)
askPGFunctionInfo source functionName = do
sourceCache <- scPostgres <$> askSchemaCache
unsafeFunctionInfo @'Postgres source functionName sourceCache
`onNothing` throw400 NotExists ("function " <> functionName <<> " not found in the cache")
runUntrackFunc runUntrackFunc
:: (CacheRWM m, MonadError QErr m, MetadataM m) :: (CacheRWM m, MonadError QErr m, MetadataM m)
=> UnTrackFunction -> m EncJSON => UnTrackFunction -> m EncJSON
runUntrackFunc (UnTrackFunction functionName sourceName) = do runUntrackFunc (UnTrackFunction functionName sourceName) = do
schemaCache <- askSchemaCache void $ askPGFunctionInfo sourceName functionName
unsafeFunctionInfo @'Postgres sourceName functionName (scPostgres schemaCache)
`onNothing` throw400 NotExists ("function not found in cache " <>> functionName)
-- Delete function from metadata
withNewInconsistentObjsCheck withNewInconsistentObjsCheck
$ buildSchemaCache $ buildSchemaCache
$ dropFunctionInMetadata defaultSource functionName $ dropFunctionInMetadata defaultSource functionName
@ -256,3 +267,93 @@ runUntrackFunc (UnTrackFunction functionName sourceName) = do
dropFunctionInMetadata :: SourceName -> QualifiedFunction -> MetadataModifier dropFunctionInMetadata :: SourceName -> QualifiedFunction -> MetadataModifier
dropFunctionInMetadata source function = MetadataModifier $ dropFunctionInMetadata source function = MetadataModifier $
metaSources.ix source.smFunctions %~ OMap.delete function metaSources.ix source.smFunctions %~ OMap.delete function
{- Note [Function Permissions]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Before we started supporting tracking volatile functions, permissions
for a function was inferred from the target table of the function.
The rationale behind this is that a stable/immutable function does not
modify the database and the data returned by the function is filtered using
the permissions that are specified precisely for that data.
Now consider mutable/volatile functions, we can't automatically infer whether or
not these functions should be exposed for the sole reason that they can modify
the database. This necessitates a permission system for functions.
So, we introduce a new API `pg_create_function_permission` which will
explicitly grant permission to a function to a role. For creating a
function permission, the role must have select permissions configured
for the target table.
Since, this is a breaking change, we enable it only when the graphql-engine
is started with
`--infer-function-permissions`/HASURA_GRAPHQL_INFER_FUNCTION_PERMISSIONS set
to false (by default, it's set to true).
-}
data CreateFunctionPermission
= CreateFunctionPermission
{ _afpFunction :: !QualifiedFunction
, _afpSource :: !SourceName
, _afpRole :: !RoleName
} deriving (Show, Eq)
$(deriveToJSON hasuraJSON ''CreateFunctionPermission)
instance FromJSON CreateFunctionPermission where
parseJSON v =
flip (withObject "CreateFunctionPermission") v $ \o ->
CreateFunctionPermission
<$> o .: "function"
<*> o .:? "source" .!= defaultSource
<*> o .: "role"
runCreateFunctionPermission
:: ( CacheRWM m
, MonadError QErr m
, MetadataM m
, HasServerConfigCtx m
)
=> CreateFunctionPermission
-> m EncJSON
runCreateFunctionPermission (CreateFunctionPermission functionName source role) = do
functionPermsCtx <- _sccFunctionPermsCtx <$> askServerConfigCtx
unless (functionPermsCtx == FunctionPermissionsManual) $
throw400 NotSupported "function permission can only be created when inferring of function permissions is disabled"
sourceCache <- scPostgres <$> askSchemaCache
functionInfo <- askPGFunctionInfo source functionName
when (role `elem` _fiPermissions functionInfo) $
throw400 AlreadyExists $
"permission of role "
<> role <<> " already exists for function " <> functionName <<> " in source: " <>> source
functionTableInfo <-
unsafeTableInfo @'Postgres source (_fiReturnType functionInfo) sourceCache
`onNothing` throw400 NotExists ("function's return table " <> (_fiReturnType functionInfo) <<> " not found in the cache")
unless (role `Map.member` _tiRolePermInfoMap functionTableInfo) $
throw400 NotSupported $
"function permission can only be added when the function's return table "
<> _fiReturnType functionInfo <<> " has select permission configured for role: " <>> role
buildSchemaCacheFor (MOSourceObjId source $ SMOFunctionPermission functionName role)
$ MetadataModifier
$ metaSources.ix source.smFunctions.ix functionName.fmPermissions
%~ (:) (FunctionPermissionMetadata role)
pure successMsg
dropFunctionPermissionInMetadata :: SourceName -> QualifiedFunction -> RoleName -> MetadataModifier
dropFunctionPermissionInMetadata source function role = MetadataModifier $
metaSources.ix source.smFunctions.ix function.fmPermissions %~ filter ((/=) role . _fpmRole)
type DropFunctionPermission = CreateFunctionPermission
runDropFunctionPermission
:: ( CacheRWM m
, MonadError QErr m
, MetadataM m
)
=> DropFunctionPermission
-> m EncJSON
runDropFunctionPermission (CreateFunctionPermission functionName source role) = do
functionInfo <- askPGFunctionInfo source functionName
unless (role `elem` _fiPermissions functionInfo) $
throw400 NotExists $
"permission of role "
<> role <<> " does not exist for function " <> functionName <<> " in source: " <>> source
buildSchemaCacheFor (MOSourceObjId source $ SMOFunctionPermission functionName role)
$ dropFunctionPermissionInMetadata source functionName role
pure successMsg

View File

@ -69,7 +69,7 @@ saveMetadataToHdbTables (MetadataNoSources tables functions schemas collections
-- sql functions -- sql functions
withPathK "functions" $ indexedForM_ functions $ withPathK "functions" $ indexedForM_ functions $
\(FunctionMetadata function config) -> addFunctionToCatalog function config \(FunctionMetadata function config _) -> addFunctionToCatalog function config
-- query collections -- query collections
systemDefined <- askSystemDefined systemDefined <- askSystemDefined
@ -407,7 +407,10 @@ fetchMetadataFromHdbTables = liftTx do
|] () False |] () False
pure $ oMapFromL _fmFunction $ pure $ oMapFromL _fmFunction $
flip map l $ \(sn, fn, Q.AltJ config) -> flip map l $ \(sn, fn, Q.AltJ config) ->
FunctionMetadata (QualifiedObject sn fn) config -- function permissions were only introduced post 43rd
-- migration, so it's impossible we get any permissions
-- here
FunctionMetadata (QualifiedObject sn fn) config []
fetchRemoteSchemas = fetchRemoteSchemas =
map fromRow <$> Q.listQE defaultTxErrorHandler map fromRow <$> Q.listQE defaultTxErrorHandler

View File

@ -93,7 +93,7 @@ validateDeleteQ query = do
runDelete runDelete
:: ( HasVersion, QErrM m, UserInfoM m, CacheRM m :: ( HasVersion, QErrM m, UserInfoM m, CacheRM m
, HasSQLGenCtx m, MonadIO m , HasServerConfigCtx m, MonadIO m
, Tracing.MonadTrace m, MonadBaseControl IO m , Tracing.MonadTrace m, MonadBaseControl IO m
) )
=> Env.Environment => Env.Environment
@ -101,7 +101,7 @@ runDelete
-> m EncJSON -> m EncJSON
runDelete env q = do runDelete env q = do
sourceConfig <- askSourceConfig (doSource q) sourceConfig <- askSourceConfig (doSource q)
strfyNum <- stringifyNum <$> askSQLGenCtx strfyNum <- stringifyNum . _sccSQLGenCtx <$> askServerConfigCtx
validateDeleteQ q validateDeleteQ q
>>= runQueryLazyTx (_pscExecCtx sourceConfig) Q.ReadWrite >>= runQueryLazyTx (_pscExecCtx sourceConfig) Q.ReadWrite
. execDeleteQuery env strfyNum Nothing . execDeleteQuery env strfyNum Nothing

View File

@ -208,7 +208,7 @@ convInsQ query = do
runInsert runInsert
:: ( HasVersion, QErrM m, UserInfoM m :: ( HasVersion, QErrM m, UserInfoM m
, CacheRM m, HasSQLGenCtx m , CacheRM m, HasServerConfigCtx m
, MonadIO m, Tracing.MonadTrace m , MonadIO m, Tracing.MonadTrace m
, MonadBaseControl IO m , MonadBaseControl IO m
) )
@ -216,7 +216,7 @@ runInsert
runInsert env q = do runInsert env q = do
sourceConfig <- askSourceConfig (iqSource q) sourceConfig <- askSourceConfig (iqSource q)
res <- convInsQ q res <- convInsQ q
strfyNum <- stringifyNum <$> askSQLGenCtx strfyNum <- stringifyNum . _sccSQLGenCtx <$> askServerConfigCtx
runQueryLazyTx (_pscExecCtx sourceConfig) Q.ReadWrite $ runQueryLazyTx (_pscExecCtx sourceConfig) Q.ReadWrite $
execInsertQuery env strfyNum Nothing res execInsertQuery env strfyNum Nothing res

View File

@ -23,15 +23,15 @@ import Hasura.Backends.Postgres.SQL.Value
import Hasura.Backends.Postgres.Translate.BoolExp import Hasura.Backends.Postgres.Translate.BoolExp
import Hasura.Backends.Postgres.Translate.Column import Hasura.Backends.Postgres.Translate.Column
import Hasura.RQL.Types import Hasura.RQL.Types
import Hasura.Session
import Hasura.SQL.Types import Hasura.SQL.Types
import Hasura.Session
newtype DMLP1T m a newtype DMLP1T m a
= DMLP1T { unDMLP1T :: StateT (DS.Seq Q.PrepArg) m a } = DMLP1T { unDMLP1T :: StateT (DS.Seq Q.PrepArg) m a }
deriving ( Functor, Applicative, Monad, MonadTrans deriving ( Functor, Applicative, Monad, MonadTrans
, MonadState (DS.Seq Q.PrepArg), MonadError e , MonadState (DS.Seq Q.PrepArg), MonadError e
, SourceM, TableCoreInfoRM b, TableInfoRM b, CacheRM, UserInfoM, HasSQLGenCtx , SourceM, TableCoreInfoRM b, TableInfoRM b, CacheRM, UserInfoM, HasServerConfigCtx
) )
runDMLP1T :: DMLP1T m a -> m (a, DS.Seq Q.PrepArg) runDMLP1T :: DMLP1T m a -> m (a, DS.Seq Q.PrepArg)

View File

@ -193,7 +193,7 @@ convOrderByElem sessVarBldr (flds, spi) = \case
throw400 UnexpectedPayload (mconcat [ fldName <<> " is a remote field" ]) throw400 UnexpectedPayload (mconcat [ fldName <<> " is a remote field" ])
convSelectQ convSelectQ
:: (UserInfoM m, QErrM m, TableInfoRM 'Postgres m, HasSQLGenCtx m) :: (UserInfoM m, QErrM m, TableInfoRM 'Postgres m, HasServerConfigCtx m)
=> TableName 'Postgres => TableName 'Postgres
-> FieldInfoMap (FieldInfo 'Postgres) -- Table information of current table -> FieldInfoMap (FieldInfo 'Postgres) -- Table information of current table
-> SelPermInfo 'Postgres -- Additional select permission info -> SelPermInfo 'Postgres -- Additional select permission info
@ -238,7 +238,7 @@ convSelectQ table fieldInfoMap selPermInfo selQ sessVarBldr prepValBldr = do
tabArgs = SelectArgs wClause annOrdByM mQueryLimit tabArgs = SelectArgs wClause annOrdByM mQueryLimit
(S.intToSQLExp <$> mQueryOffset) Nothing (S.intToSQLExp <$> mQueryOffset) Nothing
strfyNum <- stringifyNum <$> askSQLGenCtx strfyNum <- stringifyNum . _sccSQLGenCtx <$> askServerConfigCtx
return $ AnnSelectG annFlds tabFrom tabPerm tabArgs strfyNum return $ AnnSelectG annFlds tabFrom tabPerm tabArgs strfyNum
where where
@ -259,7 +259,7 @@ convExtSimple fieldInfoMap selPermInfo pgCol = do
relWhenPGErr = "relationships have to be expanded" relWhenPGErr = "relationships have to be expanded"
convExtRel convExtRel
:: (UserInfoM m, QErrM m, TableInfoRM 'Postgres m, HasSQLGenCtx m) :: (UserInfoM m, QErrM m, TableInfoRM 'Postgres m, HasServerConfigCtx m)
=> FieldInfoMap (FieldInfo 'Postgres) => FieldInfoMap (FieldInfo 'Postgres)
-> RelName -> RelName
-> Maybe RelName -> Maybe RelName
@ -297,7 +297,7 @@ convExtRel fieldInfoMap relName mAlias selQ sessVarBldr prepValBldr = do
] ]
convSelectQuery convSelectQuery
:: (UserInfoM m, QErrM m, TableInfoRM 'Postgres m, HasSQLGenCtx m) :: (UserInfoM m, QErrM m, TableInfoRM 'Postgres m, HasServerConfigCtx m)
=> SessVarBldr 'Postgres m => SessVarBldr 'Postgres m
-> (ColumnType 'Postgres -> Value -> m S.SQLExp) -> (ColumnType 'Postgres -> Value -> m S.SQLExp)
-> SelectQuery -> SelectQuery
@ -318,7 +318,7 @@ selectP2 jsonAggSelect (sel, p) =
selectSQL = toSQL $ mkSQLSelect jsonAggSelect sel selectSQL = toSQL $ mkSQLSelect jsonAggSelect sel
phaseOne phaseOne
:: (QErrM m, UserInfoM m, CacheRM m, HasSQLGenCtx m) :: (QErrM m, UserInfoM m, CacheRM m, HasServerConfigCtx m)
=> SelectQuery -> m (AnnSimpleSel 'Postgres, DS.Seq Q.PrepArg) => SelectQuery -> m (AnnSimpleSel 'Postgres, DS.Seq Q.PrepArg)
phaseOne query = do phaseOne query = do
let sourceName = getSourceDMLQuery query let sourceName = getSourceDMLQuery query
@ -332,7 +332,7 @@ phaseTwo =
runSelect runSelect
:: ( QErrM m, UserInfoM m, CacheRM m :: ( QErrM m, UserInfoM m, CacheRM m
, HasSQLGenCtx m, MonadIO m, MonadBaseControl IO m , HasServerConfigCtx m, MonadIO m, MonadBaseControl IO m
, Tracing.MonadTrace m , Tracing.MonadTrace m
) )
=> SelectQuery -> m EncJSON => SelectQuery -> m EncJSON

View File

@ -186,13 +186,13 @@ validateUpdateQuery query = do
runUpdate runUpdate
:: ( HasVersion, QErrM m, UserInfoM m, CacheRM m :: ( HasVersion, QErrM m, UserInfoM m, CacheRM m
, HasSQLGenCtx m, MonadBaseControl IO m , HasServerConfigCtx m, MonadBaseControl IO m
, MonadIO m, Tracing.MonadTrace m , MonadIO m, Tracing.MonadTrace m
) )
=> Env.Environment -> UpdateQuery -> m EncJSON => Env.Environment -> UpdateQuery -> m EncJSON
runUpdate env q = do runUpdate env q = do
sourceConfig <- askSourceConfig (uqSource q) sourceConfig <- askSourceConfig (uqSource q)
strfyNum <- stringifyNum <$> askSQLGenCtx strfyNum <- stringifyNum . _sccSQLGenCtx <$> askServerConfigCtx
validateUpdateQuery q validateUpdateQuery q
>>= runQueryLazyTx (_pscExecCtx sourceConfig) Q.ReadWrite >>= runQueryLazyTx (_pscExecCtx sourceConfig) Q.ReadWrite
. execUpdateQuery env strfyNum Nothing . execUpdateQuery env strfyNum Nothing

View File

@ -2,10 +2,10 @@ module Hasura.RQL.Types
( MonadTx(..) ( MonadTx(..)
, SQLGenCtx(..) , SQLGenCtx(..)
, HasSQLGenCtx(..)
, RemoteSchemaPermsCtx(..) , RemoteSchemaPermsCtx(..)
, HasRemoteSchemaPermsCtx(..)
, ServerConfigCtx(..)
, HasServerConfigCtx(..)
, HasSystemDefined(..) , HasSystemDefined(..)
, HasSystemDefinedT , HasSystemDefinedT
@ -110,6 +110,42 @@ askTabInfoSource
askTabInfoSource tableName = do askTabInfoSource tableName = do
lookupTableInfo tableName >>= (`onNothing` throwTableDoesNotExist tableName) lookupTableInfo tableName >>= (`onNothing` throwTableDoesNotExist tableName)
data ServerConfigCtx
= ServerConfigCtx
{ _sccFunctionPermsCtx :: !FunctionPermissionsCtx
, _sccRemoteSchemaPermsCtx :: !RemoteSchemaPermsCtx
, _sccSQLGenCtx :: !SQLGenCtx
} deriving (Show, Eq)
class (Monad m) => HasServerConfigCtx m where
askServerConfigCtx :: m ServerConfigCtx
instance (HasServerConfigCtx m)
=> HasServerConfigCtx (ReaderT r m) where
askServerConfigCtx = lift askServerConfigCtx
instance (HasServerConfigCtx m)
=> HasServerConfigCtx (StateT s m) where
askServerConfigCtx = lift askServerConfigCtx
instance (Monoid w, HasServerConfigCtx m)
=> HasServerConfigCtx (WriterT w m) where
askServerConfigCtx = lift askServerConfigCtx
instance (HasServerConfigCtx m)
=> HasServerConfigCtx (TableCoreCacheRT b m) where
askServerConfigCtx = lift askServerConfigCtx
instance (HasServerConfigCtx m)
=> HasServerConfigCtx (TraceT m) where
askServerConfigCtx = lift askServerConfigCtx
instance (HasServerConfigCtx m)
=> HasServerConfigCtx (MetadataT m) where
askServerConfigCtx = lift askServerConfigCtx
instance (HasServerConfigCtx m)
=> HasServerConfigCtx (LazyTxT QErr m) where
askServerConfigCtx = lift askServerConfigCtx
instance (HasServerConfigCtx m) => HasServerConfigCtx (Q.TxET QErr m) where
askServerConfigCtx = lift askServerConfigCtx
instance (HasServerConfigCtx m) => HasServerConfigCtx (TableCacheRT b m) where
askServerConfigCtx = lift askServerConfigCtx
data RemoteSchemaPermsCtx data RemoteSchemaPermsCtx
= RemoteSchemaPermsEnabled = RemoteSchemaPermsEnabled
| RemoteSchemaPermsDisabled | RemoteSchemaPermsDisabled
@ -127,53 +163,6 @@ instance ToJSON RemoteSchemaPermsCtx where
RemoteSchemaPermsEnabled -> "true" RemoteSchemaPermsEnabled -> "true"
RemoteSchemaPermsDisabled -> "false" RemoteSchemaPermsDisabled -> "false"
class (Monad m) => HasRemoteSchemaPermsCtx m where
askRemoteSchemaPermsCtx :: m RemoteSchemaPermsCtx
instance (HasRemoteSchemaPermsCtx m)
=> HasRemoteSchemaPermsCtx (ReaderT r m) where
askRemoteSchemaPermsCtx = lift askRemoteSchemaPermsCtx
instance (HasRemoteSchemaPermsCtx m)
=> HasRemoteSchemaPermsCtx (StateT s m) where
askRemoteSchemaPermsCtx = lift askRemoteSchemaPermsCtx
instance (Monoid w, HasRemoteSchemaPermsCtx m)
=> HasRemoteSchemaPermsCtx (WriterT w m) where
askRemoteSchemaPermsCtx = lift askRemoteSchemaPermsCtx
instance (HasRemoteSchemaPermsCtx m)
=> HasRemoteSchemaPermsCtx (TableCoreCacheRT b m) where
askRemoteSchemaPermsCtx = lift askRemoteSchemaPermsCtx
instance (HasRemoteSchemaPermsCtx m)
=> HasRemoteSchemaPermsCtx (TraceT m) where
askRemoteSchemaPermsCtx = lift askRemoteSchemaPermsCtx
instance (HasRemoteSchemaPermsCtx m)
=> HasRemoteSchemaPermsCtx (MetadataT m) where
askRemoteSchemaPermsCtx = lift askRemoteSchemaPermsCtx
instance (HasRemoteSchemaPermsCtx m)
=> HasRemoteSchemaPermsCtx (LazyTxT QErr m) where
askRemoteSchemaPermsCtx = lift askRemoteSchemaPermsCtx
class (Monad m) => HasSQLGenCtx m where
askSQLGenCtx :: m SQLGenCtx
instance (HasSQLGenCtx m) => HasSQLGenCtx (ReaderT r m) where
askSQLGenCtx = lift askSQLGenCtx
instance (HasSQLGenCtx m) => HasSQLGenCtx (StateT s m) where
askSQLGenCtx = lift askSQLGenCtx
instance (Monoid w, HasSQLGenCtx m) => HasSQLGenCtx (WriterT w m) where
askSQLGenCtx = lift askSQLGenCtx
instance (HasSQLGenCtx m) => HasSQLGenCtx (TableCoreCacheRT b m) where
askSQLGenCtx = lift askSQLGenCtx
instance (HasSQLGenCtx m) => HasSQLGenCtx (TraceT m) where
askSQLGenCtx = lift askSQLGenCtx
instance (HasSQLGenCtx m) => HasSQLGenCtx (MetadataT m) where
askSQLGenCtx = lift askSQLGenCtx
instance (HasSQLGenCtx m) => HasSQLGenCtx (Q.TxET QErr m) where
askSQLGenCtx = lift askSQLGenCtx
instance (HasSQLGenCtx m) => HasSQLGenCtx (LazyTxT QErr m) where
askSQLGenCtx = lift askSQLGenCtx
instance (HasSQLGenCtx m) => HasSQLGenCtx (TableCacheRT b m) where
askSQLGenCtx = lift askSQLGenCtx
class (Monad m) => HasSystemDefined m where class (Monad m) => HasSystemDefined m where
askSystemDefined :: m SystemDefined askSystemDefined :: m SystemDefined
@ -189,7 +178,7 @@ instance (HasSystemDefined m) => HasSystemDefined (TraceT m) where
newtype HasSystemDefinedT m a newtype HasSystemDefinedT m a
= HasSystemDefinedT { unHasSystemDefinedT :: ReaderT SystemDefined m a } = HasSystemDefinedT { unHasSystemDefinedT :: ReaderT SystemDefined m a }
deriving ( Functor, Applicative, Monad, MonadTrans, MonadIO, MonadUnique, MonadError e, MonadTx deriving ( Functor, Applicative, Monad, MonadTrans, MonadIO, MonadUnique, MonadError e, MonadTx
, HasHttpManagerM, HasSQLGenCtx, SourceM, TableCoreInfoRM b, CacheRM, UserInfoM, HasRemoteSchemaPermsCtx) , HasHttpManagerM, SourceM, TableCoreInfoRM b, CacheRM, UserInfoM, HasServerConfigCtx)
runHasSystemDefinedT :: SystemDefined -> HasSystemDefinedT m a -> m a runHasSystemDefinedT :: SystemDefined -> HasSystemDefinedT m a -> m a
runHasSystemDefinedT systemDefined = flip runReaderT systemDefined . unHasSystemDefinedT runHasSystemDefinedT systemDefined = flip runReaderT systemDefined . unHasSystemDefinedT

View File

@ -2,6 +2,7 @@ module Hasura.RQL.Types.Function where
import Hasura.Prelude import Hasura.Prelude
import qualified Data.HashSet as Set
import qualified Data.Sequence as Seq import qualified Data.Sequence as Seq
import qualified Data.Text as T import qualified Data.Text as T
@ -17,7 +18,7 @@ import qualified Hasura.Backends.Postgres.SQL.Types as PG
import Hasura.Incremental (Cacheable) import Hasura.Incremental (Cacheable)
import Hasura.RQL.Types.Common import Hasura.RQL.Types.Common
import Hasura.SQL.Backend import Hasura.SQL.Backend
import Hasura.Session
-- | https://www.postgresql.org/docs/current/xfunc-volatility.html -- | https://www.postgresql.org/docs/current/xfunc-volatility.html
data FunctionVolatility data FunctionVolatility
@ -86,28 +87,31 @@ $(deriveJSON
-- | Tracked SQL function metadata. See 'mkFunctionInfo'. -- | Tracked SQL function metadata. See 'mkFunctionInfo'.
data FunctionInfo (b :: BackendType) data FunctionInfo (b :: BackendType)
= FunctionInfo = FunctionInfo
{ fiName :: !(FunctionName b) { _fiName :: !(FunctionName b)
, fiSystemDefined :: !SystemDefined , _fiSystemDefined :: !SystemDefined
, fiVolatility :: !FunctionVolatility , _fiVolatility :: !FunctionVolatility
, fiExposedAs :: !FunctionExposedAs , _fiExposedAs :: !FunctionExposedAs
-- ^ In which part of the schema should this function be exposed? -- ^ In which part of the schema should this function be exposed?
-- --
-- See 'mkFunctionInfo' and '_fcExposedAs'. -- See 'mkFunctionInfo' and '_fcExposedAs'.
, fiInputArgs :: !(Seq.Seq (FunctionInputArgument b)) , _fiInputArgs :: !(Seq.Seq (FunctionInputArgument b))
, fiReturnType :: !(TableName b) , _fiReturnType :: !(TableName b)
-- ^ NOTE: when a table is created, a new composite type of the same name is -- ^ NOTE: when a table is created, a new composite type of the same name is
-- automatically created; so strictly speaking this field means "the function -- automatically created; so strictly speaking this field means "the function
-- returns the composite type corresponding to this table". -- returns the composite type corresponding to this table".
, fiDescription :: !(Maybe PG.PGDescription) -- FIXME: make generic , _fiDescription :: !(Maybe PG.PGDescription) -- FIXME: make generic
, _fiPermissions :: !(Set.HashSet RoleName)
-- ^ Roles to which the function is accessible
} deriving (Generic) } deriving (Generic)
deriving instance Backend b => Show (FunctionInfo b) deriving instance Backend b => Show (FunctionInfo b)
deriving instance Backend b => Eq (FunctionInfo b) deriving instance Backend b => Eq (FunctionInfo b)
instance (Backend b) => ToJSON (FunctionInfo b) where instance (Backend b) => ToJSON (FunctionInfo b) where
toJSON = genericToJSON hasuraJSON toJSON = genericToJSON hasuraJSON
$(makeLenses ''FunctionInfo)
getInputArgs :: FunctionInfo b -> Seq.Seq (FunctionArg b) getInputArgs :: FunctionInfo b -> Seq.Seq (FunctionArg b)
getInputArgs = getInputArgs =
Seq.fromList . mapMaybe (^? _IAUserProvided) . toList . fiInputArgs Seq.fromList . mapMaybe (^? _IAUserProvided) . toList . _fiInputArgs
type FunctionCache b = HashMap (FunctionName b) (FunctionInfo b) -- info of all functions type FunctionCache b = HashMap (FunctionName b) (FunctionInfo b) -- info of all functions
@ -172,3 +176,20 @@ instance Cacheable RawFunctionInfo
$(deriveJSON hasuraJSON ''RawFunctionInfo) $(deriveJSON hasuraJSON ''RawFunctionInfo)
type PostgresFunctionsMetadata = HashMap PG.QualifiedFunction [RawFunctionInfo] type PostgresFunctionsMetadata = HashMap PG.QualifiedFunction [RawFunctionInfo]
data FunctionPermissionsCtx
= FunctionPermissionsInferred
| FunctionPermissionsManual
deriving (Show, Eq)
instance FromJSON FunctionPermissionsCtx where
parseJSON = withText "FunctionPermissionsCtx" $ \t ->
case T.toLower t of
"true" -> pure FunctionPermissionsInferred
"false" -> pure FunctionPermissionsManual
_ -> fail "infer_function_permissions should be a boolean value"
instance ToJSON FunctionPermissionsCtx where
toJSON = \case
FunctionPermissionsInferred -> Bool True
FunctionPermissionsManual -> Bool False

View File

@ -51,6 +51,7 @@ instance Hashable TableMetadataObjId
data SourceMetadataObjId data SourceMetadataObjId
= SMOTable !QualifiedTable = SMOTable !QualifiedTable
| SMOFunction !QualifiedFunction | SMOFunction !QualifiedFunction
| SMOFunctionPermission !QualifiedFunction !RoleName
| SMOTableObj !QualifiedTable !TableMetadataObjId | SMOTableObj !QualifiedTable !TableMetadataObjId
deriving (Show, Eq, Generic) deriving (Show, Eq, Generic)
instance Hashable SourceMetadataObjId instance Hashable SourceMetadataObjId
@ -76,6 +77,7 @@ moiTypeName = \case
MOSourceObjId _ sourceObjId -> case sourceObjId of MOSourceObjId _ sourceObjId -> case sourceObjId of
SMOTable _ -> "table" SMOTable _ -> "table"
SMOFunction _ -> "function" SMOFunction _ -> "function"
SMOFunctionPermission _ _ -> "function_permission"
SMOTableObj _ tableObjectId -> case tableObjectId of SMOTableObj _ tableObjectId -> case tableObjectId of
MTORel _ relType -> relTypeToTxt relType <> "_relation" MTORel _ relType -> relTypeToTxt relType <> "_relation"
MTOPerm _ permType -> permTypeToCode permType <> "_permission" MTOPerm _ permType -> permTypeToCode permType <> "_permission"
@ -96,6 +98,9 @@ moiName objectId = moiTypeName objectId <> " " <> case objectId of
MOSourceObjId source sourceObjId -> case sourceObjId of MOSourceObjId source sourceObjId -> case sourceObjId of
SMOTable name -> toTxt name <> " in source " <> toTxt source SMOTable name -> toTxt name <> " in source " <> toTxt source
SMOFunction name -> toTxt name <> " in source " <> toTxt source SMOFunction name -> toTxt name <> " in source " <> toTxt source
SMOFunctionPermission functionName roleName ->
toTxt roleName <> " permission for function "
<> toTxt functionName <> " in source " <> toTxt source
SMOTableObj tableName tableObjectId -> SMOTableObj tableName tableObjectId ->
let tableObjectName = case tableObjectId of let tableObjectName = case tableObjectId of
MTORel name _ -> toTxt name MTORel name _ -> toTxt name
@ -322,20 +327,30 @@ instance FromJSON TableMetadata where
, cfKey, rrKey , cfKey, rrKey
] ]
newtype FunctionPermissionMetadata
= FunctionPermissionMetadata
{ _fpmRole :: RoleName
} deriving (Show, Eq, Generic)
instance Cacheable FunctionPermissionMetadata
$(makeLenses ''FunctionPermissionMetadata)
$(deriveJSON hasuraJSON ''FunctionPermissionMetadata)
data FunctionMetadata data FunctionMetadata
= FunctionMetadata = FunctionMetadata
{ _fmFunction :: !QualifiedFunction { _fmFunction :: !QualifiedFunction
, _fmConfiguration :: !FunctionConfig , _fmConfiguration :: !FunctionConfig
, _fmPermissions :: ![FunctionPermissionMetadata]
} deriving (Show, Eq, Generic) } deriving (Show, Eq, Generic)
instance Cacheable FunctionMetadata instance Cacheable FunctionMetadata
$(makeLenses ''FunctionMetadata) $(makeLenses ''FunctionMetadata)
$(deriveToJSON hasuraJSON ''FunctionMetadata) $(deriveToJSON hasuraJSON ''FunctionMetadata)
instance FromJSON FunctionMetadata where instance FromJSON FunctionMetadata where
parseJSON = withObject "Object" $ \o -> parseJSON = withObject "FunctionMetadata" $ \o ->
FunctionMetadata FunctionMetadata
<$> o .: "function" <$> o .: "function"
<*> o .:? "configuration" .!= emptyFunctionConfig <*> o .:? "configuration" .!= emptyFunctionConfig
<*> o .:? "permissions" .!= []
type Tables = InsOrdHashMap QualifiedTable TableMetadata type Tables = InsOrdHashMap QualifiedTable TableMetadata
type Functions = InsOrdHashMap QualifiedFunction FunctionMetadata type Functions = InsOrdHashMap QualifiedFunction FunctionMetadata
@ -454,7 +469,7 @@ instance FromJSON MetadataNoSources where
tables <- oMapFromL _tmTable <$> o .: "tables" tables <- oMapFromL _tmTable <$> o .: "tables"
functionList <- o .:? "functions" .!= [] functionList <- o .:? "functions" .!= []
let functions = OM.fromList $ flip map functionList $ let functions = OM.fromList $ flip map functionList $
\function -> (function, FunctionMetadata function emptyFunctionConfig) \function -> (function, FunctionMetadata function emptyFunctionConfig mempty)
pure (tables, functions) pure (tables, functions)
MVVersion2 -> do MVVersion2 -> do
tables <- oMapFromL _tmTable <$> o .: "tables" tables <- oMapFromL _tmTable <$> o .: "tables"
@ -652,9 +667,13 @@ metadataToOrdJSON ( Metadata
functionMetadataToOrdJSON :: FunctionMetadata -> AO.Value functionMetadataToOrdJSON :: FunctionMetadata -> AO.Value
functionMetadataToOrdJSON FunctionMetadata{..} = functionMetadataToOrdJSON FunctionMetadata{..} =
AO.object $ [("function", AO.toOrdered _fmFunction)] let confKeyPair =
<> if _fmConfiguration == emptyFunctionConfig then [] if _fmConfiguration == emptyFunctionConfig then []
else pure ("configuration", AO.toOrdered _fmConfiguration) else pure ("configuration", AO.toOrdered _fmConfiguration)
permissionsKeyPair =
if (null _fmPermissions) then []
else pure ("permissions", AO.toOrdered _fmPermissions)
in AO.object $ [("function", AO.toOrdered _fmFunction)] <> confKeyPair <> permissionsKeyPair
remoteSchemaQToOrdJSON :: RemoteSchemaMetadata -> AO.Value remoteSchemaQToOrdJSON :: RemoteSchemaMetadata -> AO.Value
remoteSchemaQToOrdJSON (RemoteSchemaMetadata name definition comment permissions) = remoteSchemaQToOrdJSON (RemoteSchemaMetadata name definition comment permissions) =

View File

@ -24,10 +24,9 @@ import Hasura.Session
data RunCtx data RunCtx
= RunCtx = RunCtx
{ _rcUserInfo :: !UserInfo { _rcUserInfo :: !UserInfo
, _rcHttpMgr :: !HTTP.Manager , _rcHttpMgr :: !HTTP.Manager
, _rcSqlGenCtx :: !SQLGenCtx , _rcServerConfigCtx :: !ServerConfigCtx
, _rcRemoteSchemaPermsCtx :: !RemoteSchemaPermsCtx
} }
newtype RunT m a newtype RunT m a
@ -54,11 +53,8 @@ instance (Monad m) => UserInfoM (RunT m) where
instance (Monad m) => HTTP.HasHttpManagerM (RunT m) where instance (Monad m) => HTTP.HasHttpManagerM (RunT m) where
askHttpManager = asks _rcHttpMgr askHttpManager = asks _rcHttpMgr
instance (Monad m) => HasSQLGenCtx (RunT m) where instance (Monad m) => HasServerConfigCtx (RunT m) where
askSQLGenCtx = asks _rcSqlGenCtx askServerConfigCtx = asks _rcServerConfigCtx
instance (Monad m) => HasRemoteSchemaPermsCtx (RunT m) where
askRemoteSchemaPermsCtx = asks _rcRemoteSchemaPermsCtx
instance (MonadResolveSource m) => MonadResolveSource (RunT m) where instance (MonadResolveSource m) => MonadResolveSource (RunT m) where
getSourceResolver = RunT . lift . lift $ getSourceResolver getSourceResolver = RunT . lift . lift $ getSourceResolver

View File

@ -51,6 +51,10 @@ data RQLMetadata
| RMPgTrackFunction !TrackFunctionV2 | RMPgTrackFunction !TrackFunctionV2
| RMPgUntrackFunction !UnTrackFunction | RMPgUntrackFunction !UnTrackFunction
-- Postgres function permissions
| RMPgCreateFunctionPermission !CreateFunctionPermission
| RMPgDropFunctionPermission !DropFunctionPermission
-- Postgres table relationships -- Postgres table relationships
| RMPgCreateObjectRelationship !CreateObjRel | RMPgCreateObjectRelationship !CreateObjRel
| RMPgCreateArrayRelationship !CreateArrRel | RMPgCreateArrayRelationship !CreateArrRel
@ -160,21 +164,19 @@ runMetadataQuery
-> InstanceId -> InstanceId
-> UserInfo -> UserInfo
-> HTTP.Manager -> HTTP.Manager
-> SQLGenCtx -> ServerConfigCtx
-> RemoteSchemaPermsCtx
-> RebuildableSchemaCache -> RebuildableSchemaCache
-> RQLMetadata -> RQLMetadata
-> m (EncJSON, RebuildableSchemaCache) -> m (EncJSON, RebuildableSchemaCache)
runMetadataQuery env instanceId userInfo httpManager sqlGenCtx remoteSchemaPermsCtx schemaCache query = do runMetadataQuery env instanceId userInfo httpManager serverConfigCtx schemaCache query = do
metadata <- fetchMetadata metadata <- fetchMetadata
((r, modMetadata), modSchemaCache, cacheInvalidations) <- ((r, modMetadata), modSchemaCache, cacheInvalidations) <-
runMetadataQueryM env query runMetadataQueryM env query
& runMetadataT metadata & runMetadataT metadata
& runCacheRWT schemaCache & runCacheRWT schemaCache
& peelRun (RunCtx userInfo httpManager sqlGenCtx remoteSchemaPermsCtx) & peelRun (RunCtx userInfo httpManager serverConfigCtx)
& runExceptT & runExceptT
& liftEitherM & liftEitherM
-- set modified metadata in storage -- set modified metadata in storage
setMetadata modMetadata setMetadata modMetadata
-- notify schema cache sync -- notify schema cache sync
@ -193,7 +195,7 @@ runMetadataQueryM
, HTTP.HasHttpManagerM m , HTTP.HasHttpManagerM m
, MetadataM m , MetadataM m
, MonadMetadataStorageQueryAPI m , MonadMetadataStorageQueryAPI m
, HasRemoteSchemaPermsCtx m , HasServerConfigCtx m
) )
=> Env.Environment => Env.Environment
-> RQLMetadata -> RQLMetadata
@ -210,6 +212,9 @@ runMetadataQueryM env = withPathK "args" . \case
RMPgTrackFunction q -> runTrackFunctionV2 q RMPgTrackFunction q -> runTrackFunctionV2 q
RMPgUntrackFunction q -> runUntrackFunc q RMPgUntrackFunction q -> runUntrackFunc q
RMPgCreateFunctionPermission q -> runCreateFunctionPermission q
RMPgDropFunctionPermission q -> runDropFunctionPermission q
RMPgCreateObjectRelationship q -> runCreateRelationship ObjRel q RMPgCreateObjectRelationship q -> runCreateRelationship ObjRel q
RMPgCreateArrayRelationship q -> runCreateRelationship ArrRel q RMPgCreateArrayRelationship q -> runCreateRelationship ArrRel q
RMPgDropRelationship q -> runDropRel q RMPgDropRelationship q -> runDropRel q

View File

@ -191,8 +191,9 @@ runQuery
=> Env.Environment => Env.Environment
-> InstanceId -> InstanceId
-> UserInfo -> RebuildableSchemaCache -> HTTP.Manager -> UserInfo -> RebuildableSchemaCache -> HTTP.Manager
-> SQLGenCtx -> RemoteSchemaPermsCtx -> RQLQuery -> m (EncJSON, RebuildableSchemaCache) -> SQLGenCtx -> RemoteSchemaPermsCtx -> FunctionPermissionsCtx
runQuery env instanceId userInfo sc hMgr sqlGenCtx remoteSchemaPermsCtx query = do -> RQLQuery -> m (EncJSON, RebuildableSchemaCache)
runQuery env instanceId userInfo sc hMgr sqlGenCtx remoteSchemaPermsCtx functionPermsCtx query = do
metadata <- fetchMetadata metadata <- fetchMetadata
result <- runQueryM env query & Tracing.interpTraceT \x -> do result <- runQueryM env query & Tracing.interpTraceT \x -> do
(((js, tracemeta), meta), rsc, ci) <- (((js, tracemeta), meta), rsc, ci) <-
@ -204,7 +205,8 @@ runQuery env instanceId userInfo sc hMgr sqlGenCtx remoteSchemaPermsCtx query =
pure ((js, rsc, ci, meta), tracemeta) pure ((js, rsc, ci, meta), tracemeta)
withReload result withReload result
where where
runCtx = RunCtx userInfo hMgr sqlGenCtx remoteSchemaPermsCtx serverConfigCtx = ServerConfigCtx functionPermsCtx remoteSchemaPermsCtx sqlGenCtx
runCtx = RunCtx userInfo hMgr serverConfigCtx
withReload (result, updatedCache, invalidations, updatedMetadata) = do withReload (result, updatedCache, invalidations, updatedMetadata) = do
when (queryModifiesSchemaCache query) $ do when (queryModifiesSchemaCache query) $ do
@ -350,8 +352,8 @@ reconcileAccessModes (Just mode1) (Just mode2)
runQueryM runQueryM
:: ( HasVersion, CacheRWM m, UserInfoM m :: ( HasVersion, CacheRWM m, UserInfoM m
, MonadBaseControl IO m, MonadIO m, MonadUnique m , MonadBaseControl IO m, MonadIO m, MonadUnique m
, HasHttpManagerM m, HasSQLGenCtx m , HasHttpManagerM m
, HasRemoteSchemaPermsCtx m , HasServerConfigCtx m
, Tracing.MonadTrace m , Tracing.MonadTrace m
, MetadataM m , MetadataM m
, MonadMetadataStorageQueryAPI m , MonadMetadataStorageQueryAPI m

View File

@ -66,9 +66,10 @@ runQuery
-> HTTP.Manager -> HTTP.Manager
-> SQLGenCtx -> SQLGenCtx
-> RemoteSchemaPermsCtx -> RemoteSchemaPermsCtx
-> FunctionPermissionsCtx
-> RQLQuery -> RQLQuery
-> m (EncJSON, RebuildableSchemaCache) -> m (EncJSON, RebuildableSchemaCache)
runQuery env instanceId userInfo schemaCache httpManager sqlGenCtx remoteSchemaPermCtx rqlQuery = do runQuery env instanceId userInfo schemaCache httpManager sqlGenCtx remoteSchemaPermCtx functionPermsCtx rqlQuery = do
metadata <- fetchMetadata metadata <- fetchMetadata
result <- runQueryM env rqlQuery & Tracing.interpTraceT \x -> do result <- runQueryM env rqlQuery & Tracing.interpTraceT \x -> do
(((js, tracemeta), meta), rsc, ci) <- (((js, tracemeta), meta), rsc, ci) <-
@ -80,7 +81,8 @@ runQuery env instanceId userInfo schemaCache httpManager sqlGenCtx remoteSchemaP
pure ((js, rsc, ci, meta), tracemeta) pure ((js, rsc, ci, meta), tracemeta)
withReload result withReload result
where where
runCtx = RunCtx userInfo httpManager sqlGenCtx remoteSchemaPermCtx runCtx = RunCtx userInfo httpManager
$ ServerConfigCtx functionPermsCtx remoteSchemaPermCtx sqlGenCtx
withReload (result, updatedCache, invalidations, updatedMetadata) = do withReload (result, updatedCache, invalidations, updatedMetadata) = do
when (queryModifiesSchema rqlQuery) $ do when (queryModifiesSchema rqlQuery) $ do
@ -92,9 +94,9 @@ runQuery env instanceId userInfo schemaCache httpManager sqlGenCtx remoteSchemaP
queryModifiesSchema :: RQLQuery -> Bool queryModifiesSchema :: RQLQuery -> Bool
queryModifiesSchema = \case queryModifiesSchema = \case
RQRunSql q -> isSchemaCacheBuildRequiredRunSQL q RQRunSql q -> isSchemaCacheBuildRequiredRunSQL q
RQBulk l -> any queryModifiesSchema l RQBulk l -> any queryModifiesSchema l
_ -> False _ -> False
runQueryM runQueryM
:: ( HasVersion :: ( HasVersion
@ -103,7 +105,7 @@ runQueryM
, MonadBaseControl IO m , MonadBaseControl IO m
, UserInfoM m , UserInfoM m
, CacheRWM m , CacheRWM m
, HasSQLGenCtx m , HasServerConfigCtx m
, Tracing.MonadTrace m , Tracing.MonadTrace m
, MetadataM m , MetadataM m
) )

View File

@ -113,6 +113,7 @@ data ServerCtx
, scResponseInternalErrorsConfig :: !ResponseInternalErrorsConfig , scResponseInternalErrorsConfig :: !ResponseInternalErrorsConfig
, scEnvironment :: !Env.Environment , scEnvironment :: !Env.Environment
, scRemoteSchemaPermsCtx :: !RemoteSchemaPermsCtx , scRemoteSchemaPermsCtx :: !RemoteSchemaPermsCtx
, scFunctionPermsCtx :: !FunctionPermissionsCtx
} }
data HandlerCtx data HandlerCtx
@ -391,15 +392,16 @@ v1QueryHandler query = do
return $ HttpResponse res [] return $ HttpResponse res []
where where
action = do action = do
userInfo <- asks hcUser userInfo <- asks hcUser
scRef <- asks (scCacheRef . hcServerCtx) scRef <- asks (scCacheRef . hcServerCtx)
schemaCache <- fmap fst $ liftIO $ readIORef $ _scrCache scRef schemaCache <- fmap fst $ liftIO $ readIORef $ _scrCache scRef
httpMgr <- asks (scManager . hcServerCtx) httpMgr <- asks (scManager . hcServerCtx)
sqlGenCtx <- asks (scSQLGenCtx . hcServerCtx) sqlGenCtx <- asks (scSQLGenCtx . hcServerCtx)
instanceId <- asks (scInstanceId . hcServerCtx) instanceId <- asks (scInstanceId . hcServerCtx)
env <- asks (scEnvironment . hcServerCtx) env <- asks (scEnvironment . hcServerCtx)
remoteSchemaPermsCtx <- asks (scRemoteSchemaPermsCtx . hcServerCtx) remoteSchemaPermsCtx <- asks (scRemoteSchemaPermsCtx . hcServerCtx)
runQuery env instanceId userInfo schemaCache httpMgr sqlGenCtx remoteSchemaPermsCtx query functionPermsCtx <- asks (scFunctionPermsCtx . hcServerCtx)
runQuery env instanceId userInfo schemaCache httpMgr sqlGenCtx remoteSchemaPermsCtx functionPermsCtx query
v1MetadataHandler v1MetadataHandler
:: ( HasVersion :: ( HasVersion
@ -414,17 +416,19 @@ v1MetadataHandler
=> RQLMetadata -> m (HttpResponse EncJSON) => RQLMetadata -> m (HttpResponse EncJSON)
v1MetadataHandler query = do v1MetadataHandler query = do
(liftEitherM . authorizeV1MetadataApi query) =<< ask (liftEitherM . authorizeV1MetadataApi query) =<< ask
userInfo <- asks hcUser userInfo <- asks hcUser
scRef <- asks (scCacheRef . hcServerCtx) scRef <- asks (scCacheRef . hcServerCtx)
schemaCache <- fmap fst $ liftIO $ readIORef $ _scrCache scRef schemaCache <- fmap fst $ liftIO $ readIORef $ _scrCache scRef
httpMgr <- asks (scManager . hcServerCtx) httpMgr <- asks (scManager . hcServerCtx)
sqlGenCtx <- asks (scSQLGenCtx . hcServerCtx) sqlGenCtx <- asks (scSQLGenCtx . hcServerCtx)
env <- asks (scEnvironment . hcServerCtx) env <- asks (scEnvironment . hcServerCtx)
instanceId <- asks (scInstanceId . hcServerCtx) instanceId <- asks (scInstanceId . hcServerCtx)
logger <- asks (scLogger . hcServerCtx) logger <- asks (scLogger . hcServerCtx)
remoteSchemaPermsCtx <- asks (scRemoteSchemaPermsCtx . hcServerCtx) remoteSchemaPermsCtx <- asks (scRemoteSchemaPermsCtx . hcServerCtx)
functionPermsCtx <- asks (scFunctionPermsCtx . hcServerCtx)
let serverConfigCtx = ServerConfigCtx functionPermsCtx remoteSchemaPermsCtx sqlGenCtx
r <- withSCUpdate scRef logger $ r <- withSCUpdate scRef logger $
runMetadataQuery env instanceId userInfo httpMgr sqlGenCtx remoteSchemaPermsCtx schemaCache query runMetadataQuery env instanceId userInfo httpMgr serverConfigCtx schemaCache query
pure $ HttpResponse r [] pure $ HttpResponse r []
v2QueryHandler v2QueryHandler
@ -453,7 +457,8 @@ v2QueryHandler query = do
instanceId <- asks (scInstanceId . hcServerCtx) instanceId <- asks (scInstanceId . hcServerCtx)
env <- asks (scEnvironment . hcServerCtx) env <- asks (scEnvironment . hcServerCtx)
remoteSchemaPermsCtx <- asks (scRemoteSchemaPermsCtx . hcServerCtx) remoteSchemaPermsCtx <- asks (scRemoteSchemaPermsCtx . hcServerCtx)
V2Q.runQuery env instanceId userInfo schemaCache httpMgr sqlGenCtx remoteSchemaPermsCtx query functionPermsCtx <- asks (scFunctionPermsCtx . hcServerCtx)
V2Q.runQuery env instanceId userInfo schemaCache httpMgr sqlGenCtx remoteSchemaPermsCtx functionPermsCtx query
v1Alpha1GQHandler v1Alpha1GQHandler
:: ( HasVersion :: ( HasVersion
@ -727,13 +732,14 @@ mkWaiApp
-> RebuildableSchemaCache -> RebuildableSchemaCache
-> EKG.Store -> EKG.Store
-> RemoteSchemaPermsCtx -> RemoteSchemaPermsCtx
-> FunctionPermissionsCtx
-> WS.ConnectionOptions -> WS.ConnectionOptions
-> KeepAliveDelay -> KeepAliveDelay
-- ^ Metadata storage connection pool -- ^ Metadata storage connection pool
-> m HasuraApp -> m HasuraApp
mkWaiApp env logger sqlGenCtx enableAL httpManager mode corsCfg enableConsole consoleAssetsDir mkWaiApp env logger sqlGenCtx enableAL httpManager mode corsCfg enableConsole consoleAssetsDir
enableTelemetry instanceId apis lqOpts _ {- planCacheOptions -} responseErrorsConfig enableTelemetry instanceId apis lqOpts _ {- planCacheOptions -} responseErrorsConfig
liveQueryHook schemaCache ekgStore enableRSPermsCtx connectionOptions keepAliveDelay = do liveQueryHook schemaCache ekgStore enableRSPermsCtx functionPermsCtx connectionOptions keepAliveDelay = do
-- See Note [Temporarily disabling query plan caching] -- See Note [Temporarily disabling query plan caching]
-- (planCache, schemaCacheRef) <- initialiseCache -- (planCache, schemaCacheRef) <- initialiseCache
@ -762,6 +768,7 @@ mkWaiApp env logger sqlGenCtx enableAL httpManager mode corsCfg enableConsole co
, scEnvironment = env , scEnvironment = env
, scResponseInternalErrorsConfig = responseErrorsConfig , scResponseInternalErrorsConfig = responseErrorsConfig
, scRemoteSchemaPermsCtx = enableRSPermsCtx , scRemoteSchemaPermsCtx = enableRSPermsCtx
, scFunctionPermsCtx = functionPermsCtx
} }
spockApp <- liftWithStateless $ \lowerIO -> spockApp <- liftWithStateless $ \lowerIO ->

View File

@ -180,8 +180,7 @@ mkServeOptions rso = do
logHeadersFromEnv <- withEnvBool (rsoLogHeadersFromEnv rso) (fst logHeadersFromEnvEnv) logHeadersFromEnv <- withEnvBool (rsoLogHeadersFromEnv rso) (fst logHeadersFromEnvEnv)
enableRemoteSchemaPerms <- enableRemoteSchemaPerms <-
bool RemoteSchemaPermsDisabled RemoteSchemaPermsEnabled <$> bool RemoteSchemaPermsDisabled RemoteSchemaPermsEnabled <$>
(withEnvBool (rsoEnableRemoteSchemaPermissions rso) $ (withEnvBool (rsoEnableRemoteSchemaPermissions rso) (fst enableRemoteSchemaPermsEnv))
(fst enableRemoteSchemaPermsEnv))
webSocketCompressionFromEnv <- withEnvBool (rsoWebSocketCompression rso) $ webSocketCompressionFromEnv <- withEnvBool (rsoWebSocketCompression rso) $
fst webSocketCompressionEnv fst webSocketCompressionEnv
@ -195,12 +194,17 @@ mkServeOptions rso = do
webSocketKeepAlive <- KeepAliveDelay . fromIntegral . fromMaybe 5 webSocketKeepAlive <- KeepAliveDelay . fromIntegral . fromMaybe 5
<$> withEnv (rsoWebSocketKeepAlive rso) (fst webSocketKeepAliveEnv) <$> withEnv (rsoWebSocketKeepAlive rso) (fst webSocketKeepAliveEnv)
inferFunctionPerms <-
maybe FunctionPermissionsInferred (bool FunctionPermissionsManual FunctionPermissionsInferred) <$>
(withEnv (rsoInferFunctionPermissions rso) (fst inferFunctionPermsEnv))
return $ ServeOptions port host connParams txIso adminScrt authHook jwtSecret return $ ServeOptions port host connParams txIso adminScrt authHook jwtSecret
unAuthRole corsCfg enableConsole consoleAssetsDir unAuthRole corsCfg enableConsole consoleAssetsDir
enableTelemetry strfyNum enabledAPIs lqOpts enableAL enableTelemetry strfyNum enabledAPIs lqOpts enableAL
enabledLogs serverLogLevel planCacheOptions enabledLogs serverLogLevel planCacheOptions
internalErrorsConfig eventsHttpPoolSize eventsFetchInterval internalErrorsConfig eventsHttpPoolSize eventsFetchInterval
logHeadersFromEnv enableRemoteSchemaPerms connectionOptions webSocketKeepAlive logHeadersFromEnv enableRemoteSchemaPerms connectionOptions webSocketKeepAlive
inferFunctionPerms
where where
#ifdef DeveloperAPIs #ifdef DeveloperAPIs
defaultAPIs = [METADATA,GRAPHQL,PGDUMP,CONFIG,DEVELOPER] defaultAPIs = [METADATA,GRAPHQL,PGDUMP,CONFIG,DEVELOPER]
@ -544,6 +548,12 @@ enableRemoteSchemaPermsEnv =
, "Enables remote schema permissions (default: false)" , "Enables remote schema permissions (default: false)"
) )
inferFunctionPermsEnv :: (String, String)
inferFunctionPermsEnv =
( "HASURA_GRAPHQL_INFER_FUNCTION_PERMISSIONS"
, "Infers function permissions (default: true)"
)
adminInternalErrorsEnv :: (String, String) adminInternalErrorsEnv :: (String, String)
adminInternalErrorsEnv = adminInternalErrorsEnv =
@ -871,6 +881,13 @@ parseEnableRemoteSchemaPerms =
help (snd enableRemoteSchemaPermsEnv) help (snd enableRemoteSchemaPermsEnv)
) )
parseInferFunctionPerms :: Parser (Maybe Bool)
parseInferFunctionPerms = optional $
option ( eitherReader parseStrAsBool )
( long "infer-function-permissions" <>
help (snd inferFunctionPermsEnv)
)
mxRefetchDelayEnv :: (String, String) mxRefetchDelayEnv :: (String, String)
mxRefetchDelayEnv = mxRefetchDelayEnv =
( "HASURA_GRAPHQL_LIVE_QUERIES_MULTIPLEXED_REFETCH_INTERVAL" ( "HASURA_GRAPHQL_LIVE_QUERIES_MULTIPLEXED_REFETCH_INTERVAL"
@ -983,6 +1000,7 @@ serveOptsToLog so =
, "remote_schema_permissions" J..= soEnableRemoteSchemaPermissions so , "remote_schema_permissions" J..= soEnableRemoteSchemaPermissions so
, "websocket_compression_options" J..= show (WS.connectionCompressionOptions . soConnectionOptions $ so) , "websocket_compression_options" J..= show (WS.connectionCompressionOptions . soConnectionOptions $ so)
, "websocket_keep_alive" J..= show (soWebsocketKeepAlive so) , "websocket_keep_alive" J..= show (soWebsocketKeepAlive so)
, "infer_function_permissions" J..= soInferFunctionPermissions so
] ]
mkGenericStrLog :: L.LogLevel -> Text -> String -> StartupLog mkGenericStrLog :: L.LogLevel -> Text -> String -> StartupLog
@ -1031,6 +1049,7 @@ serveOptionsParser =
<*> parseEnableRemoteSchemaPerms <*> parseEnableRemoteSchemaPerms
<*> parseWebSocketCompression <*> parseWebSocketCompression
<*> parseWebSocketKeepAlive <*> parseWebSocketKeepAlive
<*> parseInferFunctionPerms
-- | This implements the mapping between application versions -- | This implements the mapping between application versions
-- and catalog schema versions. -- and catalog schema versions.

View File

@ -74,6 +74,7 @@ data RawServeOptions impl
, rsoEnableRemoteSchemaPermissions :: !Bool , rsoEnableRemoteSchemaPermissions :: !Bool
, rsoWebSocketCompression :: !Bool , rsoWebSocketCompression :: !Bool
, rsoWebSocketKeepAlive :: !(Maybe Int) , rsoWebSocketKeepAlive :: !(Maybe Int)
, rsoInferFunctionPermissions :: !(Maybe Bool)
} }
-- | @'ResponseInternalErrorsConfig' represents the encoding of the internal -- | @'ResponseInternalErrorsConfig' represents the encoding of the internal
@ -124,6 +125,7 @@ data ServeOptions impl
, soEnableRemoteSchemaPermissions :: !RemoteSchemaPermsCtx , soEnableRemoteSchemaPermissions :: !RemoteSchemaPermsCtx
, soConnectionOptions :: !WS.ConnectionOptions , soConnectionOptions :: !WS.ConnectionOptions
, soWebsocketKeepAlive :: !KeepAliveDelay , soWebsocketKeepAlive :: !KeepAliveDelay
, soInferFunctionPermissions :: !FunctionPermissionsCtx
} }
data DowngradeOptions data DowngradeOptions

View File

@ -82,7 +82,7 @@ data SchemaSyncEvent
instance ToJSON SchemaSyncEvent where instance ToJSON SchemaSyncEvent where
toJSON = \case toJSON = \case
SSEListenStart time -> String $ "event listening started at " <> tshow time SSEListenStart time -> String $ "event listening started at " <> tshow time
SSEPayload payload -> toJSON payload SSEPayload payload -> toJSON payload
data ThreadError data ThreadError
= TEPayloadParse !Text = TEPayloadParse !Text
@ -192,12 +192,14 @@ startSchemaSyncProcessorThread
-> InstanceId -> InstanceId
-> UTC.UTCTime -> UTC.UTCTime
-> RemoteSchemaPermsCtx -> RemoteSchemaPermsCtx
-> FunctionPermissionsCtx
-> ManagedT m Immortal.Thread -> ManagedT m Immortal.Thread
startSchemaSyncProcessorThread sqlGenCtx logger httpMgr startSchemaSyncProcessorThread sqlGenCtx logger httpMgr
schemaSyncEventRef cacheRef instanceId cacheInitStartTime remoteSchemaPermsCtx = do schemaSyncEventRef cacheRef instanceId cacheInitStartTime remoteSchemaPermsCtx functionPermsCtx = do
-- Start processor thread -- Start processor thread
processorThread <- C.forkManagedT "SchemeUpdate.processor" logger $ processorThread <- C.forkManagedT "SchemeUpdate.processor" logger $
processor sqlGenCtx logger httpMgr schemaSyncEventRef cacheRef instanceId cacheInitStartTime remoteSchemaPermsCtx processor sqlGenCtx logger httpMgr schemaSyncEventRef
cacheRef instanceId cacheInitStartTime remoteSchemaPermsCtx functionPermsCtx
logThreadStarted logger instanceId TTProcessor processorThread logThreadStarted logger instanceId TTProcessor processorThread
pure processorThread pure processorThread
@ -258,9 +260,10 @@ processor
-> InstanceId -> InstanceId
-> UTC.UTCTime -> UTC.UTCTime
-> RemoteSchemaPermsCtx -> RemoteSchemaPermsCtx
-> FunctionPermissionsCtx
-> m void -> m void
processor sqlGenCtx logger httpMgr updateEventRef processor sqlGenCtx logger httpMgr updateEventRef
cacheRef instanceId cacheInitStartTime remoteSchemaPermsCtx = cacheRef instanceId cacheInitStartTime remoteSchemaPermsCtx functionPermsCtx =
-- Never exits -- Never exits
forever $ do forever $ do
event <- liftIO $ STM.atomically getLatestEvent event <- liftIO $ STM.atomically getLatestEvent
@ -281,7 +284,7 @@ processor sqlGenCtx logger httpMgr updateEventRef
when shouldReload $ when shouldReload $
refreshSchemaCache sqlGenCtx logger httpMgr cacheRef cacheInvalidations refreshSchemaCache sqlGenCtx logger httpMgr cacheRef cacheInvalidations
threadType remoteSchemaPermsCtx "schema cache reloaded" threadType remoteSchemaPermsCtx functionPermsCtx "schema cache reloaded"
where where
-- checks if there is an event -- checks if there is an event
-- and replaces it with Nothing -- and replaces it with Nothing
@ -307,10 +310,11 @@ refreshSchemaCache
-> CacheInvalidations -> CacheInvalidations
-> ThreadType -> ThreadType
-> RemoteSchemaPermsCtx -> RemoteSchemaPermsCtx
-> FunctionPermissionsCtx
-> Text -> Text
-> m () -> m ()
refreshSchemaCache sqlGenCtx logger httpManager refreshSchemaCache sqlGenCtx logger httpManager
cacheRef invalidations threadType remoteSchemaPermsCtx msg = do cacheRef invalidations threadType remoteSchemaPermsCtx functionPermsCtx msg = do
-- Reload schema cache from catalog -- Reload schema cache from catalog
eitherMetadata <- runMetadataStorageT fetchMetadata eitherMetadata <- runMetadataStorageT fetchMetadata
resE <- runExceptT $ do resE <- runExceptT $ do
@ -325,7 +329,8 @@ refreshSchemaCache sqlGenCtx logger httpManager
Left e -> logError logger threadType $ TEQueryError e Left e -> logError logger threadType $ TEQueryError e
Right () -> logInfo logger threadType $ object ["message" .= msg] Right () -> logInfo logger threadType $ object ["message" .= msg]
where where
runCtx = RunCtx adminUserInfo httpManager sqlGenCtx remoteSchemaPermsCtx serverConfigCtx = ServerConfigCtx functionPermsCtx remoteSchemaPermsCtx sqlGenCtx
runCtx = RunCtx adminUserInfo httpManager serverConfigCtx
logInfo :: (MonadIO m) => Logger Hasura -> ThreadType -> Value -> m () logInfo :: (MonadIO m) => Logger Hasura -> ThreadType -> Value -> m ()
logInfo logger threadType val = unLogger logger $ logInfo logger threadType val = unLogger logger $

View File

@ -164,7 +164,7 @@ computeMetrics sc _mtServiceTimings _mtPgVersion =
_mtEventTriggers = Map.size $ Map.filter (not . Map.null) _mtEventTriggers = Map.size $ Map.filter (not . Map.null)
$ Map.map _tiEventTriggerInfoMap userTables $ Map.map _tiEventTriggerInfoMap userTables
_mtRemoteSchemas = Map.size $ scRemoteSchemas sc _mtRemoteSchemas = Map.size $ scRemoteSchemas sc
_mtFunctions = Map.size $ Map.filter (not . isSystemDefined . fiSystemDefined) pgFunctionCache _mtFunctions = Map.size $ Map.filter (not . isSystemDefined . _fiSystemDefined) pgFunctionCache
_mtActions = computeActionsMetrics $ scActions sc _mtActions = computeActionsMetrics $ scActions sc
in Metrics{..} in Metrics{..}

View File

@ -37,7 +37,7 @@ newtype CacheRefT m a
= CacheRefT { runCacheRefT :: MVar RebuildableSchemaCache -> m a } = CacheRefT { runCacheRefT :: MVar RebuildableSchemaCache -> m a }
deriving deriving
( Functor, Applicative, Monad, MonadIO, MonadError e, MonadBase b, MonadBaseControl b ( Functor, Applicative, Monad, MonadIO, MonadError e, MonadBase b, MonadBaseControl b
, MonadTx, MonadUnique, UserInfoM, HTTP.HasHttpManagerM, HasSQLGenCtx) , MonadTx, MonadUnique, UserInfoM, HTTP.HasHttpManagerM, HasServerConfigCtx)
via (ReaderT (MVar RebuildableSchemaCache) m) via (ReaderT (MVar RebuildableSchemaCache) m)
instance MonadTrans CacheRefT where instance MonadTrans CacheRefT where
@ -51,7 +51,7 @@ instance (MonadBase IO m) => CacheRM (CacheRefT m) where
askSchemaCache = CacheRefT (fmap lastBuiltSchemaCache . readMVar) askSchemaCache = CacheRefT (fmap lastBuiltSchemaCache . readMVar)
instance (MonadIO m, MonadBaseControl IO m, MonadTx m, HTTP.HasHttpManagerM m instance (MonadIO m, MonadBaseControl IO m, MonadTx m, HTTP.HasHttpManagerM m
, HasSQLGenCtx m, HasRemoteSchemaPermsCtx m, MonadResolveSource m) => CacheRWM (CacheRefT m) where , MonadResolveSource m, HasServerConfigCtx m) => CacheRWM (CacheRefT m) where
buildSchemaCacheWithOptions reason invalidations metadata = CacheRefT $ flip modifyMVar \schemaCache -> do buildSchemaCacheWithOptions reason invalidations metadata = CacheRefT $ flip modifyMVar \schemaCache -> do
((), cache, _) <- runCacheRWT schemaCache (buildSchemaCacheWithOptions reason invalidations metadata) ((), cache, _) <- runCacheRWT schemaCache (buildSchemaCacheWithOptions reason invalidations metadata)
pure (cache, ()) pure (cache, ())
@ -72,8 +72,7 @@ spec
, MonadBaseControl IO m , MonadBaseControl IO m
, MonadError QErr m , MonadError QErr m
, HTTP.HasHttpManagerM m , HTTP.HasHttpManagerM m
, HasSQLGenCtx m , HasServerConfigCtx m
, HasRemoteSchemaPermsCtx m
, MonadResolveSource m , MonadResolveSource m
) )
=> SourceConfiguration -> PGExecCtx -> Q.ConnInfo -> SpecWithCache m => SourceConfiguration -> PGExecCtx -> Q.ConnInfo -> SpecWithCache m

View File

@ -97,8 +97,8 @@ buildPostgresSpecs maybeUrlTemplate = do
setupCacheRef = do setupCacheRef = do
httpManager <- HTTP.newManager HTTP.tlsManagerSettings httpManager <- HTTP.newManager HTTP.tlsManagerSettings
let sqlGenCtx = SQLGenCtx False let sqlGenCtx = SQLGenCtx False
cacheBuildParams = CacheBuildParams httpManager sqlGenCtx RemoteSchemaPermsDisabled serverConfigCtx = ServerConfigCtx FunctionPermissionsInferred RemoteSchemaPermsDisabled sqlGenCtx
(mkPgSourceResolver print) cacheBuildParams = CacheBuildParams httpManager (mkPgSourceResolver print) serverConfigCtx
run :: CacheBuild a -> IO a run :: CacheBuild a -> IO a
run = run =

View File

@ -82,6 +82,13 @@ def pytest_addoption(parser):
help="Run testcases for logging" help="Run testcases for logging"
) )
parser.addoption(
"--test-function-permissions",
action="store_true",
required=False,
help="Run manual function permission tests"
)
parser.addoption( parser.addoption(
"--test-jwk-url", "--test-jwk-url",
action="store_true", action="store_true",
@ -283,6 +290,12 @@ def actions_fixture(hge_ctx):
webhook_httpd.server_close() webhook_httpd.server_close()
web_server.join() web_server.join()
@pytest.fixture(scope='class')
def functions_permissions_fixtures(hge_ctx):
if not hge_ctx.function_permissions:
pytest.skip('These tests are meant to be run with --test-function-permissions set')
return
@pytest.fixture(scope='class') @pytest.fixture(scope='class')
def scheduled_triggers_evts_webhook(request): def scheduled_triggers_evts_webhook(request):
webhook_httpd = EvtsWebhookServer(server_address=('127.0.0.1', 5594)) webhook_httpd = EvtsWebhookServer(server_address=('127.0.0.1', 5594))

View File

@ -473,6 +473,7 @@ class HGECtx:
self.webhook_insecure = config.getoption('--test-webhook-insecure') self.webhook_insecure = config.getoption('--test-webhook-insecure')
self.metadata_disabled = config.getoption('--test-metadata-disabled') self.metadata_disabled = config.getoption('--test-metadata-disabled')
self.may_skip_test_teardown = False self.may_skip_test_teardown = False
self.function_permissions = config.getoption('--test-function-permissions')
self.engine = create_engine(self.pg_url) self.engine = create_engine(self.pg_url)
self.meta = MetaData() self.meta = MetaData()

View File

@ -0,0 +1,4 @@
type: pg_create_function_permission
args:
function: add_to_score
role: anonymous

View File

@ -0,0 +1,4 @@
type: pg_drop_function_permission
args:
function: add_to_score
role: anonymous

View File

@ -0,0 +1,20 @@
- description: Fails as anonymous role doesn't have access to the `add_to_score` function
headers:
X-Hasura-Role: anonymous
url: /v1/graphql
status: 200
query:
query: |
mutation {
add_to_score(args: {search: "Black"}){
name
score
role_echo
}
}
response:
errors:
- extensions:
path: $
code: validation-failed
message: no mutations exist

View File

@ -0,0 +1,4 @@
type: pg_create_function_permission
args:
role: user
function: get_articles

View File

@ -0,0 +1,23 @@
- description: Query function as a role without permission being configured for the role
url: /v1/graphql
status: 200
headers:
X-Hasura-Role: user
X-Hasura-User-Id: '1'
response:
data:
get_articles:
- title: Article 1
content: Sample article content 1
- title: Article 2
content: Sample article content 2
- title: Article 3
content: Sample article content 3
query:
query: |
query {
get_articles(args: {search: "art"}) {
title
content
}
}

View File

@ -0,0 +1,19 @@
description: Query function as a role without permission being configured for the role
url: /v1/graphql
status: 200
headers:
X-Hasura-Role: user
response:
errors:
- extensions:
path: $.selectionSet.get_articles
code: validation-failed
message: "field \"get_articles\" not found in type: 'query_root'"
query:
query: |
query {
get_articles(args: {search: "art"}) {
title
content
}
}

View File

@ -0,0 +1,71 @@
type: bulk
args:
- type: run_sql
args:
sql: |
create table author(
id serial primary key,
name text unique,
is_registered boolean not null default false,
remarks_internal text
);
INSERT INTO author (name, remarks_internal)
VALUES
('Author 1', 'remark 1'),
('Author 2', 'remark 2'),
('Author 3', 'remark 3');
CREATE TABLE article (
id SERIAL PRIMARY KEY,
title TEXT,
content TEXT,
author_id INTEGER NOT NULL REFERENCES author(id),
is_published BOOLEAN NOT NULL default FALSE,
published_on TIMESTAMP
);
INSERT INTO article (title, content, author_id, is_published)
VALUES
('Article 1', 'Sample article content 1', 1, false),
('Article 2', 'Sample article content 2', 1, true),
('Article 3', 'Sample article content 3', 2, true),
('Article 4', 'Sample article content 4', 3, false);
CREATE FUNCTION get_articles(search text)
RETURNS SETOF article AS $$
SELECT *
FROM article
WHERE
title ilike ('%' || search || '%')
OR content ilike ('%' || search || '%')
$$ LANGUAGE sql STABLE;
- type: track_table
args:
table: author
- type: track_table
args:
table: article
- type: track_function
args:
name: get_articles
schema: public
- type: create_select_permission
args:
table: article
role: user
permission:
columns:
- title
- content
- is_published
filter:
_or:
- id: X-HASURA-USER-ID
- is_published:
_eq: true

View File

@ -0,0 +1,9 @@
type: bulk
args:
- type: run_sql
args:
sql: |
DROP TABLE article cascade;
DROP TABLE author cascade;
cascade: true

View File

@ -588,9 +588,14 @@ class TestGraphQLMutateEnums:
def test_delete_where_enum_field(self, hge_ctx, transport): def test_delete_where_enum_field(self, hge_ctx, transport):
check_query_f(hge_ctx, self.dir() + '/delete_where_enum_field.yaml', transport) check_query_f(hge_ctx, self.dir() + '/delete_where_enum_field.yaml', transport)
use_function_permission_fixtures = usefixtures(
'per_class_db_schema_for_mutation_tests',
'per_method_db_data_for_mutation_tests',
'functions_permissions_fixtures'
)
# Tracking VOLATILE SQL functions as mutations, or queries (#1514) # Tracking VOLATILE SQL functions as mutations, or queries (#1514)
@pytest.mark.parametrize('transport', ['http', 'websocket']) @pytest.mark.parametrize('transport', ['http', 'websocket'])
@use_mutation_fixtures @use_function_permission_fixtures
class TestGraphQLMutationFunctions: class TestGraphQLMutationFunctions:
@classmethod @classmethod
def dir(cls): def dir(cls):
@ -609,7 +614,17 @@ class TestGraphQLMutationFunctions:
def test_functions_as_mutations(self, hge_ctx, transport): def test_functions_as_mutations(self, hge_ctx, transport):
check_query_f(hge_ctx, self.dir() + '/function_as_mutations.yaml', transport) check_query_f(hge_ctx, self.dir() + '/function_as_mutations.yaml', transport)
# When graphql-engine is started with `--infer-function-permissions=false` then
# a function is only accessible to a role when the permission is granted through
# the `pg_create_function_permission` definition
def test_function_as_mutation_without_function_permission(self, hge_ctx, transport):
check_query_f(hge_ctx, self.dir() + '/function_without_function_permission.yaml')
# Ensure select permissions on the corresponding SETOF table apply to # Ensure select permissions on the corresponding SETOF table apply to
# the return set of the mutation field backed by the tracked function. # the return set of the mutation field backed by the tracked function.
def test_functions_as_mutations_permissions(self, hge_ctx, transport): def test_functions_as_mutations_permissions(self, hge_ctx, transport):
st_code, resp = hge_ctx.v1metadataq_f(self.dir() + '/create_function_permission_add_to_score.yaml')
assert st_code == 200, resp
check_query_f(hge_ctx, self.dir() + '/function_as_mutations_permissions.yaml', transport) check_query_f(hge_ctx, self.dir() + '/function_as_mutations_permissions.yaml', transport)
st_code, resp = hge_ctx.v1metadataq_f(self.dir() + '/drop_function_permission_add_to_score.yaml')
assert st_code == 200, resp

View File

@ -806,3 +806,24 @@ def _test_relay_pagination(hge_ctx, transport, test_file_prefix, no_of_pages):
page_no = i + 1 page_no = i + 1
test_file = "page_" + str(page_no) + ".yaml" test_file = "page_" + str(page_no) + ".yaml"
check_query_f(hge_ctx, test_file_prefix + "/" + test_file, transport) check_query_f(hge_ctx, test_file_prefix + "/" + test_file, transport)
use_function_permission_fixtures = pytest.mark.usefixtures(
'per_method_tests_db_state',
'functions_permissions_fixtures'
)
@pytest.mark.parametrize('transport', ['http', 'websocket'])
@use_function_permission_fixtures
class TestGraphQLQueryFunctionPermissions:
@classmethod
def dir(cls):
return 'queries/graphql_query/functions/permissions/'
def test_access_function_without_permission_configured(self, hge_ctx, transport):
check_query_f(hge_ctx, self.dir() + 'get_articles_without_permission_configured.yaml')
def test_access_function_with_permission_configured(self, hge_ctx, transport):
st_code, resp = hge_ctx.v1metadataq_f(self.dir() + 'add_function_permission_get_articles.yaml')
assert st_code == 200, resp
check_query_f(hge_ctx, self.dir() + 'get_articles_with_permission_configured.yaml')