diff --git a/.circleci/server-test-names.txt b/.circleci/server-test-names.txt index 1453cfb7818..9a19ca6e17c 100644 --- a/.circleci/server-test-names.txt +++ b/.circleci/server-test-names.txt @@ -33,3 +33,4 @@ insecure-webhook-with-admin-secret allowlist-queries jwk-url horizontal-scaling +developer-api-tests diff --git a/.circleci/test-server.sh b/.circleci/test-server.sh index fdc786b0b1b..911a66ea938 100755 --- a/.circleci/test-server.sh +++ b/.circleci/test-server.sh @@ -219,6 +219,7 @@ export HASURA_GRAPHQL_STRINGIFY_NUMERIC_TYPES=true export REMOTE_SCHEMAS_WEBHOOK_DOMAIN="http://127.0.0.1:5000" export PYTEST_ADDOPTS="-vv" + HGE_PIDS="" WH_PID="" WHC_PID="" @@ -274,6 +275,7 @@ case "$SERVER_TEST_TO_RUN" in TEST_TYPE="admin-secret" export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" + start_multiple_hge_servers @@ -288,6 +290,7 @@ case "$SERVER_TEST_TO_RUN" in export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" export HASURA_GRAPHQL_UNAUTHORIZED_ROLE="anonymous" + run_hge_with_args serve @@ -306,7 +309,7 @@ case "$SERVER_TEST_TO_RUN" in init_jwt - export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" + export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" export HASURA_GRAPHQL_JWT_SECRET="$(jq -n --arg key "$(cat $OUTPUT_FOLDER/ssl/jwt_public.key)" '{ type: "RS512", key: $key }')" start_multiple_hge_servers @@ -325,6 +328,7 @@ case "$SERVER_TEST_TO_RUN" in init_jwt export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" + export HASURA_GRAPHQL_JWT_SECRET="$(jq -n --arg key "$(cat $OUTPUT_FOLDER/ssl/jwt_public.key)" '{ type: "RS512", key: $key , claims_format: "stringified_json"}')" run_hge_with_args serve @@ -343,6 +347,7 @@ case "$SERVER_TEST_TO_RUN" in init_jwt export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" + export HASURA_GRAPHQL_JWT_SECRET="$(jq -n --arg key "$(cat $OUTPUT_FOLDER/ssl/jwt_public.key)" '{ type: "RS512", key: $key , audience: "myapp-1234"}')" run_hge_with_args serve @@ -363,6 +368,7 @@ case "$SERVER_TEST_TO_RUN" in export HASURA_GRAPHQL_JWT_SECRET="$(jq -n --arg key "$(cat $OUTPUT_FOLDER/ssl/jwt_public.key)" '{ type: "RS512", key: $key , audience: ["myapp-1234", "myapp-9876"]}')" export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" + run_hge_with_args serve wait_for_port 8080 @@ -381,6 +387,7 @@ case "$SERVER_TEST_TO_RUN" in init_jwt export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" + export HASURA_GRAPHQL_JWT_SECRET="$(jq -n --arg key "$(cat $OUTPUT_FOLDER/ssl/jwt_public.key)" '{ type: "RS512", key: $key , issuer: "https://hasura.com"}')" run_hge_with_args serve @@ -404,6 +411,7 @@ case "$SERVER_TEST_TO_RUN" in # hasura claims at one level of nesting export HASURA_GRAPHQL_JWT_SECRET="$(jq -n --arg key "$(cat $OUTPUT_FOLDER/ssl/jwt_public.key)" '{ type: "RS512", key: $key , claims_namespace_path: "$.hasura_claims"}')" export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" + run_hge_with_args serve wait_for_port 8080 @@ -417,6 +425,7 @@ case "$SERVER_TEST_TO_RUN" in # hasura claims at two levels of nesting with claims_namespace_path containing special character export HASURA_GRAPHQL_JWT_SECRET="$(jq -n --arg key "$(cat $OUTPUT_FOLDER/ssl/jwt_public.key)" '{ type: "RS512", key: $key , claims_namespace_path: "$.hasura['\''claims%'\'']"}')" export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" + run_hge_with_args serve wait_for_port 8080 @@ -430,6 +439,7 @@ case "$SERVER_TEST_TO_RUN" in # hasura claims at the root of the JWT token export HASURA_GRAPHQL_JWT_SECRET="$(jq -n --arg key "$(cat $OUTPUT_FOLDER/ssl/jwt_public.key)" '{ type: "RS512", key: $key , claims_namespace_path: "$"}')" export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" + run_hge_with_args serve wait_for_port 8080 @@ -450,6 +460,7 @@ case "$SERVER_TEST_TO_RUN" in export HASURA_GRAPHQL_JWT_SECRET="$(jq -n --arg key "$(cat $OUTPUT_FOLDER/ssl/jwt_public.key)" '{ type: "RS512", key: $key , claims_map: {"x-hasura-user-id": {"path":"$.['"'"'https://myapp.com/jwt/claims'"'"'].user.id"}, "x-hasura-allowed-roles": {"path":"$.['"'"'https://myapp.com/jwt/claims'"'"'].role.allowed"}, "x-hasura-default-role": {"path":"$.['"'"'https://myapp.com/jwt/claims'"'"'].role.default"}}}')" export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" + run_hge_with_args serve wait_for_port 8080 @@ -465,6 +476,7 @@ case "$SERVER_TEST_TO_RUN" in export HASURA_GRAPHQL_JWT_SECRET="$(jq -n --arg key "$(cat $OUTPUT_FOLDER/ssl/jwt_public.key)" '{ type: "RS512", key: $key , claims_map: {"x-hasura-user-id": {"path":"$.['"'"'https://myapp.com/jwt/claims'"'"'].user.id", "default":"1"}, "x-hasura-allowed-roles": {"path":"$.['"'"'https://myapp.com/jwt/claims'"'"'].role.allowed", "default":["user","editor"]}, "x-hasura-default-role": {"path":"$.['"'"'https://myapp.com/jwt/claims'"'"'].role.default","default":"user"}}}')" export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" + run_hge_with_args serve wait_for_port 8080 @@ -483,6 +495,7 @@ case "$SERVER_TEST_TO_RUN" in init_jwt export HASURA_GRAPHQL_JWT_SECRET="$(jq -n --arg key "$(cat $OUTPUT_FOLDER/ssl/jwt_public.key)" '{ type: "RS512", key: $key , allowed_skew: 60}')" export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" + run_hge_with_args serve wait_for_port 8080 @@ -503,6 +516,7 @@ case "$SERVER_TEST_TO_RUN" in export HASURA_GRAPHQL_JWT_SECRET="$(jq -n --arg key "$(cat $OUTPUT_FOLDER/ssl/jwt_public.key)" '{ type: "RS512", key: $key , claims_map: {"x-hasura-user-id": {"path":"$.['"'"'https://myapp.com/jwt/claims'"'"'].user.id"}, "x-hasura-allowed-roles": ["user","editor"], "x-hasura-default-role": "user","x-hasura-custom-header":"custom-value"}}')" export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" + run_hge_with_args serve wait_for_port 8080 @@ -523,6 +537,7 @@ case "$SERVER_TEST_TO_RUN" in export HASURA_GRAPHQL_JWT_SECRET="$(jq -n --arg key "$(cat $OUTPUT_FOLDER/ssl/jwt_public.key)" '{ type: "RS512", key: $key , header: {"type": "Cookie", "name": "hasura_user"}}')" export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" + run_hge_with_args serve wait_for_port 8080 @@ -539,6 +554,7 @@ case "$SERVER_TEST_TO_RUN" in echo -e "\n$(time_elapsed): <########## TEST GRAPHQL-ENGINE WITH CORS DOMAINS ########>\n" export HASURA_GRAPHQL_CORS_DOMAIN="http://*.localhost, http://localhost:3000, https://*.foo.bar.com" export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" + TEST_TYPE="cors-domains" run_hge_with_args serve @@ -575,6 +591,7 @@ case "$SERVER_TEST_TO_RUN" in ws-init-cookie-noread) echo "$(time_elapsed): testcase 2: no read cookie, cors disabled" export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" + TEST_TYPE="ws-init-cookie-noread" export HASURA_GRAPHQL_AUTH_HOOK="http://localhost:9876/auth" export HASURA_GRAPHQL_AUTH_HOOK_MODE="POST" @@ -593,6 +610,7 @@ case "$SERVER_TEST_TO_RUN" in ws-init-cookie-read-cors-disabled) echo "$(time_elapsed): testcase 3: read cookie, cors disabled and ws-read-cookie" export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" + TEST_TYPE="ws-init-cookie-read-cors-disabled" export HASURA_GRAPHQL_AUTH_HOOK="http://localhost:9876/auth" export HASURA_GRAPHQL_AUTH_HOOK_MODE="POST" @@ -925,6 +943,22 @@ case "$SERVER_TEST_TO_RUN" in kill_hge_servers # end allowlist queries test ;; + + developer-api-tests) + echo -e "\n$(time_elapsed): <########## TEST GRAPHQL-ENGINE WITH DEVELOPER API ENABLED ########>\n" + TEST_TYPE="developer-api-tests" + export HASURA_GRAPHQL_ENABLED_APIS="metadata,graphql,developer,config,pgdump" + export HASURA_GRAPHQL_ADMIN_SECRET="HGE$RANDOM$RANDOM" + + run_hge_with_args serve --enabled-apis "$HASURA_GRAPHQL_ENABLED_APIS" + 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-developer-api-enabled test_dev_endpoints.py + + unset HASURA_GRAPHQL_ENABLED_APIS + + kill_hge_servers + ;; jwk-url) # TODO(swann): ditto, these have to be parallelised diff --git a/server/src-lib/Hasura/Server/App.hs b/server/src-lib/Hasura/Server/App.hs index edfec5b857c..bbd15ebf83e 100644 --- a/server/src-lib/Hasura/Server/App.hs +++ b/server/src-lib/Hasura/Server/App.hs @@ -41,7 +41,6 @@ import Network.Mime (defaultMimeLookup) import System.FilePath (joinPath, takeFileName) import Web.Spock.Core (()) -import qualified Hasura.Backends.Postgres.SQL.Types as PG import qualified Hasura.GraphQL.Execute as E import qualified Hasura.GraphQL.Execute.LiveQuery.Options as EL import qualified Hasura.GraphQL.Execute.LiveQuery.Poll as EL @@ -672,41 +671,6 @@ renderHtmlTemplate template jVal = errMsg = "template rendering failed: " ++ show errs (errs, res) = M.checkedSubstitute template jVal -newtype LegacyQueryParser m - = LegacyQueryParser - { getLegacyQueryParser :: PG.QualifiedTable -> Object -> m RQLQueryV1 } - -queryParsers :: (MonadError QErr m) => M.HashMap Text (LegacyQueryParser m) -queryParsers = - M.fromList - [ ("select", mkLegacyQueryParser RQSelect) - , ("insert", mkLegacyQueryParser RQInsert) - , ("update", mkLegacyQueryParser RQUpdate) - , ("delete", mkLegacyQueryParser RQDelete) - , ("count", mkLegacyQueryParser RQCount) - ] - where - mkLegacyQueryParser f = - LegacyQueryParser $ \qt obj -> do - let val = Object $ M.insert "table" (toJSON qt) obj - q <- decodeValue val - return $ f q - -legacyQueryHandler - :: ( HasVersion, MonadIO m, MonadBaseControl IO m, MonadMetadataApiAuthorization m, Tracing.MonadTrace m - , MonadReader HandlerCtx m - , MonadMetadataStorage m - , MonadResolveSource m - ) - => PG.TableName -> Text -> Object - -> m (HttpResponse EncJSON) -legacyQueryHandler tn queryType req = - case M.lookup queryType queryParsers of - Just queryParser -> getLegacyQueryParser queryParser qt req >>= v1QueryHandler . RQV1 - Nothing -> throw404 "No such resource exists" - where - qt = PG.QualifiedObject PG.publicSchema tn - -- | Default implementation of the 'MonadConfigApiHandler' configApiGetHandler :: forall m. (HasVersion, MonadIO m, MonadBaseControl IO m, UserAuthentication (Tracing.TraceT m), HttpLog m, Tracing.HasReporter m, HasResourceLimits m) @@ -981,12 +945,6 @@ httpApp setupHook corsCfg serverCtx enableConsole consoleAssetsDir enableTelemet Spock.post "v2/query" $ spockAction encodeQErr id $ mkPostHandler $ fmap (emptyHttpLogMetadata @m, ) <$> mkAPIRespHandler v2QueryHandler - Spock.post ("api/1/table" Spock.var Spock.var) $ \tableName queryType -> - mkSpockAction serverCtx encodeQErr id $ - mkPostHandler $ - fmap (emptyHttpLogMetadata @m, ) - <$> mkAPIRespHandler (legacyQueryHandler (PG.TableName tableName) queryType) - when enablePGDump $ Spock.post "v1alpha1/pg_dump" $ spockAction encodeQErr id $ mkPostHandler $ fmap (emptyHttpLogMetadata @m,) <$> v1Alpha1PGDumpHandler @@ -1022,7 +980,7 @@ httpApp setupHook corsCfg serverCtx enableConsole consoleAssetsDir enableTelemet Spock.get "dev/plan_cache" $ spockAction encodeQErr id $ mkGetHandler $ do onlyAdmin - respJ <- liftIO $ E.dumpPlanCache {- scPlanCache serverCtx -} + respJ <- liftIO E.dumpPlanCache {- scPlanCache serverCtx -} return (emptyHttpLogMetadata @m, JSONResp $ HttpResponse (encJFromJValue respJ) []) Spock.get "dev/subscriptions" $ spockAction encodeQErr id $ mkGetHandler $ do diff --git a/server/tests-py/conftest.py b/server/tests-py/conftest.py index 0abe00400bb..8ee8974f555 100644 --- a/server/tests-py/conftest.py +++ b/server/tests-py/conftest.py @@ -187,6 +187,12 @@ This option may result in test failures if the schema has to change between the help="Flag to specify if the pro tests are to be run" ) + parser.addoption( + "--test-developer-api-enabled", action="store_true", + help="Run Test cases with the Developer API Enabled", + default=False + ) + #By default, diff --git a/server/tests-py/context.py b/server/tests-py/context.py index 9919267039b..d0148a87660 100644 --- a/server/tests-py/context.py +++ b/server/tests-py/context.py @@ -625,3 +625,14 @@ class HGECtx: self.ws_client.teardown() self.ws_client_v1alpha1.teardown() self.ws_client_relay.teardown() + + def v1GraphqlExplain(self, q, hdrs=None): + headers = {} + + if hdrs != None: + headers = hdrs + if self.hge_key != None: + headers['X-Hasura-Admin-Secret'] = self.hge_key + + resp = self.http.post(self.hge_url + '/v1/graphql/explain', json=q, headers=headers) + return resp.status_code, resp.json() diff --git a/server/tests-py/pgdump/pg_dump_public.yaml b/server/tests-py/pgdump/pg_dump_public.yaml index 08839f71257..e3c2a247463 100644 --- a/server/tests-py/pgdump/pg_dump_public.yaml +++ b/server/tests-py/pgdump/pg_dump_public.yaml @@ -1,6 +1,8 @@ # Test output of pg_dump clean output. # Including "SET check_function_bodies = false;" avoids https://github.com/hasura/graphql-engine-mono/issues/1007 descriptions: Execute pg_dump on public schema +headers: + X-Hasura-Role: admin url: /v1alpha1/pg_dump status: 200 query: diff --git a/server/tests-py/queries/endpoints/endpoint_as_user_err.yaml b/server/tests-py/queries/endpoints/endpoint_as_user_err.yaml new file mode 100644 index 00000000000..6e6c1ed924d --- /dev/null +++ b/server/tests-py/queries/endpoints/endpoint_as_user_err.yaml @@ -0,0 +1,11 @@ +- description: call the new custom endpoint with the wrong role + url: /api/rest/simple + headers: + X-Hasura-Role: user + method: GET + status: 400 + query: + response: + path: $.selectionSet.test_table + error: "field \"test_table\" not found in type: 'query_root'" + code: validation-failed diff --git a/server/tests-py/queries/endpoints/endpoint_simple.yaml b/server/tests-py/queries/endpoints/endpoint_simple.yaml index 200a0a4763b..e2ee7efcb5d 100644 --- a/server/tests-py/queries/endpoints/endpoint_simple.yaml +++ b/server/tests-py/queries/endpoints/endpoint_simple.yaml @@ -1,5 +1,7 @@ - description: call the new custom endpoint url: /api/rest/simple + headers: + X-Hasura-Role: admin method: GET status: 200 query: diff --git a/server/tests-py/queries/graphql_query/basic/select_query_author_v1alpha1.yaml b/server/tests-py/queries/graphql_query/basic/select_query_author_v1alpha1.yaml new file mode 100644 index 00000000000..4fc225c5798 --- /dev/null +++ b/server/tests-py/queries/graphql_query/basic/select_query_author_v1alpha1.yaml @@ -0,0 +1,27 @@ +description: Simple GraphQL object query on author, excercising multiple operations +url: /v1alpha1/graphql +headers: + X-Hasura-Role: admin +status: 200 +response: + data: + author: + - id: 1 + name: Author 1 + - id: 2 + name: Author 2 +query: + # https://graphql.org/learn/serving-over-http/#post-request + operationName: chooseThisOne + query: | + query ignoreThisOne { + author { + name + } + } + query chooseThisOne { + author { + id + name + } + } diff --git a/server/tests-py/queries/graphql_query/permissions/anonymous_can_only_get_published_articles_v1alpha1.yaml b/server/tests-py/queries/graphql_query/permissions/anonymous_can_only_get_published_articles_v1alpha1.yaml new file mode 100644 index 00000000000..f34101d39be --- /dev/null +++ b/server/tests-py/queries/graphql_query/permissions/anonymous_can_only_get_published_articles_v1alpha1.yaml @@ -0,0 +1,60 @@ +- description: Other Users cannot see unpublished articles + url: /v1alpha1/graphql + status: 200 + headers: + X-Hasura-Role: anonymous + response: + data: + article: [] + query: + query: | + query { + article ( + where: { is_published: {_eq: false}} + ) { + id + title + content + is_published + author { + id + name + } + } + } +- description: Other Users can only see published articles + url: /v1/graphql + status: 200 + headers: + X-Hasura-Role: anonymous + response: + data: + article: + - id: 2 + title: Article 2 + content: Sample article content 2 + is_published: true + author: + id: 1 + name: Author 1 + - id: 3 + title: Article 3 + content: Sample article content 3 + is_published: true + author: + id: 2 + name: Author 2 + query: + query: | + query { + article { + id + title + content + is_published + author { + id + name + } + } + } diff --git a/server/tests-py/queries/v1/metadata/clear_metadata.yaml b/server/tests-py/queries/v1/metadata/clear_metadata.yaml index 72f5426c252..767405dff81 100644 --- a/server/tests-py/queries/v1/metadata/clear_metadata.yaml +++ b/server/tests-py/queries/v1/metadata/clear_metadata.yaml @@ -1,5 +1,7 @@ - description: Clear metadata url: /v1/metadata + headers: + X-Hasura-Role: admin status: 200 response: message: success @@ -9,6 +11,8 @@ - description: Check if metadata is cleared url: /v1/query + headers: + X-Hasura-Role: admin status: 200 # FIXME:- Using export_metadata will dump # the source configuration dependent on --database-url diff --git a/server/tests-py/queries/v1/metadata/metadata_as_user_err.yaml b/server/tests-py/queries/v1/metadata/metadata_as_user_err.yaml new file mode 100644 index 00000000000..940f1470ffa --- /dev/null +++ b/server/tests-py/queries/v1/metadata/metadata_as_user_err.yaml @@ -0,0 +1,490 @@ +- description: Clear metadata + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: clear_metadata + args: {} + +- description: Check if metadata is cleared + url: /v1/query + headers: + X-Hasura-Role: user + status: 400 + query: + type: export_metadata + args: {} + +- description: Export metadata + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: export_metadata + args: {} + +- description: Replace metadata + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: replace_metadata + args: + allow_inconsistent_metadata: true + metadata: + { + "version": 3, + "sources": [ + { + "name": "default", + "kind": "postgres", + "tables": [], + "configuration": { + "connection_info": { + "database_url": "postgres://postgres:postgrespassword@127.0.0.1:10432/tenant1", + "pool_settings": { + "retries": 1, + "idle_timeout": 185, + "max_connections": 50 + } + } + } + } + ] + } + +- description: Reload metadata + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: reload_metadata + args: + reload_remote_schemas: true + +- description: Get Inconsistent Metadata + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: get_inconsistent_metadata + args: {} + +- description: Drop Inconsistent Metadata + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: drop_inconsistent_metadata + args: {} + +- description: PG Add Source Metadata + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: pg_add_source + args: + { + "name": "pg1", + "configuration": { + "connection_info": { + "database_url": { + "from_env": "DB_URL_ENV_VAR" + }, + "pool_settings": { + "max_connections": 50, + "idle_timeout": 180, + "retries": 1, + "pool_timeout": 360, + "connection_lifetime": 600 + }, + "use_prepared_statements": true, + "isolation_level": "read-committed", + } + }, + "replace_configuration": false + } + +- description: PG Drop Source Metadata + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: pg_drop_source + args: + name: pg1 + cascade: true + +- description: PG Rename Source Metadata + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: rename_source + args: + name: pg1 + new_name: pg2 + +- description: PG Track Table Metadata + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: pg_track_table + args: { + "table": "author", + "source": "default", + "configuration": { + "custom_root_fields": { + "select": "Authors", + "select_by_pk": "Author", + "select_aggregate": "AuthorAggregate", + "insert": "AddAuthors", + "insert_one":"AddAuthor", + "update": "UpdateAuthors", + "update_by_pk": "UpdateAuthor", + "delete": "DeleteAuthors", + "delete_by_pk": "DeleteAuthor" + }, + "custom_column_names": { + "id": "authorId" + } + } + } + +- description: PG Untrack Table Metadata + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: pg_untrack_table + args: { + "table": { + "schema": "public", + "name": "author" + }, + "source": "default", + "cascade": true + } + +- description: PG Set Table Is Enum Metadata + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: pg_set_table_is_enum + args: { + "table": { + "schema": "public", + "name": "user_role" + }, + "source": "default", + "is_enum": true + } + +- description: PG Set Table Customization + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: pg_set_table_customization + args: { + "table": "author_details", + "source": "default", + "configuration": { + "identifier": "author", + "custom_root_fields": { + "select": "Authors", + "select_by_pk": "Author", + "select_aggregate": "AuthorAggregate", + "insert": "AddAuthors", + "insert_one":"AddAuthor", + "update": "UpdateAuthors", + "update_by_pk": "UpdateAuthor", + "delete": "DeleteAuthors", + "delete_by_pk": "DeleteAuthor" + }, + "custom_column_names": { + "id": "authorId" + } + } + } + +- description: PG Track Function + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: pg_track_function + args: { + "function": { + "schema": "public", + "name": "search_articles" + }, + "source": "default", + "configuration": { + "session_argument": "hasura_session" + } + } + +- description: PG Untrack Function + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: pg_untrack_function + args: { + "function": { + "schema": "public", + "name": "search_articles" + }, + "source": "default" + } + +- description: PG Create Function Permission + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: pg_create_function_permission + args: { + "function": "get_articles", + "source": "default", + "role": "user" + } + +- description: PG Drop Function Permission + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: pg_drop_function_permission + args: { + "function": "get_articles", + "role": "user", + "source": "default" + } + +- description: MSSQL Add Source Metadata + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: mssql_add_source + args: { + "name": "mssql1", + "configuration": { + "connection_info": { + "connection_string": { + "from_env": "CONN_STRING_ENV_VAR" + }, + "pool_settings": { + "max_connections": 50, + "idle_timeout": 180 + } + } + } + } + +- description: MSSQL Drop Source Metadata + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: mssql_drop_source + args: + name: mssql1 + +- description: MSSQL Track Table + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: mssql_track_table + args: { + "table": "author", + "source": "default" + } + +- description: MSSQL Untrack Table + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: mssql_untrack_table + args: { + "table": { + "schema": "dbo", + "name": "author" + }, + "source": "default", + "cascade": true + } + +- description: MSSQL Set Table Customization + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: mssql_set_table_customization + args: { + "table": "author_details", + "source": "default", + "configuration": { + "identifier": "author", + "custom_root_fields": { + "select": "Authors", + "select_aggregate": "AuthorAggregate", + }, + "custom_column_names": { + "id": "authorId" + } + } + } + +# TODO: Failing to parse `configuration.service_account` +# - description: BigQuery Add Source Metadata +# url: /v1/metadata +# headers: +# X-Hasura-Role: user +# status: 400 +# response: +# path: $.args +# error: 'restricted access : admin only' +# code: access-denied +# query: +# type: bigquery_add_source +# args: +# { +# "name": "bigquery1", +# "configuration": { +# "service_account": "bigquery_service_account", +# "project_id": "bigquery_project_id", +# "datasets": "dataset1, dataset2" +# } +# } + +- description: BigQuery Drop Source Metadata + url: /v1/metadata + headers: + X-Hasura-Role: user + status: 400 + response: + path: $.args + error: 'restricted access : admin only' + code: access-denied + query: + type: bigquery_drop_source + args: + name: bigquery1 diff --git a/server/tests-py/queries/v1/run_sql/sql_query_as_user_error_v2.yaml b/server/tests-py/queries/v1/run_sql/sql_query_as_user_error_v2.yaml new file mode 100644 index 00000000000..e124f2e11b6 --- /dev/null +++ b/server/tests-py/queries/v1/run_sql/sql_query_as_user_error_v2.yaml @@ -0,0 +1,14 @@ +url: /v2/query +status: 400 +headers: + X-Hasura-Role: user + X-Hasura-User-Id: '1' +response: + path: $.args + error: 'restricted access : admin only' + code: access-denied +query: + type: run_sql + args: + sql: | + SELECT * from author diff --git a/server/tests-py/queries/v1/run_sql/sql_select_query_v2.yaml b/server/tests-py/queries/v1/run_sql/sql_select_query_v2.yaml new file mode 100644 index 00000000000..513289538f2 --- /dev/null +++ b/server/tests-py/queries/v1/run_sql/sql_select_query_v2.yaml @@ -0,0 +1,21 @@ +url: /v2/query +headers: + X-Hasura-Role: admin +status: 200 +response: + result_type: TuplesOk + result: + - + - id + - name + - + - '1' + - Author 1 + - + - '2' + - Author 2 +query: + type: run_sql + args: + sql: | + SELECT * from author diff --git a/server/tests-py/queries/v1/select/basic/nested_select_query_article_author.yaml b/server/tests-py/queries/v1/select/basic/nested_select_query_article_author.yaml index 0cbca0d9179..53a8a015588 100644 --- a/server/tests-py/queries/v1/select/basic/nested_select_query_article_author.yaml +++ b/server/tests-py/queries/v1/select/basic/nested_select_query_article_author.yaml @@ -31,4 +31,4 @@ query: - name: author columns: - id - - name + - name diff --git a/server/tests-py/queries/v1/select/basic/select_article.yaml b/server/tests-py/queries/v1/select/basic/select_article.yaml index 92c9b5af796..c31b7935c76 100644 --- a/server/tests-py/queries/v1/select/basic/select_article.yaml +++ b/server/tests-py/queries/v1/select/basic/select_article.yaml @@ -1,10 +1,11 @@ +description: foo url: /v1/query status: 200 response: -- id: 1 - name: Author 1 -- id: 2 - name: Author 2 + - id: 1 + name: Author 1 + - id: 2 + name: Author 2 query: type: select args: diff --git a/server/tests-py/queries/v1/select/basic/select_article_role_error.yaml b/server/tests-py/queries/v1/select/basic/select_article_role_error.yaml new file mode 100644 index 00000000000..447d4b49f59 --- /dev/null +++ b/server/tests-py/queries/v1/select/basic/select_article_role_error.yaml @@ -0,0 +1,15 @@ +url: /v1/query +headers: + x-hasura-role: user +status: 400 +response: + code: permission-denied + error: role "user" does not have permission to select column "id" + path: $.args.columns[0] +query: + type: select + args: + table: author + columns: + - id + - name diff --git a/server/tests-py/queries/v1/select/basic/select_article_role_success.yaml b/server/tests-py/queries/v1/select/basic/select_article_role_success.yaml new file mode 100644 index 00000000000..b56eb15c59c --- /dev/null +++ b/server/tests-py/queries/v1/select/basic/select_article_role_success.yaml @@ -0,0 +1,13 @@ +url: /v1/query +headers: + x-hasura-role: user +status: 200 +response: +- name: Author 1 +- name: Author 2 +query: + type: select + args: + table: author + columns: + - name diff --git a/server/tests-py/queries/v1/select/basic/setup.yaml b/server/tests-py/queries/v1/select/basic/setup.yaml index 1cf604e9d72..f80c532b1f7 100644 --- a/server/tests-py/queries/v1/select/basic/setup.yaml +++ b/server/tests-py/queries/v1/select/basic/setup.yaml @@ -1,15 +1,14 @@ type: bulk args: -#Author table - - type: run_sql args: sql: | - create table author( - id serial primary key, - name text unique + CREATE TABLE author ( + id SERIAL PRIMARY KEY, + name TEXT UNIQUE ); + CREATE TABLE article ( id SERIAL PRIMARY KEY, title TEXT, @@ -18,12 +17,14 @@ args: is_published BOOLEAN, published_on TIMESTAMP ); - insert into author (name) - values + + INSERT INTO author (name) + VALUES ('Author 1'), - ('Author 2') ; - insert into article (title,content,author_id,is_published) - values + ('Author 2'); + + INSERT INTO article (title,content,author_id,is_published) + VALUES ( 'Article 1', 'Sample article content 1', @@ -42,6 +43,7 @@ args: 2, true ); + CREATE TABLE "user" ( id BIGSERIAL PRIMARY KEY, name TEXT NOT NULL, @@ -53,13 +55,25 @@ args: schema: public name: author -#Article table - type: track_table args: schema: public name: article -#Object relationship +- type: track_table + args: + schema: public + name: user + +- type: insert + args: + table: user + objects: + - name: User 1 + number: '123456789' + - name: User 2 + number: '123456780' + - type: create_object_relationship args: table: article @@ -67,7 +81,6 @@ args: using: foreign_key_constraint_on: author_id -#Array relationship - type: create_array_relationship args: table: author @@ -77,17 +90,11 @@ args: table: article column: author_id - -#Insert values -- type: track_table +- type: create_select_permission args: - schema: public - name: user -- type: insert - args: - table: user - objects: - - name: User 1 - number: '123456789' - - name: User 2 - number: '123456780' + table: author + role: user + permission: + columns: + - name + filter: {} diff --git a/server/tests-py/queries/v1/select/basic/teardown.yaml b/server/tests-py/queries/v1/select/basic/teardown.yaml index b4adcb6e892..c99e5e19441 100644 --- a/server/tests-py/queries/v1/select/basic/teardown.yaml +++ b/server/tests-py/queries/v1/select/basic/teardown.yaml @@ -1,16 +1,9 @@ type: bulk args: -#Drop relationship first -- type: drop_relationship - args: - relationship: articles - table: - schema: public - name: author - - type: run_sql args: sql: | - drop table article; - drop table author; - drop table "user"; + DROP TABLE article; + DROP TABLE author; + DROP TABLE "user"; + cascade: true diff --git a/server/tests-py/queries/v2/basic/select_article.yaml b/server/tests-py/queries/v2/basic/select_article.yaml new file mode 100644 index 00000000000..2cdb43224a3 --- /dev/null +++ b/server/tests-py/queries/v2/basic/select_article.yaml @@ -0,0 +1,14 @@ +url: /v2/query +status: 200 +response: +- id: 1 + name: Author 1 +- id: 2 + name: Author 2 +query: + type: select + args: + table: author + columns: + - id + - name diff --git a/server/tests-py/queries/v2/basic/select_article_role_error.yaml b/server/tests-py/queries/v2/basic/select_article_role_error.yaml new file mode 100644 index 00000000000..837c5e1a3e6 --- /dev/null +++ b/server/tests-py/queries/v2/basic/select_article_role_error.yaml @@ -0,0 +1,15 @@ +url: /v2/query +headers: + x-hasura-role: user +status: 400 +response: + code: access-denied + error: 'restricted access : admin only' + path: $.args +query: + type: select + args: + table: article + columns: + - id + - name \ No newline at end of file diff --git a/server/tests-py/queries/v2/basic/select_article_role_success.yaml b/server/tests-py/queries/v2/basic/select_article_role_success.yaml new file mode 100644 index 00000000000..59147141fc1 --- /dev/null +++ b/server/tests-py/queries/v2/basic/select_article_role_success.yaml @@ -0,0 +1,11 @@ +url: /v2/query +status: 200 +response: +- name: Author 1 +- name: Author 2 +query: + type: select + args: + table: author + columns: + - name diff --git a/server/tests-py/queries/v2/basic/setup.yaml b/server/tests-py/queries/v2/basic/setup.yaml new file mode 100644 index 00000000000..cc679f7b3f1 --- /dev/null +++ b/server/tests-py/queries/v2/basic/setup.yaml @@ -0,0 +1,104 @@ +type: bulk +args: + +#Author table + +- type: run_sql + args: + sql: | + create table author( + id serial primary key, + name text unique + ); + CREATE TABLE article ( + id SERIAL PRIMARY KEY, + title TEXT, + content TEXT, + author_id INTEGER REFERENCES author(id), + is_published BOOLEAN, + published_on TIMESTAMP + ); + insert into author (name) + values + ('Author 1'), + ('Author 2') ; + 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 + ); + CREATE TABLE "user" ( + id BIGSERIAL PRIMARY KEY, + name TEXT NOT NULL, + number BIGINT + ); + +- type: track_table + args: + schema: public + name: author + +#Article table +- type: track_table + args: + schema: public + name: article + +#Object relationship +- type: create_object_relationship + args: + table: article + name: author + using: + foreign_key_constraint_on: author_id + +#Array relationship +- type: create_array_relationship + args: + table: author + name: articles + using: + foreign_key_constraint_on: + table: article + column: author_id + + +#Insert values +- type: track_table + args: + schema: public + name: user +- type: insert + args: + table: user + objects: + - name: User 1 + number: '123456789' + - name: User 2 + number: '123456780' + +- type: create_select_permission + args: + table: + schema: public + name: author + role: user + permission: + columns: + - name + filter: {} diff --git a/server/tests-py/queries/v2/basic/teardown.yaml b/server/tests-py/queries/v2/basic/teardown.yaml new file mode 100644 index 00000000000..a2b06cb80f5 --- /dev/null +++ b/server/tests-py/queries/v2/basic/teardown.yaml @@ -0,0 +1,16 @@ +type: bulk +args: +#Drop relationship first +- type: drop_relationship + args: + relationship: articles + table: + schema: public + name: author + +- type: run_sql + args: + sql: | + drop table article cascade; + drop table author cascade; + drop table "user" cascade; diff --git a/server/tests-py/test_config_api.py b/server/tests-py/test_config_api.py index 1bb7eb4136c..23b6186480b 100644 --- a/server/tests-py/test_config_api.py +++ b/server/tests-py/test_config_api.py @@ -3,6 +3,20 @@ import re import json class TestConfigAPI(): + def test_config_api_user_role_error(self, hge_ctx): + admin_secret = hge_ctx.hge_key + auth_hook = hge_ctx.hge_webhook + jwt_conf = hge_ctx.hge_jwt_conf + if jwt_conf is not None: + jwt_conf_dict = json.loads(hge_ctx.hge_jwt_conf) + + headers = { 'x-hasura-role': 'user' } + if admin_secret is not None: + headers['x-hasura-admin-secret'] = admin_secret + + resp = hge_ctx.http.get(hge_ctx.hge_url + '/v1alpha1/config', headers=headers) + + assert resp.status_code == 400, resp def test_config_api(self, hge_ctx): admin_secret = hge_ctx.hge_key @@ -11,14 +25,14 @@ class TestConfigAPI(): if jwt_conf is not None: jwt_conf_dict = json.loads(hge_ctx.hge_jwt_conf) - headers = {} + headers = { 'x-hasura-role': 'admin' } if admin_secret is not None: headers['x-hasura-admin-secret'] = admin_secret resp = hge_ctx.http.get(hge_ctx.hge_url + '/v1alpha1/config', headers=headers) - + assert resp.status_code == 200, resp - + body = resp.json() # The tree may be dirty because we're developing tests locally while # graphql-engine was built previously when tree was clean. If we're diff --git a/server/tests-py/test_dev_endpoints.py b/server/tests-py/test_dev_endpoints.py new file mode 100644 index 00000000000..de9a3543064 --- /dev/null +++ b/server/tests-py/test_dev_endpoints.py @@ -0,0 +1,63 @@ +import requests +import pytest +from context import PytestConf + +""" + +NOTE: + These endpoints are admin-only + + "dev/ekg" + "dev/plan_cache" + "dev/subscriptions" + "dev/subscriptions/extended" + + This needs RTS to be enabled and mainly used for benchmarking: + (hence not adding any tests for this) + "dev/rts_stats" - has no "admin" role requirements + +""" + +def get_headers(hge_ctx, role='admin'): + headers = {} + if hge_ctx.hge_key != None: + headers['x-hasura-admin-secret'] = hge_ctx.hge_key + headers['x-hasura-role'] = role + return headers + +developer_api_enabled = PytestConf.config.getoption("--test-developer-api-enabled") +@pytest.mark.skipif(not developer_api_enabled, + reason="flag --test-developer-api-enabled is not set. Cannot run tests for metadata disabled") +class TestDevEndpoints: + + def test_ekg_endpoint_admin_role(self, hge_ctx): + resp = requests.get(hge_ctx.hge_url + '/dev/ekg', headers=get_headers(hge_ctx)) + assert resp.status_code == 200 + + def test_ekg_endpoint_user_role(self, hge_ctx): + resp = requests.get(hge_ctx.hge_url + '/dev/ekg', headers=get_headers(hge_ctx, 'user')) + assert resp.status_code == 400 + + def test_plan_cache_endpoint_admin_role(self, hge_ctx): + resp = requests.get(hge_ctx.hge_url + '/dev/plan_cache', headers=get_headers(hge_ctx)) + assert resp.status_code == 200 + + def test_plan_cache_endpoint_user_role(self, hge_ctx): + resp = requests.get(hge_ctx.hge_url + '/dev/plan_cache', headers=get_headers(hge_ctx, 'user')) + assert resp.status_code == 400 + + def test_subscriptions_endpoint_admin_role(self, hge_ctx): + resp = requests.get(hge_ctx.hge_url + '/dev/subscriptions', headers=get_headers(hge_ctx)) + assert resp.status_code == 200 + + def test_subscriptions_endpoint_user_role(self, hge_ctx): + resp = requests.get(hge_ctx.hge_url + '/dev/subscriptions', headers=get_headers(hge_ctx, 'user')) + assert resp.status_code == 400 + + def test_subscriptions_extended_endpoint_admin_role(self, hge_ctx): + resp = requests.get(hge_ctx.hge_url + '/dev/subscriptions/extended', headers=get_headers(hge_ctx)) + assert resp.status_code == 200 + + def test_subscriptions_extended_endpoint_user_role(self, hge_ctx): + resp = requests.get(hge_ctx.hge_url + '/dev/subscriptions/extended', headers=get_headers(hge_ctx, 'user')) + assert resp.status_code == 400 diff --git a/server/tests-py/test_endpoints.py b/server/tests-py/test_endpoints.py index df867fb9eab..8475756ef89 100644 --- a/server/tests-py/test_endpoints.py +++ b/server/tests-py/test_endpoints.py @@ -20,6 +20,9 @@ class TestCustomEndpoints: def test_missing_endpoint(self, hge_ctx, transport): check_query_f(hge_ctx, self.dir() + '/endpoint_missing.yaml', transport) + def test_endpoint_as_user_err(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/endpoint_as_user_err.yaml', transport) + def test_simple_endpoint(self, hge_ctx, transport): check_query_f(hge_ctx, self.dir() + '/endpoint_simple.yaml', transport) diff --git a/server/tests-py/test_graphql_queries.py b/server/tests-py/test_graphql_queries.py index 7a8500e673b..375bd52d85f 100644 --- a/server/tests-py/test_graphql_queries.py +++ b/server/tests-py/test_graphql_queries.py @@ -138,6 +138,9 @@ class TestGraphQLQueryBasicCommon: def test_select_query_author(self, hge_ctx, transport): check_query_f(hge_ctx, self.dir() + '/select_query_author.yaml', transport) + def test_select_query_author(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/select_query_author_v1alpha1.yaml', transport) + def test_select_query_author_quoted_col(self, hge_ctx, transport): check_query_f(hge_ctx, self.dir() + '/select_query_author_col_quoted.yaml', transport) @@ -544,6 +547,9 @@ class TestGraphqlQueryPermissions: def test_anonymous_only_published_articles(self, hge_ctx, transport): check_query_f(hge_ctx, self.dir() + '/anonymous_can_only_get_published_articles.yaml', transport) + def test_anonymous_only_published_articles_v1alpha1(self, hge_ctx, transport): + check_query_f(hge_ctx, self.dir() + '/anonymous_can_only_get_published_articles_v1alpha1.yaml', transport) + def test_user_cannot_access_remarks_col(self, hge_ctx, transport): check_query_f(hge_ctx, self.dir() + '/user_cannot_access_remarks_col.yaml', transport) @@ -1008,20 +1014,40 @@ class TestGraphQLExplain: def dir(cls): return 'queries/explain' + def test_simple_query_as_admin(self, hge_ctx, backend): + q = {"query": {"query": "query abc { __typename }", "operationName": "abc"}} + st_code, resp = hge_ctx.v1GraphqlExplain(q) + assert st_code == 200, resp + + def test_simple_query_as_user(self, hge_ctx, backend): + q = {"query": {"query": "query abc { __typename }", "operationName": "abc"}} + st_code, resp = hge_ctx.v1GraphqlExplain(q, {"x-hasura-role": "random_user"}) + assert st_code == 400, resp + + def test_simple_query_as_admin_with_user_role(self, hge_ctx, backend): + self.with_admin_secret(hge_ctx, self.dir() + hge_ctx.backend_suffix('/permissions_query') + ".yaml") + def test_simple_query(self, hge_ctx, backend): self.with_admin_secret(hge_ctx, self.dir() + hge_ctx.backend_suffix('/simple_query') + ".yaml") def test_permissions_query(self, hge_ctx, backend): self.with_admin_secret(hge_ctx, self.dir() + hge_ctx.backend_suffix('/permissions_query') + ".yaml") - def with_admin_secret(self, hge_ctx, f): + def with_admin_secret(self, hge_ctx, f, hdrs=None, req_st=200): conf = get_conf_f(f) admin_secret = hge_ctx.hge_key headers = {} - if admin_secret: - headers['X-Hasura-Admin-Secret'] = hge_ctx.hge_key + if hdrs != None: + headers = hdrs + elif admin_secret and hdrs == None: + headers['X-Hasura-Admin-Secret'] = admin_secret status_code, resp_json, _ = hge_ctx.anyq(conf['url'], conf['query'], headers) - assert status_code == 200, resp_json + assert status_code == req_st, resp_json + + if req_st != 200: + # return early in case we're testing for failures + return + # Comparing only with generated 'sql' since the 'plan' may differ resp_sql = resp_json[0]['sql'] exp_sql = conf['response'][0]['sql'] diff --git a/server/tests-py/test_metadata.py b/server/tests-py/test_metadata.py index f88450b6a33..47afe975143 100644 --- a/server/tests-py/test_metadata.py +++ b/server/tests-py/test_metadata.py @@ -25,6 +25,9 @@ class TestMetadata: def test_clear_metadata(self, hge_ctx): check_query_f(hge_ctx, self.dir() + '/clear_metadata.yaml') + def test_clear_metadata_as_user(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/metadata_as_user_err.yaml') + def test_replace_metadata(self, hge_ctx): check_query_f(hge_ctx, self.dir() + '/replace_metadata.yaml') diff --git a/server/tests-py/test_pg_dump.py b/server/tests-py/test_pg_dump.py index 70818edae35..bf2bf844002 100644 --- a/server/tests-py/test_pg_dump.py +++ b/server/tests-py/test_pg_dump.py @@ -20,7 +20,7 @@ class TestPGDump: PG_VERSION = os.getenv('PG_VERSION', 'latest') with open(query_file, 'r') as stream: q = yaml.safe_load(stream) - headers = {} + headers = q['headers'] or {} if hge_ctx.hge_key is not None: headers['x-hasura-admin-secret'] = hge_ctx.hge_key resp = hge_ctx.http.post(hge_ctx.hge_url + q['url'], json=q['query'], headers=headers) @@ -30,6 +30,18 @@ class TestPGDump: print(q[resp_pg_version_map[PG_VERSION]]) assert body == q[resp_pg_version_map[PG_VERSION]] + def test_pg_dump_for_public_schema_for_user_role(self, hge_ctx): + query_file = self.dir() + '/pg_dump_public.yaml' + with open(query_file, 'r') as stream: + q = yaml.safe_load(stream) + headers = q['headers'] or {} + if hge_ctx.hge_key is not None: + headers['x-hasura-admin-secret'] = hge_ctx.hge_key + headers['X-Hasura-Role'] = 'user' + resp = hge_ctx.http.post(hge_ctx.hge_url + q['url'], json=q['query'], headers=headers) + body = resp.text + assert resp.status_code == 400, body + @classmethod def dir(cls): return "pgdump" diff --git a/server/tests-py/test_v1_queries.py b/server/tests-py/test_v1_queries.py index 2e0053c2cf2..010edf2b36c 100644 --- a/server/tests-py/test_v1_queries.py +++ b/server/tests-py/test_v1_queries.py @@ -34,9 +34,16 @@ class TestV1General: @usefixtures('per_class_tests_db_state') class TestV1SelectBasic: - def test_select_query_author(self, hge_ctx): + def test_select_query_author_with_admin_role(self, hge_ctx): check_query_f(hge_ctx, self.dir() + '/select_article.yaml') + # TODO: fix these tests for JWT tests + # def test_select_query_author_with_user_role_success(self, hge_ctx): + # check_query_f(hge_ctx, self.dir() + '/select_article_role_success.yaml') + + # def test_select_query_author_with_user_role_failure(self, hge_ctx): + # check_query_f(hge_ctx, self.dir() + '/select_article_role_error.yaml') + def test_nested_select_article_author(self, hge_ctx): check_query_f(hge_ctx, self.dir() + '/nested_select_query_article_author.yaml') @@ -498,6 +505,11 @@ class TestRunSQL: check_query_f(hge_ctx, self.dir() + '/sql_select_query.yaml') hge_ctx.may_skip_test_teardown = True + # TODO: create a v2 query tests module + def test_select_query_v2(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/sql_select_query_v2.yaml') + hge_ctx.may_skip_test_teardown = True + def test_select_query_read_only(self, hge_ctx): check_query_f(hge_ctx, self.dir() + '/sql_select_query_read_only.yaml') hge_ctx.may_skip_test_teardown = True @@ -512,6 +524,10 @@ class TestRunSQL: def test_sql_query_as_user_error(self, hge_ctx): check_query_f(hge_ctx, self.dir() + '/sql_query_as_user_error.yaml') + # TODO: create a v2 query tests module + def test_sql_query_as_user_error_v2(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/sql_query_as_user_error_v2.yaml') + def test_sql_rename_table(self, hge_ctx): check_query_f(hge_ctx, self.dir() + '/sql_rename_table.yaml') diff --git a/server/tests-py/test_v2_queries.py b/server/tests-py/test_v2_queries.py new file mode 100644 index 00000000000..5e9caedca4a --- /dev/null +++ b/server/tests-py/test_v2_queries.py @@ -0,0 +1,25 @@ +from validate import check_query_f +import pytest + +usefixtures = pytest.mark.usefixtures + +# use_mutation_fixtures = usefixtures( +# 'per_class_db_schema_for_mutation_tests', +# 'per_method_db_data_for_mutation_tests' +# ) + +@usefixtures('per_class_tests_db_state') +class TestV2SelectBasic: # Basic RQL Tests on v2/query + @classmethod + def dir(cls): + return 'queries/v2/basic' + + def test_select_query_author(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/select_article.yaml') + + def test_select_query_author_with_user_role_success(self, hge_ctx): + check_query_f(hge_ctx, self.dir() + '/select_article_role_success.yaml') + + # TODO: Fix this test for JWT + # def test_select_query_author_with_user_role_failure(self, hge_ctx): + # check_query_f(hge_ctx, self.dir() + '/select_article_role_error.yaml')