{"id":679,"date":"2010-07-05T17:29:14","date_gmt":"2010-07-05T15:29:14","guid":{"rendered":"http:\/\/blog.benny-baumann.de\/?p=679"},"modified":"2010-07-05T17:51:03","modified_gmt":"2010-07-05T15:51:03","slug":"http-authentifizierung-mit-mantis-ertragbar-machen","status":"publish","type":"post","link":"http:\/\/blog.benny-baumann.de\/?p=679","title":{"rendered":"HTTP-Authentifizierung mit Mantis ertragbar machen"},"content":{"rendered":"<p>Mantis ist an sich ein sehr guter und gerade f\u00fcr Nicht-Informatiker gut geeigneter Bugtracker, der mit ein wenig Kreativit\u00e4t auch gut als Aufgabenverwaltung verwendet werden kann. Selber setze ich Mantis bereits geraume Zeit an verschiedenen Stellen ein; und so ist es nicht verwunderlich, dass nun auch f\u00fcr ein weiteres Projekt Mantis aufzusetzen war.<\/p>\n<p>F\u00fcr dieses Projekt gab es nun aber eine kleine H\u00fcrde, die bei Mantis bereits seit Jahren existiert: <a href=\"http:\/\/www.mantisbt.org\/bugs\/view.php?id=4235\">Die Authentifizierung gegen externe Systeme<\/a> \ud83d\ude09 Mantis besitzt zwar von Haus aus die M\u00f6glichkeit, die Nutzerkonten aus einem LDAP-Server zu beziehen, erledigt dabei aber die Authentifizierung dennoch selber. M\u00f6chte man hingegen, dass Mantis stillschweigend den Nutzer frisst, der z.B. \u00fcber den Apache oder ein Single-SignOn-System wie Shibbolleth angemeldet wurde, bei\u00dft man leider seit Jahren auf Granit. Ohne bei Mantis selber Hand anzulegen, kommt man leider nicht weit.<!--more--><\/p>\n<p>Um also f\u00fcr das vorliegende Projekt die Nutzer \u00fcber ein zentrales Login auch an Mantis anmelden zu k\u00f6nnen, gab es im Internet eine Reihe von Vorschl\u00e4gen. Einer der ausgereiftesten umfasste neben einer kurzen Erkl\u00e4rung auch eine <a href=\"http:\/\/ardvaark.net\/making-mantis-with-basic-authentication-not-suck\">gut nachvollziehbare Anleitung<\/a>, was der Reihe nach zu patchen ist.<\/p>\n<p>Fangen wir also mit einer funktionierenden Mantis-Installation an. Diese wird im Zuge dieser Installation vollst\u00e4ndig auf die externe Authentifizierung \u00fcber den Apache umgestellt. Da bei mir ein eGroupware als Login-Quelle verwendet wird, deute ich die dazu n\u00f6tigen Schritte mit an; andere Login-Systeme funktionieren jedoch analog, wenn Sie Passwort-Hashes liefern, die vom Apachen (d.h. dem Unix-Crypt-Befehl) verstanden werden. In meinem Fall sind dies Salted MD5-Hashes (Die Hashes beginnen mit <code>$1$<\/code>), alternativ funktioniert aber auch das klassische 3DES-Crypt (13-Stellige Hashes) und alle anderen von Crypt unterst\u00fctzen Passw\u00f6rter (siehe Manpages zu crypt). Die Nutzung von Klartext-Passw\u00f6rtern wird vom Apache zwar unterst\u00fctzt, sollte aber aus Sicherheitsgr\u00fcnden <strong>NICHT<\/strong> verwendet werden &#8230;<\/p>\n<p>Da Mantis nach diesem Schritt keine eigene Authentifizierung mehr vornimmt, sei an dieser Stelle zudem darauf hingewiesen, dass allein durch die externe Authentifizierung durch den Apache ALLE unberechtigten Benutzer bereits ausgesperrt werden m\u00fcssen. Die Vergabe von Rechten, d.h. die Authorisierung) erfolgt weiterhin \u00fcber Mantis (den Stress, das uzubauen tue ich mir nicht an).<\/p>\n<p>Vorgepl\u00e4nkel also gekl\u00e4rt, fangen wir mit der Anpassung von Mantis an. Der erste Schritt ist die Erg\u00e4nzung der Authentication API von Mantis. Diese befindet sich im Verzeichnis \/core in der Datei authentication_api.php. In dieser Datei suchen wir zuerst die Funktion auth_attemp_login.<\/p>\n<pre lang=\"php\" escaped=\"true\">        # --------------------\r\n        # Attempt to login the user with the given password\r\n        #  If the user fails validation, false is returned\r\n        #  If the user passes validation, the cookies are set and\r\n        #   true is returned.  If $p_perm_login is true, the long-term\r\n        #   cookie is created.\r\n       \u00a0function auth_attempt_login( $p_username, $p_password, $p_perm_login=false ) {\r\n                $t_user_id = user_get_id_by_name( $p_username );\r\n\r\n                $t_login_method = config_get( 'login_method' );\r\n\r\n                if ( false === $t_user_id ) {\r\n                        if ( BASIC_AUTH == $t_login_method ) {\r\n                                # attempt to create the user if using BASIC_AUTH\r\n                                $t_cookie_string = user_create( $p_username, auth_generate_random_password(), $_SERVER['AUTHENTICATE_CONTACT_EMAIL']);\r\n\r\n                                if ( false === $t_cookie_string ) {\r\n                                        # it didn't work\r\n                                        return false;\r\n                                }\r\n\r\n                                # ok, we created the user, get the row again\r\n                                $t_user_id = user_get_id_by_name( $p_username );\r\n\r\n                                if ( false === $t_user_id ) {\r\n                                        # uh oh, something must be really wrong\r\n\r\n                                        # @@@ trigger an error here?\r\n\r\n                                        return false;\r\n                                }\r\n                        } else {\r\n                                return false;\r\n                        }\r\n                }\r\n\r\n                # check for disabled account\r\n                if ( !user_is_enabled( $t_user_id ) ) {\r\n                        return false;\r\n                }\r\n\r\n                # max. failed login attempts achieved...\r\n                if( !user_is_login_request_allowed( $t_user_id ) ) {\r\n                        return false;\r\n                }\r\n\r\n                $t_anon_account = config_get( 'anonymous_account' );\r\n                $t_anon_allowed = config_get( 'allow_anonymous_login' );\r\n\r\n                # check for anonymous login\r\n                if ( !( ( ON == $t_anon_allowed ) &amp;&amp; ( $t_anon_account == $p_username)  ) ) {\r\n                        # anonymous login didn't work, so check the password\r\n\r\n                        # Since basic auth is authoratative, and assuming that this page\r\n                        # cannot be viewed unless it has already succeeded, then don't\r\n                        # bother checking a password, and just do a valid login.\r\n                        # -- BCV\r\n                        if (BASIC_AUTH != $t_login_method &amp;&amp; !auth_does_password_match( $t_user_id, $p_password ) ) {\r\n                                user_increment_failed_login_count( $t_user_id );\r\n                                return false;\r\n                        }\r\n                }\r\n\r\n                # ok, we're good to login now\r\n\r\n                # increment login count\r\n                user_increment_login_count( $t_user_id );\r\n\r\n                user_reset_failed_login_count_to_zero( $t_user_id );\r\n                user_reset_lost_password_in_progress_count_to_zero( $t_user_id );\r\n\r\n                # set the cookies\r\n                auth_set_cookies( $t_user_id, $p_perm_login );\r\n                auth_set_tokens( $t_user_id );\r\n\r\n                return true;\r\n        }\r\n<\/pre>\n<p>Wesentlich ist hierbei die Zeile mit dem Aufruf der Funktion user_create. Diese verwendet urspr\u00fcnglich beim Anlegen neuer Benutzerkonten das von der HTTP-Authentifizierung \u00fcbergebene Passwort. Da dieses aber in der Datenbank im Klartext abgelegt wird, wird der Aufruf ein wenig abgewandelt, so dass ein zuf\u00e4lliges Passwort erzeugt wird. Da dieses sp\u00e4ter eh ignoriert werden wird, besteht hier kein Grund, sich etwas anderes einfallen zu lassen. Ferner sorgt dieser Patch daf\u00fcr, dass der Apache \u00fcber eine Server-Variable die Email-Adresse f\u00fcr den anzulegenden Account festlegen kann, sollte der Account noch nicht existieren. Dazu aber sp\u00e4ter mehr.<\/p>\n<p>Eine zweite \u00c4nderung befindet sich in der Abfrage von auth_does_password_match(). Diese wird durch die vorangestellte Abfrage f\u00fcr BASIC_AUTH \u00fcbersprungen, womit allein die externe Authentifizierung in diesem Fall beachtet wird.<\/p>\n<p>Nachdem nun die Anmeldung nun die Passw\u00f6rter unter bestimmten Umst\u00e4nden ignoriert, geht es nun darum, die korrekten Nutzerdaten an Mantis weiterzuleiten, sowie eien Reihe von Unsch\u00f6nheiten der Session-Verwaltung auszub\u00fcgeln. Hierzu \u00f6ffnen wir als n\u00e4chstes die Datei login.php im Hauptverzeichnis der Mantisinstallation.<\/p>\n<pre lang=\"php\" escaped=\"true\">&lt;?php\r\n\r\n        # Check login then redirect to main_page.php or to login_page.php\r\n\r\n        require_once( 'core.php' );\r\n\r\n        $f_username\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 = gpc_get_string( 'username', '' );\r\n        $f_password\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 = gpc_get_string( 'password', '' );\r\n        $f_perm_login\u00a0\u00a0         = gpc_get_bool( 'perm_login' );\r\n        $f_return\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 = gpc_get_string( 'return', config_get( 'default_home_page' ) );\r\n        $f_from\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 = gpc_get_string( 'from', '' );\r\n\r\n        if ( BASIC_AUTH == config_get( 'login_method' ) ) {\r\n                $f_username = $_SERVER['REMOTE_USER'];\r\n                $f_password = $_SERVER['PHP_AUTH_PW'];\r\n        }\r\n\r\n        if ( HTTP_AUTH == config_get( 'login_method' ) ) {\r\n                if ( !auth_http_is_logout_pending() ) {\r\n                        if ( isset( $_SERVER['PHP_AUTH_USER'] ) )\r\n                                $f_username = $_SERVER['PHP_AUTH_USER'];\r\n                        if ( isset( $_SERVER['PHP_AUTH_PW'] ) )\r\n                                $f_password = $_SERVER['PHP_AUTH_PW'];\r\n                } else {\r\n                        auth_http_set_logout_pending( false );\r\n                        auth_http_prompt();\r\n                        return;\r\n                }\r\n        }\r\n\r\n        if ( auth_attempt_login( $f_username, $f_password, $f_perm_login ) ) {\r\n                $t_redirect_url = 'login_cookie_test.php?return=' . string_sanitize_url( $f_return );\r\n        } else {\r\n                $t_redirect_url = 'login_page.php?return=' . string_sanitize_url( $f_return ) . '&amp;error=1';\r\n\r\n                if ( HTTP_AUTH == config_get( 'login_method' ) ) {\r\n                        auth_http_prompt();\r\n                        exit;\r\n                }\r\n        }\r\n\r\n        print_header_redirect( $t_redirect_url );\r\n?&gt;<\/pre>\n<p>Neu ist in dieser Datei insbesondere der Teil bzgl. der Nutzerdaten durch \u00dcbernahme dieser aus den Server-Variablen  PHP_AUTH_USER und PHP_AUTH_PW. Das Passwort ist an dieser Stelle zwar eigentlich egal (weil es sp\u00e4ter durch auth_attemp_login() eh ignoriert wird, aber wenn, dann patchen wir hier mal sauber \ud83d\ude09<\/p>\n<p>Nach dem nun die Anmeldung bereits klappen sollte, gilt es nun noch eine Reihe Unsch\u00f6nheiten zu beheben. Die erste dieser Unsch\u00f6nheiten ist der Drang von Mantis, de Benutzer einen Login-Dialog zeigen zu wollen. Diese Unart l\u00e4sst sich durch Uschreiben des zugeh\u00f6rigen Redirects in der index.php (Hauptveryeichnis) aber einfach korrigieren:<\/p>\n<pre lang=\"php\" escaped=\"true\">&lt;?php require_once( 'core.php' ) ?&gt;\r\n&lt;?php\r\n        if ( auth_is_user_authenticated() ) {\r\n                print_header_redirect( config_get( 'default_home_page' ) );\r\n        } else if ( BASIC_AUTH == config_get( 'login_method' ) ) {\r\n                print_header_redirect( 'login.php' );\r\n        } else {\r\n                print_header_redirect( 'login_page.php' );\r\n        }\r\n?&gt;<\/pre>\n<p>Hierbei wird automatisch statt auf die login_page.php auf die login.php weitergeleitet, die f\u00fcr die HTTP-Basierten Authentifizierungsverfahren zust\u00e4ndig ist. Bliebe noch der Logout. Dieser funktioniert bisher nicht, ist aber gerade in einer Umgebung, wo User auch einmal kurz gewechselt werden m\u00fcssen, unverzichtbar. Daher gibt es auch f\u00fcr diese Situation noch den passenden Patch, der in der logout_page.php eingebaut werden muss:<\/p>\n<pre lang=\"php\" escaped=\"true\">&lt;?php\r\n        require_once( 'core.php' );\r\n\r\n        auth_logout();\r\n\r\n        if ( HTTP_AUTH == config_get( 'login_method' ) ) {\r\n                auth_http_set_logout_pending( true );\r\n        }\r\n\r\n        if ( BASIC_AUTH == config_get( 'login_method' ) ) {\r\n                if ( !auth_http_is_logout_pending() ) {\r\n                        auth_http_set_logout_pending( true );\r\n                        header('Status: 401 Authentication required');\r\n                        header('WWW-Authenticate: basic realm=\"MantisBT\"');\r\n                        echo '<a href=\"index.php\">Hier geht\\'s weiter<\/a>';\r\n                } else {\r\n                        auth_http_set_logout_pending( false );\r\n                        header('status: 301 Moved temporarily');\r\n                        header('Location: index.php');\r\n                }\r\n        } else {\r\n                print_header_redirect( config_get( 'logout_redirect_page' ), \/* die *\/ true, \/* sanitize *\/ false );\r\n        }\r\n\r\n?&gt;<\/pre>\n<p>Nach dem nun Mantis f\u00fcr die Nutzung der externen Authentifizierung vorbereitet ist, gilt es nun noch, diese auch bereitzustellen. Da eGroupware, was in meinem Fall die Logins bereitstellt, eine Datenbank besitzt, bietet sich die Nutzung von authn_db f\u00fcr den Apache regelrecht an. Hierf\u00fcr m\u00fcssen wir beim Apache den Datenbanklayer, sowie die zugeh\u00f6rigen Authentifzierungsmodule laden. Dies geht als root auf der Konsole mit<\/p>\n<pre lang=\"bash\">a2enmod dbd authn_dbd<\/pre>\n<p>Danach muss die Authentifizierung noch konfiguriert werden. In einem VHost (ODER, wenn gew\u00fcnscht auch global) muss hierzu die Datenbank-Verbindung zu eGroupware konfiguriert werden.<\/p>\n<pre lang=\"apache\" escaped=\"true\"># mod_dbd configuration\r\nDBDriver        mysql\r\nDBDParams       \"host=localhost user={username} pass={password} dbname={dbnae}\"\r\nDBDMin          4\r\nDBDKeep         8\r\nDBDMax          20\r\nDBDExptime      300<\/pre>\n<p>Dieser Block darf nicht in einem Directory oder Location-Kontext auftauchen, da der Datenbank-Layer des Apache auf VHost-Ebene limitiert ist. Dies st\u00f6rt aber in der Regel nicht weiter. Zu guter Letzt muss nun lediglich noch die eingerichtete Datenbank-Verbindung auch benutzt werden. F\u00fcr das Verzeichnis von Mantis reicht hierf\u00fcr ein Block wie in folgendem Beispiel:<\/p>\n<pre lang=\"apache\" escaped=\"true\">&lt;Location \/mantis&gt;\r\n        # mod_authn_dbd SQL query to authenticate a user\r\n        AuthBasicProvider dbd\r\n        AuthDBDUserPWQuery \"SELECT acc.account_pwd, ab.contact_email FROM egw_accounts AS acc, egw_acl AS acl, egw_accounts AS g, egw_addressbook AS ab WHERE acc.account_id = acl.acl_account AND acl.acl_appname = 'phpgw_group' AND acl.acl_location = -g.account_id AND (g.account_lid = 'Mantis') AND acc.account_type = 'u' AND g.account_type='g' AND acc.account_status = 'A' AND g.account_status = 'A' AND acc.account_lid = %s AND acc.account_id = ab.account_id\"\r\n\r\n        AuthType Basic\r\n        AuthName \"MantisBT\"\r\n\r\n        Require valid-user\r\n&lt;\/Location&gt;\r\n<\/pre>\n<p>Die verwendete Abfrage gew\u00e4hrt nun jedem eGroupware-Benutzer, der in der Gruppe &#8222;Mantis&#8220; ist Zugang zum Bugtracker. Die Vergabe von Berechtigungen anhand von Subgruppen wird in dieser Form NICHT unterst\u00fctzt und l\u00e4sst sich auch SEHR schlecht realisieren.<\/p>\n<p>Was durch diese Konfiguration jedoch sehr leicht realisierbar ist, ist die \u00dcbernahme der eGroupware-Kontakt-Email des jeweiligen Nutzers, womit ich auf die oben erw\u00e4hnte Server-Variable zur\u00fcckkomme, die ich bisher unkommentiert gelassen habe. Diese wird im Zuge der Login-Abfrage durch den Apache gesetzt. Der DBD-Layer erwartet in der ersten Spalte der Abfrage das Passwort; liefert die Abfrage jedoch weitere Spalten, so werden diese als Server-Variablen an die aufgerufene Website \u00fcbergeben: In unserem Fall halt Mantis.<\/p>\n<p>Bliebe eigentlich nur noch eines zu erledigen:<\/p>\n<pre lang=\"bash\" escaped=\"true\">apache2ctl restart<\/pre>\n<p>That&#8217;s it!<\/p>\n<p class=\"wp-flattr-button\"><a href=\"http:\/\/blog.benny-baumann.de\/?flattrss_redirect&amp;id=679&amp;md5=30c410f119fa21bdb15ac1df5d4aee59\" title=\"Flattr\" target=\"_blank\"><img src=\"http:\/\/blog.benny-baumann.de\/wp-content\/plugins\/flattr\/img\/flattr-badge-large.png\" srcset=\"http:\/\/blog.benny-baumann.de\/wp-content\/plugins\/flattr\/img\/flattr-badge-large.png\" alt=\"Flattr this!\"\/><\/a><\/p>","protected":false},"excerpt":{"rendered":"<p>Mantis ist an sich ein sehr guter und gerade f\u00fcr Nicht-Informatiker gut geeigneter Bugtracker, der mit ein wenig Kreativit\u00e4t auch gut als Aufgabenverwaltung verwendet werden kann. Selber setze ich Mantis bereits geraume Zeit an verschiedenen Stellen ein; und so ist es nicht verwunderlich, dass nun auch f\u00fcr ein weiteres Projekt Mantis aufzusetzen war. F\u00fcr dieses [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[29],"tags":[156,291,10,98,290,7,283,13,21],"class_list":["post-679","post","type-post","status-publish","format-standard","hentry","category-software","tag-apache","tag-authentifizierung","tag-debian","tag-developement","tag-egroupware","tag-links","tag-mantis","tag-patch","tag-php"],"_links":{"self":[{"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts\/679","targetHints":{"allow":["GET"]}}],"collection":[{"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=679"}],"version-history":[{"count":7,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts\/679\/revisions"}],"predecessor-version":[{"id":737,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=\/wp\/v2\/posts\/679\/revisions\/737"}],"wp:attachment":[{"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=679"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=679"},{"taxonomy":"post_tag","embeddable":true,"href":"http:\/\/blog.benny-baumann.de\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=679"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}