Package: graphene-protector
What does this project solve?It provides protection against malicious grapqhl requests (resource exhaustion).
Despite its name it can be used with graphql (pure), graphene, strawberry.
It is implemented via a custom ValidationRule,
supports error reporting and early bail out strategies as well as limits for single fieldsInstallationpipinstallgraphene-protectorIntegrationDjangoThis adds to django the following setting:GRAPHENE_PROTECTOR_DEPTH_LIMIT: max depthGRAPHENE_PROTECTOR_SELECTIONS_LIMIT: max selectionsGRAPHENE_PROTECTOR_COMPLEXITY_LIMIT: max (depth * selections)GRAPHENE_PROTECTOR_PATH_INGORE_PATTERN: ignore fields in calculation (but still traverse them)Integrate with:graphene:# schema.py# replace normal Schema import with:fromgraphene_protector.django.grapheneimportSchemaschema=Schema(query=Query,mutation=Mutation)and add in django settings to GRAPHENEGRAPHENE={..."SCHEMA":"path.to.schema",}or strawberry:# schema.py# replace normal Schema import with:fromgraphene_protector.django.strawberryimportSchemaschema=Schema(query=Query,mutation=Mutation)manual way (note: import from django for including defaults from settings)fromgraphene_protector.django.grapheneimportSchema# or# from graphene_protector.django.strawberry import Schemaschema=Schema(query=Query)result=schema.execute(query_string)manual way with custom default Limitsfromgraphene_protectorimportLimitsfromgraphene_protector.django.grapheneimportSchema# or# from graphene_protector.django.strawberry import Schemaschema=graphene.Schema(query=Query,limits=Limits(complexity=None))result=schema.execute(query_string)Graphene & Strawberrylimits keyword with Limits object is supported.fromgraphene_protectorimportLimitsfromgraphene_protector.grapheneimportSchema# or# from graphene_protector.strawberry import Schemaschema=Schema(query=Query,limits=Limits(depth=20,selections=None,complexity=100))result=schema.execute(query_string)pure graphqlfromgraphene_protectorimportLimitsValidationRulefromgraphql.type.schemaimportSchemaschema=Schema(query=Query,)query_ast=parse("{ hello }")self.assertFalse(validate(schema,query_ast,[LimitsValidationRule]))or with custom defaultsfromgraphene_protectorimportLimits,LimitsValidationRulefromgraphql.type.schemaimportSchemaclassCustomLimitsValidationRule(LimitsValidationRule):default_limits=Limits(depth=20,selections=None,complexity=100)schema=Schema(query=Query,)query_ast=parse("{ hello }")self.assertFalse(validate(schema,query_ast,[LimitsValidationRule]))strawberry extension variantfromgraphene_protectorimportLimitsfromgraphene_protector.strawberryimportCustomGrapheneProtectorfromstrawberryimportSchemaschema=Schema(query=Query,extensions=[CustomGrapheneProtector(Limits(depth=20,selections=None,complexity=100))])result=schema.execute(query_string)or with custom defaults via Mixinfromgraphene_protectorimportLimits,SchemaMixin,LimitsValidationRulefromgraphql.type.schemaimportSchemaclassCustomSchema(SchemaMixin,Schema):protector_default_limits=Limits(depth=20,selections=None,complexity=100)schema=CustomSchema(query=Query,)query_ast=parse("{ hello }")self.assertFalse(validate(schema,query_ast,[LimitsValidationRule]))strawberry variant with mixin (uses protector_per_operation_validation in contrast to the official graphene-protector strawberry schema)fromgraphene_protectorimportLimits,SchemaMixin,default_path_ignore_patternfromstrawberryimportSchemaclassCustomSchema(SchemaMixin,Schema):protector_default_limits=Limits(depth=20,selections=None,complexity=100)protector_path_ignore_pattern=default_path_ignore_patternschema=CustomSchema(query=Query)result=schema.execute(query_string)Note: for the mixin method all variables are prefixed in schema withprotector_. Internally theget_protector_methods are used and mapped on the validation context. The extracted functions can be customized via theprotector_decorate_graphql_schemamethod.LimitsA Limits object has following attributes:depth: max depth (default: 20, None disables feature)selections: max selections (default: None, None disables feature)complexity: max (depth subtree * selections subtree) (default: 100, None disables feature)they overwrite django settings if specified.decorating single fieldsSometimes single fields should have different limits:person1=Limits(depth=10)(graphene.Field(Person))Limits are inherited for unspecified parametersone-time disable limit checksto disable checks for one operation use check_limits=False (works for:
execute, execute_async (if available), subscribe (if available)):fromgraphene_protectorimportLimitsfromgraphene_protector.grapheneimportSchemaschema=Schema(query=Query,limits=Limits(depth=20,selections=None,complexity=100))result=schema.execute(query_string,check_limits=False)Usefull for debugging or working around errorsPath ignoringThis is a feature for ignoring some path parts in calculation but still traversing them.
It is useful for e.g. relay which inflates the depth significant and can cause problems with complexity
Currently it is set toedges/node$which reduces the depth of connections by one.
If you want to ignore all children on a path then remove $ but be warned: it can be a long path and it is still traversed.
The path the regex matches agains is composed like this:fieldname/subfields/....Other examples are:node$|id$for ignoring id fields in selection/complexity count and reducing the depth by 1 when seeing a node fieldpage_info|pageInfofor ignoring page info in calculation (Note: you need only one, in case auto_snakecase=True onlypageInfo)Note: items prefixed with__(internal names) are always ignored and not traversed.Note: if auto_snakecase is True, the path components are by default camel cased (overwritable via explicitcamelcase_path)full validationOn the validation rule the validation is stopped by default when an error is found
This default can be overwritten and it is modified for the django code pathes.
Whenever DEBUG is active a full validation happens, otherwise the shortcut is used.
See the source-code how to change your schema to have a custom hook for deciding if a full validation is done.
In addition thepath_ignore_patternandlimitsattributes can be also changed dynamically.hooksThe validation rule uses someprotector_prefixed methods from the schema.
With this you can customize the default behaviour.
It is used by the django mixin to read the settings (see django) and to react on DEBUG with full_validationDevelopmentI am open for new ideas.
If you want some new or better algorithms integrated just make a PRrelated projects:secure-graphene: lacks django integration, some features and has a not so easy findable name.
But I accept: it is the "not invented here"-syndromeTODOstop when an open path regex is used. May append an invalid char and check if it is still ignoringkeep an eye on the performance impact of the new path regex checkingadd tests for auto_snakecase and camelcase_pathskip tests in case settings are not matching