Create GraphQL service permission archtecture#838
Conversation
* Adopt a permissive base QuerySet for textual GraphQL searches to rely on Elasticsearch's version filtering logic. * Apply the same Elasticsearch filters as REST (source_version=HEAD and is_latest_version=True). * Use strict 'id = versioned_object_id' filtering only when listing without search terms. * Remove SQL fallback logic to ensure consistent ES-only behavior across REST and GraphQL search flows.
* Add reusable GraphQL permission helpers, mixin, and decorator to gate repository access and reuse existing REST visibility filters. * Update query helpers and tests to use the mixin, cache source-resolution lookups, and ensure ES and global searches respect visibility and repository permissions.
* Add shared concept search helpers (exact, wildcard, fuzzy criteria, mandatory word filters, and rescore) along with document visibility utilities so REST and GraphQL reuse the same logic. * Update BaseAPIView and the GraphQL query pipeline to call the shared helpers, including the common visibility filter, so concept text search behavior and permissions remain aligned. * Adjust GraphQL query helper tests to accommodate the new search flow and ensure the anonymous visibility filter continues to run.
* Add shared GraphQL error metadata and raise stable coded errors across permissions, queries, views, and schema logging * Align global search and mapping helpers with REST visibility rules for consistent access control * Guard Elasticsearch outages and invalid authentication paths to prevent unhandled failures * Expand tests to cover structured error responses and failure scenarios
| f"{must_not_have}*", | ||
| include_map_codes=include_map_codes, | ||
| ) | ||
| criterion = criteria if criterion is None else criterion | criteria |
There was a problem hiding this comment.
get_concept_mandatory_exclude_words_criteria uses | where the old code used &
criterion = criteria if criterion is None else criterion | criteria # new (|)
vs original in views.py:
criterion = criteria if criterion is None else criterion & criteria # old (&)
The downstream callers do search.filter(~must_not_criterion), so:
| → excludes docs matching any excluded word (stricter, likely correct semantically)
& → excludes docs matching all excluded words simultaneously (the old REST behavior)
This silently changes REST concept search too, since views.py:get_mandatory_exclude_words_criteria now delegates here for concept documents.
| mapping_prefetch, | ||
| owner=owner_value, | ||
| owner_type=owner_type, | ||
| version_label=version or HEAD if source_version else None, |
There was a problem hiding this comment.
to make it less ambiguous
(version or HEAD) if source_version else None
| if source_version: | ||
| search = search.filter('term', source=source_version.mnemonic.lower()) | ||
| if source_version.is_head: | ||
| search = search.filter('term', source=source_version.mnemonic) |
There was a problem hiding this comment.
.lower() is required since in ES
source = fields.KeywordField(attr='parent_resource', normalizer="lowercase")
|
|
||
| async def ensure_can_view_repo(user, source_version) -> None: | ||
| """Raise a GraphQL forbidden error when the repository is not visible to the user.""" | ||
| request = SimpleNamespace(user=user) |
There was a problem hiding this comment.
This fakes a DRF request object to reuse a REST permission class in a GraphQL context.
It works today but it's fragile — if CanViewConceptDictionary ever accesses anything else on the request, it silently breaks. The cleaner alternative is a thin shared function that takes (user, source_version) directly.
|
|
||
| return context | ||
|
|
||
| async def execute_operation(self, request, context, root_value, sub_response): |
There was a problem hiding this comment.
from Claude:
Overriding execute_operation on the view to return an ExecutionResult directly is not a documented Strawberry extension point — it's reaching into internals. The documented hook for this is process_errors on the schema (which the PR already uses in OCLGraphQLSchema). The auth short-circuit could instead be done via a middleware or the schema's execute hook, or simply by relying on the decorator check (which already runs before any resolver logic).
Linked Issue
Closes OpenConceptLab/ocl_issues#2356