PageRenderTime 56ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/inc/auth/ad.class.php

http://github.com/splitbrain/dokuwiki
PHP | 351 lines | 248 code | 22 blank | 81 comment | 34 complexity | 4c63fc4b54a0649b823f37fcf3f5accd MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.1, GPL-2.0
  1. <?php
  2. /**
  3. * Active Directory authentication backend for DokuWiki
  4. *
  5. * This makes authentication with a Active Directory server much easier
  6. * than when using the normal LDAP backend by utilizing the adLDAP library
  7. *
  8. * Usage:
  9. * Set DokuWiki's local.protected.php auth setting to read
  10. *
  11. * $conf['useacl'] = 1;
  12. * $conf['disableactions'] = 'register';
  13. * $conf['autopasswd'] = 0;
  14. * $conf['authtype'] = 'ad';
  15. * $conf['passcrypt'] = 'ssha';
  16. *
  17. * $conf['auth']['ad']['account_suffix'] = '@my.domain.org';
  18. * $conf['auth']['ad']['base_dn'] = 'DC=my,DC=domain,DC=org';
  19. * $conf['auth']['ad']['domain_controllers'] = 'srv1.domain.org,srv2.domain.org';
  20. *
  21. * //optional:
  22. * $conf['auth']['ad']['sso'] = 1;
  23. * $conf['auth']['ad']['ad_username'] = 'root';
  24. * $conf['auth']['ad']['ad_password'] = 'pass';
  25. * $conf['auth']['ad']['real_primarygroup'] = 1;
  26. * $conf['auth']['ad']['use_ssl'] = 1;
  27. * $conf['auth']['ad']['use_tls'] = 1;
  28. * $conf['auth']['ad']['debug'] = 1;
  29. *
  30. * // get additional information to the userinfo array
  31. * // add a list of comma separated ldap contact fields.
  32. * $conf['auth']['ad']['additional'] = 'field1,field2';
  33. *
  34. * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
  35. * @author James Van Lommel <jamesvl@gmail.com>
  36. * @link http://www.nosq.com/blog/2005/08/ldap-activedirectory-and-dokuwiki/
  37. * @author Andreas Gohr <andi@splitbrain.org>
  38. */
  39. require_once(DOKU_INC.'inc/adLDAP.php');
  40. class auth_ad extends auth_basic {
  41. var $cnf = null;
  42. var $opts = null;
  43. var $adldap = null;
  44. var $users = null;
  45. /**
  46. * Constructor
  47. */
  48. function auth_ad() {
  49. global $conf;
  50. $this->cnf = $conf['auth']['ad'];
  51. // additional information fields
  52. if (isset($this->cnf['additional'])) {
  53. $this->cnf['additional'] = str_replace(' ', '', $this->cnf['additional']);
  54. $this->cnf['additional'] = explode(',', $this->cnf['additional']);
  55. } else $this->cnf['additional'] = array();
  56. // ldap extension is needed
  57. if (!function_exists('ldap_connect')) {
  58. if ($this->cnf['debug'])
  59. msg("AD Auth: PHP LDAP extension not found.",-1);
  60. $this->success = false;
  61. return;
  62. }
  63. // Prepare SSO
  64. if($_SERVER['REMOTE_USER'] && $this->cnf['sso']){
  65. // remove possible NTLM domain
  66. list($dom,$usr) = explode('\\',$_SERVER['REMOTE_USER'],2);
  67. if(!$usr) $usr = $dom;
  68. // remove possible Kerberos domain
  69. list($usr,$dom) = explode('@',$usr);
  70. $dom = strtolower($dom);
  71. $_SERVER['REMOTE_USER'] = $usr;
  72. // we need to simulate a login
  73. if(empty($_COOKIE[DOKU_COOKIE])){
  74. $_REQUEST['u'] = $_SERVER['REMOTE_USER'];
  75. $_REQUEST['p'] = 'sso_only';
  76. }
  77. }
  78. // prepare adLDAP standard configuration
  79. $this->opts = $this->cnf;
  80. // add possible domain specific configuration
  81. if($dom && is_array($this->cnf[$dom])) foreach($this->cnf[$dom] as $key => $val){
  82. $this->opts[$key] = $val;
  83. }
  84. // handle multiple AD servers
  85. $this->opts['domain_controllers'] = explode(',',$this->opts['domain_controllers']);
  86. $this->opts['domain_controllers'] = array_map('trim',$this->opts['domain_controllers']);
  87. $this->opts['domain_controllers'] = array_filter($this->opts['domain_controllers']);
  88. // we can change the password if SSL is set
  89. if($this->opts['use_ssl'] || $this->opts['use_tls']){
  90. $this->cando['modPass'] = true;
  91. }
  92. $this->cando['modName'] = true;
  93. $this->cando['modMail'] = true;
  94. }
  95. /**
  96. * Check user+password [required auth function]
  97. *
  98. * Checks if the given user exists and the given
  99. * plaintext password is correct by trying to bind
  100. * to the LDAP server
  101. *
  102. * @author James Van Lommel <james@nosq.com>
  103. * @return bool
  104. */
  105. function checkPass($user, $pass){
  106. if($_SERVER['REMOTE_USER'] &&
  107. $_SERVER['REMOTE_USER'] == $user &&
  108. $this->cnf['sso']) return true;
  109. if(!$this->_init()) return false;
  110. return $this->adldap->authenticate($user, $pass);
  111. }
  112. /**
  113. * Return user info [required auth function]
  114. *
  115. * Returns info about the given user needs to contain
  116. * at least these fields:
  117. *
  118. * name string full name of the user
  119. * mail string email address of the user
  120. * grps array list of groups the user is in
  121. *
  122. * This LDAP specific function returns the following
  123. * addional fields:
  124. *
  125. * dn string distinguished name (DN)
  126. * uid string Posix User ID
  127. *
  128. * @author James Van Lommel <james@nosq.com>
  129. */
  130. function getUserData($user){
  131. global $conf;
  132. if(!$this->_init()) return false;
  133. $fields = array('mail','displayname','samaccountname');
  134. // add additional fields to read
  135. $fields = array_merge($fields, $this->cnf['additional']);
  136. $fields = array_unique($fields);
  137. //get info for given user
  138. $result = $this->adldap->user_info($user, $fields);
  139. //general user info
  140. $info['name'] = $result[0]['displayname'][0];
  141. $info['mail'] = $result[0]['mail'][0];
  142. $info['uid'] = $result[0]['samaccountname'][0];
  143. $info['dn'] = $result[0]['dn'];
  144. // additional information
  145. foreach ($this->cnf['additional'] as $field) {
  146. if (isset($result[0][strtolower($field)])) {
  147. $info[$field] = $result[0][strtolower($field)][0];
  148. }
  149. }
  150. // handle ActiveDirectory memberOf
  151. $info['grps'] = $this->adldap->user_groups($user,(bool) $this->opts['recursive_groups']);
  152. if (is_array($info['grps'])) {
  153. foreach ($info['grps'] as $ndx => $group) {
  154. $info['grps'][$ndx] = $this->cleanGroup($group);
  155. }
  156. }
  157. // always add the default group to the list of groups
  158. if(!is_array($info['grps']) || !in_array($conf['defaultgroup'],$info['grps'])){
  159. $info['grps'][] = $conf['defaultgroup'];
  160. }
  161. return $info;
  162. }
  163. /**
  164. * Make AD group names usable by DokuWiki.
  165. *
  166. * Removes backslashes ('\'), pound signs ('#'), and converts spaces to underscores.
  167. *
  168. * @author James Van Lommel (jamesvl@gmail.com)
  169. */
  170. function cleanGroup($name) {
  171. $sName = str_replace('\\', '', $name);
  172. $sName = str_replace('#', '', $sName);
  173. $sName = preg_replace('[\s]', '_', $sName);
  174. return $sName;
  175. }
  176. /**
  177. * Sanitize user names
  178. */
  179. function cleanUser($name) {
  180. return $this->cleanGroup($name);
  181. }
  182. /**
  183. * Most values in LDAP are case-insensitive
  184. */
  185. function isCaseSensitive(){
  186. return false;
  187. }
  188. /**
  189. * Bulk retrieval of user data
  190. *
  191. * @author Dominik Eckelmann <dokuwiki@cosmocode.de>
  192. * @param start index of first user to be returned
  193. * @param limit max number of users to be returned
  194. * @param filter array of field/pattern pairs, null for no filter
  195. * @return array of userinfo (refer getUserData for internal userinfo details)
  196. */
  197. function retrieveUsers($start=0,$limit=-1,$filter=array()) {
  198. if(!$this->_init()) return false;
  199. if ($this->users === null) {
  200. //get info for given user
  201. $result = $this->adldap->all_users();
  202. if (!$result) return array();
  203. $this->users = array_fill_keys($result, false);
  204. }
  205. $i = 0;
  206. $count = 0;
  207. $this->_constructPattern($filter);
  208. $result = array();
  209. foreach ($this->users as $user => &$info) {
  210. if ($i++ < $start) {
  211. continue;
  212. }
  213. if ($info === false) {
  214. $info = $this->getUserData($user);
  215. }
  216. if ($this->_filter($user, $info)) {
  217. $result[$user] = $info;
  218. if (($limit >= 0) && (++$count >= $limit)) break;
  219. }
  220. }
  221. return $result;
  222. }
  223. /**
  224. * Modify user data
  225. *
  226. * @param $user nick of the user to be changed
  227. * @param $changes array of field/value pairs to be changed
  228. * @return bool
  229. */
  230. function modifyUser($user, $changes) {
  231. $return = true;
  232. // password changing
  233. if(isset($changes['pass'])){
  234. try {
  235. $return = $this->adldap->user_password($user,$changes['pass']);
  236. } catch (adLDAPException $e) {
  237. if ($this->cnf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
  238. $return = false;
  239. }
  240. if(!$return) msg('AD Auth: failed to change the password. Maybe the password policy was not met?',-1);
  241. }
  242. // changing user data
  243. $adchanges = array();
  244. if(isset($changes['name'])){
  245. // get first and last name
  246. $parts = explode(' ',$changes['name']);
  247. $adchanges['surname'] = array_pop($parts);
  248. $adchanges['firstname'] = join(' ',$parts);
  249. $adchanges['display_name'] = $changes['name'];
  250. }
  251. if(isset($changes['mail'])){
  252. $adchanges['email'] = $changes['mail'];
  253. }
  254. if(count($adchanges)){
  255. try {
  256. $return = $return & $this->adldap->user_modify($user,$adchanges);
  257. } catch (adLDAPException $e) {
  258. if ($this->cnf['debug']) msg('AD Auth: '.$e->getMessage(), -1);
  259. $return = false;
  260. }
  261. }
  262. return $return;
  263. }
  264. /**
  265. * Initialize the AdLDAP library and connect to the server
  266. */
  267. function _init(){
  268. if(!is_null($this->adldap)) return true;
  269. // connect
  270. try {
  271. $this->adldap = new adLDAP($this->opts);
  272. if (isset($this->opts['ad_username']) && isset($this->opts['ad_password'])) {
  273. $this->canDo['getUsers'] = true;
  274. }
  275. return true;
  276. } catch (adLDAPException $e) {
  277. if ($this->cnf['debug']) {
  278. msg('AD Auth: '.$e->getMessage(), -1);
  279. }
  280. $this->success = false;
  281. $this->adldap = null;
  282. }
  283. return false;
  284. }
  285. /**
  286. * return 1 if $user + $info match $filter criteria, 0 otherwise
  287. *
  288. * @author Chris Smith <chris@jalakai.co.uk>
  289. */
  290. function _filter($user, $info) {
  291. foreach ($this->_pattern as $item => $pattern) {
  292. if ($item == 'user') {
  293. if (!preg_match($pattern, $user)) return 0;
  294. } else if ($item == 'grps') {
  295. if (!count(preg_grep($pattern, $info['grps']))) return 0;
  296. } else {
  297. if (!preg_match($pattern, $info[$item])) return 0;
  298. }
  299. }
  300. return 1;
  301. }
  302. function _constructPattern($filter) {
  303. $this->_pattern = array();
  304. foreach ($filter as $item => $pattern) {
  305. // $this->_pattern[$item] = '/'.preg_quote($pattern,"/").'/i'; // don't allow regex characters
  306. $this->_pattern[$item] = '/'.str_replace('/','\/',$pattern).'/i'; // allow regex characters
  307. }
  308. }
  309. }
  310. //Setup VIM: ex: et ts=4 :