From 45b142b23dcc0a7c8919f62819d84aca4258a8ca Mon Sep 17 00:00:00 2001 From: Kload Date: Thu, 8 May 2014 09:57:31 +0200 Subject: [PATCH 1/2] [fix] PHP5 CLI dependency + sources in the git repo --- scripts/install | 17 +- sources/LdapAuthentication/.gitignore | 4 + sources/LdapAuthentication/.gitreview | 5 + .../LdapAuthentication.i18n.php | 363 +++ .../LdapAuthentication/LdapAuthentication.php | 2104 +++++++++++++++++ .../LdapAutoAuthentication.php | 124 + sources/LdapAuthentication/README | 1 + .../LdapAuthentication/schema/ldap-mysql.sql | 13 + .../schema/ldap-postgres.sql | 13 + 9 files changed, 2633 insertions(+), 11 deletions(-) create mode 100644 sources/LdapAuthentication/.gitignore create mode 100644 sources/LdapAuthentication/.gitreview create mode 100644 sources/LdapAuthentication/LdapAuthentication.i18n.php create mode 100644 sources/LdapAuthentication/LdapAuthentication.php create mode 100644 sources/LdapAuthentication/LdapAutoAuthentication.php create mode 100644 sources/LdapAuthentication/README create mode 100644 sources/LdapAuthentication/schema/ldap-mysql.sql create mode 100644 sources/LdapAuthentication/schema/ldap-postgres.sql diff --git a/scripts/install b/scripts/install index 1d14e73..2d92ea4 100644 --- a/scripts/install +++ b/scripts/install @@ -17,6 +17,9 @@ db_pwd=$(dd if=/dev/urandom bs=1 count=200 2> /dev/null | tr -c -d '[A-Za-z0-9]' # Use 'mediawiki' as database name and user db_user=mediawiki +# Instal php5-cli dependency +sudo apt-get install php5-cli -y + # Initialize database and store mysql password for upgrade sudo yunohost app initdb $db_user -p $db_pwd -s $(readlink -e ../conf/SQL/mysql.initial.sql) sudo yunohost app setting mediawiki mysqlpwd -v $db_pwd @@ -24,19 +27,11 @@ sudo yunohost app setting mediawiki mysqlpwd -v $db_pwd # Copy files to the right place final_path=/var/www/mediawiki sudo mkdir -p $final_path -sudo wget http://releases.wikimedia.org/mediawiki/1.22/mediawiki-1.22.6.tar.gz -sudo tar xvzf mediawiki-*.tar.gz -sudo mv mediawiki-*/* $final_path -sudo rm -R mediawiki-* +sudo cp ../sources/mediawiki/* $final_path sudo cp ../conf/LocalSettings.php $final_path/ # LDAP Extension -sudo wget https://codeload.github.com/wikimedia/mediawiki-extensions-LdapAuthentication/legacy.tar.gz/REL1_22 -sudo tar -xzf REL1_22 -sudo mkdir $final_path/extensions/LdapAuthentication -sudo mv wikimedia-mediawiki-extensions-LdapAuthentication*/* $final_path/extensions/LdapAuthentication/ -sudo rm -R wikimedia-mediawiki-extensions-LdapAuthentication* -sudo rm REL1_22 +sudo cp -r ../sources/LdapAuthentication $final_path/extensions/ # Change variables in Mediawiki configuration sudo sed -i "s/ynh_wiki_name/$wiki_name/g" $final_path/LocalSettings.php @@ -60,4 +55,4 @@ sudo cp ../conf/nginx.conf /etc/nginx/conf.d/$domain.d/mediawiki.conf # Reload Nginx and regenerate SSOwat conf sudo service nginx reload -sudo yunohost app ssowatconf \ No newline at end of file +sudo yunohost app ssowatconf diff --git a/sources/LdapAuthentication/.gitignore b/sources/LdapAuthentication/.gitignore new file mode 100644 index 0000000..98b092a --- /dev/null +++ b/sources/LdapAuthentication/.gitignore @@ -0,0 +1,4 @@ +.svn +*~ +*.kate-swp +.*.swp diff --git a/sources/LdapAuthentication/.gitreview b/sources/LdapAuthentication/.gitreview new file mode 100644 index 0000000..48f3059 --- /dev/null +++ b/sources/LdapAuthentication/.gitreview @@ -0,0 +1,5 @@ +[gerrit] +host=gerrit.wikimedia.org +port=29418 +project=mediawiki/extensions/LdapAuthentication.git +defaultbranch=master diff --git a/sources/LdapAuthentication/LdapAuthentication.i18n.php b/sources/LdapAuthentication/LdapAuthentication.i18n.php new file mode 100644 index 0000000..26ab30c --- /dev/null +++ b/sources/LdapAuthentication/LdapAuthentication.i18n.php @@ -0,0 +1,363 @@ + 'LDAP authentication plugin with support for multiple LDAP authentication methods', +); + +/** Message documentation (Message documentation) + * @author Fryed-peach + * @author Shirayuki + */ +$messages['qqq'] = array( + 'ldapauthentication-desc' => '{{desc|name=LDAP Authentication|url=http://www.mediawiki.org/wiki/Extension:LDAP_Authentication}}', +); + +/** Afrikaans (Afrikaans) + * @author Naudefj + */ +$messages['af'] = array( + 'ldapauthentication-desc' => 'Uitbreiding vir LDAP-outentisiteit wat die meeste LDAP-outentisiteitsmetodes ondersteun', +); + +/** Gheg Albanian (Gegë) + * @author Mdupont + */ +$messages['aln'] = array( + 'ldapauthentication-desc' => 'plugin LDAP vertetimi me mbështetje për metoda të shumta tek LDAP', +); + +/** Arabic (العربية) + * @author Meno25 + */ +$messages['ar'] = array( + 'ldapauthentication-desc' => 'إضافة تحقيق LDAP بدعم لوسائل تحقيق LDAP متعددة', +); + +/** Asturian (asturianu) + * @author Xuacu + */ +$messages['ast'] = array( + 'ldapauthentication-desc' => "Complemento p'autenticación LDAP con sofitu pa dellos métodos d'autenticación LDAP", +); + +/** Belarusian (Taraškievica orthography) (беларуская (тарашкевіца)‎) + * @author EugeneZelenko + */ +$messages['be-tarask'] = array( + 'ldapauthentication-desc' => 'Дапаўненьне LDAP-аўтэнтыфікацыі з падтрымкай некалькіх мэтадаў аўтэнтыфікацыі LDAP', +); + +/** Breton (brezhoneg) + * @author Fulup + */ +$messages['br'] = array( + 'ldapauthentication-desc' => 'Adveziant gwiriekaat LDAP ennañ meur a hentenn wiriekaat LDAP', +); + +/** Bosnian (bosanski) + * @author CERminator + */ +$messages['bs'] = array( + 'ldapauthentication-desc' => 'Proširenje LDAP autentifikacije sa podrškom za mnoge metode LDAP autentifikacije', +); + +/** Catalan (català) + * @author Paucabot + */ +$messages['ca'] = array( + 'ldapauthentication-desc' => "Connector d'autentificació LDAP amb suport per a diversos mètodes d'autenticació LDAP", +); + +/** Czech (česky) + * @author Mormegil + */ +$messages['cs'] = array( + 'ldapauthentication-desc' => 'Autentizační modul pro LDAP podporující několik autentizačních metod LDAP', +); + +/** German (Deutsch) + * @author Imre + * @author Kghbln + */ +$messages['de'] = array( + 'ldapauthentication-desc' => 'Ermöglicht die LDAP-Authentifizierung mit Hilfe mehrerer Authentifizierungsmethoden', +); + +/** Lower Sorbian (dolnoserbski) + * @author Michawiki + */ +$messages['dsb'] = array( + 'ldapauthentication-desc' => 'Tykac awtentifikacije LDAP z pódpěru za někotare metody LDAP-awtentifikacije', +); + +/** Esperanto (Esperanto) + * @author Blahma + */ +$messages['eo'] = array( + 'ldapauthentication-desc' => 'LDAP-aŭtentiga kromprogramo kun subteno de pluraj LDAP-aŭtentigaj metodoj', +); + +/** Spanish (español) + * @author Translationista + */ +$messages['es'] = array( + 'ldapauthentication-desc' => 'Complemento de autentificación LDAP con apoyo de múltiples métodos de autentificación LDAP', +); + +/** Finnish (suomi) + * @author Centerlink + */ +$messages['fi'] = array( + 'ldapauthentication-desc' => 'LDAP-todentamisliitännäinen useiden LDAP-todennustapojen tuella', +); + +/** French (français) + * @author IAlex + * @author Urhixidur + */ +$messages['fr'] = array( + 'ldapauthentication-desc' => 'Extension d’authentification LDAP prenant en charge de multiples méthodes d’authentification LDAP', +); + +/** Galician (galego) + * @author Toliño + */ +$messages['gl'] = array( + 'ldapauthentication-desc' => 'Complemento de autenticación LDAP con soporte para varios métodos de autenticación LDAP', +); + +/** Swiss German (Alemannisch) + * @author Als-Holder + */ +$messages['gsw'] = array( + 'ldapauthentication-desc' => 'LDAP-Authentifizierigs-Plugin mit Unterstitzig fir multipli LDAP-Authentifizierigs-Merthode', +); + +/** Hebrew (עברית) + * @author YaronSh + */ +$messages['he'] = array( + 'ldapauthentication-desc' => 'תוסף אימות LDAP עם תמיכה במספר שיטות LDAP לאימות', +); + +/** Upper Sorbian (hornjoserbsce) + * @author Michawiki + */ +$messages['hsb'] = array( + 'ldapauthentication-desc' => 'Tykač awtentifikacije LDAP z podpěru za wjacore metody LDAP-awtentifikacije', +); + +/** Hungarian (magyar) + * @author Glanthor Reviol + */ +$messages['hu'] = array( + 'ldapauthentication-desc' => 'LDAP hitelesítési bővítmény többféle LDAP azonosítási módszer támogatásával', +); + +/** Interlingua (interlingua) + * @author McDutchie + */ +$messages['ia'] = array( + 'ldapauthentication-desc' => 'Plugin pro authentication LDAP con supporto pro multiple methodos de authentication LDAP', +); + +/** Indonesian (Bahasa Indonesia) + * @author IvanLanin + */ +$messages['id'] = array( + 'ldapauthentication-desc' => 'Pengaya otentikasi LDAP dengan dukungan untuk berbagai metode otentikasi LDAP', +); + +/** Italian (italiano) + * @author HalphaZ + */ +$messages['it'] = array( + 'ldapauthentication-desc' => 'Plugin di autenticazione LDAP con supporto a diversi metodi di autenticazione LDAP', +); + +/** Japanese (日本語) + * @author Aotake + * @author Shirayuki + */ +$messages['ja'] = array( + 'ldapauthentication-desc' => '複数の LDAP 認証方式対応の LDAP 認証プラグイン', +); + +/** Korean (한국어) + * @author 아라 + */ +$messages['ko'] = array( + 'ldapauthentication-desc' => '여러 LDAP 인증 방법에 대해 지원하는 LDAP 인증 플러그인', +); + +/** Colognian (Ripoarisch) + * @author Purodha + */ +$messages['ksh'] = array( + 'ldapauthentication-desc' => 'Dat Zohsatzprojramm för et Enlogge övver LDAP löht ungerscheidlijje Mettoohde zoh, för et Prööfe, wä wä es.', +); + +/** Luxembourgish (Lëtzebuergesch) + * @author Robby + */ +$messages['lb'] = array( + 'ldapauthentication-desc' => 'Authentifikatiouns-Plugin fir LDAP mat Ënnerstëtzung fir multipel LDAP Authentifikatiouns-Methoden', +); + +/** Macedonian (македонски) + * @author Bjankuloski06 + */ +$messages['mk'] = array( + 'ldapauthentication-desc' => 'LDAP приклучок за потврдување со поддршка за повеќе методи на LDAP потврдување', +); + +/** Malay (Bahasa Melayu) + * @author Anakmalaysia + */ +$messages['ms'] = array( + 'ldapauthentication-desc' => 'Pemalam pengesahan LDAP dengan sokongan untuk berbilang kaedah pengesahan LDAP', +); + +/** Norwegian Bokmål (norsk bokmål) + * @author Nghtwlkr + */ +$messages['nb'] = array( + 'ldapauthentication-desc' => 'Programutvidelse for LDAP-autentisering med støtte for flere LDAP-autentiseringsmetoder', +); + +/** Dutch (Nederlands) + * @author Siebrand + */ +$messages['nl'] = array( + 'ldapauthentication-desc' => 'LDAP-authenticatieplug-in met ondersteuning voor meerdere LDAP-authenticatiemethoden', +); + +/** Occitan (occitan) + * @author Cedric31 + */ +$messages['oc'] = array( + 'ldapauthentication-desc' => "Plugin d'autentificacion LDAP amb supòrt de metòdes d'autentificacion LDAP multiples", +); + +/** Polish (polski) + * @author Sp5uhe + */ +$messages['pl'] = array( + 'ldapauthentication-desc' => 'Wtyczka autoryzacji użytkowników z użyciem LDAP ze wsparciem dla wielu metod autoryzacji', +); + +/** Piedmontese (Piemontèis) + * @author Borichèt + * @author Dragonòt + */ +$messages['pms'] = array( + 'ldapauthentication-desc' => "Plugin për l'autenticassion LDAP con apògg për vàire manere d'autenticassion LDAP", +); + +/** Portuguese (português) + * @author Hamilton Abreu + */ +$messages['pt'] = array( + 'ldapauthentication-desc' => "''Plugin'' de autenticação LDAP, com suporte para vários métodos de autenticação", +); + +/** Brazilian Portuguese (português do Brasil) + * @author Giro720 + */ +$messages['pt-br'] = array( + 'ldapauthentication-desc' => "''Plugin'' de autenticação LDAP, com suporte para vários métodos de autenticação", +); + +/** tarandíne (tarandíne) + * @author Joetaras + */ +$messages['roa-tara'] = array( + 'ldapauthentication-desc' => "plugin de autendicazione LDAP cu 'u supporte pe autendicaziune multeple de metode LDAP", +); + +/** Russian (русский) + * @author Александр Сигачёв + */ +$messages['ru'] = array( + 'ldapauthentication-desc' => 'Плагин LDAP-аутентификации с поддержкой нескольких методов проверки подлинности LDAP', +); + +/** Slovak (slovenčina) + * @author Helix84 + */ +$messages['sk'] = array( + 'ldapauthentication-desc' => 'Zásuvný modul na autentifikáciu prostredníctvom LDAP s podporou viacerých metód LDAP', +); + +/** Serbian (Cyrillic script) (српски (ћирилица)‎) + * @author Михајло Анђелковић + */ +$messages['sr-ec'] = array( + 'ldapauthentication-desc' => 'Плагин за LDAP ауторизацију, са подршком за више метода LDAP ауторизације', +); + +/** Serbian (Latin script) (srpski (latinica)‎) + */ +$messages['sr-el'] = array( + 'ldapauthentication-desc' => 'Plagin za LDAP autorizaciju, sa podrškom za više metoda LDAP autorizacije', +); + +/** Swedish (svenska) + * @author Boivie + */ +$messages['sv'] = array( + 'ldapauthentication-desc' => 'LDAP-autentiseringsplugin med stöd för flera LDAP-autentiseringsmetoder', +); + +/** Tagalog (Tagalog) + * @author AnakngAraw + */ +$messages['tl'] = array( + 'ldapauthentication-desc' => 'Pampasak na pangpagpapatotoo ng LDAP na may suporta para sa maramihang mga pamamaraan ng pagpapatotoo ng LDAP', +); + +/** Turkish (Türkçe) + * @author Vito Genovese + */ +$messages['tr'] = array( + 'ldapauthentication-desc' => 'Birden çok LDAP kimlik doğrulama yöntemini destekleyen LDAP kimlik doğrulama eklentisi', +); + +/** Ukrainian (українська) + * @author Ytsukeng Fyvaprol + */ +$messages['uk'] = array( + 'ldapauthentication-desc' => 'Плагін LDAP-аутентифікації з підтримкою декількох методів перевірки автентичності LDAP', +); + +/** Vietnamese (Tiếng Việt) + * @author Minh Nguyen + */ +$messages['vi'] = array( + 'ldapauthentication-desc' => 'Phần bổ trợ xác thực LDAP hỗ trợ nhiều phương pháp xác thực LDAP', +); + +/** Simplified Chinese (中文(简体)‎) + * @author Yanmiao liu + */ +$messages['zh-hans'] = array( + 'ldapauthentication-desc' => '具有多种LDAP认证方法支持的LDAP认证插件', +); + +/** Traditional Chinese (中文(繁體)‎) + * @author Anakmalaysia + */ +$messages['zh-hant'] = array( + 'ldapauthentication-desc' => '具有多種LDAP認證方法支持的LDAP認證外掛程式', +); diff --git a/sources/LdapAuthentication/LdapAuthentication.php b/sources/LdapAuthentication/LdapAuthentication.php new file mode 100644 index 0000000..27c9166 --- /dev/null +++ b/sources/LdapAuthentication/LdapAuthentication.php @@ -0,0 +1,2104 @@ + +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# http://www.gnu.org/copyleft/gpl.html + +/** + * LdapAuthentication plugin. LDAP Authentication and authorization integration with MediaWiki. + * + * @file + * @ingroup MediaWiki + */ + +# +# LdapAuthentication.php +# +# Info available at http://www.mediawiki.org/wiki/Extension:LDAP_Authentication +# Support is available at http://www.mediawiki.org/wiki/Extension_talk:LDAP_Authentication +# + +if ( !defined( 'MEDIAWIKI' ) ) exit; + +$wgLDAPDomainNames = array(); +$wgLDAPServerNames = array(); +$wgLDAPUseLocal = false; +$wgLDAPEncryptionType = array(); +$wgLDAPOptions = array(); +$wgLDAPPort = array(); +$wgLDAPSearchStrings = array(); +$wgLDAPProxyAgent = array(); +$wgLDAPProxyAgentPassword = array(); +$wgLDAPSearchAttributes = array(); +$wgLDAPBaseDNs = array(); +$wgLDAPGroupBaseDNs = array(); +$wgLDAPUserBaseDNs = array(); +$wgLDAPWriterDN = array(); +$wgLDAPWriterPassword = array(); +$wgLDAPWriteLocation = array(); +$wgLDAPAddLDAPUsers = array(); +$wgLDAPUpdateLDAP = array(); +$wgLDAPPasswordHash = array(); +$wgLDAPMailPassword = array(); +$wgLDAPPreferences = array(); +$wgLDAPDisableAutoCreate = array(); +$wgLDAPDebug = 0; +$wgLDAPGroupUseFullDN = array(); +$wgLDAPLowerCaseUsername = array(); +$wgLDAPGroupUseRetrievedUsername = array(); +$wgLDAPGroupObjectclass = array(); +$wgLDAPGroupAttribute = array(); +$wgLDAPGroupNameAttribute = array(); +$wgLDAPGroupsUseMemberOf = array(); +$wgLDAPUseLDAPGroups = array(); +$wgLDAPLocallyManagedGroups = array(); +$wgLDAPGroupsPrevail = array(); +$wgLDAPRequiredGroups = array(); +$wgLDAPExcludedGroups = array(); +$wgLDAPGroupSearchNestedGroups = array(); +$wgLDAPAuthAttribute = array(); +$wgLDAPAutoAuthUsername = ""; +$wgLDAPAutoAuthDomain = ""; +$wgPasswordResetRoutes['domain'] = true; +$wgLDAPActiveDirectory = array(); + +define( "LDAPAUTHVERSION", "2.0f" ); + +/** + * Add extension information to Special:Version + */ +$wgExtensionCredits['other'][] = array( + 'path' => __FILE__, + 'name' => 'LDAP Authentication Plugin', + 'version' => LDAPAUTHVERSION, + 'author' => 'Ryan Lane', + 'descriptionmsg' => 'ldapauthentication-desc', + 'url' => 'https://www.mediawiki.org/wiki/Extension:LDAP_Authentication', +); + +$dir = dirname( __FILE__ ) . '/'; +$wgExtensionMessagesFiles['LdapAuthentication'] = $dir . 'LdapAuthentication.i18n.php'; + +# Schema changes +$wgHooks['LoadExtensionSchemaUpdates'][] = 'efLdapAuthenticationSchemaUpdates'; + +$wgRedactedFunctionArguments['LdapAuthenticationPlugin::ldap_bind'] = 2; +$wgRedactedFunctionArguments['LdapAuthenticationPlugin::authenticate'] = 2; +$wgRedactedFunctionArguments['LdapAuthenticationPlugin::getPasswordHash'] = 0; +$wgRedactedFunctionArguments['LdapAuthenticationPlugin::bindAs'] = 1; +$wgRedactedFunctionArguments['LdapAuthenticationPlugin::setOrDefaultPrivate'] = 0; + +/** + * @param $updater DatabaseUpdater + * @return bool + */ +function efLdapAuthenticationSchemaUpdates( $updater ) { + $base = dirname( __FILE__ ); + switch ( $updater->getDB()->getType() ) { + case 'mysql': + $updater->addExtensionTable( 'ldap_domains', "$base/schema/ldap-mysql.sql" ); + break; + case 'postgres': + $updater->addExtensionTable( 'ldap_domains', "$base/schema/ldap-postgres.sql" ); + break; + } + return true; +} + +// constants for search base +define( "GROUPDN", 0 ); +define( "USERDN", 1 ); +define( "DEFAULTDN", 2 ); + +// constants for error reporting +define( "NONSENSITIVE", 1 ); +define( "SENSITIVE", 2 ); +define( "HIGHLYSENSITIVE", 3 ); + +class LdapAuthenticationPlugin extends AuthPlugin { + + // ldap connection resource + var $ldapconn; + + // preferences + var $email, $lang, $realname, $nickname, $externalid; + + // username pulled from ldap + var $LDAPUsername; + + // userdn pulled from ldap + var $userdn; + + // groups pulled from ldap + var $userLDAPGroups; + var $allLDAPGroups; + + // boolean to test for failed auth + var $authFailed; + + // boolean to test for fetched user info + var $fetchedUserInfo; + + // the user's entry and all attributes + var $userInfo; + + // the user we are currently bound as + var $boundAs; + + /** + * Wrapper for ldap_connect + * @param null $hostname + * @param int $port + * @return resource|false + */ + public static function ldap_connect( $hostname=null, $port=389 ) { + wfSuppressWarnings(); + $ret = ldap_connect( $hostname, $port ); + wfRestoreWarnings(); + return $ret; + } + + /** + * Wrapper for ldap_bind + * @param $ldapconn + * @param null $dn + * @param null $password + * @return bool + */ + public static function ldap_bind( $ldapconn, $dn=null, $password=null ) { + wfSuppressWarnings(); + $ret = ldap_bind( $ldapconn, $dn, $password ); + wfRestoreWarnings(); + return $ret; + } + + /** + * Wrapper for ldap_unbind + * @param $ldapconn + * @return bool + */ + public static function ldap_unbind( $ldapconn ) { + if ( $ldapconn ) { + wfSuppressWarnings(); + $ret = ldap_unbind( $ldapconn ); + wfRestoreWarnings(); + } else { + $ret = false; + } + return $ret; + } + + /** + * Wrapper for ldap_modify + * @param $ldapconn + * @param $dn + * @param $entry + * @return bool + */ + public static function ldap_modify( $ldapconn, $dn, $entry ) { + wfSuppressWarnings(); + $ret = ldap_modify( $ldapconn, $dn, $entry ); + wfRestoreWarnings(); + return $ret; + } + + /** + * Wrapper for ldap_add + * @param $ldapconn + * @param $dn + * @param $entry + * @return bool + */ + public static function ldap_add( $ldapconn, $dn, $entry ) { + wfSuppressWarnings(); + $ret = ldap_add( $ldapconn, $dn, $entry ); + wfRestoreWarnings(); + return $ret; + } + + /** + * Wrapper for ldap_delete + * @param $ldapconn + * @param $dn + * @return bool + */ + public static function ldap_delete( $ldapconn, $dn ) { + wfSuppressWarnings(); + $ret = ldap_delete( $ldapconn, $dn ); + wfRestoreWarnings(); + return $ret; + } + + /** + * Wrapper for ldap_search + * @param $ldapconn + * @param $basedn + * @param $filter + * @param array|null $attributes + * @param null $attrsonly + * @param null $sizelimit + * @param null $timelimit + * @param null $deref + * @return resource + */ + public static function ldap_search( $ldapconn, $basedn, $filter, $attributes=array(), $attrsonly=null, $sizelimit=null, $timelimit=null, $deref=null ) { + wfSuppressWarnings(); + $ret = ldap_search( $ldapconn, $basedn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref ); + wfRestoreWarnings(); + return $ret; + } + + /** + * Wrapper for ldap_read + * @param $ldapconn + * @param $basedn + * @param $filter + * @param array|null $attributes + * @param null $attrsonly + * @param null $sizelimit + * @param null $timelimit + * @param null $deref + * @return resource + */ + public static function ldap_read( $ldapconn, $basedn, $filter, $attributes=array(), $attrsonly=null, $sizelimit=null, $timelimit=null, $deref=null ) { + wfSuppressWarnings(); + $ret = ldap_read( $ldapconn, $basedn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref ); + wfRestoreWarnings(); + return $ret; + } + + /** + * Wrapper for ldap_list + * @param $ldapconn + * @param $basedn + * @param $filter + * @param array|null $attributes + * @param null $attrsonly + * @param null $sizelimit + * @param null $timelimit + * @param null $deref + * @return \resource + */ + public static function ldap_list( $ldapconn, $basedn, $filter, $attributes=array(), $attrsonly=null, $sizelimit=null, $timelimit=null, $deref=null ) { + wfSuppressWarnings(); + $ret = ldap_list( $ldapconn, $basedn, $filter, $attributes, $attrsonly, $sizelimit, $timelimit, $deref ); + wfRestoreWarnings(); + return $ret; + } + + /** + * Wrapper for ldap_get_entries + * @param $ldapconn + * @param $resultid + * @return array + */ + public static function ldap_get_entries( $ldapconn, $resultid ) { + wfSuppressWarnings(); + $ret = ldap_get_entries( $ldapconn, $resultid ); + wfRestoreWarnings(); + return $ret; + } + + /** + * Wrapper for ldap_count_entries + * @param $ldapconn + * @param $resultid + * @return int + */ + public static function ldap_count_entries( $ldapconn, $resultid ) { + wfSuppressWarnings(); + $ret = ldap_count_entries( $ldapconn, $resultid ); + wfRestoreWarnings(); + return $ret; + } + + /** + * Wrapper for ldap_errno + * @param $ldapconn + * @return int + */ + public static function ldap_errno( $ldapconn ) { + wfSuppressWarnings(); + $ret = ldap_errno( $ldapconn ); + wfRestoreWarnings(); + return $ret; + } + + /** + * Get configuration defined by admin, or return default value + * + * @param string $preference + * @param string $domain + * @return mixed + */ + public function getConf( $preference, $domain='' ) { + # Global preferences + switch ( $preference ) { + case 'DomainNames': + global $wgLDAPDomainNames; + return $wgLDAPDomainNames; + case 'UseLocal': + global $wgLDAPUseLocal; + return $wgLDAPUseLocal; + case 'AutoAuthUsername': + global $wgLDAPAutoAuthUsername; + return $wgLDAPAutoAuthUsername; + case 'AutoAuthDomain': + global $wgLDAPAutoAuthDomain; + return $wgLDAPAutoAuthDomain; + } + + # Domain specific preferences + if ( !$domain ) { + $domain = $this->getDomain(); + } + switch ( $preference ) { + case 'ServerNames': + global $wgLDAPServerNames; + return self::setOrDefault( $wgLDAPServerNames, $domain ); + case 'EncryptionType': + global $wgLDAPEncryptionType; + return self::setOrDefault( $wgLDAPEncryptionType, $domain, 'tls' ); + case 'Options': + global $wgLDAPOptions; + return self::setOrDefault( $wgLDAPOptions, $domain, array() ); + case 'Port': + global $wgLDAPPort; + if ( isset( $wgLDAPPort[$domain] ) ) { + $this->printDebug( "Using non-standard port: " . $wgLDAPPort[$domain], SENSITIVE ); + return (string)$wgLDAPPort[$domain]; + } elseif ( $this->getConf( 'EncryptionType' ) == 'ssl' ) { + return "636"; + } else { + return "389"; + } + case 'SearchString': + global $wgLDAPSearchStrings; + return self::setOrDefault( $wgLDAPSearchStrings, $domain ); + case 'ProxyAgent': + global $wgLDAPProxyAgent; + return self::setOrDefault( $wgLDAPProxyAgent, $domain ); + case 'ProxyAgentPassword': + global $wgLDAPProxyAgentPassword; + return self::setOrDefaultPrivate( $wgLDAPProxyAgentPassword, $domain ); + case 'SearchAttribute': + global $wgLDAPSearchAttributes; + return self::setOrDefault( $wgLDAPSearchAttributes, $domain ); + case 'BaseDN': + global $wgLDAPBaseDNs; + return self::setOrDefault( $wgLDAPBaseDNs, $domain ); + case 'GroupBaseDN': + global $wgLDAPGroupBaseDNs; + return self::setOrDefault( $wgLDAPGroupBaseDNs, $domain ); + case 'UserBaseDN': + global $wgLDAPUserBaseDNs; + return self::setOrDefault( $wgLDAPUserBaseDNs, $domain ); + case 'WriterDN': + global $wgLDAPWriterDN; + return self::setOrDefault( $wgLDAPWriterDN, $domain ); + case 'WriterPassword': + global $wgLDAPWriterPassword; + return self::setOrDefaultPrivate( $wgLDAPWriterPassword, $domain ); + case 'WriteLocation': + global $wgLDAPWriteLocation; + return self::setOrDefault( $wgLDAPWriteLocation, $domain ); + case 'AddLDAPUsers': + global $wgLDAPAddLDAPUsers; + return self::setOrDefault( $wgLDAPAddLDAPUsers, $domain, false ); + case 'UpdateLDAP': + global $wgLDAPUpdateLDAP; + return self::setOrDefault( $wgLDAPUpdateLDAP, $domain, false ); + case 'PasswordHash': + global $wgLDAPPasswordHash; + return self::setOrDefaultPrivate( $wgLDAPPasswordHash, $domain, 'clear' ); + case 'MailPassword': + global $wgLDAPMailPassword; + return self::setOrDefaultPrivate( $wgLDAPMailPassword, $domain, false ); + case 'Preferences': + global $wgLDAPPreferences; + return self::setOrDefault( $wgLDAPPreferences, $domain, array() ); + case 'DisableAutoCreate': + global $wgLDAPDisableAutoCreate; + return self::setOrDefault( $wgLDAPDisableAutoCreate, $domain, false ); + case 'GroupUseFullDN': + global $wgLDAPGroupUseFullDN; + return self::setOrDefault( $wgLDAPGroupUseFullDN, $domain, false ); + case 'LowerCaseUsername': + global $wgLDAPLowerCaseUsername; + // Default set to true for backwards compatibility with + // versions < 2.0a + return self::setOrDefault( $wgLDAPLowerCaseUsername, $domain, true ); + case 'GroupUseRetrievedUsername': + global $wgLDAPGroupUseRetrievedUsername; + return self::setOrDefault( $wgLDAPGroupUseRetrievedUsername, $domain, false ); + case 'GroupObjectclass': + global $wgLDAPGroupObjectclass; + return self::setOrDefault( $wgLDAPGroupObjectclass, $domain ); + case 'GroupAttribute': + global $wgLDAPGroupAttribute; + return self::setOrDefault( $wgLDAPGroupAttribute, $domain ); + case 'GroupNameAttribute': + global $wgLDAPGroupNameAttribute; + return self::setOrDefault( $wgLDAPGroupNameAttribute, $domain ); + case 'GroupsUseMemberOf': + global $wgLDAPGroupsUseMemberOf; + return self::setOrDefault( $wgLDAPGroupsUseMemberOf, $domain, false ); + case 'UseLDAPGroups': + global $wgLDAPUseLDAPGroups; + return self::setOrDefault( $wgLDAPUseLDAPGroups, $domain, false ); + case 'LocallyManagedGroups': + global $wgLDAPLocallyManagedGroups; + return self::setOrDefault( $wgLDAPLocallyManagedGroups, $domain, array() ); + case 'GroupsPrevail': + global $wgLDAPGroupsPrevail; + return self::setOrDefault( $wgLDAPGroupsPrevail, $domain, false ); + case 'RequiredGroups': + global $wgLDAPRequiredGroups; + return self::setOrDefault( $wgLDAPRequiredGroups, $domain, array() ); + case 'ExcludedGroups': + global $wgLDAPExcludedGroups; + return self::setOrDefault( $wgLDAPExcludedGroups, $domain, array() ); + case 'GroupSearchNestedGroups': + global $wgLDAPGroupSearchNestedGroups; + return self::setOrDefault( $wgLDAPGroupSearchNestedGroups, $domain, false ); + case 'AuthAttribute': + global $wgLDAPAuthAttribute; + return self::setOrDefault( $wgLDAPAuthAttribute, $domain ); + case 'ActiveDirectory': + global $wgLDAPActiveDirectory; + return self::setOrDefault( $wgLDAPActiveDirectory, $domain, false ); + } + return ''; + } + + /** + * Returns the item from $array at index $key if it is set, + * else, it returns $default + * + * @param $array array + * @param $key + * @param $default mixed + * @return mixed + */ + private static function setOrDefault( $array, $key, $default = '' ) { + return isset( $array[$key] ) ? $array[$key] : $default; + } + + /** + * Returns the item from $array at index $key if it is set, + * else, it returns $default + * + * Use for sensitive data + * + * @param $array array + * @param $key + * @param $default mixed + * @return mixed + */ + private static function setOrDefaultPrivate( $array, $key, $default = '' ) { + return isset( $array[$key] ) ? $array[$key] : $default; + } + + /** + * Check whether there exists a user account with the given name. + * The name will be normalized to MediaWiki's requirements, so + * you might need to munge it (for instance, for lowercase initial + * letters). + * + * @param string $username + * @return bool + */ + public function userExists( $username ) { + $this->printDebug( "Entering userExists", NONSENSITIVE ); + + // If we can't add LDAP users, we don't really need to check + // if the user exists, the authenticate method will do this for + // us. This will decrease hits to the LDAP server. + // We do however, need to use this if we are using auto authentication. + if ( !$this->getConf( 'AddLDAPUsers' ) && !$this->useAutoAuth() ) { + return true; + } + + $ret = false; + if ( $this->connect() ) { + $searchstring = $this->getSearchString( $username ); + + // If we are using auto authentication, and we got + // anything back, then the user exists. + if ( $this->useAutoAuth() && $searchstring != '' ) { + $ret = true; + } else { + // Search for the entry. + $entry = LdapAuthenticationPlugin::ldap_read( $this->ldapconn, $searchstring, "objectclass=*" ); + + if ( $entry ) { + $this->printDebug( "Found a matching user in LDAP", NONSENSITIVE ); + $ret = true; + } else { + $this->printDebug( "Did not find a matching user in LDAP", NONSENSITIVE ); + } + } + // getSearchString is going to bind, but will not unbind + LdapAuthenticationPlugin::ldap_unbind( $this->ldapconn ); + } + return $ret; + } + + /** + * Connect to LDAP + * @param string $domain + * @return bool + */ + public function connect( $domain='' ) { + $this->printDebug( "Entering Connect", NONSENSITIVE ); + + if ( !function_exists( 'ldap_connect' ) ) { + $this->printDebug( "It looks like you are missing LDAP support; please ensure you have either compiled LDAP " + . "support in, or have enabled the module. If the authentication is working for you, the plugin isn't properly " + . "detecting the LDAP module, and you can safely ignore this message.", NONSENSITIVE ); + return false; + } + + // Set the server string depending on whether we use ssl or not + $encryptionType = $this->getConf( 'EncryptionType', $domain ); + switch( $encryptionType ) { + case "ldapi": + $this->printDebug( "Using ldapi", SENSITIVE ); + $serverpre = "ldapi://"; + break; + case "ssl": + $this->printDebug( "Using SSL", SENSITIVE ); + $serverpre = "ldaps://"; + break; + default: + $this->printDebug( "Using TLS or not using encryption.", SENSITIVE ); + $serverpre = "ldap://"; + } + + // Make a space separated list of server strings with the connection type + // string added. + $servers = ""; + $tmpservers = $this->getConf( 'ServerNames', $domain ); + $tok = strtok( $tmpservers, " " ); + while ( $tok ) { + $servers = $servers . " " . $serverpre . $tok . ":" . $this->getConf( 'Port', $domain ); + $tok = strtok( " " ); + } + $servers = rtrim( $servers ); + + $this->printDebug( "Using servers: $servers", SENSITIVE ); + + // Connect and set options + $this->ldapconn = LdapAuthenticationPlugin::ldap_connect( $servers ); + if ( !$this->ldapconn ) { + $this->printDebug( "PHP's LDAP connect method returned null, this likely implies a misconfiguration of the plugin.", NONSENSITIVE ); + return false; + } + ldap_set_option( $this->ldapconn, LDAP_OPT_PROTOCOL_VERSION, 3 ); + ldap_set_option( $this->ldapconn, LDAP_OPT_REFERRALS, 0 ); + + foreach ( $this->getConf( 'Options' ) as $key => $value ) { + if ( !ldap_set_option( $this->ldapconn, constant( $key ), $value ) ) { + $this->printDebug( "Can't set option to LDAP! Option code and value: " . $key . "=" . $value, 1 ); + } + } + + // TLS needs to be started after the connection resource is available + if ( $encryptionType == "tls" ) { + $this->printDebug( "Using TLS", SENSITIVE ); + if ( !ldap_start_tls( $this->ldapconn ) ) { + $this->printDebug( "Failed to start TLS.", SENSITIVE ); + return false; + } + } + $this->printDebug( "PHP's LDAP connect method returned true (note, this does not imply it connected to the server).", NONSENSITIVE ); + + return true; + } + + /** + * Check if a username+password pair is a valid login, or if the username + * is allowed access to the wiki. + * The name will be normalized to MediaWiki's requirements, so + * you might need to munge it (for instance, for lowercase initial + * letters). + * + * @param string $username + * @param string $password + * @return bool + */ + public function authenticate( $username, $password = '' ) { + $this->printDebug( "Entering authenticate for username $username", NONSENSITIVE ); + + // We don't handle local authentication + if ( 'local' == $this->getDomain() ) { + $this->printDebug( "User is using a local domain", SENSITIVE ); + return false; + } + + // Mediawiki munges the username before authenticate is called, + // this can mess with authentication, group pulling/restriction, + // preference pulling, etc. Let's allow the admin to use + // a lowercased username if needed. + if ( $this->getConf( 'LowerCaseUsername') ) { + $username = strtolower( $username ); + } + + // If the user is using auto authentication, we need to ensure + // that he/she isn't trying to fool us by sending a username other + // than the one the web server got from the auto-authentication method. + if ( $this->useAutoAuth() && $this->getConf( 'AutoAuthUsername' ) != $username ) { + $this->printDebug( "The username provided ($username) doesn't match the username provided by the webserver (" . $this->getConf( 'AutoAuthUsername' ) . "). The user is probably trying to log in to the auto-authentication domain with password authentication via the wiki. Denying access.", SENSITIVE ); + return false; + } + + // We need to ensure that if we require a password, that it is + // not blank. We don't allow blank passwords, so we are being + // tricked if someone is supplying one when using password auth. + // auto-authentication is handled by the webserver; a blank password + // here is wanted. + if ( '' == $password && !$this->useAutoAuth() ) { + $this->printDebug( "User used a blank password", NONSENSITIVE ); + return false; + } + + if ( $this->connect() ) { + + $this->userdn = $this->getSearchString( $username ); + + // It is possible that getSearchString will return an + // empty string; if this happens, the bind will ALWAYS + // return true, and will let anyone in! + if ( '' == $this->userdn ) { + $this->printDebug( "User DN is blank", NONSENSITIVE ); + LdapAuthenticationPlugin::ldap_unbind( $this->ldapconn ); + $this->markAuthFailed(); + return false; + } + + // If we are using password authentication, we need to bind as the + // user to make sure the password is correct. + if ( !$this->useAutoAuth() ) { + $this->printDebug( "Binding as the user", NONSENSITIVE ); + $bind = $this->bindAs( $this->userdn, $password ); + if ( !$bind ) { + $this->markAuthFailed(); + return false; + } + $result = true; + wfRunHooks( 'ChainAuth', array( $username, $password, &$result ) ); + if ( $result == false ) { + return false; + } + + $this->printDebug( "Bound successfully", NONSENSITIVE ); + + $ss = $this->getConf( 'SearchString' ); + if ( $ss ) { + if ( strstr( $ss, "@" ) || strstr( $ss, '\\' ) ) { + // We are most likely configured using USER-NAME@DOMAIN, or + // DOMAIN\\USER-NAME. + // Get the user's full DN so we can search for groups and such. + $this->userdn = $this->getUserDN( $username ); + $this->printDebug( "Fetched UserDN: $this->userdn", NONSENSITIVE ); + } else { + // Now that we are bound, we can pull the user's info. + $this->getUserInfo(); + } + } + } + + // Ensure the user's entry has the required auth attribute + $aa = $this->getConf( 'AuthAttribute' ); + if ( $aa ) { + $this->printDebug( "Checking for auth attributes: $aa", NONSENSITIVE ); + $filter = "(" . $aa . ")"; + $attributes = array( "dn" ); + $entry = LdapAuthenticationPlugin::ldap_read( $this->ldapconn, $this->userdn, $filter, $attributes ); + $info = LdapAuthenticationPlugin::ldap_get_entries( $this->ldapconn, $entry ); + if ( $info["count"] < 1 ) { + $this->printDebug( "Failed auth attribute check", NONSENSITIVE ); + LdapAuthenticationPlugin::ldap_unbind( $this->ldapconn ); + $this->markAuthFailed(); + return false; + } + } + + $this->getGroups( $username ); + + if ( !$this->checkGroups() ) { + LdapAuthenticationPlugin::ldap_unbind( $this->ldapconn ); + $this->markAuthFailed(); + return false; + } + + $this->getPreferences(); + + LdapAuthenticationPlugin::ldap_unbind( $this->ldapconn ); + } else { + $this->markAuthFailed(); + return false; + } + $this->printDebug( "Authentication passed", NONSENSITIVE ); + + // We made it this far; the user authenticated and didn't fail any checks, so he/she gets in. + return true; + } + + function markAuthFailed() { + $this->authFailed = true; + } + + /** + * Modify options in the login template. + * + * @param UserLoginTemplate $template + * @param $type + */ + public function modifyUITemplate( &$template, &$type ) { + $this->printDebug( "Entering modifyUITemplate", NONSENSITIVE ); + $template->set( 'create', $this->getConf( 'AddLDAPUsers' ) ); + $template->set( 'usedomain', true ); + $template->set( 'useemail', $this->getConf( 'MailPassword' ) ); + $template->set( 'canreset', $this->getConf( 'MailPassword' ) ); + $template->set( 'domainnames', $this->domainList() ); + wfRunHooks( 'LDAPModifyUITemplate', array( &$template ) ); + } + + /** + * @return array + */ + function domainList() { + $tempDomArr = $this->getConf( 'DomainNames' ); + if ( $this->getConf( 'UseLocal' ) ) { + $this->printDebug( "Allowing the local domain, adding it to the list.", NONSENSITIVE ); + array_push( $tempDomArr, 'local' ); + } + + if ( $this->getConf( 'AutoAuthDomain' ) ) { + $this->printDebug( "Allowing auto-authentication login, removing the domain from the list.", NONSENSITIVE ); + // There is no reason for people to log in directly to the wiki if the are using an + // auto-authentication domain. If they try to, they are probably up to something fishy. + unset( $tempDomArr[array_search( $this->getConf( 'AutoAuthDomain' ), $tempDomArr )] ); + } + + $domains = array(); + foreach ( $tempDomArr as $tempDom ) { + $domains["$tempDom"] = $tempDom; + } + return $domains; + } + + /** + * Return true if the wiki should create a new local account automatically + * when asked to login a user who doesn't exist locally but does in the + * external auth database. + * + * This is just a question, and shouldn't perform any actions. + * + * @return bool + */ + public function autoCreate() { + return !$this->getConf( 'DisableAutoCreate' ); + } + + /** + * Set the given password in LDAP. + * Return true if successful. + * + * @param User $user + * @param string $password + * @return bool + */ + public function setPassword( $user, $password ) { + $this->printDebug( "Entering setPassword", NONSENSITIVE ); + + if ( $this->getDomain() == 'local' ) { + $this->printDebug( "User is using a local domain", NONSENSITIVE ); + + // We don't set local passwords, but we don't want the wiki + // to send the user a failure. + return true; + } + if ( !$this->getConf( 'UpdateLDAP' ) ) { + $this->printDebug( "Wiki is set to not allow updates", NONSENSITIVE ); + + // We aren't allowing the user to change his/her own password + return false; + } + + $writer = $this->getConf( 'WriterDN' ); + if ( !$writer ) { + $this->printDebug( "Wiki doesn't have wgLDAPWriterDN set", NONSENSITIVE ); + + // We can't change a user's password without an account that is + // allowed to do it. + return false; + } + $pass = $this->getPasswordHash( $password ); + + if ( $this->connect() ) { + $this->userdn = $this->getSearchString( $user->getName() ); + $this->printDebug( "Binding as the writerDN", NONSENSITIVE ); + $bind = $this->bindAs( $writer, $this->getConf( 'WriterPassword' ) ); + if ( !$bind ) { + return false; + } + $values["userpassword"] = $pass; + + // Blank out the password in the database. We don't want to save + // domain credentials for security reasons. + // This doesn't do anything. $password isn't by reference + $password = ''; + + $success = LdapAuthenticationPlugin::ldap_modify( $this->ldapconn, $this->userdn, $values ); + LdapAuthenticationPlugin::ldap_unbind( $this->ldapconn ); + if ( $success ) { + $this->printDebug( "Successfully modified the user's password", NONSENSITIVE ); + return true; + } + $this->printDebug( "Failed to modify the user's password", NONSENSITIVE ); + } + return false; + } + + /** + * Update user information in LDAP + * Return true if successful. + * + * @param User $user + * @return bool + */ + public function updateExternalDB( $user ) { + global $wgMemc; + + $this->printDebug( "Entering updateExternalDB", NONSENSITIVE ); + if ( !$this->getConf( 'UpdateLDAP' ) || $this->getDomain() == 'local' ) { + $this->printDebug( "Either the user is using a local domain, or the wiki isn't allowing updates", NONSENSITIVE ); + // We don't handle local preferences, but we don't want the + // wiki to return an error. + return true; + } + + $writer = $this->getConf( 'WriterDN' ); + if ( !$writer ) { + $this->printDebug( "The wiki doesn't have wgLDAPWriterDN set", NONSENSITIVE ); + // We can't modify LDAP preferences if we don't have a user + // capable of editing LDAP attributes. + return false; + } + + $this->email = $user->getEmail(); + $this->realname = $user->getRealName(); + $this->nickname = $user->getOption( 'nickname' ); + $this->lang = $user->getOption( 'language' ); + if ( $this->connect() ) { + $this->userdn = $this->getSearchString( $user->getName() ); + $this->printDebug( "Binding as the writerDN", NONSENSITIVE ); + $bind = $this->bindAs( $writer, $this->getConf( 'WriterPassword' ) ); + if ( !$bind ) { + return false; + } + + $values = array(); + $prefs = $this->getConf( 'Preferences' ); + foreach ( array_keys( $prefs ) as $key ) { + $attr = strtolower( $prefs[$key] ); + switch ( $key ) { + case "email": + if ( is_string( $this->email ) ) { + $values[$attr] = $this->email; + } + break; + case "nickname": + if ( is_string( $this->nickname ) ) { + $values[$attr] = $this->nickname; + } + break; + case "realname": + if ( is_string( $this->realname ) ) { + $values[$attr] = $this->realname; + } + break; + case "language": + if ( is_string( $this->lang ) ) { + $values[$attr] = $this->lang; + } + break; + } + } + + if ( count( $values ) && LdapAuthenticationPlugin::ldap_modify( $this->ldapconn, $this->userdn, $values ) ) { + // We changed the user, we need to invalidate the memcache key + $key = wfMemcKey( 'ldapauthentication', 'userinfo', $this->userdn ); + $wgMemc->delete( $key ); + $this->printDebug( "Successfully modified the user's attributes", NONSENSITIVE ); + LdapAuthenticationPlugin::ldap_unbind( $this->ldapconn ); + return true; + } + $this->printDebug( "Failed to modify the user's attributes", NONSENSITIVE ); + LdapAuthenticationPlugin::ldap_unbind( $this->ldapconn ); + } + return false; + } + + /** + * Can the wiki create accounts in LDAP? + * Return true if yes. + * + * @return bool + */ + public function canCreateAccounts() { + return $this->getConf( 'AddLDAPUsers' ); + } + + /** + * Can the wiki change passwords in LDAP, or can the user + * change passwords locally? + * Return true if yes. + * + * @return bool + */ + public function allowPasswordChange() { + $this->printDebug( "Entering allowPasswordChange", NONSENSITIVE ); + + // Local domains need to be able to change passwords + if ( $this->getConf( 'UseLocal' ) && 'local' == $this->getDomain() ) { + return true; + } + if ( $this->getConf( 'UpdateLDAP' ) || $this->getConf( 'MailPassword' ) ) { + return true; + } + return false; + } + + /** + * Disallow MediaWiki from setting local passwords in the database, + * unless UseLocal is true. Warning: if you set $wgLDAPUseLocal, + * it will cause MediaWiki to leak LDAP passwords into the local database. + */ + public function allowSetLocalPassword() { + return $this->getConf( 'UseLocal'); + } + + /** + * Add a user to LDAP. + * Return true if successful. + * + * @param User $user + * @param string $password + * @param string $email + * @param string $realname + * @return bool + */ + public function addUser( $user, $password, $email = '', $realname = '' ) { + $this->printDebug( "Entering addUser", NONSENSITIVE ); + + if ( !$this->getConf( 'AddLDAPUsers' ) || 'local' == $this->getDomain() ) { + $this->printDebug( "Either the user is using a local domain, or the wiki isn't allowing users to be added to LDAP", NONSENSITIVE ); + + // Tell the wiki not to return an error. + return true; + } + if ( $this->getConf( 'RequiredGroups' ) ) { + $this->printDebug( "The wiki is requiring users to be in specific groups, and cannot add users as this would be a security hole.", NONSENSITIVE ); + // It is possible that later we can add users into + // groups, but since we don't support it, we don't want + // to open holes! + return false; + } + + $writer = $this->getConf( 'WriterDN' ); + if ( !$writer ) { + $this->printDebug( "The wiki doesn't have wgLDAPWriterDN set", NONSENSITIVE ); + + // We can't add users without an LDAP account capable of doing so. + return false; + } + + $this->email = $user->getEmail(); + $this->realname = $user->getRealName(); + $username = $user->getName(); + if ( $this->getConf( 'LowerCaseUsername' ) ) { + $username = strtolower( $username ); + } + $pass = $this->getPasswordHash( $password ); + if ( $this->connect() ) { + $writeloc = $this->getConf( 'WriteLocation' ); + $this->userdn = $this->getSearchString( $username ); + if ( '' == $this->userdn ) { + $this->printDebug( "userdn is blank, attempting to use wgLDAPWriteLocation", NONSENSITIVE ); + if ( $writeloc ) { + $this->printDebug( "wgLDAPWriteLocation is set, using that", NONSENSITIVE ); + $this->userdn = $this->getConf( 'SearchAttribute' ) . "=" . + $username . "," . $writeloc; + } else { + $this->printDebug( "wgLDAPWriteLocation is not set, failing", NONSENSITIVE ); + // getSearchString will bind, but will not unbind + LdapAuthenticationPlugin::ldap_unbind( $this->ldapconn ); + return false; + } + } + + $this->printDebug( "Binding as the writerDN", NONSENSITIVE ); + $bind = $this->bindAs( $writer, $this->getConf( 'WriterPassword' ) ); + if ( !$bind ) { + $this->printDebug( "Failed to bind as the writerDN; add failed", NONSENSITIVE ); + return false; + } + + // Set up LDAP objectclasses and attributes + // TODO: make objectclasses and attributes configurable + $values["uid"] = $username; + // sn is required for objectclass inetorgperson + $values["sn"] = $username; + $prefs = $this->getConf( 'Preferences' ); + foreach ( array_keys( $prefs ) as $key ) { + $attr = strtolower( $prefs[$key] ); + switch ( $key ) { + case "email": + if ( is_string( $this->email ) ) { + $values[$attr] = $this->email; + } + break; + case "nickname": + if ( is_string( $this->nickname ) ) { + $values[$attr] = $this->nickname; + } + break; + case "realname": + if ( is_string( $this->realname ) ) { + $values[$attr] = $this->realname; + } + break; + case "language": + if ( is_string( $this->lang ) ) { + $values[$attr] = $this->lang; + } + break; + } + } + if ( !array_key_exists( "cn", $values ) ) { + $values["cn"] = $username; + } + $values["userpassword"] = $pass; + $values["objectclass"] = array( "inetorgperson" ); + + $result = true; + # Let other extensions modify the user object before creation + wfRunHooks( 'LDAPSetCreationValues', array( $this, $username, &$values, $writeloc, &$this->userdn, &$result ) ); + if ( !$result ) { + $this->printDebug( "Failed to add user because LDAPSetCreationValues returned false", NONSENSITIVE ); + LdapAuthenticationPlugin::ldap_unbind( $this->ldapconn ); + return false; + } + + $this->printDebug( "Adding user", NONSENSITIVE ); + if ( LdapAuthenticationPlugin::ldap_add( $this->ldapconn, $this->userdn, $values ) ) { + $this->printDebug( "Successfully added user", NONSENSITIVE ); + LdapAuthenticationPlugin::ldap_unbind( $this->ldapconn ); + return true; + } + $errno = LdapAuthenticationPlugin::ldap_errno( $this->ldapconn ); + # Constraint violation, let's allow other plugins a chance to retry + if ( $errno === 19 ) { + $result = false; + wfRunHooks( 'LDAPRetrySetCreationValues', array( $this, $username, &$values, $writeloc, &$this->userdn, &$result ) ); + if ( $result && LdapAuthenticationPlugin::ldap_add( $this->ldapconn, $this->userdn, $values ) ) { + $this->printDebug( "Successfully added user", NONSENSITIVE ); + LdapAuthenticationPlugin::ldap_unbind( $this->ldapconn ); + return true; + } + } + $this->printDebug( "Failed to add user", NONSENSITIVE ); + LdapAuthenticationPlugin::ldap_unbind( $this->ldapconn ); + } + return false; + } + + /** + * Set the domain this plugin is supposed to use when authenticating. + * + * @param string $domain + */ + public function setDomain( $domain ) { + $this->printDebug( "Setting domain as: $domain", NONSENSITIVE ); + $_SESSION['wsDomain'] = $domain; + } + + /** + * Get the user's domain + * + * @return string + */ + public function getDomain() { + global $wgUser; + + $this->printDebug( "Entering getDomain", NONSENSITIVE ); + + # If there's only a single domain set, there's no reason + # to bother with sessions, tokens, etc.. This works around + # a number of bugs caused by supporting multiple domains. + # The bugs will still exist when using multiple domains, + # though. + $domainNames = $this->getConf( 'DomainNames' ); + if ( ( count( $domainNames ) === 1 ) && !$this->getConf( 'UseLocal' ) ) { + return $domainNames[0]; + } + # First check if we already have a valid domain set + if ( isset( $_SESSION['wsDomain'] ) && $_SESSION['wsDomain'] != 'invaliddomain' ) { + $this->printDebug( "Pulling domain from session.", NONSENSITIVE ); + return $_SESSION['wsDomain']; + } + # If the session domain isn't set, the user may have been logged + # in with a token, check the user options. + # If $wgUser isn't defined yet, it might be due to an LDAPAutoAuthDomain config. + if ( isset( $wgUser ) && $wgUser->isLoggedIn() && $wgUser->getToken( false ) ) { + $this->printDebug( "Pulling domain from user options.", NONSENSITIVE ); + $domain = self::loadDomain( $wgUser ); + if ( $domain ) { + return $domain; + } + } + # The user must be using an invalid domain + $this->printDebug( "No domain found, returning invaliddomain", NONSENSITIVE ); + return 'invaliddomain'; + } + + /** + * Check to see if the specific domain is a valid domain. + * Return true if the domain is valid. + * + * @param string $domain + * @return bool + */ + public function validDomain( $domain ) { + $this->printDebug( "Entering validDomain", NONSENSITIVE ); + if ( in_array( $domain, $this->getConf( 'DomainNames' ) ) || ( $this->getConf( 'UseLocal' ) && 'local' == $domain ) ) { + $this->printDebug( "User is using a valid domain ($domain).", NONSENSITIVE ); + return true; + } + $this->printDebug( "User is not using a valid domain ($domain).", NONSENSITIVE ); + return false; + } + + /** + * When a user logs in, update user with information from LDAP. + * + * @param $user User + * TODO: fix the setExternalID stuff + */ + public function updateUser( &$user ) { + $this->printDebug( "Entering updateUser", NONSENSITIVE ); + if ( $this->authFailed ) { + $this->printDebug( "User didn't successfully authenticate, exiting.", NONSENSITIVE ); + return; + } + + if ( $this->getConf( 'Preferences' ) ) { + $this->printDebug( "Setting user preferences.", NONSENSITIVE ); + if ( is_string( $this->lang ) ) { + $this->printDebug( "Setting language.", NONSENSITIVE ); + $user->setOption( 'language', $this->lang ); + } + if ( is_string( $this->nickname ) ) { + $this->printDebug( "Setting nickname.", NONSENSITIVE ); + $user->setOption( 'nickname', $this->nickname ); + } + if ( is_string( $this->realname ) ) { + $this->printDebug( "Setting realname.", NONSENSITIVE ); + $user->setRealName( $this->realname ); + } + if ( is_string( $this->email ) ) { + $this->printDebug( "Setting email.", NONSENSITIVE ); + $user->setEmail( $this->email ); + $user->confirmEmail(); + } + } + + if ( $this->getConf( 'UseLDAPGroups' ) ) { + $this->printDebug( "Setting user groups.", NONSENSITIVE ); + $this->setGroups( $user ); + } + + # We must set a user option if we want token based logins to work + if ( $user->getToken( false ) ) { + $this->printDebug( "User has a token, setting domain in user options.", NONSENSITIVE ); + self::saveDomain( $user, $_SESSION['wsDomain'] ); + } + + # Let other extensions update the user + wfRunHooks( 'LDAPUpdateUser', array( &$user ) ); + + $this->printDebug( "Saving user settings.", NONSENSITIVE ); + $user->saveSettings(); + } + + /** + * When creating a user account, initialize user with information from LDAP. + * TODO: fix setExternalID stuff + * + * @param User $user + * @param bool $autocreate + */ + public function initUser( &$user, $autocreate = false ) { + $this->printDebug( "Entering initUser", NONSENSITIVE ); + + if ( $this->authFailed ) { + $this->printDebug( "User didn't successfully authenticate, exiting.", NONSENSITIVE ); + return; + } + if ( 'local' == $this->getDomain() ) { + $this->printDebug( "User is using a local domain", NONSENSITIVE ); + return; + } + + // The update user function does everything else we need done. + $this->updateUser( $user ); + + // updateUser() won't necessarily save the user's settings + $user->saveSettings(); + } + + /** + * Return true to prevent logins that don't authenticate here from being + * checked against the local database's password fields. + * + * This is just a question, and shouldn't perform any actions. + * + * @return bool + */ + public function strict() { + $this->printDebug( "Entering strict.", NONSENSITIVE ); + + if ( $this->getConf( 'UseLocal' ) || $this->getConf( 'MailPassword' ) ) { + $this->printDebug( "Returning false in strict().", NONSENSITIVE ); + return false; + } + $this->printDebug( "Returning true in strict().", NONSENSITIVE ); + return true; + } + + /** + * Munge the username based on a scheme (lowercase, by default), by search attribute + * otherwise. + * + * @param string $username + * @return string + */ + public function getCanonicalName( $username ) { + global $wgMemc; + + $this->printDebug( "Entering getCanonicalName", NONSENSITIVE ); + if ( User::isIP( $username ) ) { + $this->printDebug( "Username is an IP, not munging.", NONSENSITIVE ); + return $username; + } + $key = wfMemcKey( 'ldapauthentication', 'canonicalname', $username ); + $canonicalname = $username; + if ( $username != '' ) { + $this->printDebug( "Username is: $username", NONSENSITIVE ); + if ( $this->getConf( 'LowerCaseUsername' ) ) { + $canonicalname = ucfirst( strtolower( $canonicalname ) ); + } else { + # Fetch username, so that we can possibly use it. + $userInfo = $wgMemc->get( $key ); + if ( is_array( $userInfo ) ) { + $this->printDebug( "Fetched userInfo from memcache.", NONSENSITIVE ); + if ( $userInfo["username"] == $username ) { + $this->printDebug( "Username matched a key in memcache, using the fetched name: " . $userInfo["canonicalname"], NONSENSITIVE ); + return $userInfo["canonicalname"]; + } + } + if ( $this->validDomain( $this->getDomain() ) && $this->connect() ) { + // Try to pull the username from LDAP. In the case of straight binds, + // try to fetch the username by search before bind. + $this->userdn = $this->getUserDN( $username, true ); + $hookSetUsername = $this->LDAPUsername; + wfRunHooks( 'SetUsernameAttributeFromLDAP', array( &$hookSetUsername, $this->userInfo ) ); + if ( is_string( $hookSetUsername ) ) { + $this->printDebug( "Username munged by hook: $hookSetUsername", NONSENSITIVE ); + $this->LDAPUsername = $hookSetUsername; + } else { + $this->printDebug( "Fetched username is not a string (check your hook code...). This message can be safely ignored if you do not have the SetUsernameAttributeFromLDAP hook defined.", NONSENSITIVE ); + } + } + + // We want to use the username returned by LDAP + // if it exists + if ( $this->LDAPUsername != '' ) { + $canonicalname = ucfirst( $this->LDAPUsername ); + $this->printDebug( "Using LDAPUsername: $canonicalname", NONSENSITIVE ); + } + + $wgMemc->set( $key, array( "username" => $username, "canonicalname" => $canonicalname ), 3600 * 24 ); + } + } + $this->printDebug( "Munged username: $canonicalname", NONSENSITIVE ); + return $canonicalname; + } + + /** + * Configures the authentication plugin for use with auto-authentication + * plugins. + */ + public function autoAuthSetup() { + $this->setDomain( $this->getConf( 'AutoAuthDomain' ) ); + } + + /** + * Gets the searchstring for a user based upon settings for the domain. + * Returns a full DN for a user. + * + * @param string $username + * @return string + * @access private + */ + function getSearchString( $username ) { + $this->printDebug( "Entering getSearchString", NONSENSITIVE ); + $ss = $this->getConf( 'SearchString' ); + if ( $ss ) { + // This is a straight bind + $this->printDebug( "Doing a straight bind", NONSENSITIVE ); + $userdn = str_replace( "USER-NAME", $username, $ss ); + } else { + $userdn = $this->getUserDN( $username, true ); + } + $this->printDebug( "userdn is: $userdn", SENSITIVE ); + return $userdn; + } + + /** + * Gets the DN of a user based upon settings for the domain. + * This function will set $this->LDAPUsername + * + * @param string $username + * @param bool $bind + * @param string $searchattr + * @return string + */ + public function getUserDN( $username, $bind=false, $searchattr='' ) { + $this->printDebug( "Entering getUserDN", NONSENSITIVE ); + if ( $bind ) { + // This is a proxy bind, or an anonymous bind with a search + $proxyagent = $this->getConf( 'ProxyAgent'); + if ( $proxyagent ) { + // This is a proxy bind + $this->printDebug( "Doing a proxy bind", NONSENSITIVE ); + $bind = $this->bindAs( $proxyagent, $this->getConf( 'ProxyAgentPassword' ) ); + } else { + // This is an anonymous bind + $this->printDebug( "Doing an anonymous bind", NONSENSITIVE ); + $bind = $this->bindAs(); + } + if ( !$bind ) { + $this->printDebug( "Failed to bind", NONSENSITIVE ); + return ''; + } + } + + if ( ! $searchattr ) { + $searchattr = $this->getConf( 'SearchAttribute' ); + } + // we need to do a subbase search for the entry + $filter = "(" . $searchattr . "=" . $this->getLdapEscapedString( $username ) . ")"; + $this->printDebug( "Created a regular filter: $filter", SENSITIVE ); + + // We explicitly put memberof here because it's an operational attribute in some servers. + $attributes = array( "*", "memberof" ); + $base = $this->getBaseDN( USERDN ); + $this->printDebug( "Using base: $base", SENSITIVE ); + $entry = LdapAuthenticationPlugin::ldap_search( $this->ldapconn, $base, $filter, $attributes ); + if ( LdapAuthenticationPlugin::ldap_count_entries( $this->ldapconn, $entry ) == 0 ) { + $this->printDebug( "Couldn't find an entry", NONSENSITIVE ); + $this->fetchedUserInfo = false; + return ''; + } + $this->userInfo = LdapAuthenticationPlugin::ldap_get_entries( $this->ldapconn, $entry ); + $this->fetchedUserInfo = true; + if ( isset( $this->userInfo[0][$searchattr] ) ) { + $username = $this->userInfo[0][$searchattr][0]; + $this->printDebug( "Setting the LDAPUsername based on fetched wgLDAPSearchAttributes: $username", NONSENSITIVE ); + $this->LDAPUsername = $username; + } + $userdn = $this->userInfo[0]["dn"]; + return $userdn; + } + + /** + * Load the current user's entry + * + * @return bool + */ + function getUserInfo() { + // Don't fetch the same data more than once + if ( $this->fetchedUserInfo ) { + return true; + } + $userInfo = $this->getUserInfoStateless( $this->userdn ); + if ( is_null( $userInfo ) ) { + $this->fetchedUserInfo = false; + } else { + $this->fetchedUserInfo = true; + $this->userInfo = $userInfo; + } + return $this->fetchedUserInfo; + } + + /** + * @param $userdn string + * @return array|null + */ + function getUserInfoStateless( $userdn ) { + global $wgMemc; + + $key = wfMemcKey( 'ldapauthentication', 'userinfo', $userdn ); + $userInfo = $wgMemc->get( $key ); + if ( !is_array( $userInfo ) ) { + $entry = LdapAuthenticationPlugin::ldap_read( $this->ldapconn, $userdn, "objectclass=*", array( '*', 'memberof' ) ); + $userInfo = LdapAuthenticationPlugin::ldap_get_entries( $this->ldapconn, $entry ); + if ( $userInfo["count"] < 1 ) { + return null; + } + $wgMemc->set( $key, $userInfo, 3600 * 24 ); + } + return $userInfo; + } + + /** + * Retrieve user preferences from LDAP + */ + private function getPreferences() { + $this->printDebug( "Entering getPreferences", NONSENSITIVE ); + + // Retrieve preferences + $prefs = $this->getConf( 'Preferences' ); + if ( !$prefs ) { + return null; + } + if ( !$this->getUserInfo() ) { + $this->printDebug( "Failed to get preferences, the user's entry wasn't found.", NONSENSITIVE ); + return null; + } + $this->printDebug( "Retrieving preferences", NONSENSITIVE ); + foreach ( array_keys( $prefs ) as $key ) { + $attr = strtolower( $prefs[$key] ); + if ( !isset( $this->userInfo[0][$attr] ) ) { + continue; + } + $value = $this->userInfo[0][$attr][0]; + switch ( $key ) { + case "email": + $this->email = $value; + $this->printDebug( "Retrieved email ($this->email) using attribute ($prefs[$key])", NONSENSITIVE ); + break; + case "language": + $this->lang = $value; + $this->printDebug( "Retrieved language ($this->lang) using attribute ($prefs[$key])", NONSENSITIVE ); + break; + case "nickname": + $this->nickname = $value; + $this->printDebug( "Retrieved nickname ($this->nickname) using attribute ($prefs[$key])", NONSENSITIVE ); + break; + case "realname": + $this->realname = $value; + $this->printDebug( "Retrieved realname ($this->realname) using attribute ($prefs[$key])", NONSENSITIVE ); + break; + } + } + } + + /** + * Checks to see whether a user is in a required group. + * + * @return bool + */ + private function checkGroups() { + $this->printDebug( "Entering checkGroups", NONSENSITIVE ); + + $excgroups = $this->getConf( 'ExcludedGroups' ); + if ( $excgroups ) { + $this->printDebug( "Checking for excluded group membership", NONSENSITIVE ); + for ( $i = 0; $i < count( $excgroups ); $i++ ) { + $excgroups[$i] = strtolower( $excgroups[$i] ); + } + + $this->printDebug( "Excluded groups:", NONSENSITIVE, $excgroups ); + + foreach ( $this->userLDAPGroups["dn"] as $group ) { + $this->printDebug( "Checking against: $group", NONSENSITIVE ); + if ( in_array( $group, $excgroups ) ) { + $this->printDebug( "Found user in an excluded group.", NONSENSITIVE ); + return false; + } + } + } + + $reqgroups = $this->getConf( 'RequiredGroups' ); + if ( $reqgroups ) { + $this->printDebug( "Checking for (new style) group membership", NONSENSITIVE ); + for ( $i = 0; $i < count( $reqgroups ); $i++ ) { + $reqgroups[$i] = strtolower( $reqgroups[$i] ); + } + + $this->printDebug( "Required groups:", NONSENSITIVE, $reqgroups ); + + foreach ( $this->userLDAPGroups["dn"] as $group ) { + $this->printDebug( "Checking against: $group", NONSENSITIVE ); + if ( in_array( $group, $reqgroups ) ) { + $this->printDebug( "Found user in a group.", NONSENSITIVE ); + return true; + } + } + + $this->printDebug( "Couldn't find the user in any groups.", NONSENSITIVE ); + return false; + } + + // Ensure we return true if we aren't checking groups. + return true; + } + + /** + * Function to get the user's groups. + * @param string $username + */ + protected function getGroups( $username ) { + $this->printDebug( "Entering getGroups", NONSENSITIVE ); + + // Ensure userLDAPGroups is set, no matter what + $this->userLDAPGroups = array( "dn"=> array(), "short"=>array() ); + + // Find groups + if ( $this->getConf( 'RequiredGroups' ) || $this->getConf( 'UseLDAPGroups' ) ) { + $this->printDebug( "Retrieving LDAP group membership", NONSENSITIVE ); + + // Let's figure out what we should be searching for + if ( $this->getConf( 'GroupUseFullDN' ) ) { + $usertopass = $this->userdn; + } else { + if ( $this->getConf( 'GroupUseRetrievedUsername' ) && $this->LDAPUsername != '' ) { + + $usertopass = $this->LDAPUsername; + } else { + $usertopass = $username; + } + } + + if ( $this->getConf( 'GroupsUseMemberOf' ) ) { + $this->printDebug( "Using memberOf", NONSENSITIVE ); + if ( !$this->getUserInfo() ) { + $this->printDebug( "Couldn't get the user's entry.", NONSENSITIVE ); + } else if ( isset( $this->userInfo[0]["memberof"] ) ) { + # The first entry is always a count + $memberOfMembers = $this->userInfo[0]["memberof"]; + array_shift( $memberOfMembers ); + $groups = array( "dn"=> array(), "short"=>array() ); + + foreach( $memberOfMembers as $mem ) { + array_push( $groups["dn"], strtolower( $mem ) ); + + // Get short name of group + $memAttrs = explode( ',', strtolower( $mem ) ); + if ( isset( $memAttrs[0] ) ) { + $memAttrs = explode( '=', $memAttrs[0] ); + if ( isset( $memAttrs[0] ) ) { + array_push( $groups["short"], strtolower( $memAttrs[1] ) ); + } + } + } + $this->printDebug( "Got the following groups:", SENSITIVE, $groups["dn"] ); + + $this->userLDAPGroups = $groups; + } else { + $this->printDebug( "memberOf attribute isn't set", NONSENSITIVE ); + } + } else { + $this->printDebug( "Searching for the groups", NONSENSITIVE ); + $this->userLDAPGroups = $this->searchGroups( $usertopass ); + if ( $this->getConf( 'GroupSearchNestedGroups' ) ) { + $this->userLDAPGroups = $this->searchNestedGroups( $this->userLDAPGroups ); + $this->printDebug( "Got the following nested groups:", SENSITIVE, $this->userLDAPGroups["dn"] ); + } + } + // Only find all groups if the user has any groups; otherwise, we are + // just wasting a search. + if ( $this->getConf( 'GroupsPrevail' ) && count( $this->userLDAPGroups ) != 0 ) { + $this->allLDAPGroups = $this->searchGroups( '*' ); + } + } + } + + /** + * Function to return an array of nested groups when given a group or list of groups. + * $searchedgroups is used for tail recursion and shouldn't be provided + * when called externally. + * + * @param $groups + * @param array $searchedgroups + * @return bool + * @access private + */ + function searchNestedGroups( $groups, $searchedgroups = array( "dn" => array(), "short" => array() ) ) { + $this->printDebug( "Entering searchNestedGroups", NONSENSITIVE ); + + // base case, no more groups left to check + if ( count( $groups["dn"] ) == 0 ) { + $this->printDebug( "No more groups to search.", NONSENSITIVE ); + return $searchedgroups; + } + + $this->printDebug( "Searching groups:", SENSITIVE, $groups["dn"] ); + $groupstosearch = array( "short" => array(), "dn" => array() ); + foreach ( $groups["dn"] as $group ) { + $returnedgroups = $this->searchGroups( $group ); + $this->printDebug( "Group $group is in the following groups:", SENSITIVE, $returnedgroups["dn"] ); + foreach ( $returnedgroups["dn"] as $searchme ) { + if ( in_array( $searchme, $searchedgroups["dn"] ) ) { + // We already searched this, move on + continue; + } else { + // We'll need to search this group's members now + $this->printDebug( "Adding $searchme to the list of groups (1)", SENSITIVE ); + $groupstosearch["dn"][] = $searchme; + } + } + foreach ( $returnedgroups["short"] as $searchme ) { + if ( in_array( $searchme, $searchedgroups["short"] ) ) { + // We already searched this, move on + continue; + } else { + $this->printDebug( "Adding $searchme to the list of groups (2)", SENSITIVE ); + // We'll need to search this group's members now + $groupstosearch["short"][] = $searchme; + } + } + } + $searchedgroups = array_merge_recursive( $groups, $searchedgroups ); + + return $this->searchNestedGroups( $groupstosearch, $searchedgroups ); + } + + /** + * Search groups for the supplied DN + * + * @param string $dn + * @return array + */ + private function searchGroups( $dn ) { + $this->printDebug( "Entering searchGroups", NONSENSITIVE ); + + $base = $this->getBaseDN( GROUPDN ); + $objectclass = $this->getConf( 'GroupObjectclass' ); + $attribute = $this->getConf( 'GroupAttribute' ); + $nameattribute = $this->getConf( 'GroupNameAttribute' ); + + // We actually want to search for * not \2a, ensure we don't escape * + $value = $dn; + if ( $value != "*" ) { + $value = $this->getLdapEscapedString( $value ); + } + + $proxyagent = $this->getConf( 'ProxyAgent' ); + if ( $proxyagent ) { + // We'll try to bind as the proxyagent as the proxyagent should normally have more + // rights than the user. If the proxyagent fails to bind, we will still be able + // to search as the normal user (which is why we don't return on fail). + $this->printDebug( "Binding as the proxyagent", NONSENSITIVE ); + $this->bindAs( $proxyagent, $this->getConf( 'ProxyAgentPassword' ) ); + } + + $groups = array( "short" => array(), "dn" => array() ); + + // AD does not include the primary group in the list of groups, we have to find it ourselves. + if ( $dn != "*" && $this->getConf('ActiveDirectory')) { + $PGfilter = "(&(distinguishedName=$value)(objectclass=user))"; + $this->printDebug( "User Filter: $PGfilter", SENSITIVE ); + $PGinfo = LdapAuthenticationPlugin::ldap_search( $this->ldapconn, $base, $PGfilter ); + $PGentries = LdapAuthenticationPlugin::ldap_get_entries( $this->ldapconn, $PGinfo ); + if ( $PGentries ) { + $Usid = $PGentries[0]['objectsid'][0]; + $PGrid = $PGentries[0]['primarygroupid'][0]; + $PGsid = bin2hex( $Usid ); + $PGSID = array(); + for ( $i=0; $i < 56; $i += 2 ) { + $PGSID[] = substr( $PGsid, $i, 2 ); + } + $dPGrid = dechex( $PGrid ); + $dPGrid = str_pad( $dPGrid, 8, '0', STR_PAD_LEFT ); + $PGRID = array(); + for ( $i = 0; $i < 8; $i += 2 ) { + array_push( $PGRID, substr( $dPGrid, $i, 2 ) ); + } + for ( $i = 24; $i < 28; $i++ ) { + $PGSID[$i] = array_pop( $PGRID ); + } + $PGsid_string = ''; + foreach ( $PGSID as $PGsid_bit ) { + $PGsid_string .= "\\" . $PGsid_bit; + } + $PGfilter = "(&(objectSid=$PGsid_string)(objectclass=$objectclass))"; + $this->printDebug( "Primary Group Filter: $PGfilter", SENSITIVE ); + $info = LdapAuthenticationPlugin::ldap_search( $this->ldapconn, $base, $PGfilter ); + $PGentries = LdapAuthenticationPlugin::ldap_get_entries( $this->ldapconn, $info ); + array_shift( $PGentries ); + $dnMember = strtolower( $PGentries[0]['dn'] ); + $groups["dn"][] = $dnMember; + // Get short name of group + $memAttrs = explode( ',', strtolower( $dnMember ) ); + if ( isset( $memAttrs[0] ) ) { + $memAttrs = explode( '=', $memAttrs[0] ); + if ( isset( $memAttrs[1] ) ) { + $groups["short"][] = strtolower( $memAttrs[1] ); + } + } + + } + } + + $filter = "(&($attribute=$value)(objectclass=$objectclass))"; + $this->printDebug( "Search string: $filter", SENSITIVE ); + $info = LdapAuthenticationPlugin::ldap_search( $this->ldapconn, $base, $filter ); + if ( !$info ) { + $this->printDebug( "No entries returned from search.", SENSITIVE ); + // Return an array so that other functions + // don't error out. + return array( "short" => array(), "dn" => array() ); + } + + $entries = LdapAuthenticationPlugin::ldap_get_entries( $this->ldapconn, $info ); + if ( $entries ){ + // We need to shift because the first entry will be a count + array_shift( $entries ); + // Let's get a list of both full dn groups and shortname groups + foreach ( $entries as $entry ) { + $shortMember = strtolower( $entry[$nameattribute][0] ); + $dnMember = strtolower( $entry['dn'] ); + $groups["short"][] = $shortMember; + $groups["dn"][] = $dnMember; + } + } + + $this->printDebug( "Returned groups:", SENSITIVE, $groups["dn"] ); + return $groups; + } + + /** + * Returns true if this group is in the list of the currently authenticated + * user's groups, else false. + * + * @param string $group + * @return bool + * @access private + */ + function hasLDAPGroup( $group ) { + $this->printDebug( "Entering hasLDAPGroup", NONSENSITIVE ); + return in_array( strtolower( $group ), $this->userLDAPGroups["short"] ); + } + + /** + * Returns true if an LDAP group with this name exists, else false. + * + * @param string $group + * @return bool + * @access private + */ + function isLDAPGroup( $group ) { + $this->printDebug( "Entering isLDAPGroup", NONSENSITIVE ); + return in_array( strtolower( $group ), $this->allLDAPGroups["short"] ); + } + + /** + * Helper function for updateUser() and initUser(). Adds users into MediaWiki security groups + * based upon groups retreived from LDAP. + * + * @param User $user + * @access private + */ + function setGroups( &$user ) { + global $wgGroupPermissions; + + // TODO: this is *really* ugly code. clean it up! + $this->printDebug( "Entering setGroups.", NONSENSITIVE ); + + # Add ldap groups as local groups + if ( $this->getConf( 'GroupsPrevail' ) ) { + $this->printDebug( "Adding all groups to wgGroupPermissions: ", SENSITIVE, $this->allLDAPGroups ); + + foreach ( $this->allLDAPGroups["short"] as $ldapgroup ) { + if ( !array_key_exists( $ldapgroup, $wgGroupPermissions ) ) { + $wgGroupPermissions[$ldapgroup] = array(); + } + } + } + + # add groups permissions + $localAvailGrps = $user->getAllGroups(); + $localUserGrps = $user->getEffectiveGroups(); + $defaultLocallyManagedGrps = array( 'bot', 'sysop', 'bureaucrat' ); + $locallyManagedGrps = $this->getConf( 'LocallyManagedGroups' ); + if ( $locallyManagedGrps ) { + $locallyManagedGrps = array_unique( array_merge( $defaultLocallyManagedGrps, $locallyManagedGrps ) ); + $this->printDebug( "Locally managed groups: ", SENSITIVE, $locallyManagedGrps ); + } else { + $locallyManagedGrps = $defaultLocallyManagedGrps; + $this->printDebug( "Locally managed groups is unset, using defaults: ", SENSITIVE, $locallyManagedGrps ); + } + + $this->printDebug( "Available groups are: ", NONSENSITIVE, $localAvailGrps ); + $this->printDebug( "Effective groups are: ", NONSENSITIVE, $localUserGrps ); + # note: $localUserGrps does not need to be updated with $cGroup added, + # as $localAvailGrps contains $cGroup only once. + foreach ( $localAvailGrps as $cGroup ) { + # did we once add the user to the group? + if ( in_array( $cGroup, $localUserGrps ) ) { + $this->printDebug( "Checking to see if we need to remove user from: $cGroup", NONSENSITIVE ); + if ( ( !$this->hasLDAPGroup( $cGroup ) ) && ( !in_array( $cGroup, $locallyManagedGrps ) ) ) { + $this->printDebug( "Removing user from: $cGroup", NONSENSITIVE ); + # the ldap group overrides the local group + # so as the user is currently not a member of the ldap group, he shall be removed from the local group + $user->removeGroup( $cGroup ); + } + } else { # no, but maybe the user has recently been added to the ldap group? + $this->printDebug( "Checking to see if user is in: $cGroup", NONSENSITIVE ); + if ( $this->hasLDAPGroup( $cGroup ) ) { + $this->printDebug( "Adding user to: $cGroup", NONSENSITIVE ); + $user->addGroup( $cGroup ); + } + } + } + } + + /** + * Returns a password that is created via the configured hash settings. + * + * @param string $password + * @return string + * @access private + */ + function getPasswordHash( $password ) { + $this->printDebug( "Entering getPasswordHash", NONSENSITIVE ); + + // Set the password hashing based upon admin preference + switch ( $this->getConf( 'PasswordHash' ) ) { + case 'crypt': + // https://bugs.php.net/bug.php?id=55439 + if ( crypt( 'password', '$1$U7AjYB.O$' ) == '$1$U7AjYB.O' ) { + die( 'The version of PHP in use has a broken crypt function. Please upgrade your installation of PHP, or use another encryption function for LDAP.' ); + } + $pass = '{CRYPT}' . crypt( $password ); + break; + case 'clear': + $pass = $password; + break; + default: + $pwd_sha = base64_encode( pack( 'H*', sha1( $password ) ) ); + $pass = "{SHA}" . $pwd_sha; + break; + } + + return $pass; + } + + /** + * Prints debugging information. $debugText is what you want to print, $debugVal + * is the level at which you want to print the information. + * + * @param string $debugText + * @param string $debugVal + * @param Array|null $debugArr + * @access private + */ + function printDebug( $debugText, $debugVal, $debugArr = null ) { + if ( !function_exists( 'wfDebugLog' ) ) { + return; + } + + global $wgLDAPDebug; + + if ( $wgLDAPDebug >= $debugVal ) { + if ( isset( $debugArr ) ) { + $debugText = $debugText . " " . implode( "::", $debugArr ); + } + wfDebugLog( 'ldap', LDAPAUTHVERSION . ' ' . $debugText, false ); + } + } + + /** + * Binds as $userdn with $password. This can be called with only the ldap + * connection resource for an anonymous bind. + * + * @param string $userdn + * @param string $password + * @return bool + * @access private + */ + function bindAs( $userdn = null, $password = null ) { + // Let's see if the user can authenticate. + if ( $userdn == null || $password == null ) { + $bind = LdapAuthenticationPlugin::ldap_bind( $this->ldapconn ); + } else { + $bind = LdapAuthenticationPlugin::ldap_bind( $this->ldapconn, $userdn, $password ); + } + if ( !$bind ) { + $this->printDebug( "Failed to bind as $userdn", NONSENSITIVE ); + return false; + } + $this->boundAs = $userdn; + return true; + } + + /** + * Returns true if auto-authentication is allowed, and the user is + * authenticating using the auto-authentication domain. + * + * @return bool + * @access private + */ + function useAutoAuth() { + return $this->getDomain() == $this->getConf( 'AutoAuthDomain' ); + } + + /** + * Returns a string which has the chars *, (, ), \ & NUL escaped to LDAP compliant + * syntax as per RFC 2254 + * Thanks and credit to Iain Colledge for the research and function. + * + * @param string $string + * @return string + * @access private + */ + function getLdapEscapedString( $string ) { + // Make the string LDAP compliant by escaping *, (, ) , \ & NUL + return str_replace( + array( "\\", "(", ")", "*", "\x00" ), + array( "\\5c", "\\28", "\\29", "\\2a", "\\00" ), + $string + ); + } + + /** + * Returns a basedn by the type of entry we are searching for. + * + * @param int $type + * @return string + * @access private + */ + function getBaseDN( $type ) { + $this->printDebug( "Entering getBaseDN", NONSENSITIVE ); + + $ret = ''; + switch( $type ) { + case USERDN: + $ret = $this->getConf( 'UserBaseDN' ); + break; + case GROUPDN: + $ret = $this->getConf( 'GroupBaseDN' ); + break; + case DEFAULTDN: + $ret = $this->getConf( 'BaseDN' ); + if ( $ret ) { + return $ret; + } else { + $this->printDebug( "basedn is not set.", NONSENSITIVE ); + return ''; + } + } + + if ( $ret == '' ) { + $this->printDebug( "basedn is not set for this type of entry, trying to get the default basedn.", NONSENSITIVE ); + // We will never reach here if $type is self::DEFAULTDN, so to avoid code + // code duplication, we'll get the default by re-calling the function. + return $this->getBaseDN( DEFAULTDN ); + } else { + $this->printDebug( "basedn is $ret", NONSENSITIVE ); + return $ret; + } + } + + /** + * @param User $user + * @return string + */ + static function loadDomain( $user ) { + $user_id = $user->getId(); + if ( $user_id != 0 ) { + $dbr = wfGetDB( DB_SLAVE ); + $row = $dbr->selectRow( + 'ldap_domains', + array( 'domain' ), + array( 'user_id' => $user_id ), + __METHOD__ ); + + if ( $row ) { + return $row->domain; + } + } + + return null; + } + + /** + * @param User $user + * @param string $domain + * @return bool + */ + static function saveDomain( $user, $domain ) { + $user_id = $user->getId(); + if ( $user_id != 0 ) { + $dbw = wfGetDB( DB_MASTER ); + $olddomain = self::loadDomain( $user ); + if ( $olddomain ) { + return $dbw->update( + 'ldap_domains', + array( 'domain' => $domain ), + array( 'user_id' => $user_id ), + __METHOD__ ); + } else { + return $dbw->insert( + 'ldap_domains', + array( 'domain' => $domain, + 'user_id' => $user_id ), + __METHOD__ ); + } + } + return false; + } + +} + +// The auto-auth code was originally derived from the SSL Authentication plugin +// http://www.mediawiki.org/wiki/SSL_authentication + +/** + * Sets up the auto-authentication piece of the LDAP plugin. + * + * @access public + */ +function AutoAuthSetup() { + global $wgHooks; + global $wgAuth; + $wgAuth = new LdapAuthenticationPlugin(); + + $wgAuth->printDebug( "Entering AutoAuthSetup.", NONSENSITIVE ); + + # We need both authentication username and domain (bug 34787) + if ( $wgAuth->getConf("AutoAuthUsername") !== "" && $wgAuth->getConf("AutoAuthDomain") !== "" ) { + $wgAuth->printDebug( "wgLDAPAutoAuthUsername and wgLDAPAutoAuthDomain is not null, adding hooks.", NONSENSITIVE ); + $wgHooks['UserLoadAfterLoadFromSession'][] = 'LdapAutoAuthentication::Authenticate'; + + $wgHooks['PersonalUrls'][] = 'LdapAutoAuthentication::NoLogout'; /* Disallow logout link */ + } +} diff --git a/sources/LdapAuthentication/LdapAutoAuthentication.php b/sources/LdapAuthentication/LdapAutoAuthentication.php new file mode 100644 index 0000000..2dd2421 --- /dev/null +++ b/sources/LdapAuthentication/LdapAutoAuthentication.php @@ -0,0 +1,124 @@ +printDebug( "Entering AutoAuthentication.", NONSENSITIVE ); + + if ( $user->isLoggedIn() ) { + $wgAuth->printDebug( "User is already logged in.", NONSENSITIVE ); + return true; + } + + $wgAuth->printDebug( "User isn't logged in, calling setup.", NONSENSITIVE ); + + // Let regular authentication plugins configure themselves for auto + // authentication chaining + $wgAuth->autoAuthSetup(); + + $autoauthname = $wgAuth->getConf( 'AutoAuthUsername' ); + $wgAuth->printDebug( "Calling authenticate with username ($autoauthname).", NONSENSITIVE ); + + // The user hasn't already been authenticated, let's check them + $authenticated = $wgAuth->authenticate( $autoauthname, '' ); + if ( !$authenticated ) { + // If the user doesn't exist in LDAP, there isn't much reason to + // go any further. + $wgAuth->printDebug( "User wasn't found in LDAP, exiting.", NONSENSITIVE ); + return false; + } + + // We need the username that MediaWiki will always use, not necessarily the one we + // get from LDAP. + $mungedUsername = $wgAuth->getCanonicalName( $autoauthname ); + + $wgAuth->printDebug( "User exists in LDAP; finding the user by name ($mungedUsername) in MediaWiki.", NONSENSITIVE ); + $localId = User::idFromName( $mungedUsername ); + $wgAuth->printDebug( "Got id ($localId).", NONSENSITIVE ); + + // Is the user already in the database? + if ( !$localId ) { + $userAdded = self::attemptAddUser( $user, $mungedUsername ); + if ( !$userAdded ) { + $result = false; + return false; + } + } else { + $wgAuth->printDebug( "User exists in local database, logging in.", NONSENSITIVE ); + $user->setID( $localId ); + $user->loadFromId(); + $user->setCookies(); + $wgAuth->updateUser( $user ); + wfSetupSession(); + $result = true; + } + + return true; + } + + /** + * @param $user User + * @param $mungedUsername String + * @return bool + */ + public static function attemptAddUser( $user, $mungedUsername ) { + /** + * @var $wgAuth LdapAuthenticationPlugin + */ + global $wgAuth; + + if ( !$wgAuth->autoCreate() ) { + $wgAuth->printDebug( "Cannot automatically create accounts.", NONSENSITIVE ); + return false; + } + + $wgAuth->printDebug( "User does not exist in local database; creating.", NONSENSITIVE ); + // Checks passed, create the user + $user->loadDefaults( $mungedUsername ); + $status = $user->addToDatabase(); + if ( $status !== null && !$status->isOK() ) { + $wgAuth->printDebug( "Creation failed: " . $status->getWikiText(), NONSENSITIVE ); + return false; + } + $wgAuth->initUser( $user, true ); + $user->setCookies(); + wfSetupSession(); + # Update user count + $ssUpdate = new SiteStatsUpdate( 0, 0, 0, 0, 1 ); + $ssUpdate->doUpdate(); + # Notify hooks (e.g. Newuserlog) + wfRunHooks( 'AuthPluginAutoCreate', array( $user ) ); + + return true; + } + + /** + * No logout link in MW + * @param $personal_urls array + * @param $title Title + * @return bool + */ + public static function NoLogout( &$personal_urls, $title ) { + /** + * @var $wgAuth LdapAuthenticationPlugin + */ + global $wgAuth; + + $wgAuth->printDebug( "Entering NoLogout.", NONSENSITIVE ); + unset( $personal_urls['logout'] ); + return true; + } + +} diff --git a/sources/LdapAuthentication/README b/sources/LdapAuthentication/README new file mode 100644 index 0000000..9db9447 --- /dev/null +++ b/sources/LdapAuthentication/README @@ -0,0 +1 @@ +This authentication plugin allows MediaWiki to use an LDAP store as its user database for authentication, and some authorization. Full functionality and configuration information can be found at: http://www.mediawiki.org/wiki/Extension:LDAP_Authentication diff --git a/sources/LdapAuthentication/schema/ldap-mysql.sql b/sources/LdapAuthentication/schema/ldap-mysql.sql new file mode 100644 index 0000000..fa96916 --- /dev/null +++ b/sources/LdapAuthentication/schema/ldap-mysql.sql @@ -0,0 +1,13 @@ +CREATE TABLE /*_*/ldap_domains ( + -- IF for domain + domain_id int not null primary key auto_increment, + + -- domain itself + domain varchar(255) binary not null, + + -- User to which this domain belongs + user_id int not null + +) /*$wgDBTableOptions*/; + +CREATE INDEX /*i*/user_id on /*_*/ldap_domains (user_id); diff --git a/sources/LdapAuthentication/schema/ldap-postgres.sql b/sources/LdapAuthentication/schema/ldap-postgres.sql new file mode 100644 index 0000000..61509fb --- /dev/null +++ b/sources/LdapAuthentication/schema/ldap-postgres.sql @@ -0,0 +1,13 @@ +CREATE TABLE ldap_domains ( + -- IF for domain + domain_id serial PRIMARY KEY, + + -- domain itself + domain varchar(255) not null, + + -- User to which this domain belongs + user_id integer not null + +) /*$wgDBTableOptions*/; + +CREATE INDEX user_id on ldap_domains (user_id); From abc67b0e495a1ef098400c5c862e8013cc8b0a72 Mon Sep 17 00:00:00 2001 From: Kload Date: Thu, 8 May 2014 10:18:45 +0200 Subject: [PATCH 2/2] [fix] Update APT repo before dependency installation + copy directories --- scripts/install | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/install b/scripts/install index 2d92ea4..91591d3 100644 --- a/scripts/install +++ b/scripts/install @@ -18,6 +18,7 @@ db_pwd=$(dd if=/dev/urandom bs=1 count=200 2> /dev/null | tr -c -d '[A-Za-z0-9]' db_user=mediawiki # Instal php5-cli dependency +sudo apt-get update sudo apt-get install php5-cli -y # Initialize database and store mysql password for upgrade @@ -27,7 +28,7 @@ sudo yunohost app setting mediawiki mysqlpwd -v $db_pwd # Copy files to the right place final_path=/var/www/mediawiki sudo mkdir -p $final_path -sudo cp ../sources/mediawiki/* $final_path +sudo cp -r ../sources/mediawiki/* $final_path sudo cp ../conf/LocalSettings.php $final_path/ # LDAP Extension