OpenID ist tot! Oder?

Am Anfang war das Internet wüst und leer. Nach und nach kamen aber immer mehr Seite, Dienste und Anbieter hinzu. Und mit ihnen kamen auch immer mehr Anmeldedaten zusammen. Als das Problem immer schlimmer wurde, versuchte man mit unterschiedlichen Methoden ein Single Sign-On (SSO) zu etablieren. Eine dieser Methoden ist OpenID.

Das Besondere dieser Methode ist, dass man nicht auf einen zentralen Anbieter angewiesen ist. Jeder kann sich mit geringem Aufwand über seine Webseite einen OpenID-Provider bauen (der dann allerdings doch nicht ganz ohne einen anderen Anbieter auskommt), mit etwas mehr Aufwand ist auch ein eigener OpenID-Server möglich.

Einen solchen OpenID-Server betreibe ich seit geraumer Zeit. Leider musste ich feststellen, dass es über die Jahre nicht wirklich viele Einsatzmöglichkeiten für OpenID gab. Heute wurde ich dann plötzlich gewahr, dass auch die Webseiten des StackExchange-Netzwerks OpenID nicht mehr als Authentifizierungsmethode akzeptieren. Damit ist für mich der letzte Anwendungsfall für meinen OpenID-Server weggebrochen. Und auch die letzte Hoffnung, dass es Abseits von Google und Facebook so etwas wie ein dezentrales SSO geben könnte. Fortschritt ist eben doch ein Vektor. Nicht nur die Geschwindigkeit ist interessant, auch die Richtung kann entscheidend sein.

Wo ist rst2man?

Der Fortran-Parser von ctags versteht bisher das protected-Attribut nicht, mit dem der schreibende Zugriff auf eine öffentliche Modulvariable von außerhalb des Moduls unterbunden werden kann. ctags liegt auf github, also war es kein Problem, den Parser soweit anzupassen, dass er beim Auftreten des Attributs zumindest die Variable weiterhin verarbeitet.

Problematisch wurde es allerdings, als ich den Parser lokal gebaut habe. Denn die manpage war nur sehr übersichtlich. In der Doku findet man dazu den Satz, dass für die Erstellung der manpage das Python-Tool rst2man benötigt wird. Aber woher bekommt man es? Es gehört wohl zu den docutils, aber pip install docutils hat es nicht installiert. Auch port install py-docutils hab nicht weitergeholfen. Im Netz habe ich dann den Hinweis für Mercurial-Entwickler gefunden, dass man sich das Sandbox-Archiv des docutils-Projekts laden soll. Aber auch dort ist es nicht enthalten. Schlussendlich habe ich das Tool direkt aus dem docutils-Repository geladen. Dort liegt es unterhalb des Ordners, der via pip installiert wird. Eventuell kann man das PyPi-Package so anpassen, dass rst2man mitinstalliert wird, aber ob das gewollt ist und wie das geht, weiß ich gerade nicht. Daher habe ich den pragmatisch und schnellen Weg gewählt.

Lazy-load JavaScript

Mein kürzlich schon mal erwähntes Blogprojekt basiert, wie diese Seite hier auch, auf einem Static Site Generator zur Erzeugung des HTML. Hier läuft Nikola im Hintergrund, allerdings bin ich mit einigen Dingen nicht ganz so zufrieden. Zuvorderst ist mir das Datenvolumen, welches für das Laden einer Seite transferiert wird, viel zu groß. So werden aktuell beim Aufruf von blog.pvitt.de 488,04 kB versendet. Davon entfallen aber nur 42,3 kB auf HTML, dafür aber 136,39 kB auf CSS und sogar 311,13 kB auf JavaScript. Das Problem kann man sicher über die Erstellung eines Themes angehen, aber Nikola macht es den Nutzern durch fehlende oder schlechte Dokumentation oder komplexe Strukturen nicht gerade einfach.

Daher habe ich für das neue Projekt nach einem alternativen Static Site Generator umgesehen und habe einige ausprobiert. Nach einigen Tests bin ich dann bei Pelican gelandet, der mich bisher auch nicht enttäuscht hat. Nach der einfachen Installation und der ebenfalls einfachen Installation eines Themes für die Seite sowie ein wenig Optimierung von CSS und mit Verwendung von SVG als Image-Format komme ich mit zwei Testposts auf der Startseite aktuell auf ein Transfervolumen von von 7,13 kB, 3,8 kB HTML, 2,74 kB für CSS und 609 B für SVG. Mit Transportkomprimierung sind das sogar nur 3,85 kB, die tatsächlich über die Leitung gehen. Das ist mal eine andere Hausnummer.

Umso ernüchterter war ich, als ich dann die Kommentarfunktion via isso nachrüsten wollte. Denn dafür ist ein JavaScript-Paket nötig, welches zwar schon minimiert wurde, trotzdem aber noch 54,63 kB auf die Waage (und 20,36 kB auf die Leitung) bringt. Das ist eindeutig zu viel für eine einfache Kommentarfunktion.

Meine Idee war nun, die Kommentarfunktionalität nur dann zu laden, wenn der Nutzer diese auch nutzen möchte. Dazu musste ich zwar ein wenig HTML ergänzen, aber die paar Hundert Byte fallen im Relation zu den 54 kB dann doch nicht wirklich ins Gewicht.

Der Platzhalter im DOM, an dem das JavaScript seinen HTML-Code ablegt, blieb unverändert im Code. Aber das Script-Tag aus dem Header habe ich entfernt:

<script data-isso="//comments.example.tld/"
    src="//comments.example.tld/js/embed.min.js"></script>

Stattdessen wird dieses nun erst auf Benutzerwunsch geladen:

{% if ISSO_SITENAME %}
<input id="issoBtn" type="image" height="16" src="/theme/images/comment.svg" onclick="
    var s = document.createElement('script');
    s.type = 'text/javascript';
    s.src = '//{{ ISSO_SITENAME }}/js/embed.min.js';
    document.body.appendChild(s);
    this.style.display = 'none';
    " />
{% endif %}

Das input-Tag wird (wie auch der Platzhalter) nur eingefügt, wenn ISSO auch konfiguriert wurde. Es zeigt eine Sprechblase an, mit SVG realisiert nur 184 Byte groß. Wenn der Benutzer auf diese Sprechblase klickt, wird das JavaScript geladen und angehängt, sodass es aktiv werden kann. Im Anschluss wird noch die Sprechblase versteckt, da nun die Kommentare ja geladen sind.

Isso verfügt auch noch über die Möglichkeit, das clientseitige JavaScript zu konfigurieren. Dies geschieht an Hand von data-*-Attributen, die am Script-Element definiert werden. Da ich das Script-Element erst zur Laufzeit erzeuge, müssen natürlich auch die Attribute zur Laufzeut erzeugt werden. Dazu fügen wir diese Zeilen vor dem Aufruf von document.body.append ein:

var aia = document.createAttribute('data-isso-avatar');
aia.value = 'false';
s.setAttributeNode(aia);
var aiv = document.createAttribute('data-isso-vote');
aiv.value = 'false';
s.setAttributeNode(aiv);

Mit dieser Lösung bleibt das Datenvolumen bei einem normalen Pageload klein, und nur, wenn kommentiert werden soll, wird auch das große JavaScript nachgeladen. Was mich an dieser Lösung noch stört, sind zwei Dinge:

  1. Der Leser sieht nicht, ob es schon Kommentare gibt. Hier überlege ich aktuell noch, wie ich die Anzahl der bisher schon vorhandenen Kommentare ermitteln kann, bevor ich das JavaScript geladen habe.
  2. Die Sprechblase auf der Seite wird auch dann versteckt, wenn das Javscript nicht geladen wurde. Das ist in der Praxis sicher kein wirkliches Problem, da im Fall, dass das JavaScript nicht geladen werden kann, wahrscheinlich die Seite an sich oder der Server einen Knacks hat. Da wäre ein Reload der Seite sicher besser. Aber aus Gründen der Vollständigkeit und weil ich es einfach gern wissen würde, interessiert mich schon, wie ich den HTTP-Response für das Script ermitteln kann.

Zeitnotation

Die Notation von Zeitpunkten kann schon mal zu Verwirrung führen. Bleibt man in einem Kulturkreis, ist es noch recht einfach. Das Datum 02. April 2018 kann jeder Deutsche lesen, wahrscheinlich auch viele Menschen aus anderen Kulturkreisen, sofern sie Januar als Monat (er)kennen. Schwieriger wird es schon, wenn das Datum ohne textuelle Monatsangabe einfach als 02.04.2018 notiert wird. Personen aus dem angelsächsischen Raum werden dies wahrscheinlich als 04. Februar 2018 lesen. Denn sie sind es gewohnt, die Monatsangabe vor die Tagesangabe zu stellen.

In meiner täglichen Arbeit in einem internationalen Team haben wir gelegentlich das Problem, dass Datumsangaben nicht sauber notiert und daher nicht eindeutig erkennbar sind. Zwar haben wir uns auf eine einheitliche Verwendung verständigt, aber manchmal siegt die Gewohnheit und manchmal legen einem Tools Steine in den Weg, weil sie nicht in der Lage sind, das Datum nach unseren Wünschen zu notieren.

Aber wie sieht nun unser gemeinsamer Standard aus, der für Kulturgruppen weltweit lesbar ist? Nun, es gibt mit der ISO 8601 tatsächlich seit längerer Zeit eine ISO-Norm für Datumsangaben. Diese Norm wurde durch die Übernahme in die europäische Norm EN 28601:1992 auch für Deutschland gültig und nennt sich hierzulande ISO 8601:1988.

Mir als Softwerker ist dieses Format schon länger ein Begriff, aber eine Verwendung im Alltag habe ich nie bewusst bemerkt. Daher war ich umso überraschter, als ich in einer sehr guten Zusammenfassung des Formats von Markus Kuhn las, dass dieses Format das Standardformat in Deutschland sei.

In der Wikipedia liest man nun dazu:

Darüber hinaus ist das Datumsformat nach DIN EN 28601 in Deutschland am 1. Mai 1996 zum einzigen normgerechten numerischen Datumsformat (z. B. 1996-05-01) erhoben worden und löste damit das traditionelle Format nach DIN 1355-1 (1.5.1996) ab. Alle Einrichtungen, die im Einflussbereich der DIN-Normen stehen (so auch alle Bildungseinrichtungen und öffentliche Einrichtungen), sind zur Benutzung des neuen Formats angehalten. Allerdings verwendeten große Teile der Bevölkerung im Alltag weiterhin das alte Format, was durch die Neuregelung der DIN 5008 im Jahr 2001 zur Wiederzulassung des üblichen Formats führte, wenn damit keine Missverständnisse entstehen.

Um es meinem in vielen Bereich durch internationale Kontakte geprägten Umfeld zu erleichtern, werde ich daher nur noch das Datum nach ISO8601 verwenden.

Alternierende Navigationselemente mit CSS

Bei einem anderen Blogprojekte verwende ich ein Theme, das auf einem zweispaltiegen Layout aufbaut. Das Theme ist responsiv, es nimmt also abhängig von der Größe des Darstellungsfensters eine andere Struktur an, in meinem Fall bei schmalem Screen ein einspaltiges Layout statt eines zweispaltigen. Nun ist es so, dass im zweispaltigen Layout im Menübereich jeweils eine Liste mit Kategorien und Tags anzeigt werden, die ich im einspaltigen Layout wegen ihrer Position über dem Inhalt nicht mehr anzeigen möchte. Andernfalls müsste der User zu lange scrollen, um beim eigentlichen Inhalt anzukommen.

Nach etwas ratlosem Rumprobieren und CSS-Doku lesen bin ich über das display-Property gestolpert. Damit konnte ich dann etwas umständlich, da ich die Klasse sowohl an der Überschrift als auch an der Liste anhängen musste, die längeren Teile verstecken:

@media (max-width: 800px) {
  .horizontal { display: none; }
}
{% if DISPLAY_CATEGORIES_ON_MENU and categories %}
    <h2 class="horizontal">Kategorien</h2>
    <ul class="navbar horizontal">
        {% for cat, null in categories %}
            <li{% if cat == category %} class="active"{% endif %}>
                <a href="{{ SITEURL }}/{{ cat.url }}">{{ cat }}</a>
            </li>
        {% endfor %}
    </ul>
{% endif %}

display: none ist in diesem Fall visibility: hidden vorzuziehen, da so vermieden wird, dass die versteckten Elemente Platz im Layout belegen. Siehe dazu auch den Abschnitt Hide an Element - display:none or visibility:hidden auf der Seite der W3school.

Als das Verstecken funktioniert hat, kam auch schnell die Idee auf, im einspaltigen Layout etwas Anderes anzeigen zu lassen. Daher habe ich ein inverses media-Tag erstellt und damit die Menüeinträge dekoriert:

@media (min-width: 801px) {
  .vertical { display: none; }
}
<nav class="nav">
    <ul class="list-bare">

        {% for title, link in MENUITEMS %}
            <li><a class="nav__link" href="{{ link }}">{{ title }}</a></li>
        {% endfor %}

        {% if DISPLAY_PAGES_ON_MENU and pages %}{% for p in pages %}
            <li><a class="nav__link" href="{{ SITEURL }}/{{ p.url }}">{{ p.title }}</a></li>
        {% endfor %}{% endif %}

        {% if DISPLAY_CATEGORIES_ON_MENU and categories %}
            <li class="vertical"><a class="nav__link" href="/categories.html">Kategorien</a></li>
        {% endif %}

        {% if DISPLAY_TAGS_ON_MENU and tags %}
            <li class="vertical"><a class="nav__link" href="/tags.html">Tags</a></li>
        {% endif %}
    </ul>
</nav>

Funktioniert prima. Ich bin mir zwar nicht sicher, ob das korrektes und sinnvolles CSS ist und ob man dies eigentlich anders macht, daher bin ich über Hinweise immer dankbar. Was mich noch ein wenig ärgert ist die doppelte Verwendung der horizontal-Klasse für Überschrift und Liste. Ich muss noch mal tiefer in die CSS-Doku eintauchen um zu sehen, ob man dies nicht auch eleganter lösen kann.

Grundig disqualifiziert sich selbst

Vor einiger Zeit habe ich ein Gerät von Grundig gekauft, welches mit einem Akku ausgestattet ist. Nun hat der Akku nach erstaunlich wenigen Ladezyklen die Flügel gestreckt. Das ist zwar ärgerlich, aber sicherlich verschmerzbar, denn man bekommt heutzutage ja fast an jeder Straßenecke die exotischsten Akkus angeboten. Leider lassen sich von den sechs Schrauben im Gehäuse nur fünf entfernen, da die sechste einen Innendreikant aufweist, für den wohl die wenigsten ein passendes Werkzeug vorrätig halten.

Liebe Firma Grundig: Warum? Ich kann es ja verstehen, dass man verschiedene Sollbruchstellen einbaut, um z.B. Sicherheitsbestimmungen zu erfüllen. Aber was überhaupt nicht geht, ist es, zu verhindern, dass Verschleißteile, zu denen der Akku offensichtlich gehört, getauscht werden können. Statt eines neuen Akkus muss ich mir nun ein neues Gerät kaufen. Aber, liebe Firma Grundig, seien sie versichert, dass das neue Gerät nicht aus ihrem Hause stammen wird.

Kein Web-Interface nach Netgear NAS Update

Aus Gründen habe ich mich in das Web-Interface meines Netgear ReadyNAS eingeloggt. Direkt nach dem Login zeigte es mir eine Benachrichtigung über eine neue Version des Betriebssystems, welche ich direkt installieren ließ. Leider war nach dem Update das Web-Interface nicht mehr verfügbar. Er zeigt zwar den Netgear-Splashscreen, und auch die Authentifizierung erfolgte, aber anschließend wurde die Admin-Oberfläche nicht gelanden. Stattdessen wurde eine Fehlerseite angezeigt, die auf eine nicht hilfreiche Supportseite verlinkte.

Da die Kiste prinzipiell ja erreichbar war und sich auch Shares mounten ließen, konnte es eigentlich keine große Sache sein. Also per ssh verbinden und mal im Log schauen. Stellt sich raus, dass der apache über das php-Modul stolpert:

apache2[2555]: apache2: Syntax error on line 244 of /etc/apache2/apache2.conf: Syntax error on line 1 of /etc/apache2/mods-enabled/php5.load: Cannot load /usr/lib/apache2/modules/libphp5.so into server: /usr/lib/apache2/modules/libphp5.so: cannot open shared object file: No such file or directory

systemd[1]: apache2.service: Control process exited, code=exited statAus=1

Netterweise reicht es, /etc/apache2/apache2.conf und /etc/apache2/apache2.load zu löschen, um dem apache wieder auf die Füße zu helfen. Nun halt ohne PHP, aber das scheint nicht benötigt zu werden.

Python iterator in nested types

Will man in Python einen Iterator für eine Container-Klasse erstellen, deren Elemente wiederum Instanzen der Container-Klasse sind, so kann dieser Iterator einfach so erstellt werden:

class Container:

    def __init__(self):
        self.list = []

    def __iter__(self):
        for element in self.list:
            if isinstance(element, Container):
                for subelement in element:
                    yield subelement
            else:
                yield element

As simple as that!

File Access auf Netgear NAS

Ich habe seit einien Jahren ein kleines NAS, um dort Musik, Bilder, Backups und so weiter abzulegen. Nun war es so, dass sich anfänglich die Rechte nicht so sauber konfigurieren ließen, wie man es sich eigentlich wünscht. Und so kam es, dass ein anderer User des Systems nicht auf die Bilder zugreifen konnte, die ich auf dem NAS gespeichert habe und ich umgekehrt nicht auf die Bilder des anderen Users. Dies blieb leider auch so, nachdem die Software in der Lage war, ein sauberes Gruppenkonzept für die Rechtevergabe anzubieten. Damals vermutete ich, dass bereits gesetzte Berechtigungen nicht aktualisiert werden, wenn man die Berechtigungen im Web-Frontend anpasst. Irgendwie habe ich mich aber nicht eingehend mit dem Problem beschäftigt.

Zufällig ergab es sich nun, dass ich auf dem NAS unterwegs war und da dachte ich mir, es wäre ja mal eine gute Gelegeneheit, auch bei die Rechte zu schauen. Im ersten Schritt habe ich mal das klassische ls bemüht. Diese zeigte aber an, dass die Gruppe, in der beide Benutzer sind, eigentlich die korrekten Rechte hätte:

$ ls -dal HDR
drwxrwx---+ 1 pvitt users 258 Nov 13  2013 HDR

Schaut man sich das an, sollte man meinen, dass die Gruppe user Leserechte hätte. Dem ist aber nicht so. Nach einigem Überlegen hat mich dann das kleine + auf die ACLs gebracht. Selbst hatte ich damit noch nicht viele Berührungen (zumindest in der Linux-Welt), daher musste ich erst mal nachlesen. Wie so oft, kam mir hier das ArchLinux-Wiki zu Hilfe. Im ersten Schritt habe ich mir die ACLs für einen Datei anzeigen lassen:

$ getfacl Moi.JPG
# file: Moi.JPG
# owner: pvitt
# group: users
user::rwx
user:admin:rwx
user:pvitt:rwx
group::---
group:admin:rwx
group:users:---
mask::rwx
other::---

Dort sieht man schön, dass sowohl der admin als auch mein User auf die Datei zugreifen dürfen. Auch die admin-Gruppe hat Zugriff. Die interessante Gruppe user allerdings nicht. Im Arch-Wiki ist anschaulich beschrieben, wie man nun für alle Dateien den Schreib- und Lese-Zugriff für eine Gruppe freigibt:

$ setfacl -m "g:users:rwx" -R *

Im Anschluss sieht die Sache dann so aus:

$ getfacl Moi.JPG
# file: Moi.JPG
# owner: pvitt
# group: users
user::rwx
user:admin:rwx
user:pvitt:rwx
group::---
group:admin:rwx
group:users:rwx
mask::rwx
other::---

Ziel erreicht, Zugriff für alle Mitglieder der Gruppe user freigegeben.

Taskwarrior Context, extended

Wie man Taskwarrior auf einem uberspace installiert, hatte ich ja schon geschrieben. Dort erwähnte ich schon, dass ich Taskwarrior nicht nur für die private Aufgabenverwaltung nutze, sondern auch dienstliche Tasks damit verwalte. Nun ist Taskwarrior allerdings so aufgebaut, dass alle Tasks einer Taskwarrior-Instanz in einer gemeinsamen Datensenke landen. In meinem Fall ist das der Taskserver, der auf dem uberspace läuft. Schaue ich nun zu Hause meine Taskliste an, sehe ich auch alle dienstlichen Tasks, im Büro auch alle privaten. Nicht sehr schön.

Um diesem Problem etwas entgegenzusteuern, hat das Taskwarrior-Team Contexts eingeführt. Dabei handelt es sich im Grunde genommen um einen permanenten Filter, der bei jedem Kommando angewendet wird. Ein Beispiel: Ich möchte einen Context mit dem Namen work, der nur Tasks anzeigt, die das Tag +work haben. Das Kommando task context define work +work erstellt ebendieses Context. In der Konfigurationsdatei wird ein solcher Context ganz einfach mit der Zeile context.work=+work hinterlegt. Um diesen Context nun zu nutzen, muss er noch per task context work aktiviert werden. Rufe ich nun task list auf, werden nur noch die Tasks aufgelistet, die mit dem Tag +work versehen sind.

Prinzipiell eine super Sache. Nach einiger Gewöhnungszeit stieß ich allerdings schnell an die Grenze, dass mir dieses Konzept etwas zu unflexibel ist. Wenn ich im Büro sitze und mal eben schnell etwas in meiner privaten Taskliste nachschauen möchte, muss ich den Context von work nach private wechseln, dann mein Kommando ausführen, um dann wieder von private zurück nach work zu wechseln. Das ist gelegentlich in Ordnung, macht man das aber häufiger, wird es schnell nervig.

Auf der Suche nach einer Lösung für dieses Komfortproblem wurde ich auf der Taskwarrior-Mailingliste in eine Richtung geschubst, die ich gar nicht auf dem Schirm hatte. Dirk Deimke schlug vor, diese Context Peeking mit Hilfe eines Shell-Scripts zu lösen. Nun bin ich mit meinen gut drei Jahren Linux-Erfahrung ja noch eher ein Linux-Kindergartenkind, daher bin ich selbst gar nicht auf diese Idee gekommen, aber wie das mit Kindergartenkindern so ist, fangen sie sofort an zu spielen, wenn man ihnen ein neues Spielzeug gibt. So habe ich es auch gemacht.

Das resultierende Shell-Script sieht nun so aus:

# Make task work with contexts like I want it to
task () {
  # get the task binary to avoid recurrence
  tb=`which task`
  # extract the selected context by matching the regex
  # context has to be the first argument and consist only
  # of alphanumeric characters
  context=`echo $1 | egrep -o "(=)[[:alnum:]]*"`
  # check whether there was a = character
  if [ -n "$context" ]
  then
    # remove the = char
    context=`echo $context | cut -c 2-`
    # check whether a context name was given
    if [ -n "$context" ]
    then
      # name given, temporarily set this context
      $tb rc.context:$context "${@:2}"
    else
      # no name given, temporarily set no context
      $tb rc.context:none "${@:2}"
    fi
  else
    # no = char, so just call task with all arguments
    $tb "${@}"
  fi
}

Was macht das Script nun? Zum einen habe ich die Möglichkeit, beim einem Aufruf von Taskwarrior einen bestimmten Context vorzugeben. Dazu nutze ich die Syntax =Context. Will ich also auf der Arbeit kurz in meine private Liste schauen, verwende ich den Aufruf task =private list.

Zusätzlich zur Wahl eines bestimmten Contexts kann ich allerdings auch das Context-Feature temporär ausschalten. Dazu verwende ich =ohne Contextnamen. Der Aufruf task = list zeigt mir somit alle Tasks, unabhängig ihres Contexts, an.

Bisher hat mir dieses simple Script schon viele gute Dienste geleistet. Und außerdem hat es mir wieder einen weiteren Teil der Linux-/Unix-Welt nähergebracht.