flarum-msteams-webhook/README.md

17 KiB
Raw Blame History

flarum-msteams-webhook

Flarum-Erweiterung zur Spiegelung ausgewählter Flarum-Benachrichtigungen in den Microsoft Teams Activity Feed.

Die Erweiterung ergänzt Flarum um einen eigenen Benachrichtigungskanal namens Microsoft Teams. Benutzer können diesen Kanal in ihren persönlichen Benachrichtigungseinstellungen aktivieren. Neue Aktivitäten beispielsweise Antworten in gefolgten Diskussionen oder Erwähnungen werden anschließend über Microsoft Graph an den Teams Activity Feed des jeweiligen Benutzers gesendet.

Beim Öffnen einer Teams-Benachrichtigung erscheint eine schlanke Zwischenansicht im persönlichen Teams-Tab. Diese enthält einen Link zur zugehörigen Flarum-Diskussion beziehungsweise direkt zum konkreten Beitrag. Das eigentliche Forum muss dadurch nicht in Microsoft Teams eingebettet werden und kann weiterhin mit restriktiven Frame-Sicherheitsheadern geschützt bleiben.

Projektstatus: Interne Erweiterung für Flarum 1.8. Vor einem produktiven Einsatz sollten Konfiguration, Berechtigungen, Logging und Fehlerbehandlung an die jeweilige Umgebung angepasst und getestet werden.

Funktionsumfang

  • eigener Flarum-Benachrichtigungskanal teams
  • rudimentäres Admin-UI zur Konfiguration der Microsoft-Graph-Anbindung
  • Erweiterung der persönlichen Flarum-Benachrichtigungseinstellungen um Microsoft Teams
  • asynchroner Versand über einen dedizierten Queue-Job
  • Auflösung des Microsoft-Graph-Zielbenutzers über E-Mail-Adresse, UPN oder eine Benutzerpräferenz
  • Abruf eines App-only Access Tokens über Microsoft Entra ID
  • Versand von Activity-Feed-Benachrichtigungen über Microsoft Graph
  • Vorschautext mit auslösendem Benutzer und Diskussionstitel
  • Teams Deep Link zu einem persönlichen Teams-Tab
  • Übergabe der konkreten Flarum-Ziel-URL über context.subEntityId
  • Auslesen des Ziels im Teams-Tab über TeamsJS v2 und context.page.subPageId
  • sichere Zwischenansicht mit validiertem Link zur Diskussion oder zum Beitrag
  • Test-Command für einzelne Flarum-Benutzer

Unterstützte Benachrichtigungstypen

Die Erweiterung enthält Unterstützung beziehungsweise Textvorlagen für folgende Flarum-Typen:

  • newPost neue Antwort in einer gefolgten Diskussion
  • postMentioned Erwähnung in einem Beitrag
  • userMentioned Benutzererwähnung
  • postLiked eigener Beitrag wurde mit „Gefällt mir“ markiert
  • discussionRenamed Diskussion wurde umbenannt
  • teamsTest interne Testbenachrichtigung des CLI-Commands

Welche Typen tatsächlich in den persönlichen Einstellungen angeboten oder standardmäßig aktiviert werden, wird durch die registrierten Flarum-Blueprints und die Frontend-Erweiterung bestimmt.

Systemvoraussetzungen

  • PHP 8.1 oder neuer
  • Flarum 1.8
  • Composer
  • Node.js und npm zum Bauen des Flarum-Frontends
  • ein funktionierender Flarum-Queue-Worker
  • öffentlich beziehungsweise für Microsoft Teams erreichbare HTTPS-URL
  • Microsoft-Entra-App-Registrierung mit den erforderlichen Microsoft-Graph-Berechtigungen
  • installierte beziehungsweise im Mandanten veröffentlichte Microsoft-Teams-App

Relevante Composer-Abhängigkeiten:

{
  "php": "^8.1",
  "flarum/core": "^1.8",
  "guzzlehttp/guzzle": "^7.8"
}

Architektur und Ablauf

Der Versand läuft vereinfacht über folgende Komponenten:

Flarum Notification Blueprint
    -> TeamsNotificationDriver
    -> SendTeamsActivityNotificationJob
    -> TeamsActivityNotifier
    -> NotificationPayloadFactory
    -> Microsoft Graph sendActivityNotification
    -> Microsoft Teams Activity Feed

Notification Driver

TeamsNotificationDriver ist in extend.php als Flarum Notification Driver registriert:

(new Extend\Notification())
    ->driver('teams', TeamsNotificationDriver::class, []),

Der Driver prüft die Benachrichtigungseinstellungen der Empfänger und legt den Versand als SendTeamsActivityNotificationJob in der Queue teams ab.

Queue-Job

SendTeamsActivityNotificationJob führt den eigentlichen Versand außerhalb des Web-Requests aus. Der Job besitzt Wiederholungs- und Backoff-Einstellungen, damit vorübergehende Graph- oder Netzwerkfehler nicht sofort zum endgültigen Verlust einer Nachricht führen.

Nach Änderungen am PHP-Code müssen lang laufende Queue-Worker neu gestartet werden, da sie andernfalls weiterhin bereits geladene Klassen verwenden können.

Token Provider

GraphTokenProvider ruft über den OAuth-2.0-Client-Credentials-Flow ein App-only Access Token bei Microsoft Entra ID ab. Tenant-ID, Client-ID und Client Secret werden aus den Flarum-Einstellungen gelesen.

Secrets dürfen weder in das Git-Repository eingecheckt noch in Logdateien geschrieben werden.

Zielauflösung

TargetResolver ordnet einen Flarum-Benutzer einem Microsoft-Graph-Benutzer zu. Unterstützt werden abhängig von der Admin-Konfiguration unter anderem:

  • Flarum-E-Mail-Adresse
  • Microsoft Entra User Principal Name (UPN)
  • benutzerdefinierte Flarum-Präferenz

Der aufgelöste Wert wird für den Graph-Endpunkt verwendet:

POST https://graph.microsoft.com/v1.0/users/{target}/teamwork/sendActivityNotification

Payload-Erzeugung

NotificationPayloadFactory erstellt den Request-Body für den Microsoft Teams Activity Feed. Der Payload enthält unter anderem:

  • topic.source
  • topic.value
  • topic.webUrl
  • activityType
  • previewText
  • templateParameters

Aktuell wird systemDefault als Activity Type verwendet. Dadurch ist kein eigener Activity Type im Teams-Manifest erforderlich.

Für Discussion- und Post-Subjects wird eine passende Ziel-URL erzeugt:

https://forum.example/d/{discussionId}
https://forum.example/d/{discussionId}/{postNumber}

Die Ziel-URL wird nicht direkt im Teams-Frame geladen. Stattdessen wird sie im Deep Link als context.subEntityId an den persönlichen Teams-Tab übergeben.

Zwischenansicht im Teams-Tab

Die Teams-App lädt als Personal Tab eine statische Seite, beispielsweise:

https://forum.example/static/flarum-notify-v2.html

Die Seite bindet TeamsJS ein und liest den Deep-Link-Kontext aus:

await window.microsoftTeams.app.initialize();
const context = await window.microsoftTeams.app.getContext();
const target = context.page.subPageId;

Anschließend wird nur dann ein Link angezeigt, wenn:

  • die Ziel-URL dieselbe Origin wie die Landing Page besitzt und
  • der Pfad mit /d/ beginnt.

Dadurch kann subEntityId nicht als offener Redirect auf fremde Domains missbraucht werden.

Die Seite unterstützt zusätzlich einen direkten Browser-Test über einen Query-Parameter:

https://forum.example/static/flarum-notify-v2.html?target=https%3A%2F%2Fforum.example%2Fd%2F123%2F17

Teams-App-Manifest

Das Teams-Manifest benötigt mindestens einen persönlichen statischen Tab und die Forum-Domain in validDomains.

Beispiel:

{
  "staticTabs": [
    {
      "entityId": "YOUR_TEAMS_ENTITY_ID",
      "name": "Flarum Notify",
      "contentUrl": "https://forum.example/static/flarum-notify-v2.html",
      "websiteUrl": "https://forum.example/static/flarum-notify-v2.html",
      "scopes": [
        "personal"
      ],
      "context": [
        "personalTab"
      ]
    }
  ],
  "validDomains": [
    "forum.example"
  ]
}

Die folgenden Werte müssen zwischen Manifest und Erweiterung übereinstimmen:

  • Teams App ID
  • Entity ID des Personal Tabs
  • öffentliche Forum-Basis-URL

Microsoft Entra ID und Graph

Für den App-only-Versand werden eine Microsoft-Entra-App-Registrierung und geeignete Microsoft-Graph-Anwendungsberechtigungen benötigt. Die konkrete Berechtigungskonfiguration hängt vom verwendeten Graph-Endpunkt, dem Mandanten und den internen Sicherheitsrichtlinien ab.

Erforderliche Konfigurationswerte sind typischerweise:

  • Tenant ID
  • Client ID
  • Client Secret
  • Teams App ID
  • Forum Base URL
  • Strategie zur Auflösung des Zielbenutzers

Nach dem Hinzufügen von Application Permissions ist in der Regel eine Administratorzustimmung im Microsoft-Entra-Mandanten erforderlich.

Flarum-Admin-Einstellungen

Das Admin-Frontend enthält Einstellungen für:

  • Aktivierung der Erweiterung
  • Tenant ID
  • Client ID
  • Client Secret
  • Forum Base URL
  • User Lookup Strategy
  • UPN Preference Key
  • User Preference Key
  • Activity Type
  • Teams App ID
  • optionale Icon ID
  • HTTP Timeout

Beispiel für die Forum Base URL:

https://forum.example

Keinen abschließenden Pfad wie /public, /api oder /static eintragen.

Installation

1. Erweiterung bereitstellen

Das Repository muss als Composer-Paket beziehungsweise lokales Flarum-Paket verfügbar sein. Danach im Flarum-Root die Abhängigkeiten beziehungsweise den Autoloader aktualisieren:

cd /var/www/html/flarum
composer dump-autoload

Je nach Installationsart kann zuvor ein composer require oder ein Composer-Path-Repository erforderlich sein.

2. Admin- und Forum-Frontend bauen

cd js
npm install
npm run build

Für die Entwicklung mit Watch-Modus:

cd js
npm install
npm run dev

3. Flarum-Cache leeren

Im Flarum-Root:

php flarum cache:clear

4. Queue-Worker neu starten

Bei Supervisor beispielsweise:

sudo supervisorctl restart all
sudo supervisorctl status

In einer produktiven Umgebung sollte möglichst nur die betroffene Worker-Gruppe neu gestartet werden.

Statische Teams-Dateien

Die Landing Page besteht aus:

flarum-notify-v2.html
flarum-notify-v2.js

Bei einer Apache-Konfiguration mit folgendem Alias:

Alias "/static" "/var/www/html/static"

liegen die Dateien beispielsweise hier:

/var/www/html/static/flarum-notify-v2.html
/var/www/html/static/flarum-notify-v2.js

Apache-, CSP- und Frame-Konfiguration

Teams stellt Personal Tabs in einem eingebetteten Browserkontext dar. Ein globales

X-Frame-Options: DENY

oder

Content-Security-Policy: frame-ancestors 'none'

blockiert deshalb die Landing Page und erzeugt im Teams-Desktop typischerweise eine weiße Fläche.

Das eigentliche Forum kann weiterhin global gegen Framing geschützt bleiben. Nur die statische Landing Page sollte für Microsoft Teams freigegeben werden.

Beispiel für Apache:

<IfModule mod_headers.c>
    <LocationMatch "^/static/flarum-notify-v2\.html$">
        Header always unset X-Frame-Options
        Header always unset Content-Security-Policy
        Header always unset Content-Security-Policy-Report-Only

        Header always set Content-Security-Policy "default-src 'self'; script-src 'self' https://res.cdn.office.net; style-src 'self' 'unsafe-inline'; img-src 'self' data:; frame-ancestors 'self' https://teams.microsoft.com https://*.teams.microsoft.com https://*.cloud.microsoft; base-uri 'self'; form-action 'self'"

        Header always set Cache-Control "no-cache, must-revalidate, max-age=0"
    </LocationMatch>
</IfModule>

Anschließend:

sudo apache2ctl configtest
sudo systemctl reload apache2

Header prüfen:

curl -sSI https://forum.example/static/flarum-notify-v2.html

Für die Landing Page darf kein restriktiver X-Frame-Options-Header mehr ausgeliefert werden. Die CSP muss die benötigten Teams-Hosts als frame-ancestors und das TeamsJS-CDN unter script-src erlauben.

Das eigentliche Forum insbesondere /d/..., Login und Admin-Bereiche muss für diesen Ansatz nicht in einem Frame freigegeben werden.

Cache-Verhalten in Microsoft Teams

Microsoft Teams kann persönliche Tabs im Desktop-Client im Speicher halten beziehungsweise suspendieren. Änderungen an HTML oder JavaScript werden deshalb nicht immer bei jedem Klick sofort neu geladen.

Empfehlungen:

  • Landing-Page-HTML mit Cache-Control: no-cache, must-revalidate ausliefern
  • JavaScript bei Änderungen versionieren, zum Beispiel:
<script src="/static/flarum-notify-v2.js?v=20260728-1" defer></script>
  • nach größeren Änderungen Teams vollständig beenden und neu starten
  • ein manuelles Löschen des vollständigen Teams-Caches sollte im Normalbetrieb nicht erforderlich sein

Test-Command

Eine Testbenachrichtigung kann an einen einzelnen Flarum-Benutzer gesendet werden:

php flarum teams:test-user <flarumUserId>

Beispiel:

php flarum teams:test-user 42

Nur das aufgelöste Graph-Ziel anzeigen:

php flarum teams:test-user 42 --show-target

Ziel testweise überschreiben:

php flarum teams:test-user 42 --target user@example.com

Der Test-Blueprint besitzt keinen realen Discussion- oder Post-Subject. Deshalb führt eine reine Testbenachrichtigung üblicherweise nicht zu einem konkreten Beitragslink, sondern zur allgemeinen Zwischenansicht.

Benutzerkonfiguration

Die Erweiterung ergänzt die persönlichen Flarum-Benachrichtigungseinstellungen um Microsoft Teams. Benutzer können Teams für die angebotenen Benachrichtigungstypen ein- oder ausschalten.

Wenn ein Benutzer keine Teams-Benachrichtigungen erhält, sollten folgende Punkte geprüft werden:

  1. Ist Microsoft Teams für den betreffenden Typ in Flarum aktiviert?
  2. Kann TargetResolver eine E-Mail-Adresse oder einen UPN auflösen?
  3. Ist die Teams-App für den Benutzer installiert beziehungsweise verfügbar?
  4. Besitzt die Entra-App die benötigten Berechtigungen und Admin Consent?
  5. Läuft der Queue-Worker?
  6. Enthalten Worker- und Supervisor-Logs Graph-Fehler?

Fehlersuche

Weiße Fläche im Teams-Desktop

Response Header prüfen:

curl -sSI https://forum.example/static/flarum-notify-v2.html

Typische Ursachen:

  • X-Frame-Options: DENY
  • X-Frame-Options: SAMEORIGIN
  • frame-ancestors 'none'
  • frame-ancestors 'self' ohne Teams-Domains

Seite wird ohne CSS dargestellt

Wenn CSS als <style>-Block in der HTML-Datei enthalten ist, benötigt die CSP entweder einen Hash/Nonce oder für diese kleine interne statische Seite beispielsweise:

style-src 'self' 'unsafe-inline'

Langfristig kann das CSS in eine separate Datei ausgelagert werden, damit 'unsafe-inline' nicht erforderlich ist.

Prüfen, ob die PHP-Datei den Deep-Link-Kontext erzeugt:

grep -nE 'subEntityId|context|subjectUrl' \
  vendor/sbp-jm/flarum-msteams-webhook/src/Support/NotificationPayloadFactory.php

Prüfen, ob die Landing Page TeamsJS lädt und page.subPageId ausliest:

grep -nE 'microsoftTeams|subPageId|getContext|initialize' \
  /var/www/html/static/flarum-notify-v2.js

Nur neu versendete Activity-Feed-Einträge enthalten den aktualisierten Deep Link. Bereits vorhandene Teams-Aktivitäten werden nicht nachträglich geändert.

Änderungen am PHP-Code werden nicht verwendet

Queue-Worker neu starten:

php flarum cache:clear
sudo supervisorctl restart all

Queue und Supervisor prüfen

sudo supervisorctl status

Logs abhängig von der lokalen Supervisor-Konfiguration beispielsweise:

sudo supervisorctl tail \
  flarum-teams-worker:flarum-teams-worker_00 \
  stderr

HTTP-Aufrufe der Landing Page beobachten

sudo tail -f /var/log/apache2/access.log |
  grep --line-buffered 'flarum-notify-v2'

Teams kann den Tab nach dem ersten Laden im Speicher halten. Weitere Klicks müssen daher nicht zwingend neue HTTP-Requests erzeugen.

Projektstruktur

Wichtige Dateien und Verzeichnisse:

extend.php
composer.json
js/
  admin.js
  forum.js
  src/admin/
  src/forum/
locale/
  de.yml
  en.yml
src/
  Console/
    TestTeamsUserCommand.php
  Job/
    SendTeamsActivityNotificationJob.php
  Notification/
    TeamsNotificationDriver.php
    TestActivityBlueprint.php
  Service/
    GraphTokenProvider.php
    TeamsActivityNotifier.php
  Support/
    NotificationPayloadFactory.php
    TargetResolver.php

Im Repository können zusätzlich ältere oder experimentelle Klassen wie TeamsChannel oder TeamsDriver vorhanden sein. Der aktuell in extend.php registrierte Einstiegspunkt ist TeamsNotificationDriver.

Sicherheitshinweise

  • Client Secrets niemals im Repository speichern.
  • Tokens und vollständige Graph-Payloads nur kurzfristig und geschützt protokollieren.
  • Die Landing Page darf nur Links zur eigenen Forum-Origin akzeptieren.
  • Das eigentliche Forum nicht pauschal für fremde Frames freigeben.
  • HTTPS ist verpflichtend.
  • Berechtigungen der Entra-App nach dem Least-Privilege-Prinzip vergeben.
  • Produktive Secrets regelmäßig rotieren.
  • Logdateien auf Benutzerkennungen, E-Mail-Adressen und Diskussionstitel prüfen und angemessen schützen.

Entwicklung

Frontend-Abhängigkeiten installieren:

cd js
npm install

Produktions-Build:

npm run build

Watch-Modus:

npm run dev

Formatierung:

npm run format

Nach einem Frontend-Build:

cd /var/www/html/flarum
php flarum cache:clear

Nach PHP-Änderungen zusätzlich den Queue-Worker neu starten.

Lizenz

MIT siehe composer.json beziehungsweise eine vorhandene LICENSE-Datei.