这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/graphql/manual/remote-schemas/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ started is to use one of our boilerplates:

- `Boilerplates <https://github.com/hasura/graphql-engine/tree/master/community/boilerplates/remote-schemas>`__

.. _merge_remote_schema:

Step 2: Merge remote schema
^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down Expand Up @@ -141,6 +143,33 @@ community tooling to write your own client-facing GraphQL gateway that interacts
it out of the box** (*by as much as 4x*). If you need any help with remodeling these kind of use cases to use the
built-in remote schemas feature, please get in touch with us on `Discord <https://discord.gg/vBPpJkS>`__.


Authorization in your remote schema server
------------------------------------------

Hasura will forward the resolved ``x-hasura-*`` values as headers to your remote
schema. You can use this information to apply authorization rules in your
server. You don't have to redo authentication in your remote schema server.

You can also configure Hasura to have (as shown :ref:`above <merge_remote_schema>`):

1. static header values that are sent to the remote server
2. forward all headers from the client (like ``Authorization``, ``Cookie`` headers etc.)

In case there are multiple headers with same name, the order of precedence is:
configuration headers > resolved user (``x-hasura-*``) variables > client headers

So for example, if client sends an ``Authorization`` header, and the
configuration also has ``Authorization`` header, the configuration header value
will selected.

.. note::

The headers from client behave similar to the authorization system. If
``x-hasura-admin-secret`` is sent, then all ``x-hasura-*`` values from the
client are respected, otherwise they are ignored.


Bypassing Hasura's authorization system for remote schema queries
-----------------------------------------------------------------

Expand Down
19 changes: 16 additions & 3 deletions server/src-lib/Hasura/GraphQL/Execute.hs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import Hasura.HTTP
import Hasura.Prelude
import Hasura.RQL.DDL.Headers
import Hasura.RQL.Types
import Hasura.Server.Utils (bsToTxt)

import qualified Hasura.GraphQL.Execute.LiveQuery as EL
import qualified Hasura.GraphQL.Execute.Plan as EP
Expand Down Expand Up @@ -322,7 +323,14 @@ execRemoteGQ manager userInfo reqHdrs q rsi opDef = do
hdrs <- getHeadersFromConf hdrConf
let confHdrs = map (\(k, v) -> (CI.mk $ CS.cs k, CS.cs v)) hdrs
clientHdrs = bool [] filteredHeaders fwdClientHdrs
options = wreqOptions manager (userInfoToHdrs ++ clientHdrs ++ confHdrs)
-- filter out duplicate headers
-- priority: conf headers > resolved userinfo vars > client headers
hdrMaps = [ Map.fromList confHdrs
, Map.fromList userInfoToHdrs
, Map.fromList clientHdrs
]
finalHdrs = foldr Map.union Map.empty hdrMaps
options = wreqOptions manager (Map.toList finalHdrs)

res <- liftIO $ try $ Wreq.postWith options (show url) q
resp <- either httpThrow return res
Expand All @@ -334,10 +342,15 @@ execRemoteGQ manager userInfo reqHdrs q rsi opDef = do
httpThrow err = throw500 $ T.pack . show $ err

userInfoToHdrs = map (\(k, v) -> (CI.mk $ CS.cs k, CS.cs v)) $
userInfoToList userInfo
filteredHeaders = flip filter reqHdrs $ \(n, _) ->
userInfoToList userInfo
filteredHeaders = filterUserVars $ flip filter reqHdrs $ \(n, _) ->
n `notElem` [ "Content-Length", "Content-MD5", "User-Agent", "Host"
, "Origin", "Referer" , "Accept", "Accept-Encoding"
, "Accept-Language", "Accept-Datetime"
, "Cache-Control", "Connection", "DNT"
]

filterUserVars hdrs =
let txHdrs = map (\(n, v) -> (bsToTxt $ CI.original n, bsToTxt v)) hdrs
in map (\(k, v) -> (CI.mk $ CS.cs k, CS.cs v)) $
filter (not . isUserVar . fst) txHdrs
31 changes: 30 additions & 1 deletion server/tests-py/graphql_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -578,6 +578,34 @@ def post(self, req):
return Response(HTTPStatus.OK, respDict,
{'Content-Type': 'application/json'})


class HeaderTest(graphene.ObjectType):
wassup = graphene.String(arg=graphene.String(default_value='world'))

def resolve_wassup(self, info, arg):
headers = info.context
print('recvd headers: ', headers)
if not (headers.get_all('x-hasura-test') == ['abcd'] and
headers.get_all('x-hasura-role') == ['user'] and
headers.get_all('x-hasura-user-id') == ['abcd1234'] and
headers.get_all('Authorization') == ['Bearer abcdef']):
raise Exception('headers dont match')

return "Hello " + arg

header_test_schema = graphene.Schema(query=HeaderTest)

class HeaderTestGraphQL(RequestHandler):
def get(self, request):
return Response(HTTPStatus.METHOD_NOT_ALLOWED)

def post(self, request):
if not request.json:
return Response(HTTPStatus.BAD_REQUEST)
res = header_test_schema.execute(request.json['query'],
context=request.headers)
return mkJSONResp(res)

handlers = MkHandlers({
'/hello': HelloWorldHandler,
'/hello-graphql': HelloGraphQL,
Expand All @@ -597,7 +625,8 @@ def post(self, req):
'/union-graphql-err-no-member-types' : UnionGraphQLSchemaErrNoMemberTypes,
'/union-graphql-err-wrapped-type' : UnionGraphQLSchemaErrWrappedType,
'/default-value-echo-graphql' : EchoGraphQL,
'/person-graphql': PersonGraphQL
'/person-graphql': PersonGraphQL,
'/header-graphql': HeaderTestGraphQL
})


Expand Down
40 changes: 38 additions & 2 deletions server/tests-py/test_schema_stitching.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@
from validate import check_query_f, check_query


def mk_add_remote_q(name, url):
def mk_add_remote_q(name, url, headers=None, client_hdrs=False):
return {
"type": "add_remote_schema",
"args": {
"name": name,
"comment": "testing " + name,
"definition": {
"url": url,
"forward_client_headers": False
"headers": headers,
"forward_client_headers": client_hdrs
}
}
}
Expand Down Expand Up @@ -236,6 +237,41 @@ def test_add_schema_same_type_containing_same_scalar(self, hge_ctx):
hge_ctx.v1q({"type": "remove_remote_schema", "args": {"name": "person-graphql"}})
assert st_code == 200, resp

def test_remote_schema_forward_headers(self, hge_ctx):
"""
test headers from client and conf and resolved info gets passed
correctly to remote schema, and no duplicates are sent. this test just
tests if the remote schema returns success or not. checking of header
duplicate logic is in the remote schema server
"""
conf_hdrs = [{'name': 'x-hasura-test', 'value': 'abcd'}]
add_remote = mk_add_remote_q('header-graphql',
'http://localhost:5000/header-graphql',
headers=conf_hdrs, client_hdrs=True)
st_code, resp = hge_ctx.v1q(add_remote)
assert st_code == 200, resp
q = {'query': '{ wassup }'}
hdrs = {
'x-hasura-test': 'xyzz',
'x-hasura-role': 'user',
'x-hasura-user-id': 'abcd1234',
'Authorization': 'Bearer abcdef'
}
if hge_ctx.hge_key:
hdrs['x-hasura-admin-secret'] = hge_ctx.hge_key

resp = hge_ctx.http.post(hge_ctx.hge_url+'/v1alpha1/graphql', json=q,
headers=hdrs)
print(resp.status_code, resp.json())
assert resp.status_code == 200
res = resp.json()
assert 'data' in res
assert res['data']['wassup'] == 'Hello world'

hge_ctx.v1q({'type': 'remove_remote_schema',
'args': {'name': 'header-graphql'}})
assert st_code == 200, resp


class TestRemoteSchemaQueriesOverWebsocket:
dir = 'queries/remote_schemas'
Expand Down