Login-Name des Autoren verschlüsseln

Schöner wäre es wenn man dafür ein Plugin (oder zumindest etwas für die functions.php) hätte. Also mal kurz überlegen was wir dazu benötigen:

  1. Der Login-Name im Link muss verschlüsselt werden
  2. Der verschlüsselte Login-Name muss entschlüsselt werden bevor WordPress irgendwelche Abfragen startet und an WordPress zurück gegeben werden

Huh! Nur zwei Sachen? Ist ja schnell gemacht, also los!

Das Plugin

Wir benötigen also lediglich zwei Funktionen, fangen wir mit dem Verschlüsseln an. Die Funktion get_author_posts_url() bietet zum Glück einen Filter an den wir nutzen können. add_filter('author_link', 'author_crypto') ist unser Startpunkt für die Funktion. Nun brauchen wir noch eine Funktion die den Login-Namen verschlüsselt.
Der Filter liefert uns den kompletten Link, dass heisst, wir müssen uns den Login-Namen (user_nicename) aus den Link angeln, ihn verschlüsseln und dann den Link neu zusammen stellen.

add_filter('author_link', 'author_crypto');

function author_crypto ($link){
	global $wp_rewrite;

	// fetch the user_nicename from link
	preg_match( "/.*\/(.*)?\//", $link, $parts );
	// cipher the user_nicename
	$author_nicename = md5( $parts[1] );

	// get permalink structur
	$perma_link = $wp_rewrite->get_author_permastruct();
		// if we did not use a permalink structur, there is nothing to do. return the link unmodified
		if( empty( $perma_link ) )
			return $link;	

	// replace and rebuild the link
	$link = str_replace('%author%', $author_nicename, $perma_link);
	$link = home_url( user_trailingslashit( $link ) );

	return $link;
}

Damit hätten wir Teil 1 unserer Aufgabe bereits gelöst. Fährt man nun mit der Maus in den Metadaten des Posts über den Autoren-Namen, so sieht man nicht mehr einen Link wie z.B. http://www.example.com/artikel-author/MyLoginName/, sondern in etwa so etwas: http://www.example.com/artikel-author/be42eb98b7621ccff041eecb19f94bd5. Klickt man diesen Link an, dann bekommt man eine 404 – Nicht Gefunden Seite. Ist ja auch klar, denn einen Autor mit den Namen be42eb98b7621ccff041eecb19f94bd5 oder einer solchen ID gibt es nicht.

Wir müssen also aus be42eb98b7621ccff041eecb19f94bd5 erst einmal wieder einen “richtigen” Login-Namen machen an dem WordPress den Autor identifizieren kann. Und vor allem müssen wir dies machen bevor WordPress irgend welche Abfragen startet.

Die Query

Ich habe etwas länger nach einer Stelle gesucht wo ich mich diesbezüglich einhaken kann. Hier kommt eigentlich nur eine Stelle in Frage und zwar kurz nachdem man auf den Link geklickt hat. Klickt man einen Link in WordPress an, dann werden verschiedene Prozesse in Gang gesetzt. Zum einen wird aus dem Link mittels wp_rewrite eine Abfrage erstellt. Zum anderen wird die Abfrage durchgeführt.
Die Rewrite-Rules bieten kaum oder gar keine Möglichkeiten hier einzugreifen. Sinnvoller ist es da schon die Abfrage (query) abzufangen, ggf. zu modifizieren und die modifizierte Abfrage zurück zu geben. Und tatsächlich bietet WordPress einen Filter ‘query‘ an. Überlegen wir uns also kurz was wir mit der query die wir abfangen anstellen müssen.

Der Filter ‘query’ greift auf alle Abfragen zu die in WordPress durchgeführt werden. Ob sie nun etwas mit den Benutzernamen zu tun haben oder nicht. Punkt 1: Feststellen ob überhaupt ein Benutzername abgefragt wird.
Dann gibt es natürlich noch Abfragen in denen der Benutzername nicht von uns verschlüsselt wurde. Punkt 2: Feststellen ob es sich um einen verschlüsselten Benutzernamen handelt.
Ist dies der Fall, müssen wir feststellen ob der verschlüsselte String (Hash-Wert) überhaupt ein Benutzernamen darstellt oder nicht. Punkt 3: Hash-Wert einem Benutzernamen zuordnen.
Entspricht der Hash-Wert einem Benutzernamen, müssen wir die query modifizieren und den Hash-Wert durch den Benutzernamen ersetzen. Punkt 4: Die query modifizieren
Alles zusammen noch mal schön übersichtlich:

  1. Feststellen ob die Abfrage einen Benutzernamen betrifft
  2. Ist der Benutzername verschlüsselt?
  3. Hash-Wert einem Benutzernamen zuordnen
  4. Wenn ein Benutzername gefunden wurde, query modifizieren
add_filter('query', 'author_decrypt');

function author_decrypt( $query ){

	global $wpdb;

	// fetch user_nicename from query
	preg_match( "/user_nicename = '(.*)'/", $query, $md5 );

	// if $md5 is empty, there was no match and nothing is do to.
	// if $md5 isn't empty, we have to do some things
	if( !empty( $md5 ) ){

		// we found a md5-hash or a normal user_nicename. let's check what it is
		$user_md5 = $md5[1];

		// is this user_nicename a ciphered user_nicename?
		//get all normal (non ciphered) user_nicenames
		$users = $wpdb->get_col( "SELECT user_nicename FROM {$wpdb->users}" );

		// no, $user_md5 is a normal user_nicename, it is in the array with the user_nicenames.
		// so we return the unmodified query
		if( in_array( $user_md5, $users ) )
	 		return $query;

	 	// yes, it is a md5-hash (or something else)
	 	// try to decipher the md5-hash
	 	$user_nicename = '';

	 	foreach( $users as $user ){

	 		// if the found md5-hash ($user_md5) match the md5-hash of the user_nicename (md5( $user ))
	 		// $user_md5 is the md5-hash of this user_nicename. break the loop and use this user_nicename
	 		if( $user_md5 == md5( $user ) ){
	 			$user_nicename = $user;
	 			break;
	 		}
	 	}

	 	// we found a matching user_nicename, so we replace it in the query
	 	if( '' != $user_nicename ){
	 		$query = str_replace($user_md5, $user_nicename, $query);
	 	}

		// if we did not find a matching user_nicename, nothing will be modified
	}

	// return the query
	// if a user_nicename was found, the query was modified. we replace the md5-hash with the matching user_nicename
	// if we did not found a matching user_nicename, the query is still unmodified. WordPress should handle this problem. mostly with a "404 - not found" error

	return $query;
}

Ich denke der Code ist soweit kommentiert das ich nicht auf jede einzelne Zeile eingehen muss (wer Probleme mit meinem Pidgin-English hat, fragt einfach Google Translate ;) )
Aber man sollte zumindest erwähnen das der Code alles andere als performant ist. Die zusätzliche Datenbank-Abfrage stellt dabei noch das geringste Problem dar. Viel aufwendiger ist es jeden Benutzernamen in einen Hash-Wert umzuwandeln und diesen mit den gefundenen Hash-Wert zu vergleichen. Leider geht es nicht anders, da man einen MD5-Hash nicht wieder in einen String (Benutzernamen) umwandeln kann. Man muss also alle Benutzer abfragen, aus den jeweiligen Benutzernamen einen MD5-Hash erzeugen und diesen mit den gefundenen vergleichen.

Ich sehe das jedoch nicht als all zu problematisch an da es keine Funktion ist die bei jeden Seitenaufruf aufgerufen wird. Umgehen kann man dieses Problem indem man die Hash-Werte z.B. in den User-Metas speichert und erst einmal diese abfragt ob ein entsprechender Hash-Wert bereits existiert. Eine andere Möglichkeit wäre ein anderes Krypto-Verfahren zu verwenden das erlaubt aus dem verschlüsselten String wieder einen Klartext-String zu erzeugen.

Das Plugin, welches durchaus auch in der functions.php abgelegt werden kann, soll im Grunde genommen nur eine Anregung sein. Es erhebt keinen Anspruch auf Vollständigkeit, vollständige Funktionalität oder Fehlerfreiheit. Deswegen stelle ich es hier auch nur zum experimentieren bereit. Den vollständigen Code gibt es im GitHub. Über Anregungen und Verbesserungsvorschlägen freue ich mich natürlich jederzeit.

1 Trackbacks

  1. [...] Ralf hat in seinem Blog auch noch etwas lesenswertes dazu geschrieben. Kategorie: WordPress Schlagworte: loginname, sicherheitslücke, WordPress. ← WP 3.1 – Sicherheitslücke durch Gestaltungsmöglichkeit WP 3 – comment_form() Gestaltung und Ausgabe → [...]

Ein Kommentar

  1. Hi Ralf,

    ein lesenswertes Artikel! :)
    Dazu hab ich meinen Artikel gleich mal durch eine Zeile und einen Link hierher ergänzt.

    Gruß
    Klaus

    Veröffentlicht 11. März 2011 am 15:28 | Permalink