Source code for scim2_tester.checkers.schemas

import uuid

from scim2_client import SCIMClientError
from scim2_models import Error
from scim2_models import Schema

from ..utils import CheckContext
from ..utils import CheckResult
from ..utils import Status
from ..utils import check_result
from ..utils import checker
from ._discovery_utils import _test_discovery_endpoint_methods


def _schemas_endpoint(context: CheckContext) -> list[CheckResult]:
    """Orchestrate validation of the Schemas discovery endpoint.

    Runs comprehensive tests on the ``/Schemas`` endpoint including listing
    all schemas, querying individual schemas by ID, and error handling for
    invalid schema requests.

    **Status:**

    - :attr:`~scim2_tester.Status.SUCCESS`: All sub-checks completed successfully
    - :attr:`~scim2_tester.Status.ERROR`: One or more sub-checks failed

    .. pull-quote:: :rfc:`RFC 7644 Section 4 - Discovery <7644#section-4>`

       "An HTTP GET to this endpoint is used to retrieve information about
       resource schemas supported by a SCIM service provider."

       "Service providers MUST provide this endpoint."

    """
    schemas_result_list = query_all_schemas(context)
    results = []
    results.extend(schemas_result_list)
    schemas_result = schemas_result_list[0]  # Get the first (and only) result

    if schemas_result.status == Status.SUCCESS:
        results.extend(access_schema_by_id(context))

    results.extend(access_invalid_schema(context))

    return results


[docs] @checker("*", "discovery", "schemas") def query_all_schemas(context: CheckContext) -> list[CheckResult]: """Validate retrieval of all available schemas. Tests that the ``/Schemas`` endpoint returns a complete list of all supported schemas including core schemas, extensions, and custom schemas. **Status:** - :attr:`~scim2_tester.Status.SUCCESS`: Endpoint returns valid list of :class:`~scim2_models.Schema` objects - :attr:`~scim2_tester.Status.ERROR`: Endpoint is inaccessible or returns invalid response .. pull-quote:: :rfc:`RFC 7644 Section 4 - Discovery <7644#section-4>` "An HTTP GET to this endpoint is used to retrieve information about resource schemas supported by a SCIM service provider." """ response = context.client.query( Schema, expected_status_codes=context.conf.expected_status_codes or [200] ) available = ", ".join([f"'{resource.name}'" for resource in response.resources]) return [ check_result( context, status=Status.SUCCESS, reason=f"Schemas available are: {available}", data=response.resources, ) ]
[docs] @checker("discovery", "schemas") def access_invalid_schema(context: CheckContext) -> list[CheckResult]: """Validate error handling for non-existent schema IDs. Tests that accessing ``/Schemas/{invalid_id}`` with a non-existent schema ID returns appropriate :class:`~scim2_models.Error` object with 404 status. **Status:** - :attr:`~scim2_tester.Status.SUCCESS`: Server returns :class:`~scim2_models.Error` object with 404 status - :attr:`~scim2_tester.Status.ERROR`: Server returns non-:class:`~scim2_models.Error` object or incorrect status .. pull-quote:: :rfc:`RFC 7644 Section 3.12 - Error Response Handling <7644#section-3.12>` "When returning HTTP error status codes, the server SHOULD return a SCIM error response." """ probably_invalid_id = str(uuid.uuid4()) response = context.client.query( Schema, probably_invalid_id, expected_status_codes=context.conf.expected_status_codes or [404], raise_scim_errors=False, ) if not isinstance(response, Error): return [ check_result( context, status=Status.ERROR, reason=f"/Schemas/{probably_invalid_id} invalid URL did not return an Error object", data=response, ) ] if response.status != 404: return [ check_result( context, status=Status.ERROR, reason=f"/Schemas/{probably_invalid_id} invalid URL did return an Error object, but the status code is {response.status}", data=response, ) ] return [ check_result( context, status=Status.SUCCESS, reason=f"/Schemas/{probably_invalid_id} invalid URL correctly returned a 404 error", data=response, ) ]
[docs] @checker("discovery", "schemas") def schemas_endpoint_methods( context: CheckContext, ) -> list[CheckResult]: """Validate that unsupported HTTP methods return 405 Method Not Allowed. Tests that POST, PUT, PATCH, and DELETE methods on the ``/Schemas`` endpoint correctly return HTTP 405 Method Not Allowed status, as only GET is supported. **Status:** - :attr:`~scim2_tester.Status.SUCCESS`: All unsupported methods return 405 status - :attr:`~scim2_tester.Status.ERROR`: One or more methods return unexpected status .. pull-quote:: :rfc:`RFC 7644 Section 4 - Discovery <7644#section-4>` "An HTTP GET to this endpoint is used to retrieve information about resource schemas supported by a SCIM service provider." Only GET method is specified, other methods should return appropriate errors. """ return _test_discovery_endpoint_methods(context, "/Schemas")
[docs] @checker("discovery", "schemas") def access_schema_by_id( context: CheckContext, ) -> list[CheckResult]: """Validate individual schema retrieval by ID. Tests that all schemas can be retrieved using GET requests to ``/Schemas/{id}`` with their complete attribute definitions and metadata. **Status:** - :attr:`~scim2_tester.Status.SUCCESS`: All schemas retrieved successfully with valid data - :attr:`~scim2_tester.Status.ERROR`: One or more schemas failed to retrieve .. pull-quote:: :rfc:`RFC 7644 Section 7 - Schema Definition <7644#section-7>` "Each schema specifies the name of the resource, the resource's base URI, and any attributes (including sub-attributes) of the resource." """ schemas_response = context.client.query( Schema, expected_status_codes=context.conf.expected_status_codes or [200] ) results = [] for schema in schemas_response.resources: if schema.id: try: response = context.client.query( Schema, schema.id, expected_status_codes=context.conf.expected_status_codes or [200], ) results.append( check_result( context, status=Status.SUCCESS, reason=f"Successfully accessed schema: {schema.id}", data=response, ) ) except SCIMClientError as e: results.append( check_result( context, status=Status.ERROR, reason=f"Failed to access schema {schema.id}: {str(e)}", ) ) if not results: results.append( check_result( context, status=Status.SUCCESS, reason="No schemas with IDs found to test", ) ) return results
[docs] @checker("discovery", "schemas") def core_schemas_validation( context: CheckContext, ) -> list[CheckResult]: """Validate that mandatory core schemas are provided. Tests that the ``/Schemas`` endpoint provides the three mandatory core schemas: ResourceType, ServiceProviderConfig, and Schema schemas themselves. **Status:** - :attr:`~scim2_tester.Status.SUCCESS`: All mandatory core schemas are present - :attr:`~scim2_tester.Status.ERROR`: One or more mandatory schemas are missing .. pull-quote:: :rfc:`RFC 7644 Section 4 - Discovery <7644#section-4>` "Service providers MUST provide this endpoint." The core schemas for ResourceType, ServiceProviderConfig, and Schema objects are fundamental to SCIM operation and should always be available. """ response = context.client.query( Schema, expected_status_codes=context.conf.expected_status_codes or [200] ) required_schemas = { "ResourceType": False, "ServiceProviderConfig": False, "Schema": False, } for schema in response.resources: schema_name = getattr(schema, "name", "") if schema_name in required_schemas: required_schemas[schema_name] = True missing_schemas = [name for name, found in required_schemas.items() if not found] if missing_schemas: return [ check_result( context, status=Status.ERROR, reason=f"Missing mandatory core schemas: {', '.join(missing_schemas)}", data=response, ) ] return [ check_result( context, status=Status.SUCCESS, reason="All mandatory core schemas (ResourceType, ServiceProviderConfig, Schema) are present", data=response, ) ]