Repository: streamlit/streamlit
File: lib/streamlit/web/server/authlib_tornado_integration.py (Python)

Code before:
# limitations under the License.

from __future__ import annotations

import json
from typing import TYPE_CHECKING, Any

from authlib.integrations.base_client import (
    FrameworkIntegration,
)

from streamlit.runtime.secrets import AttrDict

if TYPE_CHECKING:
    from collections.abc import Sequence

    from streamlit.web.server.oidc_mixin import TornadoOAuth


class TornadoIntegration(FrameworkIntegration):
    """Authlib framework integration for Tornado.

    Tornado doesn't have persistent sessions like Flask/Django, so we override
    the state data methods to use only the cache. This is compatible with both
    older authlib versions and authlib 1.6.6+ which changed to always track
    state in session.
    """

    def _get_cache_data_safe(self, key: str) -> dict[str, Any] | None:
        """Get and decode JSON data from cache with fallback for authlib compatibility.

        This method provides a safe way to retrieve cached data that works across
        different authlib versions. It first tries the parent class's _get_cache_data
        method, and falls back to direct cache access if that method isn't available
        or if the method signature has changed.
        """
        # Try using authlib's helper method if available (it's a private method,
        # so we check for its existence to maintain compatibility across versions)
        get_cache_data = getattr(self, "_get_cache_data", None)
        if get_cache_data is not None:
            try:
                result: dict[str, Any] | None = get_cache_data(key)
                return result
            except TypeError:
                # Method signature may have changed in a newer authlib version,
                # fall through to the direct cache access fallback below
                pass

        # Fallback: direct cache access with JSON decoding
        # This mirrors the implementation in FrameworkIntegration._get_cache_data
        value = self.cache.get(key)

Review comment (security):
```suggestion
    """Authlib framework integration for Tornado.

    Authlib always tracks state in sessions (post 1.6.6+), but Tornado doesn't have persistent sessions like Flask/Django, so we override
    the state data methods to use only the cache. This is compatible with both
    older authlib versions and authlib 1.6.6+.
    """
```

Code after:
# limitations under the License.

from __future__ import annotations

import json
from typing import TYPE_CHECKING, Any

from authlib.integrations.base_client import (
    FrameworkIntegration,
)

from streamlit.runtime.secrets import AttrDict

if TYPE_CHECKING:
    from collections.abc import Sequence

    from streamlit.web.server.oidc_mixin import TornadoOAuth


class TornadoIntegration(FrameworkIntegration):
    """Authlib framework integration for Tornado.

    Authlib always tracks state in sessions (post 1.6.6+), but Tornado doesn't have persistent
    sessions like Flask/Django, so we override the state data methods to use only the cache.
    This is compatible with both older authlib versions and authlib 1.6.6+.
    """

    def _get_cache_data(self, key: str) -> dict[str, Any] | None:
        """Get and decode JSON data from cache.

        This reimplements the cache data retrieval logic directly rather than
        relying on authlib's private _get_cache_data method, ensuring stability
        across authlib versions.
        """
        value = self.cache.get(key)
        if not value:
            return None
        try:
            result = json.loads(value)
            if isinstance(result, dict):
                return result
            return None
        except (TypeError, ValueError):
            return None

    def get_state_data(
        self,
        session: dict[str, Any],  # noqa: ARG002
        state: str,
    ) -> dict[str, Any] | None:
        """Get OAuth state data from cache.