LDAP目錄服務折騰之后的總結


前言

公司管理員工信息以及組織架構的后台系統要和Active Directory目錄服務系統打通,后台系統使用PHP開發,

折騰了二十多天,終於上線了,期間碰到過各種疑難問題,不過總算在GOOGLE大叔的幫忙下還有運維部AD管理員的幫助下解決了。

LDAP協議定義

LDAP(Lightweight Directory Access Protocol)輕量目錄訪問協議,定義了目錄服務實現以及訪問規范。

目錄定義

A directory is a specialized database specifically designed for searching and browsing,
in additional to supporting basic lookup and update functions.

LDAP協議實現

0.基於TCP/IP的應用層協議 默認端口389 加密端口636
1.客戶端發送命令,服務器端響應
2.目錄主要操作
    2.0 用戶驗證(bind操作)
     2.1 添加節點
     2.2 更新節點
     2.3 移動節點
     2.4 刪除節點
     2.5 節點搜索
3.節點類型
    3.0 節點屬性規范(SCHEMA)
4.節點
    4.0 目錄里的對象
     4.1 屬性即是節點的數據
     4.2 目錄中通過DN(Distinguished Name)唯一標識(可以認為是路徑)
     4.2.0 節點DN = RDN(Relative Distinguished Name) + 父節點的DN
     4.3 目錄是TREE結構,節點可以有子節點,也可以有父節點
5.屬性
    5.0 同一個屬性可以有多個值
     5.1 包含屬性名稱,屬性類型
6.節點唯一標識DN說明
    6.0 示例: dn:CN=John Doe,OU=Texas,DC=example,DC=com
     6.1 從右到左 根節點 -> 子節點
     6.2 DC:所在控制域 OU:組織單元 CN:通用名稱
7.目錄規范(SCHEMA)
    7.0 目錄節點相關規則
     7.1 Attribute Syntaxes
     7.2 Matching Rules
     7.3 Matching Rule Uses
     7.4 Attribute Types
     7.5 Object Classes
     7.6 Name Forms
     7.7 Content Rules
     7.8 Structure Rule

LDAP服務器端的實現

openLDAP,Active Directory(Microsoft)等等,除了實現協議之外的功能,還對它進行了擴展

LDAP應用場景

0.單點登錄(用戶管理)
1.局域網資源統一管理

封裝的簡單PHP類

適合AD服務器 其他的LDAP服務器需要做相應的修改

zend框架有個開源的LDAP庫實現 完全面向對象

  1 <?php
  2 /**
  3  * @description LDAP客戶端類
  4  *
  5  * @author WadeYu
  6  * @date 2015-04-28
  7  * @version 0.0.1
  8  */
  9 class o_ldap{
 10     private $_conn = NULL;
 11     private $_sErrLog = '';
 12     private $_sOperLog = '';
 13     private $_aOptions = array(
 14         'host' => 'ldap://xxx.com',
 15         'port' => '389',
 16         'dnSuffix' => 'OU=xx,OU=xx,DC=xx,DC=com',
 17         'loginUser' => '',
 18         'loginPass' => '',
 19     );
 20     private $_aAllowAttrName = array(
 21         'objectClass',
 22         'objectGUID', //AD對象ID
 23         'userPassword', //AD密碼不是這個字段 密碼暫時不能通過程序設置
 24         'unicodePwd', //AD密碼專用字段 $unicodePwd  = mb_convert_encoding('"' . $newPassword . '"', 'utf-16le');
 25         'cn', //comman name 兄弟節點不能相同
 26         'ou', //organizationalUnit
 27         'description', //員工填工號
 28         'displayName', //中文名
 29         'name', //姓名
 30         'sAMAccountName', //英文名(RTX賬號,唯一)
 31         'userPrincipalName', //登陸用戶名 和 英文名一致
 32         'ProtectedFromAccidentalDeletion', //對象刪除保護
 33         'givenName', //
 34         'sn', //
 35         'employeeNumber', //一卡通卡號
 36         'mail',
 37         'mailNickname',
 38         'manager', //上級 (節點路徑 示例:CN=Texas Poker9,OU=Texas Poker,OU=Dept,OU=BoyaaSZ,DC=by,DC=com)
 39         'title', //頭銜
 40         'pager', //性別 0男 1女 -1未知
 41         'userAccountControl', //用戶賬號策略(暫時不能設置) 資料說明地址:https://support.microsoft.com/en-gb/kb/305144
 42         'department',
 43         'managedBy',//部門負責人
 44         'distinguishedName',
 45         'pwdLastSet', //等於0時 下次登錄時需要修改密碼
 46     );
 47     
 48     public function __construct(array $aOptions = array()){
 49         if (!extension_loaded('ldap')){
 50             $this->_log('LDAP extension not be installed.',true);
 51         }
 52         $this->setOption($aOptions);
 53     }
 54     
 55     /**
 56      * @return exit || true
 57      */
 58     public function connect($force = false){
 59         if (!$this->_conn || $force){
 60             $host = $this->_aOptions['host'];
 61             $port = $this->_aOptions['port'];
 62             $this->_conn = ldap_connect($host,$port);
 63             if ($this->_conn === false){
 64                 $this->_log("Connect LDAP SERVER Failure.[host:{$post}:{$port}]",true);
 65             }
 66             ldap_set_option($this->_conn, LDAP_OPT_PROTOCOL_VERSION, 3);
 67             ldap_set_option($this->_conn, LDAP_OPT_REFERRALS, 3);
 68             $this->_bind();
 69         }
 70         return true;
 71     }
 72     
 73     /**
 74      * @return exit || true
 75      */
 76     private function _bind(){
 77         $u = $this->_aOptions['loginUser'];
 78         $p = $this->_aOptions['loginPass'];
 79         $ret = @ldap_bind($this->_conn,$u,$p);
 80         if ($ret === false){
 81             $this->_log(__FUNCTION__.'----'.$this->_getLastExecErrLog().'----'."u:{$u},p:{$p}",true);
 82         }
 83         return $ret;
 84     }
 85     
 86     public function setOption(array $aOptions = array()){
 87         foreach($this->_aOptions as $k => $v){
 88             if (isset($aOptions[$k])){
 89                 $this->_aOptions[$k] = $aOptions[$k];
 90             }
 91         }
 92     }
 93     
 94     public function getOption($field,$default = ''){
 95         return isset($this->_aOptions[$field]) ? $this->_aOptions[$field] : $default;
 96     }
 97     
 98     /**
 99      * @description 查詢$dn下符合屬性條件的節點 返回$limit條
100      *
101      * @return array [count:x,[[prop:[count:xx,[],[]]],....]]
102      */
103     public function getEntryList($dn,$aAttrFilter,array $aField=array(),$limit = 0,$bFixedDn = true){
104         if (!$dn = trim($dn)){
105             return array();
106         }
107         if (!$this->_checkDn($dn)){
108             return array();
109         }
110         $limit = max(0,intval($limit));
111         $this->connect();
112         if ($bFixedDn){
113             $dn = $this->_getFullDn($dn);
114         }
115         $aOldTmp = $aAttrFilter;
116         $this->_checkAttr($aAttrFilter);
117         if (!$aAttrFilter){
118             $this->_log(__FUNCTION__.'---無效的搜索屬性---'.json_encode($aOldTmp));
119             return array();
120         }
121         $sAttrFilter = $this->_mkAttrFilter($aAttrFilter);
122         $attrOnly = 0;
123         $this->_log(__FUNCTION__."---DN:{$dn}---sAttr:{$sAttrFilter}",false,'oper');
124         $rs = @ldap_search($this->_conn,$dn,$sAttrFilter,$aField,$attrOnly,$limit);
125         if ($rs === false){
126             $this->_log(__FUNCTION__."---dn:{$dn}---sAttr:{$sAttrFilter}---" . $this->_getLastExecErrLog());
127             return array();
128         }
129         $aRet = @ldap_get_entries($this->_conn,$rs);
130         ldap_free_result($rs);
131         if ($aRet === false){
132             $this->_log(__FUNCTION__.'---'.$this->_getLastExecErrLog());
133             return array();
134         }
135         return $aRet;
136     }
137     
138     /**
139      * @description 刪除節點 暫時不考慮遞歸刪除
140      * 
141      * @return boolean
142      */
143     public function delEntry($dn,$bFixedDn = true,$force = 0){
144         if (!$dn = trim($dn)){
145             return false;
146         }
147         if (!$this->_checkDn($dn)){
148             return false;
149         }
150         if ($bFixedDn){
151             $dn = $this->_getFullDn($dn);
152         }
153         $this->_log(__FUNCTION__."---DN:{$dn}",false,'oper');
154         $this->connect();
155         /*if($force){
156             $aEntryList = $this->getEntryList($dn,array('objectClass'=>'*'),array('objectClass'));
157             if ($aEntryList && ($aEntryList['count'] > 0)){ 
158                 for($i = 0; $i < $aEntryList['count']; $i++){
159                     $aDel[] = $aEntryList[$i]['dn'];
160                 }
161             }
162             $aDel = array_reverse($aDel); //默認順序 祖先->子孫 需要先刪除子孫節點
163             $ret = true;
164             foreach($aDel as $k => $v){
165                 $ret &= @ldap_delete($this->_conn,$v);
166             }
167             if ($ret === false){
168                 $this->_log(__FUNCTION__.'dn(recursive):'.$dn.'----'.$this->_getLastExecErrLog());
169             }
170             return $ret;
171         }*/
172         $ret = @ldap_delete($this->_conn,$dn);
173         if ($ret === false){
174             $this->_log(__FUNCTION__.'----dn:'.$dn.'-----'.$this->_getLastExecErrLog());
175         }
176         return $ret;
177     }
178     
179     /**
180      * @description 更新節點
181      * 
182      * @return boolean
183      */
184     public function updateEntry($dn,$aAttr = array(),$bFixedDn = true){
185         if (!$dn = trim($dn)){
186             return false;
187         }
188         $this->_checkAttr($aAttr);
189         if (!$aAttr){
190             return false;
191         }
192         if (!$this->_checkDn($dn)){
193             return false;
194         }
195         if ($bFixedDn){
196             $dn = $this->_getFullDn($dn);
197         }
198         $this->_log(__FUNCTION__."---DN:{$dn}---aAttr:".str_replace("\n",'',var_export($aAttr,true)),false,'oper');
199         $this->connect();
200         $ret = @ldap_modify($this->_conn,$dn,$aAttr);
201         if ($ret === false){
202             $this->_log(__FUNCTION__.'---'.$this->_getLastExecErrLog().'---dn:'.$dn.'---attr:'.json_encode($aAttr));
203         }
204         return $ret;
205     }
206     
207     /**
208      * @description 添加節點
209      *
210      * @return boolean
211      */
212     public function addEntry($dn,$aAttr = array(), $type = 'employee'/*employee,group*/){
213         if (!$dn = trim($dn)){
214             return false;
215         }
216         $this->_checkAttr($aAttr);
217         if (!$aAttr){
218             return false;
219         }
220         if (!$this->_checkDn($dn)){
221             return false;
222         }
223         $aAttr['objectClass'] = (array)$this->_getObjectClass($type);
224         $this->_log(__FUNCTION__."---DN:{$dn}---aAttr:".str_replace("\n",'',var_export($aAttr,true)),false,'oper');
225         $this->connect();
226         $dn = $this->_getFullDn($dn);
227         $ret = @ldap_add($this->_conn,$dn,$aAttr);
228         if ($ret === false){
229             $this->_log(__FUNCTION__.'----dn:'.$dn.'----aAttr:'.json_encode($aAttr).'-----'.$this->_getLastExecErrLog());
230         }
231         return $ret;
232     }
233     
234     /**
235      * @description 移動葉子節點 v3版才支持此方法
236      *
237      * @param $newDn 相對於$parentDn
238      * @param $parentDn 完整DN
239      * @param $bMoveRecur 
240      *
241      * @return boolean
242      */
243     public function moveEntry($oldDn,$newDn,$parentDn,$bDelOld = true,$bFixDn = true,$bMoveRecur = true){
244         //對於AD服務器 此方法可以移動用戶節點以及組織節點
245         //$newDn只能包含一個 比如OU=xxx
246         $oldDn = trim($oldDn);
247         $newDn = trim($newDn);
248         $parentDn = trim($parentDn);
249         if(!$oldDn || !$newDn || ($bFixDn && !$parentDn)){
250             return false;
251         }
252         if(!$this->_checkDn($oldDn) || !$this->_checkDn($newDn) || !$this->_checkDn($parentDn)){
253             return false;
254         }
255         $this->connect();
256         if($bFixDn){
257             $oldDn = $this->_getFullDn($oldDn);
258             $parentDn = $this->_getFullDn($parentDn);
259         }
260         $this->_log(__FUNCTION__."---DN:{$oldDn} -> {$newDn},{$parentDn}",false,'oper');
261         $aTmpMove = $aDelDn = array();
262         $aTmpMove[] = array('old'=>$oldDn,'new'=>$newDn);
263         /*if($bMoveRecur){
264             $aDelDn[] = $oldDn;
265             $aTmpList = $this->getEntryList($oldDn,array('objectClass'=>'*'),array('objectClass'),0,0);
266             if($aTmpList && ($aTmpList['count'] > 1)){
267                 for($i = 1; $i < $aTmpList['count']; $i++){
268                     if(!in_array('user',$aTmpList[$i]['objectclass'])){ //$bDelOld=true時,用戶節點移動時會自動刪除
269                         $aDelDn[] = $aTmpList[$i]['dn'];
270                     }
271                     $aTmpSep = explode($oldDn,$aTmpList[$i]['dn']);
272                     $aTmpMove[] = array(
273                         'old' => $aTmpList[$i]['dn'],
274                         'new' => $aTmpSep[0] . $newDn,
275                     );
276                 }
277             }
278         }*/
279         $bFlag = true;
280         foreach($aTmpMove as $k => $v){
281             $bTmpFlag = ldap_rename($this->_conn,$v['old'],$v['new'],$parentDn,(boolean)$bDelOld);
282             if(!$bTmpFlag){
283                 $this->_log(__FUNCTION__."---o:{$v['old']}-n:{$v['new']}-p:{$parentDn}-recur:{$bMoveRecur}-----".$this->_getLastExecErrLog());
284             }
285             $bFlag &= $bTmpFlag;
286         }
287         /*if(!$bFlag){
288             $this->_log(__FUNCTION__."---o:{$oldDn}-n:{$newDn}-p:{$parentDn}-recur:{$bMoveRecur}-----".$this->_getLastExecErrLog());
289         }*/
290         /*if($bFlag && $bDelOld && $aDelDn){
291             $aDelDn = array_reverse($aDelDn);
292             foreach($aDelDn as $k => $v){
293                 $this->delEntry($v,false);
294             }
295         }*/
296         return $bFlag;
297     }
298     
299     public function modEntry($dn,$act = 'add',$aAttr = array()){
300         return false;
301         $dn = $this->_getFullDn($dn);
302         $this->_log(__FUNCTION__."---DN:{$dn}---Act:{$act}---aAttr:".str_replace("\n",'',var_export($aAttr,true)),false,'oper');
303         $this->connect();
304         $ret = false;
305         switch($act){
306             case 'add': $ret = ldap_mod_add($this->_conn,$dn,$aAttr); break;
307             case 'replace': $ret = ldap_mod_replace($this->_conn,$dn,$aAttr); break;
308             case 'del': $ret = ldap_mod_del($this->_conn,$dn,$aAttr); break;
309         }
310         if(!$ret){
311             $this->_log(__FUNCTION__."---dn:{$dn}---act:{$act}---attr:".json_encode($aAttr).'---'.$this->_getLastExecErrLog());
312         }
313         return $ret;
314     }
315     
316     /**
317      * @description 批量添加節點
318      * 
319      * @return boolean
320      */
321     public function addBatchEntry($aNodeList = array()){
322     }
323     
324     public function getAttrKv(array $aAttr = array()){
325         if(!isset($aAttr['count']) || ($aAttr['count'] < 1)){
326             return array();
327         }
328         $aRet = array();
329         for($i = 0; $i < $aAttr['count']; $i++){
330             $field = $aAttr[$i];
331             if (!isset($aAttr[$field])){
332                 return array();
333             }
334             unset($aAttr[$field]['count']);
335             $aRet[$field] = $aAttr[$field];
336         }
337         if(isset($aAttr['dn'])){ //dn是字符串
338             $aRet['dn'] = $aAttr['dn'];
339         }
340         return $aRet;
341     }
342     
343     private function _getObjectClass($type = 'employee'){
344         $aRet = array();
345         switch($type){
346             case 'employee' : $aRet = array('top','person','organizationalPerson','user'); break;
347             case 'group' : $aRet = array('top','organizationalUnit'); break;
348         }
349         return $aRet;
350     }
351     
352     public function getFullDn($partDn = ''){
353         return $this->_getFullDn($partDn);
354     }
355     
356     private function _getFullDn($partDn = ''){
357         $partDn = trim($partDn);
358         $partDn = rtrim($partDn,',');
359         return "{$partDn},{$this->_aOptions['dnSuffix']}";
360     }
361     
362     private function _checkDn($dn = ''){
363         $dn = trim($dn,',');
364         $aDn = explode(',',$dn);
365         foreach($aDn as $k => $v){
366             $aTmp = explode('=',$v);
367             $aTmp[0] = strtolower(trim($aTmp[0]));
368             $aTmp[1] = trim($aTmp[1]);
369             $flag = false;
370             switch($aTmp[0]){ //distingushed name 暫時只允許這3個field
371                 case 'dc': $flag = $this->_checkDc($aTmp[1]); break;
372                 case 'ou': $flag = $this->_checkOu($aTmp[1]); break;
373                 case 'cn': $flag = $this->_checkCn($aTmp[1]); break;
374             }
375             if (!$flag){
376                 $this->_log(__FUNCTION__.'----無效的節點路徑----dn:'.$dn);
377                 return false;
378             }
379         }
380         return true;
381     }
382     
383     private function _checkOu($ou = ''){
384         if (!$ou){
385             return false;
386         }
387         if (preg_match('/[^a-zA-Z\s\d\.&\'\d]/',$ou)){
388             $this->_log(__FUNCTION__.'----OU只能包含字母數字空格以及點');
389             return false;
390         }
391         return true;
392     }
393     
394     private function _checkCn($cn = ''){
395         if (!$cn){
396             return false;
397         }
398         return true;
399     }
400     
401     private function _checkDc($dc = ''){
402         if (!$dc){
403             return false;
404         }
405         if (preg_match('/[^a-zA-Z]/',$dc)){
406             $this->_log(__FUNCTION__.'----DC只能包含英文字母');
407             return false;
408         }
409         return true;
410     }
411     
412     private function _mkAttrFilter(array $aAttrFilter = array()){
413         $sStr = '(&';
414         foreach($aAttrFilter as $k => $v){
415             $v = (string)$v;
416             if($k === 'objectGUID'){
417                 $v = $this->_GUIDtoStr($v);
418             }
419             $v = addcslashes($v,'()=');
420             $sStr .= "({$k}={$v})";
421         }
422         $sStr .= ')';
423         return $sStr;
424     }
425     
426     //來自PHP.NET http://php.net/manual/en/function.ldap-search.php
427     //http://php.net/manual/en/function.ldap-get-values-len.php
428     //GUID關鍵字
429     private function _GUIDtoStr($binary_guid){
430         $hex_guid = unpack("H*hex", $binary_guid); 
431         $hex = $hex_guid["hex"];
432         $j = 0;$str = '\\';
433         for($i = 0; $i < strlen($hex); $i++){
434             if($j == 2){
435                 $str .= '\\';
436                 $j = 0;
437             }
438             $str .= $hex[$i];
439             $j++;
440         }
441         return $str;
442         /*$hex1 = substr($hex, -26, 2) . substr($hex, -28, 2) . substr($hex, -30, 2) . substr($hex, -32, 2);
443         $hex2 = substr($hex, -22, 2) . substr($hex, -24, 2);
444         $hex3 = substr($hex, -18, 2) . substr($hex, -20, 2);
445         $hex4 = substr($hex, -16, 4);
446         $hex5 = substr($hex, -12, 12);
447         $guid_str = $hex1 . "-" . $hex2 . "-" . $hex3 . "-" . $hex4 . "-" . $hex5;
448         return $guid_str;*/
449     }
450     
451     private function _checkAttr(& $aAttr = array()){
452         foreach((array)$aAttr as $k => $v){
453             if (!in_array($k,$this->_aAllowAttrName)){
454                 unset($aAttr[$k]);
455             }
456         }
457         return true;
458     }
459     
460     public function getErrLog(){
461         return $this->_sErrLog;
462     }
463     
464     public function getOperLog(){
465         return $this->_sOperLog;
466     }
467     
468     private function _log($str = '',$bExit = false,$type = 'err'/*err,oper*/){
469         if ($bExit){
470             die($str);
471         }
472         $date = date('Y-m-d H:i:s');
473         if($type === 'err'){
474             $this->_sErrLog .= "{$date}----{$str}\n";
475         } else if ($type === 'oper'){
476             $this->_sOperLog .= "{$date}----{$str}\n";
477         }
478     }
479     
480     public function close(){
481         ldap_close($this->_conn);
482     }
483     
484     private function _getLastExecErrLog(){
485         $no = ldap_errno($this->_conn);
486         $err = ldap_error($this->_conn);
487         return "---exec Error:{$no}---{$err}";
488     }
489 }
View Code

輔助PHP類---漢字轉拼音

<?php
/**
 * @desc 漢字轉拼音 參考 http://www.php100.com/html/webkaifa/PHP/PHP/2012/0820/10914.html
 */
class o_lib_helper_pinyin{
	private $_DataKey = "a|ai|an|ang|ao|ba|bai|ban|bang|bao|bei|ben|beng|bi|bian|biao|bie|bin|bing|bo|bu|ca|cai|can|cang|cao|ce|ceng|cha
		|chai|chan|chang|chao|che|chen|cheng|chi|chong|chou|chu|chuai|chuan|chuang|chui|chun|chuo|ci|cong|cou|cu|
		cuan|cui|cun|cuo|da|dai|dan|dang|dao|de|deng|di|dian|diao|die|ding|diu|dong|dou|du|duan|dui|dun|duo|e|en|er
		|fa|fan|fang|fei|fen|feng|fo|fou|fu|ga|gai|gan|gang|gao|ge|gei|gen|geng|gong|gou|gu|gua|guai|guan|guang|gui
		|gun|guo|ha|hai|han|hang|hao|he|hei|hen|heng|hong|hou|hu|hua|huai|huan|huang|hui|hun|huo|ji|jia|jian|jiang
		|jiao|jie|jin|jing|jiong|jiu|ju|juan|jue|jun|ka|kai|kan|kang|kao|ke|ken|keng|kong|kou|ku|kua|kuai|kuan|kuang
		|kui|kun|kuo|la|lai|lan|lang|lao|le|lei|leng|li|lia|lian|liang|liao|lie|lin|ling|liu|long|lou|lu|lv|luan|lue
		|lun|luo|ma|mai|man|mang|mao|me|mei|men|meng|mi|mian|miao|mie|min|ming|miu|mo|mou|mu|na|nai|nan|nang|nao|ne
		|nei|nen|neng|ni|nian|niang|niao|nie|nin|ning|niu|nong|nu|nv|nuan|nue|nuo|o|ou|pa|pai|pan|pang|pao|pei|pen
		|peng|pi|pian|piao|pie|pin|ping|po|pu|qi|qia|qian|qiang|qiao|qie|qin|qing|qiong|qiu|qu|quan|que|qun|ran|rang
		|rao|re|ren|reng|ri|rong|rou|ru|ruan|rui|run|ruo|sa|sai|san|sang|sao|se|sen|seng|sha|shai|shan|shang|shao|
		she|shen|sheng|shi|shou|shu|shua|shuai|shuan|shuang|shui|shun|shuo|si|song|sou|su|suan|sui|sun|suo|ta|tai|
		tan|tang|tao|te|teng|ti|tian|tiao|tie|ting|tong|tou|tu|tuan|tui|tun|tuo|wa|wai|wan|wang|wei|wen|weng|wo|wu
		|xi|xia|xian|xiang|xiao|xie|xin|xing|xiong|xiu|xu|xuan|xue|xun|ya|yan|yang|yao|ye|yi|yin|ying|yo|yong|you
		|yu|yuan|yue|yun|za|zai|zan|zang|zao|ze|zei|zen|zeng|zha|zhai|zhan|zhang|zhao|zhe|zhen|zheng|zhi|zhong|
		zhou|zhu|zhua|zhuai|zhuan|zhuang|zhui|zhun|zhuo|zi|zong|zou|zu|zuan|zui|zun|zuo";
	private $_DataValue = "-20319|-20317|-20304|-20295|-20292|-20283|-20265|-20257|-20242|-20230|-20051|-20036|-20032|-20026|-20002|-19990
		|-19986|-19982|-19976|-19805|-19784|-19775|-19774|-19763|-19756|-19751|-19746|-19741|-19739|-19728|-19725
		|-19715|-19540|-19531|-19525|-19515|-19500|-19484|-19479|-19467|-19289|-19288|-19281|-19275|-19270|-19263
		|-19261|-19249|-19243|-19242|-19238|-19235|-19227|-19224|-19218|-19212|-19038|-19023|-19018|-19006|-19003
		|-18996|-18977|-18961|-18952|-18783|-18774|-18773|-18763|-18756|-18741|-18735|-18731|-18722|-18710|-18697
		|-18696|-18526|-18518|-18501|-18490|-18478|-18463|-18448|-18447|-18446|-18239|-18237|-18231|-18220|-18211
		|-18201|-18184|-18183|-18181|-18012|-17997|-17988|-17970|-17964|-17961|-17950|-17947|-17931|-17928|-17922
		|-17759|-17752|-17733|-17730|-17721|-17703|-17701|-17697|-17692|-17683|-17676|-17496|-17487|-17482|-17468
		|-17454|-17433|-17427|-17417|-17202|-17185|-16983|-16970|-16942|-16915|-16733|-16708|-16706|-16689|-16664
		|-16657|-16647|-16474|-16470|-16465|-16459|-16452|-16448|-16433|-16429|-16427|-16423|-16419|-16412|-16407
		|-16403|-16401|-16393|-16220|-16216|-16212|-16205|-16202|-16187|-16180|-16171|-16169|-16158|-16155|-15959
		|-15958|-15944|-15933|-15920|-15915|-15903|-15889|-15878|-15707|-15701|-15681|-15667|-15661|-15659|-15652
		|-15640|-15631|-15625|-15454|-15448|-15436|-15435|-15419|-15416|-15408|-15394|-15385|-15377|-15375|-15369
		|-15363|-15362|-15183|-15180|-15165|-15158|-15153|-15150|-15149|-15144|-15143|-15141|-15140|-15139|-15128
		|-15121|-15119|-15117|-15110|-15109|-14941|-14937|-14933|-14930|-14929|-14928|-14926|-14922|-14921|-14914
		|-14908|-14902|-14894|-14889|-14882|-14873|-14871|-14857|-14678|-14674|-14670|-14668|-14663|-14654|-14645
		|-14630|-14594|-14429|-14407|-14399|-14384|-14379|-14368|-14355|-14353|-14345|-14170|-14159|-14151|-14149
		|-14145|-14140|-14137|-14135|-14125|-14123|-14122|-14112|-14109|-14099|-14097|-14094|-14092|-14090|-14087
		|-14083|-13917|-13914|-13910|-13907|-13906|-13905|-13896|-13894|-13878|-13870|-13859|-13847|-13831|-13658
		|-13611|-13601|-13406|-13404|-13400|-13398|-13395|-13391|-13387|-13383|-13367|-13359|-13356|-13343|-13340
		|-13329|-13326|-13318|-13147|-13138|-13120|-13107|-13096|-13095|-13091|-13076|-13068|-13063|-13060|-12888
		|-12875|-12871|-12860|-12858|-12852|-12849|-12838|-12831|-12829|-12812|-12802|-12607|-12597|-12594|-12585
		|-12556|-12359|-12346|-12320|-12300|-12120|-12099|-12089|-12074|-12067|-12058|-12039|-11867|-11861|-11847
		|-11831|-11798|-11781|-11604|-11589|-11536|-11358|-11340|-11339|-11324|-11303|-11097|-11077|-11067|-11055
		|-11052|-11045|-11041|-11038|-11024|-11020|-11019|-11018|-11014|-10838|-10832|-10815|-10800|-10790|-10780
		|-10764|-10587|-10544|-10533|-10519|-10331|-10329|-10328|-10322|-10315|-10309|-10307|-10296|-10281|-10274
		|-10270|-10262|-10260|-10256|-10254";
	private $_Data = array();
	
	public function __construct(){
		$_TDataKey = explode('|', str_replace(array("\t","\n","\r\n",' '),array('','','',''),$this->_DataKey));
		$_TDataValue = explode('|', str_replace(array("\t","\n","\r\n",' '),array('','','',''),$this->_DataValue));
		$_Data = (PHP_VERSION>='5.0') ? array_combine($_TDataKey, $_TDataValue) : $this->_Array_Combine($_TDataKey, $_TDataValue);
		arsort($_Data);
		$this->_Data = $_Data;
	}
	
	public function Pinyin($_String, $_Code='gb2312'){
		reset($this->_Data);
		if($_Code != 'gb2312') $_String = $this->_U2_Utf8_Gb($_String);
		$_Res = '';$_aRes = array();$_en = '';
		for($i=0; $i<strlen($_String); $i++)
		{
			$_P = ord(substr($_String, $i, 1));
			if($_P < 160){
				$_en .= chr($_P);
				continue;
			}
			if($_en){
				$_aRes[] = $_en;
				$_en = '';
			}
			if($_P>160) { $_Q = ord(substr($_String, ++$i, 1)); $_P = $_P*256 + $_Q - 65536; }
			//$_Res .= _Pinyin($_P, $_Data);
			$_aRes[] = $this->_Pinyin($_P, $this->_Data);
		}
		foreach($_aRes as $k => $v){
			$v = preg_replace("/[^a-zA-Z0-9]*/", '', $v);
			$v = ucfirst($v);
			$_aRes[$k] = $v;
		}
		return implode(' ',$_aRes);
		//return preg_replace("/[^a-zA-Z0-9]*/", '', $_Res);
	}
	
	private function _Pinyin($_Num, $_Data){
		if ($_Num>0 && $_Num<160 ) return chr($_Num);
		elseif($_Num<-20319 || $_Num>-10247) return '';
		else {
		foreach($_Data as $k=>$v){ if($v<=$_Num) break; }
		return $k;
		}
	}
	
	private function _U2_Utf8_Gb($_C){
		$_String = '';
		if($_C < 0x80) $_String .= $_C;
		elseif($_C < 0x800)
		{
			$_String .= chr(0xC0 | $_C>>6);
			$_String .= chr(0x80 | $_C & 0x3F);
		}elseif($_C < 0x10000){
			$_String .= chr(0xE0 | $_C>>12);
			$_String .= chr(0x80 | $_C>>6 & 0x3F);
			$_String .= chr(0x80 | $_C & 0x3F);
		} elseif($_C < 0x200000) {
			$_String .= chr(0xF0 | $_C>>18);
			$_String .= chr(0x80 | $_C>>12 & 0x3F);
			$_String .= chr(0x80 | $_C>>6 & 0x3F);
			$_String .= chr(0x80 | $_C & 0x3F);
		}
		return iconv('UTF-8', 'GB2312', $_String);
	}
	
	private function _Array_Combine($_Arr1, $_Arr2){
		for($i=0; $i<count($_Arr1); $i++) $_Res[$_Arr1[$i]] = $_Arr2[$i];
		return $_Res;
	}
}

輔助PHP類---簡單TREE處理

<?php
/**
 * @description 簡單tree類
 *
 * @author WadeYu
 * @date 2015-04-30
 * @version 0.0.1
 */
class o_simpletree{
	private $_aNodeList = array(); // [id => []]
	private $_aChildNodeList = array(); // [parentId => [childId,childId,]]
	private $_aTopNodeList = array(); //[id => []]
	private $_pk = '';
	private $_parentPk = '';
	private $_aTreeKV = array(); //tree key的顯示的內容
	private $_aJoinKey = array();
	
	public function __construct(array $aData = array()/*[[id:0,fid:1],[]]*/, array $aOption = array()){
		$this->_pk = $aOption['pk'];
		$this->_parentPk = $aOption['parentPk'];
		$this->_aTreeKV = (array)$aOption['aTreeKV'];
		$this->_mkNodeList($aData);
		$this->_mkChildNodeList();
	}
	
	public function getTree($parentPk){
		$aRet = array();
		$aChild = array();
		if (!isset($this->_aChildNodeList[$parentPk])){
			return $aRet;
		}
		$aChild = $this->_aChildNodeList[$parentPk];
		foreach((array)$aChild as $k => $v){
			$currNode = $this->_aNodeList[$v];
			$tmpK = '';
			$i = 0;
			foreach($this->_aTreeKV as $k2 => $v2){
				$tmpV = $currNode[$v2];
				if($i == 0){
					$tmpK .= $tmpV;
				} else if ($i == 1){
					$tmpK .= "({$tmpV})";
				} else if ($i == 2){
					$tmpK .= "[{$tmpV}]";
				}
				$i++;
			}
			if (isset($this->_aChildNodeList[$v])){
				$aRet[$tmpK] = $this->getTree($v);
			} else {
				$aRet[$tmpK] = 1;
			}
		}
		return $aRet;
	}
	
	public function joinKey($aTree,$prefix = ''){
		$prefix = trim($prefix);
		if(!is_array($aTree)){
			return $prefix;
		}
		foreach((array)$aTree as $k => $v){
			if (is_array($v)){
				$this->joinKey($v,"{$prefix}{$k}/");
			} else {
				$this->_aJoinKey["{$prefix}{$k}"] = 1;
			}
		}
		return true;
	}
	
	public function getJoinKey(){
		return $this->_aJoinKey;
	}
	
	private function _mkNodeList(array $aData = array()){
		foreach($aData as $k => $v){
			$this->_aNodeList[$v[$this->_pk]] = $v;
		}
	}
	
	private function _mkChildNodeList(){
		foreach($this->_aNodeList as $k => $v){
			if ($v['fid']){
				$this->_aChildNodeList[$v[$this->_parentPk]][] = $v[$this->_pk];
			} else {
				$this->_aTopNodeList[$v[$this->_pk]] = $v;
			}
		}
	}
}

  

 

 

LDAP開源實現:openLDAP

0.相關環境說明
    a.操作系統

1 [root@vm ldap]# lsb_release -a
2 LSB Version:    :core-3.1-amd64:core-3.1-ia32:core-3.1-noarch:graphics-3.1-amd64:graphics-3.1-ia32:graphics-3.1-noarch
3 Distributor ID:    CentOS
4 Description:    CentOS release 5.4 (Final)
5 Release:    5.4
6 Codename:    Final

 1.安裝

    a.yum -y install openldap-servers openldap-clients

2.配置
    a.配置HOST
        [root@vm ldap]# vi /etc/hosts
       127.0.0.1 test.com
     b.創建證書

cd /etc/pki/tls/certs
[root@vm certs]# pwd
/etc/pki/tls/certs
[root@vm certs]# rm -rf slapd.pem
[root@vm certs]# make slapd.pem
#執行命令之后 顯示如下信息 按照提示填寫即可
umask 77 ; \
	PEM1=`/bin/mktemp /tmp/openssl.XXXXXX` ; \
	PEM2=`/bin/mktemp /tmp/openssl.XXXXXX` ; \
	/usr/bin/openssl req -utf8 -newkey rsa:2048 -keyout $PEM1 -nodes -x509 -days 365 -out $PEM2 -set_serial 0 ; \
	cat $PEM1 >  slapd.pem ; \
	echo ""    >> slapd.pem ; \
	cat $PEM2 >> slapd.pem ; \
	rm -f $PEM1 $PEM2
Generating a 2048 bit RSA private key
.....................................+++
.........+++
writing new private key to '/tmp/openssl.IQ8972'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [GB]:CN
State or Province Name (full name) [Berkshire]:GuangDong
Locality Name (eg, city) [Newbury]:ShenZhen
Organization Name (eg, company) [My Company Ltd]:Boyaa
Organizational Unit Name (eg, section) []:OA
Common Name (eg, your name or your server's hostname) []:Test LDAP  
Email Address []:WadeYu@boyaa.com
[root@vm certs]#

   c.生成管理員密碼

            root@vm ~]# slappasswd 

            New password:
           Re-enter new password:
          {SSHA}2eG1IBeHhSjfgS7pjoAci1bHz5p4AVeS

    d.配置slapd.conf
        [root@vm certs]# vi /etc/openldap/slapd.conf

       去掉TLS相關注釋

       設置數據庫配置

    e.BerkeleyDb配置

        [root@vm certs]# cd /etc/openldap/
        [root@vm openldap]# mv ./DB_CONFIG.example /var/lib/ldap/DB_CONFIG

    f.配置ldap.conf

        [root@vm openldap]# vi ldap.conf

    g.開啟加密支持
        [root@vm ~]# vim /etc/sysconfig/ldap
        SLAPD_LDAPS=yes

3.使用slapadd命令添加根節點 未啟動前

1 [root@vm ~]# cd ~
2 [root@vm ~]# vim root.ldif
3 dn: dc=test,dc=com
4 dc: test
5 objectClass: dcObject
6 objectClass: organizationalUnit
7 ou: test.com
8 [root@vm ~]# slapadd -v -n 1 -l root.ldif

4.啟動slapd
[root@vm ~]# slapd -h "ldap:// ldaps://"
389非加密端口 636加密端口

5.檢測

[root@vm ~]# ldapsearch -x -H ldap://localhost
# extended LDIF
#
# LDAPv3
# base <> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# test.com
dn: dc=test,dc=com
dc: test
objectClass: dcObject
objectClass: organizationalUnit
ou: test.com

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

[root@vm ~]# ldapsearch -x -H ldaps://localhost
# extended LDIF
#
# LDAPv3
# base <> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# test.com
dn: dc=test,dc=com
dc: test
objectClass: dcObject
objectClass: organizationalUnit
ou: test.com

# search result
search: 2
result: 0 Success

# numResponses: 2
# numEntries: 1

  

6.phpldapadmin客戶端訪問
    a.官網下載源碼放入WEB目錄下 下載頁面:http://phpldapadmin.sourceforge.net/wiki/index.php/Download
    b.安裝依賴的擴展gettext ldap這2個擴展
    c.按需配置 源碼目錄下config/config.php

問題

1.ldaps無法訪問
/etc/openldap/ldap.conf TLS_REQCERT never加上這個

源碼安裝PHP擴展步驟

1.下載PHP源碼
2.切換到擴展源碼所在目錄 示例:cd /src path/ext/extname
3.執行phpize命令 (1.PHP源代碼或者PHP安裝目錄包含此命令 2.當前目錄會生成一個檢查編譯擴展的環境腳本configure)
4../configure --with-php-config=/usr/local/php/bin/php-config (1.編譯環境配置檢查 2.生成make命令需要的編譯配置文件makefile)
5.make && make install (編譯安裝)
6.php.ini 加上配置 extension = "xxxxx.so"
7.重啟php service php-fpm restart

安裝PHP LDAP擴展

示例:centos系統安裝LDAP擴展
1.安裝依賴庫openldap openldap-devel
yum install openldap
yum install openldap-devel
2.參考:源碼安裝PHP擴展步驟

 

補充(最新LDAP操作類LARAVEL框架版)

LDAP基礎類

  1 <?php
  2 /**
  3  * @description LDAP客戶端類
  4  *
  5  * @author WadeYu
  6  * @date 2015-04-28
  7  * @version 0.0.1
  8  */
  9 namespace App\Lib;
 10 class Ldap{
 11     private $_conn = NULL;
 12     private $_sErrLog = '';
 13     private $_sOperLog = '';
 14     private $_aOptions = array(
 15         'host' => 'ldap://xxx.com',
 16         'port' => '389',
 17         'dnSuffix' => 'OU=xx,OU=xx,DC=xx,DC=com',
 18         'loginUser' => '',
 19         'loginPass' => '',
 20         'logDir' => '',
 21     );
 22     private $_aAllowAttrName = array(
 23         'objectClass',
 24         'objectGUID', //AD對象ID
 25         'userPassword', //AD密碼不是這個字段 密碼暫時不能通過程序設置
 26         'unicodePwd', //AD密碼專用字段 $unicodePwd  = mb_convert_encoding('"' . $newPassword . '"', 'utf-16le');
 27         'cn', //comman name 兄弟節點不能相同
 28         'ou', //organizationalUnit
 29         'description', //員工填工號
 30         'displayName', //中文名
 31         'name', //姓名
 32         'sAMAccountName', //英文名(RTX賬號,唯一)
 33         'userPrincipalName', //登陸用戶名 和 英文名一致
 34         'ProtectedFromAccidentalDeletion', //對象刪除保護
 35         'givenName', //
 36         'sn', //
 37         'employeeNumber', //一卡通卡號
 38         'mail',
 39         'mailNickname',
 40         'manager', //上級 (節點路徑 示例:CN=Texas Poker9,OU=Texas Poker,OU=Dept,OU=BoyaaSZ,DC=by,DC=com)
 41         'title', //頭銜
 42         'pager', //性別 0男 1女 -1未知
 43         'userAccountControl', //用戶賬號策略(暫時不能設置) 資料說明地址:https://support.microsoft.com/en-gb/kb/305144
 44         'department',
 45         'managedBy',//部門負責人
 46         'distinguishedName',
 47         'pwdLastSet', //等於0時 下次登錄時需要修改密碼
 48         'memberOf', //用戶所屬組
 49         'member',//組成員
 50     );
 51     
 52     public function __construct(array $aOptions = array()){
 53         if (!extension_loaded('ldap')){
 54             $this->_log('LDAP extension not be installed.',true);
 55         }
 56         $this->setOption($aOptions);
 57     }
 58     
 59     /**
 60      * @return exit || true
 61      */
 62     public function connect($force = false){
 63         if (!$this->_conn || $force){
 64             $host = $this->_aOptions['host'];
 65             $port = $this->_aOptions['port'];
 66             $this->_conn = ldap_connect($host,$port);
 67             if ($this->_conn === false){
 68                 $this->_log("Connect LDAP SERVER Failure.[host:{$post}:{$port}]",true);
 69             }
 70             ldap_set_option($this->_conn, LDAP_OPT_PROTOCOL_VERSION, 3);
 71             ldap_set_option($this->_conn, LDAP_OPT_REFERRALS, 3);
 72             $this->_bind();
 73         }
 74         return $this->_conn;
 75     }
 76     
 77     /**
 78      * @return exit || true
 79      */
 80     private function _bind(){
 81         $u = $this->_aOptions['loginUser'];
 82         $p = $this->_aOptions['loginPass'];
 83         $ret = @ldap_bind($this->_conn,$u,$p);
 84         if ($ret === false){
 85             $this->_log(__FUNCTION__.'----'.$this->_getLastExecErrLog().'----'."u:{$u},p:{$p}",true);
 86         }
 87         return $ret;
 88     }
 89     
 90     public function setOption(array $aOptions = array()){
 91         foreach($this->_aOptions as $k => $v){
 92             if (isset($aOptions[$k])){
 93                 $this->_aOptions[$k] = $aOptions[$k];
 94             }
 95         }
 96     }
 97     
 98     public function getOption($field,$default = ''){
 99         return isset($this->_aOptions[$field]) ? $this->_aOptions[$field] : $default;
100     }
101     
102     /**
103      * @description 查詢$dn下符合屬性條件的節點 返回$limit條
104      *
105      * @return array [count:x,[[prop:[count:xx,[],[]]],....]]
106      */
107     public function getEntryList($dn,$aAttrFilter,array $aField=array(),$limit = 0,$bFixedDn = true){
108         if (!$dn = trim($dn)){
109             return array();
110         }
111         if (!$this->_checkDn($dn)){
112             return array();
113         }
114         $limit = max(0,intval($limit));
115         $this->connect();
116         if ($bFixedDn){
117             $dn = $this->_getFullDn($dn);
118         }
119         $aOldTmp = $aAttrFilter;
120         $this->_checkAttr($aAttrFilter);
121         if (!$aAttrFilter){
122             $this->_log(__FUNCTION__.'---無效的搜索屬性---'.json_encode($aOldTmp));
123             return array();
124         }
125         $sAttrFilter = $this->_mkAttrFilter($aAttrFilter);
126         $attrOnly = 0;
127         $this->_log(__FUNCTION__."---DN:{$dn}---sAttr:{$sAttrFilter}",false,'oper');
128         $aRet = [];
129         for($try = 1; $try <= 3; $try++){
130             $rs = @ldap_search($this->_conn,$dn,$sAttrFilter,$aField,$attrOnly,$limit);
131             if ($rs === false){
132                 $this->_log(__FUNCTION__."---dn:{$dn}--try:{$try}---sAttr:{$sAttrFilter}---" . $this->_getLastExecErrLog());
133                 if($this->_getLastErrNo() == -1){ //未連接上LDAP服務器至多重試3次
134                     $this->connect(true);
135                     continue;
136                 } else { //其它情況直接退出
137                     break;
138                 }
139             }
140             $aRet = @ldap_get_entries($this->_conn,$rs);
141             ldap_free_result($rs);
142             if ($aRet === false){
143                 $this->_log(__FUNCTION__.'---try:{$try}---'.$this->_getLastExecErrLog());
144                 if($this->_getLastErrNo() == -1){
145                     $this->connect(true);
146                     continue;
147                 } else {
148                     break;
149                 }
150             } else {
151                 break;
152             }
153         }
154         return $aRet;
155     }
156     
157     /**
158      * @description 刪除節點 暫時不考慮遞歸刪除
159      * 
160      * @return boolean
161      */
162     public function delEntry($dn,$bFixedDn = true,$force = 0){
163         return false;
164         if (!$dn = trim($dn)){
165             return false;
166         }
167         if (!$this->_checkDn($dn)){
168             return false;
169         }
170         if ($bFixedDn){
171             $dn = $this->_getFullDn($dn);
172         }
173         $this->_log(__FUNCTION__."---DN:{$dn}",false,'oper');
174         $this->connect();
175         /*if($force){
176             $aEntryList = $this->getEntryList($dn,array('objectClass'=>'*'),array('objectClass'));
177             if ($aEntryList && ($aEntryList['count'] > 0)){ 
178                 for($i = 0; $i < $aEntryList['count']; $i++){
179                     $aDel[] = $aEntryList[$i]['dn'];
180                 }
181             }
182             $aDel = array_reverse($aDel); //默認順序 祖先->子孫 需要先刪除子孫節點
183             $ret = true;
184             foreach($aDel as $k => $v){
185                 $ret &= @ldap_delete($this->_conn,$v);
186             }
187             if ($ret === false){
188                 $this->_log(__FUNCTION__.'dn(recursive):'.$dn.'----'.$this->_getLastExecErrLog());
189             }
190             return $ret;
191         }*/
192         $ret = @ldap_delete($this->_conn,$dn);
193         if ($ret === false){
194             $this->_log(__FUNCTION__.'----dn:'.$dn.'-----'.$this->_getLastExecErrLog());
195         }
196         return $ret;
197     }
198     
199     /**
200      * @description 更新節點
201      * 
202      * @return boolean
203      */
204     public function updateEntry($dn,$aAttr = array(),$bFixedDn = true){
205         if (!$dn = trim($dn)){
206             return false;
207         }
208         $this->_checkAttr($aAttr);
209         if (!$aAttr){
210             return false;
211         }
212         if (!$this->_checkDn($dn)){
213             return false;
214         }
215         if ($bFixedDn){
216             $dn = $this->_getFullDn($dn);
217         }
218         $this->_log(__FUNCTION__."---DN:{$dn}---aAttr:".str_replace("\n",'',var_export($aAttr,true)),false,'oper');
219         $this->connect();
220         $ret = false;
221         for($try = 1; $try <= 3; $try++){
222             $ret = @ldap_modify($this->_conn,$dn,$aAttr);
223             if ($ret === false){
224                 $this->_log(__FUNCTION__.'---try:{$try}---'.$this->_getLastExecErrLog().'---dn:'.$dn.'---attr:'.json_encode($aAttr));
225                 if($this->_getLastErrNo() == -1){ //未連上服務器至多重試3次
226                     $this->connect(true);
227                     continue;
228                 }else{
229                     break;
230                 }
231             } else {
232                 break;
233             }
234         }
235         return $ret;
236     }
237     
238     /**
239      * @description 添加節點
240      *
241      * @return boolean
242      */
243     public function addEntry($dn,$aAttr = array(), $type = 'employee'/*employee,group*/){
244         if (!$dn = trim($dn)){
245             return false;
246         }
247         $this->_checkAttr($aAttr);
248         if (!$aAttr){
249             return false;
250         }
251         if (!$this->_checkDn($dn)){
252             return false;
253         }
254         $aAttr['objectClass'] = (array)$this->_getObjectClass($type);
255         $this->_log(__FUNCTION__."---DN:{$dn}---aAttr:".str_replace("\n",'',var_export($aAttr,true)),false,'oper');
256         $this->connect();
257         $dn = $this->_getFullDn($dn);
258         $ret = false;
259         for($try = 1; $try <= 3; $try++){
260             $ret = @ldap_add($this->_conn,$dn,$aAttr);
261             if ($ret === false){
262                 $this->_log(__FUNCTION__.'----dn:'.$dn.'----aAttr:'.json_encode($aAttr).'-----'.$this->_getLastExecErrLog());
263                 if($this->_getLastErrNo() == -1){ //未連上服務器至多重試3次
264                     $this->connect(true);
265                     continue;
266                 } else {
267                     break;
268                 }
269             } else {
270                 break;
271             }
272         }
273         return $ret;
274     }
275     
276     /**
277      * @description 移動葉子節點 v3版才支持此方法
278      *
279      * @param $newDn 相對於$parentDn
280      * @param $parentDn 完整DN
281      * @param $bMoveRecur 
282      *
283      * @return boolean
284      */
285     public function moveEntry($oldDn,$newDn,$parentDn,$bDelOld = true,$bFixDn = true,$bMoveRecur = true){
286         //對於AD服務器 此方法可以移動用戶節點以及組織節點
287         //$newDn只能包含一個 比如OU=xxx
288         $oldDn = trim($oldDn);
289         $newDn = trim($newDn);
290         $parentDn = trim($parentDn);
291         if(!$oldDn || !$newDn || ($bFixDn && !$parentDn)){
292             return false;
293         }
294         if(!$this->_checkDn($oldDn) || !$this->_checkDn($newDn) || !$this->_checkDn($parentDn)){
295             return false;
296         }
297         $this->connect();
298         if($bFixDn){
299             $oldDn = $this->_getFullDn($oldDn);
300             $parentDn = $this->_getFullDn($parentDn);
301         }
302         $this->_log(__FUNCTION__."---DN:{$oldDn} -> {$newDn},{$parentDn}",false,'oper');
303         $aTmpMove = $aDelDn = array();
304         $aTmpMove[] = array('old'=>$oldDn,'new'=>$newDn);
305         /*if($bMoveRecur){
306             $aDelDn[] = $oldDn;
307             $aTmpList = $this->getEntryList($oldDn,array('objectClass'=>'*'),array('objectClass'),0,0);
308             if($aTmpList && ($aTmpList['count'] > 1)){
309                 for($i = 1; $i < $aTmpList['count']; $i++){
310                     if(!in_array('user',$aTmpList[$i]['objectclass'])){ //$bDelOld=true時,用戶節點移動時會自動刪除
311                         $aDelDn[] = $aTmpList[$i]['dn'];
312                     }
313                     $aTmpSep = explode($oldDn,$aTmpList[$i]['dn']);
314                     $aTmpMove[] = array(
315                         'old' => $aTmpList[$i]['dn'],
316                         'new' => $aTmpSep[0] . $newDn,
317                     );
318                 }
319             }
320         }*/
321         $bFlag = true;
322         foreach($aTmpMove as $k => $v){
323             $bTmpFlag = ldap_rename($this->_conn,$v['old'],$v['new'],$parentDn,(boolean)$bDelOld);
324             if(!$bTmpFlag){
325                 $this->_log(__FUNCTION__."---o:{$v['old']}-n:{$v['new']}-p:{$parentDn}-recur:{$bMoveRecur}-----".$this->_getLastExecErrLog());
326             }
327             $bFlag &= $bTmpFlag;
328         }
329         /*if(!$bFlag){
330             $this->_log(__FUNCTION__."---o:{$oldDn}-n:{$newDn}-p:{$parentDn}-recur:{$bMoveRecur}-----".$this->_getLastExecErrLog());
331         }*/
332         /*if($bFlag && $bDelOld && $aDelDn){
333             $aDelDn = array_reverse($aDelDn);
334             foreach($aDelDn as $k => $v){
335                 $this->delEntry($v,false);
336             }
337         }*/
338         return $bFlag;
339     }
340     
341     public function modEntry($dn,$act = 'add',$aAttr = array()){
342         //return false;
343         $dn = $this->_getFullDn($dn);
344         $this->_log(__FUNCTION__."---DN:{$dn}---Act:{$act}---aAttr:".str_replace("\n",'',var_export($aAttr,true)),false,'oper');
345         $this->connect();
346         $ret = false;
347         switch($act){
348             case 'add': $ret = ldap_mod_add($this->_conn,$dn,$aAttr); break;
349             case 'replace': $ret = ldap_mod_replace($this->_conn,$dn,$aAttr); break;
350             case 'del': $ret = ldap_mod_del($this->_conn,$dn,$aAttr); break;
351         }
352         if(!$ret){
353             $this->_log(__FUNCTION__."---dn:{$dn}---act:{$act}---attr:".json_encode($aAttr).'---'.$this->_getLastExecErrLog());
354         }
355         return $ret;
356     }
357     
358     /**
359      * @description 批量添加節點
360      * 
361      * @return boolean
362      */
363     public function addBatchEntry($aNodeList = array()){
364     }
365     
366     public function getAttrKv(array $aAttr = array()){
367         if(!isset($aAttr['count']) || ($aAttr['count'] < 1)){
368             return array();
369         }
370         $aRet = array();
371         for($i = 0; $i < $aAttr['count']; $i++){
372             $field = $aAttr[$i];
373             if (!isset($aAttr[$field])){
374                 return array();
375             }
376             unset($aAttr[$field]['count']);
377             $aRet[$field] = $aAttr[$field];
378         }
379         if(isset($aAttr['dn'])){ //dn是字符串
380             $aRet['dn'] = $aAttr['dn'];
381         }
382         return $aRet;
383     }
384     
385     private function _getObjectClass($type = 'employee'){
386         $aRet = array();
387         switch($type){
388             case 'employee' : $aRet = array('top','person','organizationalPerson','user'); break;
389             case 'group' : $aRet = array('top','organizationalUnit'); break;
390         }
391         return $aRet;
392     }
393     
394     public function getFullDn($partDn = ''){
395         return $this->_getFullDn($partDn);
396     }
397     
398     private function _getFullDn($partDn = ''){
399         $partDn = trim($partDn);
400         $partDn = rtrim($partDn,',');
401         return "{$partDn},{$this->_aOptions['dnSuffix']}";
402     }
403     
404     private function _checkDn($dn = ''){
405         $dn = trim($dn,',');
406         $aDn = explode(',',$dn);
407         foreach($aDn as $k => $v){
408             $aTmp = explode('=',$v);
409             $aTmp[0] = strtolower(trim($aTmp[0]));
410             $aTmp[1] = trim($aTmp[1]);
411             $flag = false;
412             switch($aTmp[0]){ //distingushed name 暫時只允許這3個field
413                 case 'dc': $flag = $this->_checkDc($aTmp[1]); break;
414                 case 'ou': $flag = $this->_checkOu($aTmp[1]); break;
415                 case 'cn': $flag = $this->_checkCn($aTmp[1]); break;
416             }
417             if (!$flag){
418                 $this->_log(__FUNCTION__.'----無效的節點路徑----dn:'.$dn);
419                 return false;
420             }
421         }
422         return true;
423     }
424     
425     private function _checkOu($ou = ''){
426         if (!$ou){
427             return false;
428         }
429         if (preg_match('/[^a-zA-Z\s\d\.&\'\d]/',$ou)){
430             $this->_log(__FUNCTION__.'----OU只能包含字母數字空格以及點');
431             return false;
432         }
433         return true;
434     }
435     
436     private function _checkCn($cn = ''){
437         if (!$cn){
438             return false;
439         }
440         return true;
441     }
442     
443     private function _checkDc($dc = ''){
444         if (!$dc){
445             return false;
446         }
447         if (preg_match('/[^a-zA-Z]/',$dc)){
448             $this->_log(__FUNCTION__.'----DC只能包含英文字母');
449             return false;
450         }
451         return true;
452     }
453     
454     private function _mkAttrFilter(array $aAttrFilter = array()){
455         $sStr = '(&';
456         foreach($aAttrFilter as $k => $v){
457             $v = (string)$v;
458             if($k === 'objectGUID'){
459                 $v = $this->_GUIDtoStr($v);
460             }
461             $v = addcslashes($v,'()=');
462             $sStr .= "({$k}={$v})";
463         }
464         $sStr .= ')';
465         return $sStr;
466     }
467     
468     //來自PHP.NET http://php.net/manual/en/function.ldap-search.php
469     //http://php.net/manual/en/function.ldap-get-values-len.php
470     //GUID關鍵字
471     private function _GUIDtoStr($binary_guid){
472         $hex_guid = unpack("H*hex", $binary_guid); 
473         $hex = $hex_guid["hex"];
474         $j = 0;$str = '\\';
475         for($i = 0; $i < strlen($hex); $i++){
476             if($j == 2){
477                 $str .= '\\';
478                 $j = 0;
479             }
480             $str .= $hex[$i];
481             $j++;
482         }
483         return $str;
484         /*$hex1 = substr($hex, -26, 2) . substr($hex, -28, 2) . substr($hex, -30, 2) . substr($hex, -32, 2);
485         $hex2 = substr($hex, -22, 2) . substr($hex, -24, 2);
486         $hex3 = substr($hex, -18, 2) . substr($hex, -20, 2);
487         $hex4 = substr($hex, -16, 4);
488         $hex5 = substr($hex, -12, 12);
489         $guid_str = $hex1 . "-" . $hex2 . "-" . $hex3 . "-" . $hex4 . "-" . $hex5;
490         return $guid_str;*/
491     }
492     
493     private function _checkAttr(& $aAttr = array()){
494         foreach((array)$aAttr as $k => $v){
495             if (!in_array($k,$this->_aAllowAttrName)){
496                 unset($aAttr[$k]);
497             }
498         }
499         return true;
500     }
501     
502     public function getErrLog(){
503         return $this->_sErrLog;
504     }
505     
506     public function getOperLog(){
507         return $this->_sOperLog;
508     }
509     
510     public function clearLog($type = 'err'){
511         if($type == 'err'){
512             $this->_sErrLog = '';
513         } else {
514             $this->_sOperLog = '';
515         }
516     }
517     
518     private function _log($str = '',$bExit = false,$type = 'err'/*err,oper*/){
519         $date = date('Y-m-d H:i:s');
520         if ($bExit){
521             if($this->_aOptions['logDir']){
522                 if(!file_exists($this->_aOptions['logDir'])){
523                     mkdir($this->_aOptions['logDir'],0666,true);
524                 }
525                 $file = rtrim($this->_aOptions['logDir'],'\\/') . '/ldap_exit_' . date('Ym') . '.log';
526                 file_put_contents($file,"{$date}---type:{$type}---{$str}\n",FILE_APPEND);
527             }
528             die($str);
529         }
530         if($type === 'err'){
531             $this->_sErrLog .= "{$date}----{$str}\n";
532         } else if ($type === 'oper'){
533             $this->_sOperLog .= "{$date}----{$str}\n";
534         }
535     }
536     
537     public function close(){
538         ldap_close($this->_conn);
539     }
540     
541     private function _getLastExecErrLog(){
542         $no = $this->_getLastErrNo();
543         $err = ldap_error($this->_conn);
544         return "---exec Error:{$no}---{$err}";
545     }
546     
547     public function getLastExecErrLog(){
548         return $this->_getLastExecErrLog();
549     }
550     
551     private function _getLastErrNo(){
552         return ldap_errno($this->_conn);
553     }
554 }
View Code

組織架構信息更新類

   1 <?php
   2 /**
   3  * @description 測試用例說明
   4  *
   5  * @組織節點
   6  * 0.添加是否正常
   7  * 1.修改節點是否正常
   8  *   1.0修改了中文名稱是否正常
   9  *     1.1修改了其它屬性是否正常
  10  *     1.2修改了上級組織是否正常
  11  *   1.3修改了需要移動節點的屬性也修改了其它屬性是否正常
  12  * 2.移動節點是否正常
  13  *   2.0參考編號1
  14  * 3.刪除節點是否正常
  15  *   3.0暫時未提供刪除入口 
  16  * 4.沒有修改時是否有更新操作
  17  *   4.0原則上沒有修改不應該更新AD服務器
  18  *
  19  * @用戶節點
  20  * 0.添加是否正常
  21  *   0.0敏感屬性設置是否正常 例如用戶密碼 用戶賬號策略控制
  22  *   0.1RTX賬號不能重復
  23  *   0.2沒有填的項不能設置 否則會覆蓋掉老數據
  24  *   0.3同一父節點下中文名稱不能相同
  25  * 1.修改或者移動是否正常
  26  *   1.0RTX賬號必須唯一
  27  *   1.1姓名同一父節點下必須唯一
  28  *   1.2修改了姓名或者修改了所在部門需要移動節點 
  29  *   1.3修改了其它屬性 執行更新操作
  30  *   1.4沒有屬性修改 原則上不更新AD服務器
  31  *   1.5設置敏感屬性信息是否正常
  32  * 2.刪除是否正常
  33  *   2.0暫時未提供入口
  34  *
  35  * @description 碰到的問題說明
  36  *
  37  * 0.敏感屬性只能通過加密連接設置
  38  * 1.解決中文亂碼 移動節點 只有LDAPV3協議支持
  39  * 2.運維建議盡量只進行更新操作 域服務器OBJECTGUID數量有限制 超過了域服務器就不能使用了
  40  */
  41 namespace App\Lib;
  42 use App\Lib\Ldap;
  43 use App\Lib\Pinyin;
  44 use App\Lib\fn;
  45 use DB;
  46 use App\Http\Model\Group;
  47 use App\Http\Model\User;
  48 use Mail;
  49 class Hrldap{
  50     private static $_aCfg = array();
  51     private static $_oLdap = null;
  52     private $_adGuidField = 'adguid';
  53     private $_tbluser = 'pb_user';
  54     private $_tblgroup = 'pb_group';
  55     private $_isTestSvr = false;
  56     public $aOpertor = array();
  57     const STATUS_USER_LEAVE = 2; //員工離職狀態
  58     const STATUS_USER_NOLEAVE = 1; //員工在職狀態
  59     const STATUS_GROUP_DEL = 1; //組織架構刪除狀態
  60     
  61     public function __construct(){
  62         if (!self::$_aCfg){
  63             self::$_aCfg = config('ldap');
  64         }
  65         if (self::$_oLdap === null){
  66             self::$_oLdap = new Ldap($this->_getReMapCfg());
  67         }
  68         $this->_isTestSvr = (env('APP_ENV') == 'local');
  69         $this->aOpertor = ['username'=>'cron','id'=>0]; //設置一個默認值
  70     }
  71     
  72     private function _getReMapCfg(){
  73         return array(
  74             'host' => self::$_aCfg['ldapHost'],
  75             'port' => self::$_aCfg['ldapPort'], 
  76             'dnSuffix' => self::$_aCfg['ldapDnSuffix'],
  77             'loginUser' => self::$_aCfg['ldapUser'],
  78             'loginPass' => self::$_aCfg['ldapPass'],
  79             'logDir' => self::$_aCfg['logDir'],
  80         );
  81     }
  82     
  83     public function getCfg($field = ''){
  84         if($field && isset(self::$_aCfg[$field])){
  85             return self::$_aCfg[$field];
  86         }
  87         return self::$_aCfg;
  88     }
  89     
  90     /**
  91      * @desc 通用入口
  92      *
  93      */
  94     public function apiHr(array &$newData, $type = 'group'/*group,user*/,$aOpertor = [],$bLog = true){
  95         //$newData 數據庫最新的數據
  96         $this->aOpertor = $aOpertor;
  97         try{
  98             switch($type){
  99                 case 'group':
 100                     $aRet = $this->_frmGroupData($newData);
 101                 break;
 102                 case 'user':
 103                     $aRet = $this->_frmUserData($newData);
 104                 break;
 105             }
 106             if($bLog){
 107                 //$this->_log('endToFile');
 108                 $this->_log('ldap');
 109                 //$this->_log('ldapOperLog');
 110                 $this->_sendMonitorMail();
 111             }
 112             return $aRet;
 113         }catch(Exception $ex){
 114             if($bLog){
 115                 //$this->_log('endToFile');
 116                 $this->_log('ldap');
 117                 $this->_sendMonitorMail();
 118             }
 119             return $this->_genRet(-9999,$ex->getMessage());
 120         }
 121     }
 122     
 123     public function afterHandleCb(){
 124         //$this->_log('endToFile');
 125         $this->_log('ldap');
 126         //$this->_log('ldapOperLog');
 127         $this->_sendMonitorMail();
 128     }
 129     
 130     /**
 131      * @desc 批量更新通用入口
 132      *
 133      * @param $aData [type:[id:{},...,]]
 134      *
 135      */
 136     public function apiHrBat(array &$aData,$aOpertor = []){
 137         if(!$aData || !is_array($aData)){
 138             return false;
 139         }
 140         $this->aOpertor = $aOpertor;
 141         $aType = array('group' => $this->_tblgroup,'user' => $this->_tbluser);
 142         $aColumns = array(
 143             'group'=>'*',
 144             'user'=>'id,username,cname,sex,groupid,position,mposition,utype,boss,phone,email,status,degree',
 145         );
 146         $bTmp = true;
 147         $st = microtime(true);
 148         foreach($aData as $k => $v){
 149             if(!isset($aType[$k])){
 150                 continue;
 151             }
 152             $aTmpIds = array_keys($v);
 153             if(!$aTmpIds){
 154                 continue;
 155             }
 156             $sTmpIds = implode(',',$aTmpIds);
 157             $aTmpList = DB::select( "SELECT {$aColumns[$k]} FROM {$aType[$k]} WHERE id in ({$sTmpIds})" );
 158             foreach((array)$aTmpList as $k2 => $v2){
 159                 $aTmpRet = array();
 160                 switch($k){
 161                     case 'group': $aTmpRet = $this->_frmGroupData((array)$v2); break;
 162                     case 'user': $aTmpRet = $this->_frmUserData((array)$v2); break;
 163                 }
 164                 $bTmp &= (($aTmpRet['sts'] === 0) ? true : false);
 165             }
 166         }
 167         $et = microtime(true);
 168         //$this->_log('endToFile');
 169         $this->_log('ldap');
 170         $this->_log('def','def',array('f'=>'aApiHrBatCnt.log','s'=>date('Y-m-d H:i:s').'---批量更新消耗時間:'.($et-$st)."---更新狀態:{$bTmp}\n"));
 171         $this->_log('def','def',array('f'=>'aApiHrBatData.log','s'=>date('Y-m-d H:i:s').json_encode($aData)."\n"));
 172         //$this->_log('ldapOperLog');
 173         $this->_sendMonitorMail();
 174         return $bTmp;
 175     }
 176     
 177     /**
 178      * @desc 根據數據庫數據批量更新信息到AD服務器(組織節點)
 179      *
 180      * @return boolean
 181      */
 182     public function apiSyncOuToAd(){    
 183     }
 184     
 185     /**
 186      * @desc 根據數據庫數據批量更新信息到AD服務器(員工節點)
 187      *
 188      * @return boolean
 189      */
 190     public function apiSyncUserToAd($bOnlyUpdate = false,$aUpdateField = array(),$aUid = array()){ 
 191     }
 192     
 193     public function getLdapObj(){
 194         return self::$_oLdap;
 195     }
 196     
 197     /**
 198      * @desc 灰度開放部門
 199      *
 200      */
 201     private function _checkOpOk($id = 1){//組織ID
 202         return true; //開啟全部部門
 203         $aOuList = $this->_getOuList();
 204         if(!isset($aOuList[$id])){
 205             return false;
 206         }
 207         $aIdLvl = array();
 208         $aTmpOu = $aOuList[$id];
 209         while($aTmpOu){
 210             $aIdLvl[] = $aTmpOu['id'];
 211             $aTmpOu = $aOuList[$aTmpOu['fid']];
 212         }
 213         $topId = array_pop($aIdLvl);
 214         if(in_array($topId,array(328,/*業務支持中心*/))){ //暫時開放這些部門
 215             return true;
 216         }
 217         return false;
 218     }
 219     
 220     private $_aNewestAdguidMap = array();
 221     private function _frmGroupData(array &$newData){
 222         $sNewDes = $newData['name'] = trim($newData['name']);
 223         $iBoss = $newData['boss'] = intval($newData['boss']);
 224         $id = $newData['id'] = intval($newData['id']);
 225         if( !$sNewDes || !$id /*|| !$iBoss*/){
 226             return $this->_genRet(-1);
 227         }
 228         if(!$this->_checkOpOk($id)){ //灰度測試 暫時只對部分部門開放
 229             return $this->_genRet(0);
 230         }
 231         if($newData['del'] == self::STATUS_GROUP_DEL){ //關閉節點 有在職員工 AD不移動到LEFTOUT
 232             if( count(User::getOcolsByGroup($id)) > 0 ){
 233                 return $this->_genRet(0);
 234             }
 235         }
 236         //關閉節點 移動節點到OU=OU,OU=HasLeft
 237         /*if($newData['del'] == 1){//關閉節點 AD服務器不做處理
 238             return $this->_genRet(0);
 239         }*/
 240         //AD服務器老節點存在 執行更新移動操作
 241         //否則走添加新節點流程
 242         //新添加節點業務機需要保存節點GUID
 243         if(isset($this->_aNewestAdguidMap[$id])){ //循環更新的話ADGUID可能會變化
 244             $newData[$this->_adGuidField] = $this->_aNewestAdguidMap[$id];
 245         }
 246         $sNewAdName = $this->_getOuPinYin($sNewDes,$id);
 247         $this->_getOuList($newData);//更新OU LIST 節點緩存
 248         $aNewAttr = $this->_getGroupData($sNewDes,$sNewAdName,$iBoss);
 249         $sGuidField = $this->_adGuidField;
 250         $sAdGuid = $newData[$sGuidField] ? $this->_decodeAdGuid($newData[$sGuidField]) : ''; //AD服務器節點唯一標識
 251         $aOldEntry = array();
 252         $sTopOu = self::$_oLdap->getOption('dnSuffix');
 253         if($sAdGuid){ //沒找到是否需要退出 ?
 254             $aTmpEntryList = self::$_oLdap->getEntryList($sTopOu,array('objectClass'=>'organizationalUnit','objectGUID'=>$sAdGuid),array(),0,0);
 255             if($aTmpEntryList && ($aTmpEntryList['count'] > 0)){
 256                 $aOldEntry = $aTmpEntryList[0];
 257             }
 258         }
 259         if(($newData['del'] == self::STATUS_GROUP_DEL) && !$aOldEntry){ //關閉節點 域中節點不存在的話 不做任何處理
 260             return $this->_genRet(0);
 261         }
 262         if($aOldEntry){ //AD服務器節點已存在 更新或者移動
 263             $sTmpOldDn = str_replace(",{$sTopOu}",'',$aOldEntry['dn']);
 264             $aTmpAttr = $this->_getAttrDiff($aNewAttr,$aOldEntry);
 265             unset($aTmpAttr['name']); //惡魔屬性 不能更新
 266             $bTmp = true;
 267             if($aTmpAttr && ($newData['del'] != self::STATUS_GROUP_DEL)){
 268                 $bTmp = self::$_oLdap->updateEntry($sTmpOldDn,$aTmpAttr);
 269                 $this->_log('update','group',array('flag'=>$bTmp,'dn'=>$sTmpOldDn,'attr'=>$aTmpAttr),$newData,$aOldEntry);
 270             }
 271             if(!$bTmp){
 272                 return $this->_genRet(-2,'部門ID:'.$id.'---節點更新失敗---line:'.__LINE__);
 273             }
 274             $sTmp2 = "OU={$sNewAdName}";
 275             if($newData['del'] == self::STATUS_GROUP_DEL){ //關閉節點 把節點移動到OU=OU,OU=HasLeft
 276                 $sTmp4 = self::$_aCfg['ldapDnHasLeftOu'];
 277             }else{
 278                 $sTmp4 = $this->_getOuDn($newData['fid']);
 279             }
 280             $sTmp5 = $sTmp2 . ($sTmp4 ? ",{$sTmp4}" : '');
 281             if(strpos($sTmpOldDn,$sTmp5) === false){ //DN變了 需要移動節點
 282                 $bTmp = self::$_oLdap->moveEntry($sTmpOldDn,$sTmp2,$sTmp4);
 283                 $this->_log('move','group',array('flag'=>$bTmp,'path'=>"{$sTmpOldDn}->{$sTmp5}"),$newData,$aOldEntry);
 284             }
 285             return $this->_genRet($bTmp ? 0 : -3,'部門ID:'.$id.'---移動節點失敗---line:'.__LINE__);
 286         }
 287         //走到這一步只剩下添加節點操作了
 288         //父級節點不存在自動遞歸創建
 289         //否則新組織節點也創建不了
 290         $aAdd = array();
 291         $aOuList = $this->_getOuList(); //數據庫中的組織架構列表
 292         $aTmpOu = $newData;
 293         do{
 294             if($aTmpOu[$sGuidField]){ //數據庫中存在域對象的GUID AD域中存在的概率99% 還需要走AD服務器驗證下?
 295                 $bTmpBreak = true;
 296                 if($this->_isTestSvr){
 297                     $aTmpFilter = array('objectClass'=>'organizationalUnit','objectGUID'=>$this->_decodeAdGuid($aTmpOu[$sGuidField]));
 298                     $aTmpEntryList = self::$_oLdap->getEntryList($sTopOu,$aTmpFilter,array(),0,0);
 299                     if($aTmpEntryList && ($aTmpEntryList['count'] > 0)){    
 300                     } else {
 301                         $bTmpBreak = false;
 302                     }
 303                 }
 304                 if($bTmpBreak){
 305                     break;
 306                 }
 307             }
 308             $aAdd[] = $aTmpOu;
 309             $aTmpOu = isset($aOuList[$aTmpOu['fid']]) ? $aOuList[$aTmpOu['fid']] : [];
 310         }while($aTmpOu);
 311         if(!$aAdd){
 312             return $this->_genRet(0);
 313         }
 314         $aAdd = array_reverse($aAdd); //祖先節點要先添加 
 315         $bTmpFlag = true;
 316         $sPreFixDn = $this->_getOuDn($aAdd[0]['fid']);
 317         if(!$sPreFixDn){
 318             return $this->_genRet(-6,'部門ID:'.$id.'---新建節點失敗---取第一個父節點(fid:'.$aAdd[0]['fid'].')DN失敗');
 319         }
 320         foreach($aAdd as $k => $v){
 321             $sTmpOuPinYin = $this->_getOuPinYin($v['name'],$v['id']);
 322             $aTmpAttr = $this->_getGroupData($v['name'],$sTmpOuPinYin,$v['boss']);
 323             //$sTmpDn = $this->_getOuDn($v['id']);
 324             $sTmpDn = "OU={$sTmpOuPinYin}," . $sPreFixDn;
 325             $bTmpFlag2 = self::$_oLdap->addEntry($sTmpDn,$aTmpAttr,'group');
 326             $bTmpFlag = $bTmpFlag2;
 327             $this->_log('add','group',array('flag'=>$bTmpFlag2,'dn'=>$sTmpDn,'attr'=>$aTmpAttr),$v,array());
 328             if($bTmpFlag2){ //更新業務機組織表AD節點objectGUID
 329                 $this->_addAdGuidMap($sTmpDn,$v['id'],array('name'=>$aTmpAttr['name'][0]),'group',true);
 330             } else { //父節點都更新失敗,子節點無地方落腳啊,果斷不往下走
 331                 break;
 332             }
 333             $sPreFixDn = $sTmpDn;
 334         }
 335         return $this->_genRet($bTmpFlag ? 0 : -5,'部門ID:'.$id.'---新建節點失敗');
 336     }
 337     
 338     private function _encodeAdGuid($str){
 339         return base64_encode($str);
 340     }
 341     
 342     private function _decodeAdGuid($str){
 343         return base64_decode($str);
 344     }
 345     
 346     private $_aCachePinYin = array();
 347     private function _getOuPinYin($name,$id){
 348         if(isset($this->_aCachePinYin[$id])){
 349             return $this->_aCachePinYin[$id];
 350         }
 351         $this->_aCachePinYin[$id] = (new Pinyin())->Pinyin($name,1) . " {$id}"; //加上ID后綴 避免名稱沖突
 352         return $this->_aCachePinYin[$id];
 353     }
 354     
 355     private function _addAdGuidMap($dn,$id,$aAttrFilter = array(),$type = 'group'/*group,user*/,$bUpdateGuid = false){
 356         $adGuid = '';
 357         $sErrFile = 'aLdapAddAdGuidMapErr.log';
 358         $aTmpEntryList = self::$_oLdap->getEntryList($dn,$aAttrFilter,array('objectGUID'));
 359         if($aTmpEntryList && ($aTmpEntryList['count'] > 0)){
 360             $adGuid = $aTmpEntryList[0]['objectguid'][0];
 361         }
 362         if(!$adGuid){
 363             $this->_debug(date('Y-m-d H:i:s')."---dn:{$dn}---id:{$id}---type:{$type}---取objectGUID失敗\n",$sErrFile);
 364             return false;
 365         }
 366         $aMapTbl = array(
 367             'group' => $this->_tblgroup,
 368             'user' => $this->_tbluser,
 369         );
 370         if(!isset($aMapTbl[$type])){
 371             return false;
 372         }
 373         $tbl = $aMapTbl[$type];
 374         $adGuid = $this->_encodeAdGuid($adGuid);
 375         $ret = DB::update( "UPDATE {$tbl} SET {$this->_adGuidField} = '{$adGuid}' WHERE id = '{$id}'" );
 376         if(!$ret){
 377             $this->_debug(date('Y-m-d H:i:s')."---dn:{$dn}---id:{$id}---type:{$type}---guid:{$adGuid}---objectGUID入庫失敗\n",$sErrFile);
 378         }
 379         if($bUpdateGuid && $ret){
 380             switch($type){
 381                 case 'group': 
 382                     $aTmpOuList = $this->_getOuList();
 383                     $aTmpOu = $aTmpOuList[$id];
 384                     $aTmpOu[$this->_adGuidField] = $adGuid;
 385                     $this->_getOuList($aTmpOu);
 386                     $this->_aNewestAdguidMap[$id] = $adGuid;
 387                     break;
 388             }
 389         }
 390         return $ret;
 391     }
 392     
 393     private function _debug($msg,$sFile){
 394         $this->_log('def','def',array('f'=>$sFile,'s'=>$msg));
 395     }
 396     
 397     /**
 398      * @desc 拼接域節點路徑
 399      *
 400      */
 401     private $_aCacheOuDn = array();
 402     private function _getOuDn($id){
 403         if($id == 1){ //一級部門OU = Dept;
 404             return self::$_aCfg['ldapDnDept']; //組織架構DN專有后綴;
 405         }
 406         $aRet = array();
 407         $aOuList = (array)$this->_getOuList();
 408         $aTmpOu = $aOuList[$id];
 409         if (!$aTmpOu){
 410             throw new Exception(__FUNCTION__.'----非法的組織ID:'.$id.'---line:'.__LINE__);
 411         }
 412         $adguid = 'x';
 413         if($aTmpOu[$this->_adGuidField]){
 414             $adguid = $aTmpOu[$this->_adGuidField];
 415         }
 416         if(isset($this->_aCacheOuDn[$id][$adguid])){
 417             return $this->_aCacheOuDn[$id][$adguid];
 418         }
 419         if(!isset($this->_aCacheOuDn[$id])){
 420             $this->_aCacheOuDn[$id] = array();
 421         }
 422         if($aTmpOu[$this->_adGuidField]){ //通過GUID找到當前節點DN 不通過規則拼接
 423             $sTopOu = self::$_oLdap->getOption('dnSuffix');
 424             $sAdGuid = $this->_decodeAdGuid($aTmpOu[$this->_adGuidField]);
 425             $aTmpEntryList = self::$_oLdap->getEntryList($sTopOu,array('objectClass'=>'organizationalUnit','objectGUID'=>$sAdGuid),array('objectGUID'),0,0);
 426             if($aTmpEntryList && ($aTmpEntryList['count'] > 0)){
 427                 $sTmpOldDn = str_replace(",{$sTopOu}",'',$aTmpEntryList[0]['dn']);
 428                 $this->_aCacheOuDn[$id][$adguid] = $sTmpOldDn;
 429             } else {
 430                 $this->_aCacheOuDn[$id][$adguid] = '';
 431             }
 432             return $this->_aCacheOuDn[$id][$adguid];
 433         }
 434         while($aTmpOu){
 435             $ou = $this->_getOuPinYin($aTmpOu['name'],$aTmpOu['id']);
 436             $aRet[] = "OU={$ou}";
 437             $aTmpOu = isset( $aOuList[$aTmpOu['fid']] ) ? $aOuList[$aTmpOu['fid']] : [];
 438         }
 439         $aRet[] = self::$_aCfg['ldapDnDept']; //組織架構DN專有后綴
 440         $this->_aCacheOuDn[$id][$adguid] = implode(',',$aRet);
 441         return $this->_aCacheOuDn[$id][$adguid];
 442     }
 443     
 444     /**
 445      * @desc 返回單個節點信息
 446      *
 447      */
 448     private function _getOneEntry($dn,$aFilter){
 449         $aTmp = array();
 450         $aEntryList = self::$_oLdap->getEntryList($dn,$aFilter);
 451         if ($aEntryList && ($aEntryList['count'] > 0)){ 
 452             $aTmp = $aEntryList[0];
 453         }
 454         return $aTmp;
 455     }
 456     
 457     /**
 458      * @desc 組織架構節點信息
 459      *
 460      * @return array
 461      */
 462     private $_aGroupNodeAttr = array( //組織節點屬性
 463         'description' => '中文名', //組織中文名
 464         'name' => '拼音',//組織英文名稱
 465         'managedBy' => '負責人', //部門負責人
 466     );
 467     private function _getGroupData($desc,$adname,$iBoss){
 468         $aRet = array(
 469             'description' => array($desc), //組織中文名
 470             'name' => array($adname),//組織英文名稱
 471         );
 472         $sManagedBy = $this->_getUserObjectId($iBoss,true); //部門負責人域對象ID
 473         if($sManagedBy){
 474             $aRet['managedBy'] = array($sManagedBy); //部門負責人
 475         }
 476         return $aRet;
 477     }
 478     
 479     /**
 480      * @desc 用戶節點對象ID
 481      *
 482      * @return String
 483      */
 484     private $_aCacheUserObjDn = array();
 485     private function _getUserObjectId($uid,$bChecked = false){
 486         if(isset($this->_aCacheUserObjDn[$uid])){
 487             return $this->_aCacheUserObjDn[$uid];
 488         }
 489         //if(LOCAL){
 490             $aUser = DB::select( "SELECT id,groupid AS pid,cname AS name FROM {$this->_tbluser} WHERE id = '{$uid}'" );
 491         //} else {
 492             //$aUser = o('hr')->getUser($uid);
 493         //}
 494         if (!$aUser){
 495             $this->_aCacheUserObjDn[$uid] = '';
 496             return '';
 497         }
 498         $aUser = (array)$aUser[0];
 499         $sOuDn = $this->_getOuDn($aUser['pid']);
 500         if(!$sOuDn){
 501             $this->_aCacheUserObjDn[$uid] = '';
 502             return '';
 503         }
 504         $dn = "CN={$aUser['name']},{$sOuDn}";
 505         if ($bChecked){
 506             $exists = $this->_getOneEntry($dn,array('objectClass'=>'user'));
 507             if (!$exists){
 508                 $dn = '';
 509             }
 510         }
 511         if ($dn){
 512             $dn = self::$_oLdap->getFullDn($dn);
 513         }
 514         $this->_aCacheUserObjDn[$uid] = $dn;
 515         return $dn;
 516     }
 517     
 518     /**
 519      * @desc 組織架構信息
 520      *
 521      * @return array
 522      */
 523     private function _getOuList(array $aNewNode = array()){
 524         static $aCache = NULL;
 525         if ($aCache === NULL){                                         
 526             $aCache = array();
 527             $aList = DB::select("SELECT * FROM {$this->_tblgroup} WHERE fid > 0");
 528             foreach((array)$aList as $k => $v){
 529                 $v = (array)$v;
 530                 $aCache[$v['id']] = $v;
 531             }
 532         }
 533         if($aNewNode && ($aNewNode['fid'] > 0)){
 534             $aCache[$aNewNode['id']] = $aNewNode;
 535             return true;
 536         }
 537         return $aCache;
 538     }
 539     
 540     private function _genRet($errno = 0,$errMsg = ''){
 541         return array('sts' => $errno, 'msg' => $errMsg);
 542     }
 543     
 544     private $_cacheLog = '';
 545     private $_cacheLogFile = 'aLdapOper_#1.log';
 546     private $_ldapLogFile = 'aLdapOperErr_#1.log';
 547     private $_ldapLogRawFile = 'aLdapOperRaw_#1.log';
 548     private function _log($actType = '',$oType = 'normal',array $aPar = array(), array $aNewData = array(), array $aOldData = array()){
 549         if(in_array($actType,array('endToFile','ldap','def','ldapOperLog'))){
 550             $sDir = rtrim(self::$_aCfg['logDir'],'\\/') . '/';
 551             if(!file_exists($sDir)){
 552                 mkdir($sDir,0666,true);
 553             }
 554         }
 555         if ($actType === 'endToFile'){
 556             $sFile = $sDir.str_replace('#1',date('Y'),$this->_cacheLogFile);
 557             file_put_contents($sFile,$this->_cacheLog,FILE_APPEND);
 558             $this->_cacheLog = '';
 559             //$this->_sendMonitorMail();
 560             return true;
 561         } else if ($actType === 'ldap'){
 562             $sFile = $sDir.str_replace('#1',date('Y'),$this->_ldapLogFile);
 563             $sTmp = date('Y-m-d H:i:s').'----Start----'.$this->aOpertor['username'].'('.$this->aOpertor['id'].')';
 564             $sLdapLog = self::$_oLdap->getErrLog();
 565             file_put_contents($sFile,"{$sTmp}\n{$sLdapLog}\n",FILE_APPEND);
 566             return true;
 567         } else if ($actType === 'def'){
 568             file_put_contents($sDir.$aPar['f'],$aPar['s'],FILE_APPEND);
 569             return true;
 570         } else if ($actType === 'ldapOperLog'){
 571             $sFile = $sDir.str_replace('#1',date('Y'),$this->_ldapLogRawFile);
 572             $sTmp = date('Y-m-d H:i:s').'----Start----'.$this->aOpertor['username'].'('.$this->aOpertor['id'].')';
 573             $sLdapLog = self::$_oLdap->getOperLog();
 574             file_put_contents($sFile,"{$sTmp}\n{$sLdapLog}\n",FILE_APPEND);
 575             return true;
 576         }
 577         $this->_logMail($actType,$oType,$aPar,$aNewData,$aOldData);
 578         $aLog = array(
 579             date('Y-m-d H:i:s'),
 580             $actType,
 581             $oType,
 582             fn::serialize($aPar),
 583         );
 584         $this->_cacheLog .= implode('##',$aLog);
 585         $this->_cacheLog .= "\n";
 586         return true;
 587     }
 588     private $_aLogMail = array('group'=>array(),'user'=>array(),'delUserDn'=>array());//離職用戶單獨通知 刪除權限組
 589     private function _logMail($actType,$oType,$aPar,$aNewData,$aOldData){
 590         if(!isset($this->_aLogMail[$oType])){
 591             return false;
 592         }
 593         if(!$aPar['flag']){ //沒有更新成功過濾掉
 594             return false;
 595         }
 596         $aFieldMap = array('group'=>$this->_aGroupNodeAttr,'user'=>$this->_aUserNodeAttr);
 597         if($actType === 'move'){
 598             if(($oType === 'user') && isset($aNewData['del']) && ($aNewData['del'] == self::STATUS_USER_LEAVE)){//離職用戶單獨通知 
 599                 $sTmpDepart = $this->_fixDepartment($aNewData['pid']);
 600                 $this->_aLogMail['delUserDn'][] = "DN:{$aPar['dn']},RTX賬號:{$aNewData['rtx']},部門:{$sTmpDepart}";
 601             }
 602             $this->_aLogMail[$oType][$actType][] = "<tr><td>{$aPar['path']}</td></tr>";
 603         }else if (in_array($actType,array('update','add'))){
 604             $sTmp = '<tr>';
 605             $aFieldTmp = $aFieldMap[$oType];
 606             $sTmp .= '<td>'.(isset($aPar['dn']) ? $aPar['dn'] : $aPar['cn']).'</td>';
 607             foreach($aFieldTmp as $k => $v){
 608                 $sK = strtolower($k);
 609                 if(isset($aOldData[$sK]['count'])){
 610                     unset($aOldData[$sK]['count']);
 611                 }
 612                 $sTmpVal = isset($aPar['attr'][$k]) ? ( (isset($aOldData[$sK]) ? implode(',',$aOldData[$sK]).' => ' : '').implode(',',$aPar['attr'][$k]) ) : '';
 613                 $sTmp .= "<td>{$sTmpVal}</td>";
 614             }
 615             $sTmp .= '</tr>';
 616             $this->_aLogMail[$oType][$actType][] = $sTmp;
 617         }
 618         return true;
 619     }
 620     private function _sendMonitorMail(/*$bClear = true*/){
 621         $sTmp = '';
 622         $aFieldMap = array('group'=>$this->_aGroupNodeAttr,'user'=>$this->_aUserNodeAttr);
 623         $aHeadMap = array('group' => '','user'=>'');
 624         foreach($aFieldMap as $k => $v){
 625             $aHeadMap[$k] = '<tr>';
 626             $aHeadMap[$k] .= '<td>DN</td>';
 627             foreach($v as $k2 => $v2){
 628                 $aHeadMap[$k] .= "<td>{$v2}</td>";
 629             }
 630             $aHeadMap[$k] .= '</tr>';
 631         }
 632         foreach($this->_aLogMail as $k => $v){
 633             if(!$v || ($k === 'delUserDn')){
 634                 continue;
 635             }
 636             $sTmp .= "<h1>被操作實體:{$k}</h1>";
 637             foreach($v as $k2 => $v2){
 638                 $sTmp .= "<h5>操作類型:{$k2}</h5>";
 639                 $sTmp .= '<table border=1 cellspacing=1>';
 640                 if($k2 === 'move'){
 641                     $sTmp .= '<tr><td>Old DN => New DN</td></tr>';
 642                 } else {
 643                     $sTmp .= $aHeadMap[$k];
 644                 }
 645                 $sTmp .= implode("\n",$v2);
 646                 $sTmp .= '</table>';
 647             }
 648             $this->_aLogMail[$k] = array();
 649         }
 650         if(!$sTmp){
 651             return true;
 652         }
 653         $title = '【周知】BY后台同步信息到AD域服務器變更通知';
 654         Mail::queue('emails.ldap_notify',['data'=>$sTmp],function($m)use($title){
 655             $m->to( self::$_aCfg['ldapMailTo'] )->subject($title);
 656         });
 657         $this->_isTestSvr && $this->_log('def','def',array('f'=>'aMonitorLog.html','s'=>$sTmp));
 658         if($this->_aLogMail['delUserDn']){ //離職用戶單獨通知
 659             $title = '【員工離職周知】BY后台員工變更為離職狀態';
 660             $content = '離職用戶:<br />'.implode('<br />',$this->_aLogMail['delUserDn']);
 661             Mail::queue('emails.ldap_notify',['data'=>$content],function($m) use($title){
 662                 $m->to( self::$_aCfg['ldapMailTo'] )->subject($title);
 663             });
 664             $this->_isTestSvr && $this->_log('def','def',array('f'=>'aMonitorLog.html','s'=>$content));
 665             $this->_aLogMail['delUserDn'] = array();
 666         }
 667         return true;
 668     }
 669     
 670     /**
 671      * @desc 刷新用戶節點信息
 672      *
 673      * @to do list 0.用戶重要信息設置 1.用戶說所在父節點不存在時自動遞歸創建
 674      *
 675      * @return boolean
 676      */
 677     private function _frmUserData(array &$newData,$bUpdate = false/*true只更新AD中已存在的員工節點*/,$aUpdateField=array()){
 678         $rtx = trim($newData['username']);
 679         $name = trim($newData['cname']);
 680         $uid = intval($newData['id']);
 681         $pid = intval($newData['groupid']); //部門ID
 682         $oldData = array();
 683         if(!$rtx || !$name || !$uid || !$pid){
 684             return $this->_genRet(-1,'參數不正確username,cname,id,groupid有空值---line:'.__LINE__);
 685         }
 686         if(!$this->_checkOpOk($pid)){ //灰度測試 暫時只對部分部門開放
 687             return $this->_genRet(0);
 688         }
 689         //0 深圳總部 8000外包客服 80000泰國分公司 加入域 其他過濾掉
 690         $uType = fn::getUidType($uid);
 691         if(!in_array($uType,array(0,8000,80000))){
 692             return $this->_genRet(0); 
 693         }
 694         //-1.只對AD服務器執行添加 更新 移動操作 不執行刪除操作
 695         //0.同一節點下兄弟節點姓名需要唯一
 696         //1.組織架構下RTX需要唯一
 697         //2.節點存在 直接執行更新操作
 698         //    2.0節點存在 工號不相同時 同一父節點下姓名沖突了 提示錯誤
 699         //3.如果姓名修改了 如果老姓名存在 需要刪除節點
 700         //    3.0刪除之前需要判斷用戶ID是否一致啊 一致才能刪除啊 
 701         //4.更改了組織怎么處理?
 702         //    4.0即使更改了組織 刪除老節點就行了嘛
 703         //    4.1是否可以移動節點呢
 704         //5.RTX名必須唯一 否則會導致用戶節點添加不成功
 705         $sFixUid = $this->_fixUid($uid);
 706         $sTopOu = self::$_oLdap->getOption('dnSuffix');
 707         $aTmpEntryList = self::$_oLdap->getEntryList($sTopOu,array('objectClass'=>'user','sAMAccountName'=>$rtx),array('description'),0,0);
 708         if($aTmpEntryList && ($aTmpEntryList['count'] > 0)){
 709             for($i = 0; $i < $aTmpEntryList['count']; $i++){
 710                 $aTmpEntry = $aTmpEntryList[$i];
 711                 if($aTmpEntry['description'][0] !== $sFixUid){
 712                     return $this->_genRet(-2,"RTX:{$rtx}域服務器已存在---line:".__LINE__);
 713                 }
 714             }
 715         }
 716         $dn = $this->_getOuDn($pid);
 717         $aTmpEntryList = self::$_oLdap->getEntryList($dn,array('objectClass'=>'user','name'=>$name),array('description'));
 718         if($aTmpEntryList && ($aTmpEntryList['count'] > 0)){
 719             for($i = 0; $i < $aTmpEntryList['count']; $i++){
 720                 $aTmpEntry = $aTmpEntryList[$i];
 721                 if($aTmpEntry['description'][0] !== $sFixUid){
 722                     return $this->_genRet(-3,"中文名:{$name}同一小組下域服務器已存在---line:".__LINE__);
 723                 }
 724             }
 725         }
 726         $aAttr = $this->_getUserData($newData);
 727         //移動節點
 728         //0.老節點存在 移動老節點到新節點
 729         //    0.0名稱修改了 或者部門修改了 才需要移動 否則只需要更新即可
 730         //    0.1先更新老節點信息 再移動到指定的節點
 731         //    0.3AD服務器老節點用戶ID跟數據庫中用戶ID相同時,才能移動老節點,否則移動了其它人的節點,則悲劇了
 732         //1.老節點不存在 
 733         //    1.0 新節點不存在 添加
 734         //    1.1 新節點存在 更新
 735         //2.無論節點屬性怎么修改 工號是不會變化的
 736         //    2.0根據工號找到AD服務器已存在的節點
 737         //    2.1根據工號找出了多個用戶節點 需要找運維開發人功能核對處理
 738         $sNewCn = "CN={$name},".$dn; //未包含DN根路徑
 739         $nameDel = "{$name}{$sFixUid}";
 740         if($newData['status'] == self::STATUS_USER_LEAVE){ //員工離職需要移動到HasLeft節點下
 741             $dn = self::$_aCfg['ldapDnHasLeftUser'];
 742             $sNewCn = "CN={$nameDel},".$dn;
 743         }
 744         $aTmpEntryList = self::$_oLdap->getEntryList($sTopOu,array('objectClass'=>'user','description'=>$sFixUid),array(),0,0);
 745         if($aTmpEntryList && ($aTmpEntryList['count'] > 0)){ //移動或者更新節點
 746             if($aTmpEntryList['count'] > 1){
 747                 return $this->_genRet(-4,"AD服務器ERR:UID工號({$sFixUid})不唯一,請找運維開發人工處理".__LINE__);
 748             }
 749             $sOldCn = $aTmpEntryList[0]['dn']; //完整的DN路徑
 750             $sOldCn = str_replace(",$sTopOu",'',$sOldCn); //為包含DN根路徑
 751             unset($aAttr['name']);
 752             $sUacField = 'userAccountControl';
 753             $ssUacField = strtolower($sUacField);
 754             if($aTmpEntryList[0][$ssUacField][0] & 0x10000){//如果密碼不過期策略存在,一直保留(運維同事要求)
 755                 $aAttr[$sUacField][0] = $aAttr[$sUacField][0] | 0x10000;
 756             }
 757             $sAccountField = 'sAMAccountName';
 758             $ssAccountField = strtolower($sAccountField);
 759             if( strtolower($aTmpEntryList[0][$ssAccountField][0]) === strtolower($aAttr[$sAccountField][0]) ){
 760                 unset($aAttr[$sAccountField],$aAttr['userPrincipalName'],$aAttr['mail']); //當RTX賬號字母相同時【不區分大小寫】,不更新RTX賬號【運維同事要求】
 761             } else { //不同時,不能更新AD賬號,會影響深信服賬號策略同步,發郵件周知
 762                 $this->_notifyModAdAccount(array('new'=>$aAttr,'old'=>$aTmpEntryList[0],'acc1'=>$aAttr[$sAccountField][0],'acc2'=>$aTmpEntryList[0][$ssAccountField][0]));
 763                 unset($aAttr[$sAccountField],$aAttr['userPrincipalName'],$aAttr['mail']); 
 764             }
 765             $aAttr = $this->_getAttrDiff($aAttr,$aTmpEntryList[0]);
 766             if($bUpdate && $aUpdateField){ //腳本批量更新某些字段
 767                 $aTmpAttr = array();
 768                 foreach($aUpdateField as $k => $v){
 769                     if(isset($aAttr[$v])){
 770                         $aTmpAttr[$v] = $aAttr[$v];
 771                     }
 772                 }
 773                 $aAttr = $aTmpAttr;
 774             }
 775             //域管理員信息無權限修改 發郵件周知
 776             if(isset($aTmpEntryList[0]['admincount']) && ($aTmpEntryList[0]['admincount']['count'] > 0)){ 
 777                 $this->_notifyModAdAdmin(array('attr'=>$aAttr,'dn'=>array($sNewCn,$sOldCn,$sTopOu)));
 778                 return $this->_genRet(0);
 779             }
 780             $bTmp = true;
 781             if($aAttr){ //更新老節點屬性
 782                 $bTmp = self::$_oLdap->updateEntry($sOldCn,$aAttr);
 783                 $this->_log('update','user',array('flag'=>$bTmp,'cn'=>$sOldCn,'attr'=>$aAttr),$newData,$aTmpEntryList[0]);
 784             }
 785             if(!$bTmp){
 786                 return $this->_genRet(-5,'工號:'.$uid.'---更新節點屬性失敗---line:'.__LINE__);
 787             }
 788             if($bUpdate){
 789                 return $this->_genRet(0);
 790             }
 791             if(strpos($sOldCn,$sNewCn) === false){ //節點DN路徑修改了 需要移動節點
 792                 $sNewPartDn = "CN={$name}";
 793                 if($newData['status'] == 2){//員工離職需要移動到HasLeft節點下 加上工號 
 794                     $sNewPartDn = "CN={$nameDel}";
 795                 }
 796                 $bTmp = self::$_oLdap->moveEntry($sOldCn,$sNewPartDn,$dn);
 797                 $this->_log('move','user',array('flag'=>$bTmp,'path'=>"{$sOldCn}->{$sNewPartDn},{$dn}",'dn'=>"{$sNewPartDn},{$dn}"),$newData,$oldData);
 798             }
 799             return $this->_genRet($bTmp ? 0 : -6,'工號:'.$uid.'---移動用戶節點失敗---line:'.__LINE__);
 800         }
 801         if($bUpdate){
 802             return $this->_genRet(0);
 803         }
 804         //走到這一步 只剩下向域服務器添加新節點了
 805         $aAttr = array_merge($this->_getUserDataForAdd(),$aAttr);
 806         $bTmp = self::$_oLdap->addEntry($sNewCn,$aAttr);
 807         $this->_log('add','user',array('flag'=>$bTmp,'cn'=>$sNewCn,'attr'=>$aAttr),$newData,$oldData);
 808         return $this->_genRet($bTmp ? 0 : -7,'工號:'.$uid.'---更新用戶節點失敗---line:'.__LINE__);
 809     }
 810     
 811     /**
 812      * @param $aPar = array(
 813      *        'dn' => array(new,old,top),
 814      *        'attr' => array(),
 815      * )
 816      */
 817     private function _notifyModAdAdmin(array $aPar = array()){
 818         $sTmpMail = '';
 819         if($aPar['attr']){
 820             $sTmpHead = '<tr><td>DN</td>';
 821             $sTmp .= "<tr><td>{$aPar['dn'][1]},{$aPar['dn'][2]}</td>";
 822             foreach($aPar['attr'] as $k => $v){
 823                 $sTmpHead .= "<td>{$k}</td>";
 824                 $sTmp .= '<td>'.implode(',',$v).'</td>';
 825             }
 826             $sTmpHead .= '</tr>';
 827             $sTmp .= '</tr>';
 828             $sTmpMail .= '<h2>更新屬性信息</h2>';
 829             $sTmpMail .= '<table border=1 cellspacing=1>';
 830             $sTmpMail .= $sTmpHead;
 831             $sTmpMail .= $sTmp;
 832             $sTmpMail .= '</table>';
 833         }
 834         if($aPar['dn'] && ($aPar['dn'][0] !== $aPar['dn'][1])){
 835             $sTmpMail .= '<h2>移動節點</h2>';
 836             $sTmpMail .= "<p>從{$aPar['dn'][1]},{$aPar['dn'][2]}移動到{$aPar['dn'][0]},{$aPar['dn'][2]}</p>";
 837         }
 838         $title = 'BY后台嘗試修改域管理員信息,請關注';
 839         if($sTmpMail){
 840             $this->_isTestSvr && $this->_log('def','def',array('f'=>__FUNCTION__.'.html','s'=>$sTmpMail));
 841             Mail::queue('emails.ldap_notify',['data'=>$sTmpMail],function($m)use($title){
 842                 $m->to( self::$_aCfg['ldapMailTo'] )->subject($title);
 843             });
 844         }
 845     }
 846     
 847     /**
 848      * @param $aPar = array(
 849      *        'dn' => array(new,old,top),
 850      *        'attr' => array(),
 851      * )
 852      */
 853     private function _notifyModAdAccount(array $aPar = array()){
 854         $sTmpMail = '';
 855         if($aPar['acc1'] && $aPar['acc2']){
 856             $sTmpMail = '<h2>更新賬號</h2>';
 857             $sTmpMail .= "<p>老賬號:{$aPar['acc2']} => 新賬號:{$aPar['acc1']}</p>";
 858         }
 859         $title = 'BY后台嘗試修改員工RTX賬號,請關注';
 860         if($sTmpMail){
 861             $this->_isTestSvr && $this->_log('def','def',array('f'=>__FUNCTION__.'.html','s'=>$sTmpMail));
 862             Mail::queue('emails.ldap_notify',['data'=>$sTmpMail],function($m)use($title){
 863                 $m->to( self::$_aCfg['ldapMailTo'] )->subject($title);
 864             });
 865         }
 866     }
 867     
 868     private function _getAttrDiff(array $aNewData = array(), array $aOldData = array()){
 869         foreach($aNewData as $k => $v){
 870             $k = strtolower($k);
 871             if(!isset($aOldData[$k])){
 872                 continue;
 873             }
 874             if($v[0] === $aOldData[$k][0]){
 875                 unset($aNewData[$k]);
 876             }
 877         }
 878         return $aNewData;
 879     }
 880     
 881     /**
 882      * @desc 組裝用戶節點數據
 883      *
 884      * @return array
 885      */
 886     private $_aUserNodeAttr = array(
 887         'givenName' => '名', //
 888         'sn' => '姓', //
 889         'sAMAccountName' => 'RTX', //RTX
 890         'userPrincipalName' => '登陸名', //登陸名
 891         'description' => '工號', //工號
 892         'pager' => '性別', //性別
 893         'title' => '職位頭銜', //職位頭銜
 894         'displayName' => '顯示名', //顯示名
 895         'name' => '姓名', //姓名
 896         //'employeeNumber' => array(00000000), //一卡通號
 897         'mail' => '郵箱', //郵箱
 898         //'mailNickname' => array($rtx), //郵箱昵稱 啟用了郵箱才有這個屬性
 899         'department' => '部門', //部門
 900         'manager' => '上級', //上級
 901         'userAccountControl' => '用戶賬號策略', //用戶賬號策略 0x0200正常賬號 0x0002賬號禁用 
 902     );
 903     private function _getUserData(array $aData = array()){
 904         //id,username,cname,sex,groupid,position,mposition,utype,boss,phone,email,status,degree
 905         $rtx = trim($aData['username']);
 906         $name = trim($aData['cname']);
 907         $aNameSplit = $this->_getFirstLastName($name);
 908         $uid = intval($aData['id']);
 909         $sex = trim($aData['sex']);
 910         $pid = intval($aData['groupid']); //部門ID
 911         $jobId = intval($aData['position']);
 912         $jobId2 = intval($aData['mposition']);
 913         $boss = intval($aData['boss']);
 914         $del = intval($aData['status']);
 915         
 916         $sSuffixDn = self::$_oLdap->getOption('dnSuffix');
 917         $sTmpDc = substr($sSuffixDn,strpos($sSuffixDn,'DC=')+3);
 918         $sTmpDc = str_replace(',DC=','.',$sTmpDc);
 919         
 920         $aAttr = array(
 921             'givenName' => array($aNameSplit[1]), //
 922             'sn' => array($aNameSplit[0]), //
 923             'sAMAccountName' => array($rtx), //RTX
 924             'userPrincipalName' => array("{$rtx}@{$sTmpDc}"), //登陸名
 925             'description' => array($this->_fixUid($uid)), //工號
 926             'pager' => array($this->_fixSex($sex)), //性別
 927             'title' => array($this->_fixTitle($jobId,$jobId2)), //職位頭銜
 928             'displayName' => array($name), //顯示名
 929             'name' => array($name), //姓名
 930             //'employeeNumber' => array(00000000), //一卡通號
 931             'mail' => array($rtx.'@boyaa.com'), //郵箱
 932             //'mailNickname' => array($rtx), //郵箱昵稱 啟用了郵箱才有這個屬性
 933             'department' => array($this->_fixDepartment($pid)/*o('hr')->getGroup($pid, 'name')*/), //部門
 934             'manager' => array($this->_getUserObjectId($boss,true)), //上級
 935             'userAccountControl' => array($this->_fixUAC($del)), //用戶賬號策略 0x0200正常賬號 0x0002賬號禁用 0x10000密碼永不過期
 936         );
 937         foreach($aAttr as $k => $v){
 938             if(($v[0] === 0) || ($v[0] === '0')){
 939                 continue;
 940             }
 941             if(!$v[0]){
 942                 unset($aAttr[$k]);
 943             }
 944         }
 945         return $aAttr;
 946     }
 947     
 948     private function _getUserDataForAdd(){
 949         return array(
 950             //初始密碼
 951             'unicodePwd' => mb_convert_encoding('"'.self::$_aCfg['ldapInitPasswd'].'"', 'utf-16le'), 
 952             //設置新用戶第一次登陸更改密碼
 953             'pwdLastSet' => array(0),
 954         );
 955     }
 956     
 957     private function _fixUid($uid){
 958         if ($uid < 10000){
 959             $uid = str_pad($uid,5,'0',STR_PAD_LEFT);
 960         }
 961         return (string)$uid;
 962     }
 963     
 964     private function _fixSex($sex){
 965         $sex = strtolower($sex);
 966         $aMap = array(
 967             1 => '0', // male
 968             2 => '1', //female
 969         );
 970         return isset($aMap[$sex]) ? $aMap[$sex] : '-1';
 971     }
 972     
 973     private function _fixTitle($j0 = 0,$j1 = 0){
 974         $o = new User();
 975         $o->position = $j0;
 976         $o->mposition = $j1;
 977         $s1 = $o->defPosition;
 978         $s2 = '';
 979         if($j1){
 980             $s2 = $o->defMpositionName;
 981         }
 982         $title = '';
 983         if($s1){
 984             $title .= $s1;
 985         }
 986         if($s2){
 987             $title .= "/{$s2}";
 988         }
 989         return $title;
 990     }
 991     
 992     private function _fixUAC($status){
 993         //用戶賬號策略 0x0200正常賬號 0x0002賬號禁用
 994         //$status 1在職 2離職 0待入職
 995         $ret = '';
 996         $status = intval($status);
 997         if($status === self::STATUS_USER_NOLEAVE){
 998             $ret = 0x0200;
 999         } else {
1000             $ret = 0x0200 | 0x0002;
1001         }
1002         return (string)$ret;
1003     }
1004     
1005     private $_aDepartmentCache = array();
1006     private function _fixDepartment($id){
1007         if($id == 1){ //一級部門OU = Dept;
1008             return '';
1009         }
1010         if(isset($this->_aDepartmentCache[$id])){
1011             return $this->_aDepartmentCache[$id];
1012         }
1013         $aRet = array();
1014         $aOuList = (array)$this->_getOuList();
1015         $aTmpOu = $aOuList[$id];
1016         if (!$aTmpOu){
1017             return '';
1018         }
1019         while($aTmpOu){
1020             $aRet[] = $aTmpOu['name'];
1021             $aTmpOu = isset( $aOuList[$aTmpOu['fid']] ) ? $aOuList[$aTmpOu['fid']] : [];
1022         }
1023         $aRet = (array)array_reverse($aRet);
1024         $this->_aDepartmentCache[$id] = implode('\\',$aRet);
1025         return $this->_aDepartmentCache[$id];
1026     }
1027     
1028     /**
1029      * @desc 中國現存復姓 來自:http://wenku.baidu.com/view/0d7070bc1a37f111f1855b24.html
1030      *
1031      */
1032     private $_aCnDblFamilyName = array(
1033         '歐陽','太史','端木','上官','司馬','東方','獨孤',
1034         '南宮','萬俟','聞人','夏侯','諸葛','尉遲','公羊',
1035         '赫連','澹台','皇甫','宗政','濮陽','公冶','太叔',
1036         '申屠','公孫','慕容','仲孫','鍾離','長孫','宇文',
1037         '司徒','鮮於','司空','閭丘','子車','亓官','司寇',
1038         '巫馬','公西','顓孫','壤駟','公良','漆雕','樂正',
1039         '宰父','谷梁','拓跋','夾谷','軒轅','令狐','段干',
1040         '百里','呼延','東郭','南門','羊舌','微生','公戶',
1041         '公玉','公儀','梁丘','公仲','公上','公門','公山',
1042         '公堅','左丘','公伯','西門','公祖','第五','公乘',
1043         '貫丘','公皙','南榮','東里','東宮','仲長','子書',
1044         '子桑','即墨','達奚','褚師',
1045     );
1046     
1047     /**
1048      * @desc 分離姓和名
1049      *
1050      * @return array
1051      */
1052     private function _getFirstLastName($name = ''){
1053         $name = trim($name);
1054         if(!$name){
1055             return array('',''); //[姓,名]
1056         }
1057         if(strpos($name,' ')){
1058             $name = preg_replace('/\s+/',' ',$name);
1059             $aName = explode(' ',$name);
1060             return array($aName[1],$aName[0]);
1061         }
1062         $family = mb_substr($name,0,1,'utf-8');
1063         $given = mb_substr($name,1,mb_strlen($name,'utf-8')-1,'utf-8');
1064         if(mb_strlen($name) > 2){
1065             $tmp = mb_substr($name,0,2);
1066             if (in_array($tmp,$this->_aCnDblFamilyName)){
1067                 $family = $tmp;
1068                 $given = mb_substr($name,2);
1069             }
1070         }
1071         return array($family,$given);
1072     }
1073     
1074     public function __destruct(){
1075     }
1076     
1077 }
View Code

 查看客戶端

ApacheDirectoryStudio-win32-x86_64-2.0.0.v20130628

 

參考資料

[1] LDAPV3協議

http://tools.ietf.org/html/rfc4511

[2] LDAP百度百科

http://baike.baidu.com/view/159263.htm

[3] WIKI LDAP

http://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol

[4] LDAP Data Interchange Format

http://en.wikipedia.org/wiki/LDAP_Data_Interchange_Format

[5] OpenLDAP介紹

http://en.wikipedia.org/wiki/OpenLDAP

[6] OpenLDAP管理員文檔

[7] Zend LDAP API

http://framework.zend.com/manual/1.11/en/zend.ldap.api.html

[8] BerkeleyDb以及OPENLDAP安裝指南

http://www.openldap.org/lists/openldap-technical/201001/msg00046.html

[9] LDAP環境搭建 OpenLDAP和phpLDAPadmin -- yum版

http://www.cnblogs.com/yafei236/p/4141897.html

[10] phpldapadmin開源項目

http://phpldapadmin.sourceforge.net/wiki/index.php/Main_Page


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM