Skip to content

v0.7.10

Choose a tag to compare

@mpge mpge released this 30 Oct 09:30
· 15 commits to main since this release
0b6eb32

Upgrading

  • Single sign-on is an install-level switch, off by default
    (ESCALATED["ACCESS"]["sso"], env ESCALATED_SSO; ADR
    2026-10-29-configurable-access-features). Nothing ever signed anyone in
    through SSO, so off is how every install already behaved; while it is off
    Admin > Settings > SSO and every /support/sso/* route answer 404. To turn
    it on: python manage.py migrate escalated (0038), give the IdP the
    endpoints from the SSO page, make sure a SAML install has the IdP
    certificate, decide on ESCALATED["SSO"]["create_users"] and
    ["sync_roles"], then set the switch. See docs/access-control.md.
  • escalated.services.sso_service.SsoService is rewritten on signxml,
    Authlib and PyJWT (new dependencies): validate_saml_assertion and
    validate_jwt_token are replaced by validate_saml_response and
    validate_jwt, which check far more (see below). The SSO settings page now
    validates the fields each provider needs and lists the endpoints for the IdP.
  • Two-factor authentication is now an install-level switch, off by default
    (ESCALATED["ACCESS"]["two_factor"], env ESCALATED_TWO_FACTOR; ADR
    2026-10-29-configurable-access-features). Nothing ever asked for the code, so
    off is how every install already behaved; while it is off the enrolment
    screen answers 404. Run python manage.py migrate escalated (0037 adds
    last_used_step and stores the secret and recovery codes sealed), then set
    the switch. Users who enrolled before are challenged from their next visit.
    Set ESCALATED_TWO_FACTOR_REQUIRED=true (or the setting) only after telling
    staff. See docs/access-control.md.
  • The enrolment endpoints (admin/settings/two-factor/setup|confirm|disable/)
    now answer the shared page with a redirect and flash, and JSON to API
    clients (Accept: application/json), as escalated-laravel does; they were
    JSON only, and admin only.
  • New dependency: pyotp (TOTP); the hand-rolled HMAC is gone.
  • Roles & permissions is now an install-level switch, off by default
    (ESCALATED["ACCESS"]["roles_permissions"], env
    ESCALATED_ROLES_PERMISSIONS; ADR 2026-10-29-configurable-access-features).
    Roles were stored but only the newsletter screens checked them, so off is how
    almost every install already behaved. While it is off the roles screens
    (/support/admin/roles/*) answer 404, and the newsletter screens and plugin
    item permissions no longer check roles (the admin check decides). Installs
    that relied on those must turn the switch on. To enforce roles: run
    python manage.py seed_permissions, give every staff user a role
    (python manage.py assign_role you@example.com admin), then turn the switch
    on. An admin without a role is refused everywhere but their own two-factor
    screen. See docs/access-control.md.
  • The shared escalated.permissions prop is ['*'] for staff while the switch
    is off, and escalated.features carries roles_permissions, two_factor
    and sso.
  • With SDK plugins on (ESCALATED['SDK_ENABLED']), run
    python manage.py escalated_plugin_cron every minute (cron, Celery Beat,
    django-crontab) so SDK plugins' cron schedules run. Without it they never
    run; nothing else changes.
  • Run python manage.py migrate escalated. The knowledge_base_public
    setting now defaults on, as in escalated-laravel, so a new install
    serves its knowledge base (customer pages and widget articles) to guests.
    Migration 0036_knowledge_base_public_default_on keeps existing installs
    as they were: if the install already has Escalated data (any ticket,
    department, article, article category or chat session, or 0001_initial
    applied more than an hour earlier) and no stored knowledge_base_public,
    it stores 0, so the knowledge base stays private. A value an admin already
    saved is left alone. To open the knowledge base to guests afterwards, turn
    on "public knowledge base" in the admin Settings page.
  • Plugin UI extensions are shared as escalated.plugins (menuItems,
    pageComponents, dashboardWidgets), the shape the shared frontend reads.
    Before, nothing was shared, so plugin menus, widgets and slots never
    appeared, and EscalatedInertiaShareMiddleware called inertia-django's
    share() with positional arguments, which it refuses. Admin menu items go
    to admins only, items whose permission the viewer lacks are left out, and
    SDK plugins' widgets and components are included. {} when there is
    nothing to show.
  • Plugins receive the canonical domain events. ticket.created,
    reply.created, note.created, ticket.status_changed and the rest of
    the event vocabulary go, once the change commits, to Python plugins
    (add_action("ticket.created", lambda payload: ...)) and SDK plugins, with
    the plugin contract's payload: the ticket flat and under ticket, plus the
    event's extras; replies flat plus reply and ticket. SDK plugins used to
    get only ten of them, with ticket_id instead of the ticket. The Python hook
    names (ticket_created, ...) still fire for Python plugins. A failing
    plugin is logged and never fails the change.
  • ctx.tickets.query and ctx.replies.query only accept the plugin
    contract's filter keys.
    Tickets: id, reference, status, priority,
    channel, ticket_type, department_id, requester_id, contact_id,
    assigned_to. Replies: ticket_id, is_internal, type. Any other key
    (including Django lookups such as pk__gt or assigned_to__email), or a
    non-scalar value, raises instead of reaching objects.filter(). Results are
    capped at 500 rows.

Added

  • Admin > Users roles picker. While roles & permissions is on, the users
    screen sends roles ({id, name, slug}, by name) and each user's role slugs,
    and PUT C:/Program Files/Git/support/admin/users/<id>/roles/ ({role_ids: [...]}, needs role.manage) replaces a user's
    roles, as escalated-laravel's escalated.admin.users.roles. 404 while the
    switch is off.
  • Sign-in through SSO when switched on, with the provider chosen in Admin >
    Settings > SSO. SAML checks the assertion signature against the configured
    certificate, Issuer, Audience, Destination/Recipient, validity windows,
    InResponseTo against requests issued in the last 10 minutes, and replay;
    OAuth uses a session-bound state and PKCE; JWT pins the algorithm and
    requires exp, aud and a single-use jti. Users are matched by email,
    created only with create_users, and given the asserted role only with
    roles on and sync_roles.
  • Two-factor authentication is enforced when switched on. A staff user
    with a confirmed enrolment must pass Auth/TwoFactorChallenge
    (/support/two-factor/challenge/; TOTP code or single-use recovery code;
    replayed codes refused; 5 attempts per minute) in their session before any
    admin or agent view; JSON requests get 403 {two_factor_required: true}.
    Enrolment is open to agents as well as admins. Admins can require enrolment
    (ESCALATED["TWO_FACTOR"]["REQUIRED"] or the two_factor_required
    setting), and python manage.py reset_two_factor <user> clears a lost
    device. Pages get escalated.two_factor_required.
  • Roles & permissions are enforced when switched on. Every admin, agent and
    staff API view needs its permission from the user's Escalated roles as well
    as the admin or agent check (map in escalated/access.py; unmapped staff URLs
    are refused and a test checks every one is mapped). Status changes to
    resolved/closed/reopened also need ticket.close, bulk actions need
    each action's permission, internal API replies need reply.create_internal,
    and API tokens are checked as their owner. Failing answers 403.
  • python manage.py assign_role <user> <role>.
  • SDK plugin cron schedules run. python manage.py escalated_plugin_cron
    (every minute; --at picks the minute) sends cron {plugin, schedule} to
    the plugin runtime for each schedule in a plugin's manifest cronSchedules
    that is due that minute: five-field expressions in UTC (or @hourly,
    @daily, ...). Each runs as no user within 30 s; a failing schedule is
    logged and never stops the others, and one that cannot be read is logged
    and skipped. As with actions, every SDK plugin the runtime loaded takes part.
  • SDK widget badges. An SDK widget with a badge function (manifest
    hasBadge: true) carries badge in escalated.plugins.dashboardWidgets:
    the plugin's answer to badge {plugin, component}, asked as the viewer
    (ctx.currentUser is the signed-in user during that call) within 5 s; a
    number or a string, null when it fails. Widgets without one carry no
    badge key.

Changed

  • knowledge_base_public defaults on for new installs (was off), and the
    admin Settings page shows it on when nothing is stored.

Security

  • Plugin ticket and reply queries passed the filter to objects.filter(), so
    an SDK plugin could filter on any field or follow relations (such as the
    assignee's password hash). Keys are now whitelisted.
  • ctx.emit drops hook names that would impersonate a canonical event, a
    legacy alias of one, or a plugin lifecycle action.