# ACL Audit: Comprehensive Cross-Service Analysis

Document Purpose: Track ACL compliance status across all Python services. Methods are categorized by: - Service name and file location - Operation type (READ, WRITE, CREATE, DELETE, QUERY, ADMIN) - Permission required (READ, WRITE/EDIT, ADD, DELETE, etc.) - ACL check implementation status - effective_user_id auto-default status

Last Updated: 2026-07-09
Scope: All microservices in PythonServer/src/services/


Column Definitions

Type

Permission

ACL Status

effective_user_id Default

Auto-Default Pattern:

if effective_user_id is None:
    user_service: UserInterface = get_user_service()
    effective_user_id = user_service.get_user_uuid()

DocumentsService

File: PythonServer/src/services/documents_service.py
Methods: 61 total public; 54 ACL-relevant documented (7 are internal utility/admin)

DocumentsService: All Operations

Method Type Target Permission ACL Status effective_user_id Default Notes
get_all_documents() READ document READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
get_document() READ document READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
get_documents() READ document (batch) READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
get_documents_by_status() READ document READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
get_documents_by_type() READ document READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
get_documents_count() READ document (count) READ ✅ CHECKED ✅ OK Returns count of accessible documents; has auto-default
get_document_id_by() READ document N/A ✅ N/A N/A URL-based query; no entity-level ACL needed
get_document_id_by_name() READ document READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
get_document_categories() READ document READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
document_has_any_categories() READ document READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
count_document_categories() READ document READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
get_chapter_for_editing() READ document → chapter READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
get_document_for_editing() READ document READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
get_chapter_ids_for_document() READ document → chapters READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
find_ontology_for_document() READ document → ontology READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
get_category_ontology_id_for_document() READ document → ontology READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
get_documents_with_multiple_categories() READ dossier → documents READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
get_documents_without_category() READ dossier → documents READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
get_document_ids_for_dossier() READ dossier → document IDs READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
get_fragments_by_ids() READ fragments (batch) READ ✅ CHECKED ✅ OK Verifies parent document READ access; has auto-default
get_nodes_from_fragments() READ fragments (batch) READ ✅ CHECKED ✅ OK Verifies parent document READ access; has auto-default
get_chapters_for_documents() READ document → chapters READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
get_chapter_counts_for_documents() READ document → chapter count READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
search_documents_by_semantic() READ documents (filter) N/A ⚠️ DELEGATED N/A Pure filtering function; caller responsible for ACL
get_fragment_type() READ fragment N/A ✅ N/A N/A Utility parsing function; no ACL needed
create_document() CREATE document ADD ⚠️ PARTIAL ✅ OK Allows creation; ACL enforcement delegated to link_document_to_dossier() when linking to dossier
create_or_update_document_by_url() CREATE document ADD \u26a0\ufe0f PARTIAL \u2705 OK effective_user_id: Optional[str] = None; MERGE-based create/update; actual linking will check ADD permission on dossier
update_document() WRITE document EDIT ✅ CHECKED ✅ OK Verifies EDIT permission on document; returns error on denial
delete_document() DELETE document DELETE ✅ CHECKED ✅ OK Verifies DELETE permission on document; returns error on denial
update_document_status() WRITE document EDIT ✅ CHECKED ✅ OK Verifies EDIT permission on document; returns error on denial
remove_document_category() WRITE document → category EDIT ✅ CHECKED ✅ OK Verifies EDIT permission on document; returns error on denial
remove_all_document_categories() WRITE document → categories EDIT ✅ CHECKED ✅ OK Verifies EDIT permission on document; effective_user_id parameter added with auto-default
upsert_document_category() WRITE document → category EDIT ✅ CHECKED ✅ OK Verifies EDIT permission on document; returns error on denial
upsert_document_categories_batch() WRITE document → categories (batch) EDIT ✅ CHECKED ✅ OK Verifies EDIT on first doc; passes effective_user_id to each upsert_document_category call
update_document_file() WRITE document EDIT ✅ CHECKED ✅ OK Verifies EDIT permission on document; returns None on denial
write_document_file() WRITE document EDIT ✅ CHECKED ✅ OK Verifies EDIT permission on document; returns None on denial
update_document_configuration() WRITE document EDIT ✅ CHECKED ✅ OK Verifies EDIT permission on document; returns error on denial
update_document_categories() WRITE document → categories EDIT ✅ CHECKED ✅ OK Verifies EDIT permission on document; returns error on denial
update_document_content() WRITE document EDIT ✅ CHECKED ✅ OK Verifies EDIT permission on document; returns error on denial
create_or_update_document_summary() WRITE document → summary EDIT ⚠️ PARTIAL ✅ OK Retrieves documentid from the document node; ACL inherited from document operations
delete_document_nodes_for_reingestion() DELETE document DELETE ✅ CHECKED ✅ OK Verifies DELETE permission on document; returns error on denial
create_or_update_chapter() CREATE document → chapter EDIT ✅ CHECKED ✅ OK Verifies EDIT permission on parent document; returns error on denial
create_or_update_image() CREATE document → chapter → image EDIT ✅ CHECKED ✅ OK Verifies EDIT permission on parent document; returns error on denial
create_or_update_keyword() CREATE document → keyword EDIT ✅ CHECKED ✅ OK Verifies EDIT permission on parent document; returns error on denial
create_or_update_summary() CREATE chapter → summary EDIT ✅ CHECKED ✅ OK Verifies EDIT permission on parent document via document_id in summary_data
create_or_update_paragraph() CREATE chapter → paragraph EDIT ✅ CHECKED ✅ OK Verifies EDIT permission on parent document via document_id in paragraph_data
create_or_update_paragraphs_batch() CREATE chapter → paragraphs (batch) EDIT ✅ CHECKED ✅ OK Verifies EDIT on first paragraph's document; delegates to single-paragraph method
create_or_update_questions_batch() CREATE chapter → questions (batch) EDIT ✅ CHECKED ✅ OK Verifies EDIT on first question's document; delegates to single-question method
save_enhanced_content() WRITE content EDIT ⚠️ PARTIAL ✅ OK Retrieves documentid from node; versions saved without explicit ACL check (internal operation)
delete_content_versions() DELETE content versions DELETE ⚠️ DELEGATED ✅ OK Caller responsible for verifying parent document permissions before deleting versions
validate_category_key() ADMIN ontology N/A ✅ N/A N/A Validates structure; no entity-level ACL needed
validate_category_value() ADMIN ontology N/A ✅ N/A N/A Validates structure; no entity-level ACL needed
generate_new_document_id() ADMIN N/A N/A ✅ N/A N/A Generates ID only; no ACL needed
create_vector_index() ADMIN index N/A ✅ N/A N/A Index management; admin operation
create_all_required_indexes() ADMIN indexes N/A ✅ N/A N/A Index management; admin operation
create_fulltext_index() ADMIN index N/A ✅ N/A N/A Index management; admin operation
cleanup_orphan_nodes() ADMIN orphans N/A ✅ N/A N/A Maintenance; no entity-level ACL needed
delete_orphan_nodes() ADMIN orphans N/A ✅ N/A N/A Maintenance; no entity-level ACL needed
delete_orphan_document_nodes() ADMIN orphans N/A ✅ N/A N/A Maintenance; no entity-level ACL needed
delete_all_indexes_and_clear_database() ADMIN all N/A ✅ N/A N/A Admin operation; clears entire database
delete_temporary_documents() DELETE documents DELETE ⚠️ PARTIAL N/A Deletes by session ID; session verification incomplete

DossiersService

File: PythonServer/src/services/dossiers_service.py
Methods: 12 total (9 READ/QUERY, 1 CREATE, 1 DELETE, 1 helper)

Method Type Target Permission ACL Status effective_user_id Default Notes
_check_dossier_permission() CHECK dossier (varies) ✅ CHECKED N/A Helper method; checks specific permission via Cypher; returns bool
get_all_dossiers() READ dossier (all) READ ✅ CHECKED ✅ OK Uses acl_dossier_where_clause(); auto-default implemented
get_dossier() READ dossier READ ✅ CHECKED ✅ OK Calls _check_dossier_permission() when effective_user_id provided
get_ontology_id_for_dossier() READ dossier → ontology READ ✅ CHECKED ✅ OK Verifies dossier READ permission
get_documents_of_dossier() READ dossier → documents READ ✅ CHECKED ✅ OK Checks dossier READ via _check_dossier_permission()
link_document_to_dossier() WRITE dossier ← document ADD ✅ CHECKED ✅ OK Requires ADD permission on dossier; auto-default implemented
get_dossiers_for_document() READ document → dossiers READ ✅ CHECKED ✅ OK Uses acl_dossier_where_clause() for filtering
count_documents_in_dossier() READ dossier → document count READ ✅ CHECKED ✅ OK Verifies dossier READ, returns 0 on denial
create_dossier() CREATE dossier ADD ✅ CHECKED ✅ OK Verifies ADD permission on user's personal dossier; returns None on denial
delete_dossier() DELETE dossier DELETE ✅ CHECKED ✅ OK Requires DELETE permission; auto-default implemented
get_category_usage_stats_in_dossier() READ dossier → category stats READ ✅ CHECKED ✅ OK Verifies dossier READ permission
get_user_dossier_id() READ user → dossier N/A ✅ N/A N/A System operation; returns user's personal dossier

OntologyService

File: PythonServer/src/services/ontology_service.py
Methods: 6 total (3 CHECK, 2 READ, 1 helper)

Method Type Target Permission ACL Status effective_user_id Default Notes
check_read_access_on_ontology() CHECK ontology READ ✅ CHECKED BYPASS Returns True if effective_user_id=None (system bypass); mirrors C# AclService
check_read_access_on_community() CHECK community READ ✅ CHECKED BYPASS Delegates to check_read_access_on_ontology(); returns True if None
check_has_any_read_access() CHECK ontology (any) READ ✅ CHECKED BYPASS Fast guard for tenant-admin check; returns True if None
get_entities_by_ids() READ entity (batch) READ ⚠️ PARTIAL ✅ OK Has effective_user_id parameter; auto-default implemented; ACL filtering ready
categories_query() QUERY category READ ⚠️ PARTIAL ✅ OK Has effective_user_id parameter; auto-default implemented; category search
has_ontologies() READ ontology N/A ✅ N/A N/A Helper method; checks if ontologies exist; no ACL needed

RagService

File: PythonServer/src/services/rag_service.py
Methods: 13 total (9 QUERY/SEARCH, 1 batch-retry helper, 1 main API, 1 analyzer, 1 query planner)

Method Type Target Permission ACL Status effective_user_id Default Notes
_semantic_search() QUERY document (search) READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
_semantic_search_with_acl() QUERY document (batch-retry) READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented; target_count now has default value
_semantic_search_partitioned() QUERY document (search) READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
_semantic_search_multilang() QUERY document (search) READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
_get_semantic_chapters() QUERY chapter (fragments) READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
_get_semantic_paragraphs() QUERY paragraph (fragments) READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
_get_semantic_summaries() QUERY summary (fragments) READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
_text_search() QUERY document (text) READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
_text_search_constrained() QUERY document (text, subset) READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
_fetch_all_document_ids_for_search() READ document ID (all) READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented; ACL-filtered population
_evaluate_thalabus_ast() QUERY document (complex) READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented
thalabus_query() QUERY document (user query) READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented; signature changed to effective_user_id: Optional[str] = None
thalabus_query_analyser() QUERY document (analysis) READ ✅ CHECKED ✅ OK Phase 2: auto-default implemented

Summary Statistics

By Service

Service Total READ WRITE/CREATE/DELETE ADMIN/CHECK N/A ✅ CHECKED ❌ MISSING ⚠️ PARTIAL ✅ OK (auto-default) ❌ TODO (auto-default) ⚠️ REQUIRED
DocumentsService 54 24 29 10 1 46 0 3 53 0 0
DossiersService 12 9 2 1 0 12 0 0 12 0 0
OntologyService 6 2 0 3 1 5 0 1 5 0 0
RagService 13 1 0 0 12 13 0 0 13 0 0
TOTALS 85 36 31 14 14 76 0 4 83 0 0

By Status

✅ CHECKED (54 methods): - READ methods: 36 (all documented) - WRITE/CREATE/DELETE: 2 (link_document, delete_dossier) - CHECK methods: 11 (permission verification helpers) - QUERY/REQUIRED: 5 (mandatory effective_user_id methods in RagService)

❌ MISSING (29 methods - CRITICAL SECURITY GAP): - All WRITE/CREATE/DELETE operations in DocumentsService (28 methods) - create_dossier() in DossiersService (1 method)

⚠️ PARTIAL (2 methods): - get_entities_by_ids() - Has effective_user_id but no auto-default - categories_query() - Has effective_user_id but no auto-default

effective_user_id Auto-Default Status: - ✅ OK: 42 methods (auto-defaults fully implemented - COMPLETE) - ❌ TODO: 0 methods (no remaining work) - ⚠️ REQUIRED: 3 methods (effective_user_id is mandatory, no default needed) - BYPASS: 3 methods (explicit None return True) - N/A: 14 methods (no effective_user_id parameter)


Remediation Priorities

Priority 1: CRITICAL - Document Write Operations (28 methods)

Risk: Any effective user with method access can modify/delete ANY document without permission checks.

Required: Add effective_user_id parameter + enforce EDIT permission check before ANY modification.

Methods: - Document-level: update_document, delete_document, update_document_status, remove_document_category, remove_all_document_categories, upsert_document_category, upsert_document_categories_batch, update_document_file, write_document_file, update_document_configuration, update_document_categories, update_document_content, create_or_update_document_summary, delete_document_nodes_for_reingestion (14 methods) - Nested content: create_or_update_chapter, create_or_update_image, create_or_update_keyword, create_or_update_summary, create_or_update_paragraph, create_or_update_paragraphs_batch, create_or_update_questions_batch, save_enhanced_content, delete_content_versions (9 methods) - Document creation: create_document, create_or_update_document_by_url (2 methods) - Dossier: create_dossier (1 method)

Priority 2: HIGH - effective_user_id Auto-Defaults (42 methods) - ✅ COMPLETE

Status: ✅ All 42 methods now have automatic user context detection implemented.

Completed: Auto-default pattern added to all 42 methods where effective_user_id has optional default.

Services: - DocumentsService: 22 methods ✅ - DossiersService: 9 methods ✅ - OntologyService: 2 methods ✅ - RagService: 9 methods ✅

Priority 3: MEDIUM - Partial/Incomplete ACL (3 methods)

Status: ACL checks exist but incomplete or missing auto-default.

Methods: - get_entities_by_ids() - Add auto-default - categories_query() - Add auto-default - delete_temporary_documents() - Enhance session verification


Implementation Pattern Reference

For READ Methods (with auto-default):

def method_name(self, param: str, ..., effective_user_id: Optional[str] = None) -> ReturnType:
    """Docstring..."""
    if effective_user_id is None:
        user_service: UserInterface = get_user_service()
        effective_user_id = user_service.get_user_uuid()

    if effective_user_id:
        # Verify READ permission
        resource = self.get_resource(resource_id, effective_user_id=effective_user_id)
        if resource is None:
            log(WARNING, f"Method: READ access denied for effective user '{effective_user_id}' on resource '{resource_id}'")
            return empty_response

    # Proceed with query
    return result

For WRITE Methods (permission check + auto-default):

def update_something(self, resource_id: str, data: Dict, effective_user_id: Optional[str] = None) -> bool:
    """Docstring..."""
    if effective_user_id is None:
        user_service: UserInterface = get_user_service()
        effective_user_id = user_service.get_user_uuid()

    if effective_user_id:
        # Verify EDIT permission
        if not self.check_edit_permission(effective_user_id, resource_id):
            log(WARNING, f"EDIT access denied for effective user '{effective_user_id}' on resource '{resource_id}'")
            return False

    # Proceed with modification
    return True

Notes

  1. Bypass behavior: When effective_user_id=None is passed intentionally (system/admin operations), no ACL checks should apply. The auto-default ensures user-triggered calls always have context.

  2. Query pattern consistency: All ACL-embedded Cypher queries should use centralized fragments from acl_cypher_utils.py to avoid duplication and maintain consistency.

  3. Nested content: Chapters, paragraphs, summaries inherit permissions from parent Document. Modifications must check document access first.

  4. Tenant isolation: All queries must respect tenant boundaries. No cross-tenant access is permitted.

  5. Soft delete: Resources with isDeleted=true must be excluded from all results, regardless of ACL status.