{"record":{"id":"65469bdc980254be","repo":"infiniflow/ragflow","slug":"invalid-moodle-api-response","errorCode":null,"errorMessage":"Invalid Moodle API response","messagePattern":"Invalid Moodle API response","errorType":"validation","errorClass":"InsufficientPermissionsError","httpStatus":null,"severity":"error","filePath":"common/data_source/moodle_connector.py","lineNumber":90,"sourceCode":"        if not token:\n            raise ConnectorMissingCredentialError(\"Moodle API token is required\")\n\n        try:\n            self.moodle_client = MoodleClient(self.moodle_url + \"/webservice/rest/server.php\", token)\n            self.moodle_client.core.webservice.get_site_info()\n        except MoodleException as e:\n            if \"invalidtoken\" in str(e).lower():\n                raise CredentialExpiredError(\"Moodle token is invalid or expired\")\n            raise ConnectorMissingCredentialError(f\"Failed to initialize Moodle client: {e}\")\n\n    def validate_connector_settings(self) -> None:\n        if not self.moodle_client:\n            raise ConnectorMissingCredentialError(\"Moodle client not initialized\")\n\n        try:\n            site_info = self.moodle_client.core.webservice.get_site_info()\n            if not site_info.sitename:\n                raise InsufficientPermissionsError(\"Invalid Moodle API response\")\n        except MoodleException as e:\n            msg = str(e).lower()\n            if \"invalidtoken\" in msg:\n                raise CredentialExpiredError(\"Moodle token is invalid or expired\")\n            if \"accessexception\" in msg:\n                raise InsufficientPermissionsError(\"Insufficient permissions. Ensure web services are enabled and permissions are correct.\")\n            raise ConnectorValidationError(f\"Moodle validation error: {e}\")\n        except Exception as e:\n            raise ConnectorValidationError(f\"Unexpected validation error: {e}\")\n\n    # -------------------------------------------------------------------------\n    # Data loading & polling\n    # -------------------------------------------------------------------------\n\n    def load_from_state(self) -> Generator[list[Document], None, None]:\n        if not self.moodle_client:\n            raise ConnectorMissingCredentialError(\"Moodle client not initialized\")\n","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/infiniflow/ragflow/blob/554fb1133ac3861732235ad9c377eb5e0a770665/common/data_source/moodle_connector.py#L72-L108","documentation":"An InsufficientPermissionsError raised in validate_connector_settings when the get_site_info() call succeeds but the response has an empty sitename. The connector treats a site-info response without a sitename as structurally invalid — a well-formed Moodle answer always includes sitename, so its absence signals a gateway/SSO interception or a broken web-service response rather than a permission scope issue per se.","triggerScenarios":"get_site_info() returns 200 with an object whose sitename is empty/None — e.g. a proxy returning an empty shell response, a non-Moodle endpoint answering at moodle_url, or a Moodle plugin/version returning a degenerate payload.","commonSituations":"moodle_url pointing at the wrong path or a reverse proxy login page that happens to parse; Moodle version with web services partially disabled returning an empty info object; SAML/SSO fronting that rewrites responses.","solutions":["Verify moodle_url is the Moodle root (e.g. https://moodle.org) and that /webservice/rest/server.php is reachable.","Call the web service manually: curl '<moodle_url>/webservice/rest/server.php?wstoken=TOKEN&wsfunction=core_webservice_get_site_info&moodlewsrestformat=json' and inspect the JSON for sitename.","If the raw response lacks sitename, fix the Moodle web-services configuration or the proxy in front of it."],"exampleFix":"# before\nconnector = MoodleConnector(moodle_url='https://org.example/sso')  # proxy answers, sitename empty\nconnector.load_credentials(creds)\nconnector.validate_connector_settings()  # InsufficientPermissionsError('Invalid Moodle API response')\n\n# after\nconnector = MoodleConnector(moodle_url='https://moodle.org')  # real Moodle root\nconnector.load_credentials(creds)\nconnector.validate_connector_settings()","handlingStrategy":"validation","validationCode":"import requests\n\ndef moodle_site_info_ok(moodle_url: str, token: str) -> bool:\n    r = requests.get(\n        f\"{moodle_url}/webservice/rest/server.php\",\n        params={\"wstoken\": token, \"wsfunction\": \"core_webservice_get_site_info\", \"moodlewsrestformat\": \"json\"},\n        timeout=10,\n    )\n    data = r.json()\n    return bool(data.get(\"sitename\"))","typeGuard":null,"tryCatchPattern":"try:\n    connector.validate_connector_settings()\nexcept InsufficientPermissionsError as exc:\n    if \"Invalid Moodle API response\" in str(exc):\n        check_proxy_and_url(connector.moodle_url)  # structural response problem\n    else:\n        fix_web_service_permissions()","preventionTips":["Point moodle_url at the Moodle root; ensure no SSO/proxy rewrites webservice responses.","Pre-flight get_site_info with plain HTTP and assert sitename is non-empty."],"tags":["moodle","api-response","validation","proxy","configuration"],"backgroundTag":null,"analyzedSha":"554fb1133ac3861732235ad9c377eb5e0a770665","analyzedAt":"2026-08-15T09:20:16.380Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}