def execute_graphql_request(self, request, data, query, variables, operation_name): """Execute a GraphQL request and return the result Args: request (HttpRequest): Request object from Django data (dict): Parsed content of the body of the request. query (dict): GraphQL query variables (dict): Optional variables for the GraphQL query operation_name (str): GraphQL operation name: query, mutations etc.. Returns: ExecutionResult: Execution result object from GraphQL with response or error message. """ if not query: raise HttpError( HttpResponseBadRequest("Must provide query string.")) try: backend = self.get_backend(request) document = backend.document_from_string(self.graphql_schema, query) except Exception as e: return ExecutionResult(errors=[e], invalid=True) operation_type = document.get_operation_type(operation_name) if operation_type and operation_type != "query": raise HttpError( HttpResponseBadRequest( f"'{operation_type}' is not a supported operation, Only query are supported." )) try: extra_options = {} if self.executor: # We only include it optionally since # executor is not a valid argument in all backends extra_options["executor"] = self.executor options = { "root_value": self.get_root_value(request), "variable_values": variables, "operation_name": operation_name, "context_value": self.get_context(request), "middleware": self.get_middleware(request), } options.update(extra_options) operation_type = document.get_operation_type(operation_name) return document.execute(**options) except Exception as e: return ExecutionResult(errors=[e], invalid=True)
def execute_graphql_request( self, request, data, query, variables, operation_name, ): if not query: raise HttpError( HttpResponseBadRequest("Must provide query string.")) with opentracing.global_tracer().start_active_span( "graphql_query") as scope: span = scope.span span.set_tag(opentracing.tags.COMPONENT, "GraphQL") try: document = parse(query) except GraphQLError as e: return ExecutionResult(errors=[e], data=dict(invalid=True)) if request.method.lower() == "get": operation_ast = get_operation_ast(document, operation_name) if operation_ast and operation_ast.operation != OperationType.QUERY: raise HttpError( HttpResponseNotAllowed( ["POST"], "Can only perform a {} operation from a POST request." .format(operation_ast.operation.value), )) validation_errors = validate(self.schema.graphql_schema, document) if validation_errors: return ExecutionResult(data=None, errors=validation_errors) try: with connection.execute_wrapper(tracing_wrapper): return self.schema.execute( source=query, root_value=self.get_root_value(request), variable_values=variables, operation_name=operation_name, context_value=self.get_context(request), middleware=self.get_middleware(request), ) except GraphQLError as e: span.set_tag(opentracing.tags.ERROR, True) return ExecutionResult(errors=[e])
def dispatch(self, request, *args, **kwargs): data = self.parse_body(request) show_graphiql = self.graphiql and self.can_display_graphiql( request, data) if self.batch: responses = [self.get_response(request, entry) for entry in data] result = "[{}]".format(",".join( [response[0] for response in responses])) status_code = (responses and max( responses, key=lambda response: response[1])[1] or 200) else: result, status_code = self.get_response(request, data, show_graphiql) _res = json.loads(result) if result else None if _res and 'errors' in _res and _res['errors'] and len( _res['errors']) > 0: _error = _res['errors'][0] _code = 400 if _error['code'] == 'error' else int(_error['code']) e = HttpError( HttpResponse(status=_code, content_type='application/json'), _error['message']) response = e.response # response.content = self.json_encode(request, {'errors': [self.format_error(e)]}) response.content = self.json_encode(request, _res) return response if result and status_code: return HttpResponse(status=status_code, content=result, content_type="application/json") else: return super().dispatch(request, *args, **kwargs)
def get_user(self, request): token = self.get_bearer_token(request) if token is None: return models.AnonymousUser() if not settings.OIDC_USERINFO_ENDPOINT: raise ImproperlyConfigured( 'Token was provided, but "OIDC_USERINFO_ENDPOINT" is not set.') userinfo_method = functools.partial(self.get_userinfo, token=token) # token might be too long for key so we use hash sum instead. hashsum_token = hashlib.sha256(force_bytes(token)).hexdigest() try: userinfo = cache.get_or_set( f"authentication.userinfo.{hashsum_token}", userinfo_method, timeout=settings.OIDC_BEARER_TOKEN_REVALIDATION_TIME, ) return models.OIDCUser(token=token, userinfo=userinfo) except requests.HTTPError as e: try: if (e.response.status_code in [401, 403] and settings.OIDC_INTROSPECT_ENDPOINT): introspect_method = functools.partial( self.get_introspection, token=token) introspection = cache.get_or_set( f"authentication.introspect.{hashsum_token}", introspect_method, timeout=settings.OIDC_BEARER_TOKEN_REVALIDATION_TIME, ) if "client_id" not in introspection: response = HttpResponse(status=401) raise HttpError(response) return models.OIDCUser(token=token, introspection=introspection) else: raise e except requests.HTTPError as internal_exception: # convert request error to django http error response = HttpResponse() response.status_code = internal_exception.response.status_code raise HttpError(response, message=str(internal_exception))
def get_bearer_token(self, request): auth = get_authorization_header(request).split() header_prefix = "Bearer" if not auth: return None if smart_text(auth[0].lower()) != header_prefix.lower(): raise HttpError( HttpResponseUnauthorized("No Bearer Authorization header")) if len(auth) == 1: msg = "Invalid Authorization header. No credentials provided" raise HttpError(HttpResponseUnauthorized(msg)) elif len(auth) > 2: msg = ("Invalid Authorization header. Credentials string should " "not contain spaces.") raise HttpError(HttpResponseUnauthorized(msg)) return auth[1]
def dispatch(self, request, *args, **kwargs): if request.method.lower() not in ('get', 'post', 'options'): raise HttpError( HttpResponseNotAllowed( ['GET', 'POST', 'OPTIONS'], 'GraphQL only supports GET, POST and OPTIONS requests.')) if request.method.lower() == 'options': return HttpResponse(status=200, content='', content_type='application/json') return super().dispatch(request, *args, **kwargs)
def dispatch(self, request, *args, **kwargs): request_type = request.META.get("CONTENT_TYPE", '') # import pdb; pdb.set_trace() # if request.method == 'POST': # import pdb; pdb.set_trace() # NOTE: need to overwrite inherited dispatch func and handle explicitly if multipart since batch=True incorrectly treats multipart as a batch request if "multipart/form-data" in request_type: try: if request.method.lower() not in ("get", "post"): raise HttpError( HttpResponseNotAllowed( ["GET", "POST"], "GraphQL only supports GET and POST requests." ) ) data = self.parse_body(request) show_graphiql = self.graphiql and self.can_display_graphiql(request, data) result, status_code = self.get_response(request, data, show_graphiql) if show_graphiql: query, variables, operation_name, id = self.get_graphql_params( request, data ) return self.render_graphiql( request, graphiql_version=self.graphiql_version, query=query or "", variables=json.dumps(variables) or "", operation_name=operation_name or "", result=result or "", ) return HttpResponse( status=status_code, content=result, content_type="application/json" ) except HttpError as e: response = e.response response["Content-Type"] = "application/json" response.content = self.json_encode( request, {"errors": [self.format_error(e)]} ) return response else: return super(MyGraphQLView, MyGraphQLView).dispatch(self, request, *args, **kwargs)
def execute_graphql_request(self, request, data, query, variables, operation_name, show_graphiql=False): if isinstance(data, JsonResponse): if data.status_code == 403: raise HttpError(HttpResponseForbidden(data.content)) return super(AuthenticatedGraphQLView, self).execute_graphql_request(request, data, query, variables, operation_name, show_graphiql)
def dispatch(self, request, *args, **kwargs): """Dispatch method to allow OPTIONS requests. This fixes Apollo's graphql client hanging on OPTIONS requests. """ if request.method.lower() not in ("get", "post", "options"): raise HttpError( HttpResponseNotAllowed( ["GET", "POST", "OPTIONS"], "GraphQL only supports GET, POST and OPTIONS requests.", ) ) if request.method.lower() == "options": return HttpResponse(status=200, content="", content_type="application/json") return super().dispatch(request, *args, **kwargs)
def parse_body(self, request): """Analyze the request and based on the content type, extract the query from the body as a string or as a JSON payload. Args: request (HttpRequest): Request object from Django Returns: dict: GraphQL query """ content_type = GraphQLView.get_content_type(request) if content_type == "application/graphql": return {"query": request.body.decode()} elif content_type == "application/json": try: return request.data except ParseError: raise HttpError(HttpResponseBadRequest("Request body contained Invalid JSON.")) return {}
def execute_graphql_request(self, request, data, query, variables, operation_name, show_graphiql=False): """ Largely a copy of GraphQLView execute_graphql_request function, the check has to happen in the middle of its processing so whole function needs to be replicated here """ if not query: if show_graphiql: return None raise HttpError( HttpResponseBadRequest("Must provide query string.")) try: backend = self.get_backend(request) document = backend.document_from_string(self.schema, query) except Exception as e: return ExecutionResult(errors=[e], invalid=True) if request.method.lower() == "get": operation_type = document.get_operation_type(operation_name) if operation_type and operation_type != "query": if show_graphiql: return None raise HttpError( HttpResponseNotAllowed( ["POST"], "Can only perform a {} operation from a POST request.". format(operation_type), )) # Check request weight try: if self.list_limit or self.weight_limit or self.depth_limit: if document: fragments = get_fragments( document.document_ast.definitions) definitions_total_weight = 0 for definition in document.document_ast.definitions: if not isinstance(definition, OperationDefinition): continue if operation_name and definition.name != operation_name: continue def_weight = self.calculate_action_weight( definition.selection_set, fragments) definitions_total_weight += def_weight if self.weight_limit and definitions_total_weight > self.weight_limit: raise QueryWeightExceeded( "Your query exceeds the maximum query weight allowed" ) except Exception as e: return ExecutionResult(errors=[e], invalid=True) try: extra_options = {} if self.executor: # We only include it optionally since # executor is not a valid argument in all backends extra_options["executor"] = self.executor return document.execute(root_value=self.get_root_value(request), variable_values=variables, operation_name=operation_name, context_value=self.get_context(request), middleware=self.get_middleware(request), **extra_options) except Exception as e: return ExecutionResult(errors=[e], invalid=True)
def execute_graphql_request(self, request, data, query, variables, operation_name, show_graphiql=False): if not query: if show_graphiql: return None raise HttpError( HttpResponseBadRequest("Must provide query string.")) try: backend = self.get_backend(request) with tracer.trace(op="backend.document_from_string"): document = backend.document_from_string(self.schema, query) except Exception as e: return ExecutionResult(errors=[e], invalid=True) if request.method.lower() == "get": operation_type = document.get_operation_type(operation_name) if operation_type and operation_type != "query": if show_graphiql: return None raise HttpError( HttpResponseNotAllowed( ["POST"], "Can only perform a {} operation from a POST request.". format(operation_type), )) try: extra_options = {} if self.executor: # We only include it optionally since # executor is not a valid argument in all backends extra_options["executor"] = self.executor options = { "root_value": self.get_root_value(request), "variable_values": variables, "operation_name": operation_name, "context_value": self.get_context(request), "middleware": self.get_middleware(request), } options.update(extra_options) operation_type = document.get_operation_type(operation_name) if operation_type == "mutation" and ( graphene_settings.ATOMIC_MUTATIONS is True or connection.settings_dict.get("ATOMIC_MUTATIONS", False) is True): with transaction.atomic(): result = document.execute(**options) if getattr(request, MUTATION_ERRORS_FLAG, False) is True: transaction.set_rollback(True) return result with tracer.trace(op="document.execute"): return document.execute(**options) except Exception as e: return ExecutionResult(errors=[e], invalid=True)