Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(graphql): disable introspection endpoint on production #2098

Closed
wants to merge 4 commits into from
Closed
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
30 changes: 30 additions & 0 deletions caluma/caluma_core/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,36 @@ def _should_include_filter(filt):
return filter_coll()


def InterfaceMetaFactory():
"""
Build a meta class suitable for the schema type classes that represent
"interface" types (those that have concrete subclasses, but could be mixed
in a connection type, such as Question, Answer, and Task).

Usage:

>>> class Foo(Node, graphene.Interface):
>>> ...
>>> Meta = InterfaceMetaFactory()
"""

class _meta(graphene.types.interface.InterfaceOptions):
@classmethod
# This is kinda useless but required as graphene tries to freeze()
# it's meta class objects
def freeze(cls):
cls._frozen = True

# This is what we're actually "fixing": On non-Interface types,
# this somehow works (or isn't needed), but here, if _meta.registry
# is not set, the whole schema construction fails
registry = get_global_registry()

# We need a new type (= class) each time it's called, because reuse
# triggers some weird errors
return type("Meta", (), {"_meta": _meta})


def CollectionFilterSetFactory(filterset_class, orderset_class=None):
"""
Build single-filter filterset classes.
Expand Down
5 changes: 5 additions & 0 deletions caluma/caluma_form/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
CollectionFilterSetFactory,
DjangoFilterConnectionField,
DjangoFilterInterfaceConnectionField,
InterfaceMetaFactory,
)
from ..caluma_core.mutation import Mutation, UserDefinedPrimaryKeyMixin
from ..caluma_core.relay import extract_global_id
Expand Down Expand Up @@ -159,6 +160,8 @@ def get_queryset(cls, queryset, info):
def resolve_type(cls, instance, info):
return resolve_question(instance)

Meta = InterfaceMetaFactory()


class Option(FormDjangoObjectType):
meta = generic.GenericScalar()
Expand Down Expand Up @@ -810,6 +813,8 @@ class Answer(Node, graphene.Interface):
def resolve_type(cls, instance, info):
return resolve_answer(instance)

Meta = InterfaceMetaFactory()


class AnswerQuerysetMixin(object):
"""Mixin to combine all different answer types into one queryset."""
Expand Down
4 changes: 4 additions & 0 deletions caluma/caluma_user/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from django.http.response import HttpResponse
from django.utils.encoding import force_bytes, smart_str
from django.utils.module_loading import import_string
from graphene.validation import DisableIntrospection
from graphene_django.views import GraphQLView, HttpError
from rest_framework.authentication import get_authorization_header

Expand All @@ -19,6 +20,9 @@ class HttpResponseUnauthorized(HttpResponse):


class AuthenticationGraphQLView(GraphQLView):
if settings.DISABLE_INTROSPECTION: # pragma: no cover
validation_rules = (DisableIntrospection,)

def get_bearer_token(self, request):
auth = get_authorization_header(request).split()
header_prefix = "Bearer"
Expand Down
3 changes: 3 additions & 0 deletions caluma/caluma_workflow/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
CollectionFilterSetFactory,
DjangoFilterConnectionField,
DjangoFilterInterfaceConnectionField,
InterfaceMetaFactory,
)
from ..caluma_core.mutation import Mutation, UserDefinedPrimaryKeyMixin
from ..caluma_core.types import (
Expand Down Expand Up @@ -97,6 +98,8 @@ def resolve_type(cls, instance, info):

return TASK_TYPE[instance.type]

Meta = InterfaceMetaFactory()


class TaskConnection(CountableConnectionBase):
class Meta:
Expand Down
1 change: 1 addition & 0 deletions caluma/settings/caluma.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ def default(default_dev=env.NOTSET, default_prod=env.NOTSET):
"MIDDLEWARE": [],
"RELAY_CONNECTION_MAX_LIMIT": None,
}
DISABLE_INTROSPECTION = env.bool("DISABLE_INTROSPECTION", default=default(False, True))

# OpenID connect

Expand Down
15 changes: 15 additions & 0 deletions caluma/tests/__snapshots__/test_schema.ambr
Original file line number Diff line number Diff line change
Expand Up @@ -831,6 +831,21 @@
type DjangoDebug {
"""Executed SQL queries for this API query."""
sql: [DjangoDebugSQL]

"""Raise exceptions for this API query."""
exceptions: [DjangoDebugException]
}

"""Represents a single exception raised."""
type DjangoDebugException {
"""The class of the exception"""
excType: String!

"""The message of the exception"""
message: String!

"""The stack trace"""
stack: String!
}

"""Represents a single database query made to a Django managed DB."""
Expand Down
23 changes: 12 additions & 11 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ django-postgres-extra = "^2.0.4"
django-watchman = "^1.2.0"
djangorestframework = "^3.13.1"
django-simple-history = "^3.0.0"
graphene-django = "3.0.0b7"
graphene-django = "3.2.0"
graphql-relay = "^3.1.5"
idna = "^3.3"
minio = "^7.1.4"
Expand Down Expand Up @@ -101,6 +101,7 @@ filterwarnings = [
"error::DeprecationWarning",
"error::PendingDeprecationWarning",
"ignore:The 'arrayconnection' module is deprecated:DeprecationWarning", # deprecation in graphene
"ignore:'cgi' is deprecated:DeprecationWarning",
"ignore:distutils Version classes are deprecated:DeprecationWarning", # deprecation in pytest-freezegun
"ignore:'django_extensions' defines default_app_config:PendingDeprecationWarning", # deprecation in django_extensions
"ignore::requests.packages.urllib3.exceptions.InsecureRequestWarning", # MinIO tests do "insecure" requests - that's ok
Expand Down
Loading