TYPO3 7.x: Branding des Backend-Logins vereinfacht

Im 7er-Zweig von TYPO3 CMS wurde das Branding des Backend-Logins stark vereinfacht.

Anstatt über PHP-Aufrufe, wie es bisher auch schon möglich war, könnt Ihr die nötigen Variablen nun auch direkt im Backend setzen. Dazu müsst Ihr lediglich in den Extension Manager gehen und dort die Konfiguration der Core-Extension backend aufrufen. Hier könnt Ihr dann den Pfad zu einem Logo, eine Highlightfarbe (insbesondere für den Login-Button wichtig) sowie den Pfad zu einem Bild festlegen, das als Fullscreen-Hintergrundbild im Login genutzt wird.

Gespeichert werden diese Daten, wie im Extension Manager üblich, als Konfigurationsvariable in der Datei LocalConfiguration.php. Für mich ist das sehr wichtig, da ich alle meine Projekte über Git versioniere und diese Anpassung darin sauber mit einfließen kann.

TYPO3: Dateien vor nicht eingeloggten Nutzern schützen

Besuchern einer Website einen geschützten Login-Bereich bereitzustellen, ist mit TYPO3 CMS sehr schön und recht einfach lösbar. Was aber in der Natur der Sache liegt ist dabei, dass Datei-Downloads dadurch nicht geschützt werden, da sie ohne PHP-seitige Prüfung der Zugriffsrechte direkt vom Webserver geholt werden. Ein Link, der direkt die Datei aufruft, kann von TYPO3 CMS leider nicht abgefangen werden.

Um das Problem zu lösen, gibt es mehrere Ansätze. Der korrekte Weg ist, die Dateien nicht direkt zu verlinken, sondern stattdessen über PHP-Code, der vorher die Zugriffsrechte prüft, zu „dispatchen“. Eine Extension, die sich genau das auf die Fahne geschrieben hat, ist fal_securedownload. Sie klinkt sich in die FAL-Storages ein und führt vor der Auslieferung die Zugriffsrechteprüfung durch.

Nicht immer jedoch ist es möglich, diese Extension einzusetzen, zum Beispiel, weil die schiere schon bestehende Datenmenge eine Migration schwierig macht. Oder es ist den Aufwand nicht wert, weil die Daten nur rudimentär geschützt werden müssen, aber nicht wirklich kritisch sind.

Eine Notlösung, die leider recht einfach hackbar ist, aber zumindest vor unerlaubtem Zugriff nicht technik-affiner Menschen schützt und bei vielleicht nicht ganz so kritischen Dateien anwendbar ist: Ihr könnt einem Apache2-Webserver auch über die .htaccess-Datei sagen, dass bei Fehlen bspw. eines bestimmten Cookies die angeforderte Ressource nicht ausgeliefert, sondern der Benutzer umgelenkt wird. In folgendem Beispiel nutze ich das, um nur eingeloggten Frontend-Benutzern Dateien auch auszuliefern:

RewriteEngine On
RewriteCond %{HTTP_COOKIE} !^.*fe_typo_user.*$ [NC]
RewriteRule ^ http://www.domain.de [R,L]

Um es noch ein wenig sicherer zu machen, könntet Ihr den Cookie umbenennen. Im Install-Tool im Bereich FE bzw. Frostend findet Ihr zu dem Zweck eine Option, die die Variable für den Cookie-Namen ändert. Das könnte dann zum Beispiel so aussehen:

// /typo3conf/LocalConfiguration.php
$GLOBALS['TYPO3_CONF_VARS']['FE']['cookieName'] = 'diesenCookieNamenKennstDuNicht';

In der .htaccess-Datei ist der Name dann natürlich entsprechend zu ersetzen.

Aber Achtung: Dies sollte keine Lösung sein, auf der man sich bei kritischen Daten ausruht!

Ich bin gespannt, ob jemand Vorschläge hat, wie man dies sicherer lösen kann, ohne den „großen“ Weg über eine Extension zu gehen. Hat da jemand Ideen?

TYPO3: Umgebungskontexte per .htaccess

Wer kennt es nicht? Wenn man eine Website implementiert, benötigt man oft Einstellungen für unterschiedliche Umgebungen. Meist sind das die Entwicklungsversion auf Eurem Rechner, eventuell eine Staging-Version auf einem internen oder externen Server und die Produktiv-Version, in der die Website schlussendlich läuft.

Je nachdem wie Ihr arbeitet, zum Beispiel über ein SCM wie Git, oder über direktes Veröffentlichen via PHP, benötigt Ihr für jede dieser Versionen unterschiedliche Einstellungen. Mindestens sind das in der Regel die Zugangsdaten zur Datenbank, aber auch weitere Konfigurationsvariablen für z.B. ImageMagick sind von Server zu Server unterschiedlich.

Wie also löst man das Umschalten der jeweiligen mit möglichst wenig Aufwand, also ohne, dass man aufpassen muss, dass man auch die richtigen Daten in der jeweiligen Umgebung hat?

Seit Längerem schon liefert TYPO3 CMS die Grundlage dafür mit, verschiedene Setups anhand von Umgebungsvariablen des Webservers auszuliefern.

Hier zeige ich Euch kurz beispielhaft, wofür man es nutzen kann.

Der wichtigste Punkt ist schon auskommentiert als Vorlage in der Datei .htaccess Eurer Installation zu finden:

# Rules to set ApplicationContext based on hostname
#RewriteCond %{HTTP_HOST} ^dev\.example\.com$
#RewriteRule .? - [E=TYPO3_CONTEXT:Development]
#RewriteCond %{HTTP_HOST} ^staging\.example\.com$
#RewriteRule .? - [E=TYPO3_CONTEXT:Production/Staging]
#RewriteCond %{HTTP_HOST} ^www\.example\.com$
#RewriteRule .? - [E=TYPO3_CONTEXT:Production]

Hier müsst Ihr im Prinzip nur Eure Hosts eintragen und es wieder einkommentieren. Ich halte es zum Beispiel so, dass ich auf meinem lokalen Rechner die Entwicklungsversion und auf einem externen Server je eine Staging- und eine Produktionsumgebung betreibe. Die Staging-Version ist für den Demo-Rollout für den Kunden und/oder die Redakteure, und sobald alles abgesegnet ist, geht es in die Produktivumgebung. Das könnt Ihr aber beliebig Euren Anforderungen anpassen. Hier ein Beispiel dessen, wie ich es handhabe:

# Rules to set ApplicationContext based on hostname
RewriteCond %{HTTP_HOST} ^dev\.kundenwebsite\.de$
RewriteRule .? - [E=TYPO3_CONTEXT:Development]
RewriteCond %{HTTP_HOST} ^staging\.kundenwebsite\.de$
RewriteRule .? - [E=TYPO3_CONTEXT:Production/Staging]
RewriteCond %{HTTP_HOST} ^www\.kundenwebsite\.de$
RewriteRule .? - [E=TYPO3_CONTEXT:Production]

Ihr könnt es aber natürlich auch vereinfachen, solltet Ihr nur die Entwicklungs- und die Live-Version haben:

# Rules to set ApplicationContext based on hostname
RewriteCond %{HTTP_HOST} ^dev\.kundenwebsite\.de$
RewriteRule .? - [E=TYPO3_CONTEXT:Development]
RewriteCond %{HTTP_HOST} ^www\.kundenwebsite\.de$
RewriteRule .? - [E=TYPO3_CONTEXT:Production]

Die hiermit gesetzte Umgebungsvariable ist dann für Euch PHP- und damit TYPO3-seitig einfach auswertbar. Ihr könnt also Bedingungen daran knüpfen, sowohl im PHP als auch im TypoScript, welche Umgebung Ihr gerade vor Euch habt.

Regelmäßig nutze ich dies insbesondere für unterschiedliche Einstellungen in der Datei LocalConfiguration.php. Hier werden wie oben angesprochen Zugangsdaten zur Datenbank, aber auch Einstellungen für z.B. ImageMagick abgelegt.

Aber bevor wir fortfahren, stellt sich ein Problem: Die LocalConfiguration.php wird vom Install-Tool von TYPO3 selbst verwaltet. Wenn man also doch noch einmal ins Install-Tool muss, werden die Daten mitunter wieder weggeschmissen und neu überschrieben, ein Context-Switch in dieser Datei ist damit also recht schwierig.

Abhilfe schafft hier, dass TYPO3 CMS für genau solche Fälle anbietet, parallel zu dieser Datei eine Datei namens AdditionalConfiguration.php anzulegen. Diese wird nach Abarbeitung der LocalConfiguration.php automatisch inkludiert und von TYPO3 nicht angefasst, was uns ermöglicht, hier genau die Konfigurationsdetails abzulegen, die sich von Umgebung zu Umgebung unterscheiden.

Da es sich um PHP handelt, könnt Ihr hier also eine einfache if-Abfrage platzieren, die je nach Umgebungsvariable aus der .htaccess-Datei andere Variablen setzt. Dazu fragt Ihr einfach die Variable $_SERVER["TYPO3_CONTEXT"] ab und erhaltet den String zurück, den Ihr in der .htaccess-Datei hierfür gesetzt habt. Hier müsst Ihr aber etwas aufpassen, da je nach Konfiguration des Webservers diese Variable auch mal nur als $_SERVER["REDIRECT_TYPO3_CONTEXT"] verfügbar ist. In meinem gleich folgenden Beispiel berücksichtige ich das. Es beinhaltet wie oben angesprochen zwei Setups mit Variablen für den Datenbankzugriff sowie ImageMagick. Außerdem setze ich nur für die Entwicklungsversion ein paar Einstellungen, die mir beim Debugging helfen:

// /typo3conf/AdditionalConfiguration.php
$context = !empty($_SERVER["TYPO3_CONTEXT"]) ? $_SERVER["TYPO3_CONTEXT"] : $_SERVER["REDIRECT_TYPO3_CONTEXT"];

if ( $context == "Development") {
    \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath('DB/database', "kundenwebsite_local");
    \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath('DB/host', "127.0.0.1");
    \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath('DB/port', 3306);
    \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath('DB/username', "root");
    \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath('DB/password', "root");
    \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath('SYS/displayErrors', "1");
    \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath('SYS/systemLogLevel', 0);
    \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath('GFX/im_path', '/opt/ImageMagick/bin/');
    \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath('GFX/im_path_lzw', '/opt/ImageMagick/bin/');
}

if ( $context == "Production/Staging") {
    \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath('DB/database', "db1");
    \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath('DB/host', "127.0.0.1");
    \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath('DB/port', 3307);
    \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath('DB/username', "username_db1");
    \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath('DB/password', "daspasswort");
    \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath('SYS/displayErrors', "1");
    \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath('SYS/systemLogLevel', 2);
    \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath('GFX/im_path', '/usr/bin/');
    \TYPO3\CMS\Core\Configuration\ConfigurationManager::setLocalConfigurationValueByPath('GFX/im_path_lzw', '/usr/bin/');
}

Ein kleiner Wermutstropfen an der Stelle: TYPO3 CMS arbeitet derzeit so, dass es diese Einstellungen in die lokale LocalConfiguration.php übernimmt. Das ist aber kein Problem, da es nach einem Austausch der Dateien einfach wieder gemacht wird. Man muss lediglich damit leben, dass beim ersten Reload nach Austausch der Dateien die Daten noch nicht stimmen, wodurch ein Fehler geworfen wird. Bei diesem Aufruf aber übernimmt TYPO3 CMS die richtigen Daten, so dass es ab dem zweiten Aufruf dann läuft. Ein Reload zu viel, anstatt jede Datei immer wieder manuell zu synchronisieren.

Wir können also auf diese Art und Weise alle drei Dateien nur ein Mal anfassen und dennoch auf allen Umgebungen lauffähig behalten:

  • .htaccess
  • typo3conf/LocalConfiguration.php
  • typo3conf/AdditionalConfiguration.php

Darüber hinaus gibt es, wie oben angesprochen, auch die Möglichkeit, die Variable via TypoScript auszulesen, um auch hier if-Abfragen zu platzieren. Dies ist insbesondere dann von Vorteil, wenn man das TypoScript über TypoScript-Includes auf Dateiebene versioniert. Ein Beispiel in TypoScript:

[applicationContext = Development,Production/Staging]
config.admPanel = 1

[applicationContext = Production]
config.admPanel = 0

[global]

Zusammenfassung

Über das Setzen und Auswerten einer Umgebungsvariable des Webservers kann man sich das Leben beim Synchronhalten mehrerer Server sehr stark erleichtern und TYPO3 CMS bringt die dafür notwendigen Vorkehrungen mit.

Getestet und laufen habe ich das mit mehreren Installationen auf Apache2-Webservern, die jeweils TYPO3 CMS in Version 6.2 bis 7.6 fahren. Von 6.2 LTS bis 7.6 LTS gibt es hier auch keinerlei mir bekannte Unterschiede.

Rails: Aktivitäten-Logging leicht gemacht mit public_activity

Dieses schöne Stück Software habe ich gestern in unsere Agentur-Software eingebaut: public_activity. Leichter kann einem das Loggen von Aktivitäten wirklich nicht gemacht werden. Vom Namen lässt man sich dabei am Besten nicht irritieren.

Meine Quick-and-dirty-Lösung für eine _default.html.slim sieht ungefähr so aus:

= t("public_activity.key.#{activity.key}", default: "%{model} #%{id} %{key}.", \
    model: t("activerecord.models.#{activity.trackable_type.underscore}"), \
    id: activity.trackable.id, \
    key: "public_activity.key.#{activity.key.split('.')[1]}" \
  ).html_safe