17 KiB
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 DiskussionpostMentioned– Erwähnung in einem BeitraguserMentioned– BenutzererwähnungpostLiked– eigener Beitrag wurde mit „Gefällt mir“ markiertdiscussionRenamed– Diskussion wurde umbenanntteamsTest– 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.sourcetopic.valuetopic.webUrlactivityTypepreviewTexttemplateParameters
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-revalidateausliefern - 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:
- Ist Microsoft Teams für den betreffenden Typ in Flarum aktiviert?
- Kann
TargetResolvereine E-Mail-Adresse oder einen UPN auflösen? - Ist die Teams-App für den Benutzer installiert beziehungsweise verfügbar?
- Besitzt die Entra-App die benötigten Berechtigungen und Admin Consent?
- Läuft der Queue-Worker?
- 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: DENYX-Frame-Options: SAMEORIGINframe-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.
Allgemeiner Link statt konkretem Beitrag
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.