Skip to content

v0.1.0b5

Choose a tag to compare

@github-actions github-actions released this 07 May 21:40
· 255 commits to current since this release

Added

  • Per-app PKCE override via the new AllianceAuthApplication.pkce_required
    boolean. New applications default to True (RFC 9700 secure-by-default);
    existing rows are backfilled from the previous global value at migration
    time, preserving live behaviour. Unknown client_id falls back to True
    with a logged warning (fail-safe to strict). Configurable via Django
    admin (changelist column, edit-form checkbox, list filter).

Changed

  • OAUTH2_PROVIDER['PKCE_REQUIRED'] now points at a callable
    (per_app_pkce_required) living in the lightweight
    allianceauth_oidc.pkce module. The adapter resolves client_id to
    an application row via .only("pkce_required") and delegates the
    decision to AccessPolicy.requires_pkce(app) in security.py,
    which stays a pure-logic method (no ORM, testable through the
    AppLike Protocol DI seam). Unknown client_id is logged at
    WARNING (with %a for log-injection safety) and falls back to
    True.
  • The PKCE schema/data migration is now split: 0010 adds the column
    (schema-only, reversible), 0011 performs the
    environment-dependent backfill in a separate file. Greenfield
    installs (no pre-existing rows) skip the data step entirely.
  • The data backfill now accepts only an explicit bool value verbatim;
    any other shape (callable, None, missing key, str, int) is
    ambiguous and falls back to pkce_required=True per RFC 9700,
    emitting a RuntimeWarning rather than a raw stderr write.
  • AccessDecision is now a tagged discriminated union
    (AllowedDecision | GlobalDeny | AppDeny) instead of a single
    NamedTuple with three nullable fields. The invariant
    "deny_reason=APPapp is non-None" now lives in the type;
    AuthAuthorizationView.dispatch uses match plus
    typing_extensions.assert_never for exhaustiveness, replacing
    the previous assert decision.app is not None-by-comment.
  • AccessPolicy.pkce_required(app) renamed to
    AccessPolicy.requires_pkce(app) so the policy method does not
    collide with the AppLike.pkce_required attribute it reads. The
    rename is internal API; production callers wire through
    OAUTH2_PROVIDER['PKCE_REQUIRED'] = per_app_pkce_required and
    are unaffected.

⚠️ Configuration change: OAUTH2_PROVIDER['PKCE_REQUIRED'] semantics
changed from a boolean to a callable. Existing operator settings that
use True/False continue to work but no longer match the recommended
configuration shown in the README. Update to import
per_app_pkce_required from allianceauth_oidc.pkce and assign it as
the value to take advantage of per-app overrides. If running migrations
against a long-lived process, call oauth2_settings.reload() to refresh
DOT's cached descriptor; new processes pick up the change automatically.

⚠️ Upgrade ordering: run manage.py migrate before swapping
OAUTH2_PROVIDER['PKCE_REQUIRED'] from the previous boolean to the
per_app_pkce_required callable. The migration's data step reads the
previous global at runtime; the reverse order causes every existing
app to be force-flipped to pkce_required=True (RFC 9700 fail-safe).
The full upgrade recipe is in the Upgrading from a previous release
section of the README.

Build

  • Build backend switched from flit_core to uv_build. version and
    description are now static [project] fields in pyproject.toml
    (single source of truth); runtime __version__ resolves via
    importlib.metadata.version("allianceauth-oidc-provider-eveo7")
    with a 0.0.0+local fallback for editable / source checkouts.
    Wheel contents are bit-equivalent to the previous build (same
    allianceauth_oidc/* + locale catalogues, no test artefacts).

Tooling

  • Internal typing tightened. New TokenLike / OAuthRequestLike
    Protocols in security.py and a local ClaimsUser Protocol in
    auth_provider.py replace the previous object-typed parameters
    on TokenAudit, audit_oidc_token_issued, ClaimsBuilder,
    app_log, and build_oidc_debug_meta. Opaque attribute access on
    these helpers is now statically typo-checked.
  • mypy strict ramp: mypy_django_plugin.main is enabled (django-stubs
    was already in the dev group but never wired in), plus
    check_untyped_defs, warn_unused_ignores, warn_no_return,
    warn_unreachable, strict_equality, extra_checks, and the
    redundant-expr / possibly-undefined / truthy-bool /
    unused-awaitable / explicit-override error codes. Ten
    Django command / AppConfig overrides gained @override decorators
    (via typing_extensions to keep the 3.10 floor) as a result of
    explicit-override.
  • ruff select expanded from 11 groups to 28: added DJ, LOG, G,
    RET, DTZ, ISC, BLE, PTH, TC, TID, A, FURB,
    TRY, PERF, SLF, ICN, PGH, ARG, plus the four pylint
    groups PLE, PLW, PLC, PLR. Carve-outs at framework-contract
    boundaries (signal handlers, view dispatch, oauthlib validator
    overrides, Django migrations) keep the noise focused on real
    signals.
  • New pre-commit hooks: vulture (dead-code detection,
    min_confidence=80 with Django-contract ignore_names),
    xenon (cyclomatic complexity at D/B/A thresholds), and
    uv lock --check (locks-file drift on pyproject.toml /
    uv.lock changes).
  • [tool.coverage] consolidated into pyproject.toml; the legacy
    .coveragerc was deleted. New branch = true, plus exclude_also
    patterns for if TYPE_CHECKING:, assert_never(...), Protocol
    method-body stubs (...), def __repr__, and
    raise AssertionError. Total coverage 96% → 97% on the unchanged
    suite, mostly from honest exclusion of unreachable branches.
  • [tool.ruff.lint.per-file-ignores] cleaned up: redundant
    duplicates removed (tests/test_settingsAA4.py now declares only
    the file-specific N999), stale D107 ignore dropped from
    migrations, codes alphabetised within each list, and a docblock
    added explaining ruff's accumulate-not-override semantics.