1<?php
2/*
3    +-----------------------------------------------------------------------------+
4    | ILIAS open source                                                           |
5    +-----------------------------------------------------------------------------+
6    | Copyright (c) 1998-2006 ILIAS open source, University of Cologne            |
7    |                                                                             |
8    | This program is free software; you can redistribute it and/or               |
9    | modify it under the terms of the GNU General Public License                 |
10    | as published by the Free Software Foundation; either version 2              |
11    | of the License, or (at your option) any later version.                      |
12    |                                                                             |
13    | This program is distributed in the hope that it will be useful,             |
14    | but WITHOUT ANY WARRANTY; without even the implied warranty of              |
15    | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               |
16    | GNU General Public License for more details.                                |
17    |                                                                             |
18    | You should have received a copy of the GNU General Public License           |
19    | along with this program; if not, write to the Free Software                 |
20    | Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA. |
21    +-----------------------------------------------------------------------------+
22*/
23
24include_once('Services/LDAP/classes/class.ilLDAPServer.php');
25
26/**
27*
28* @author Stefan Meyer <meyer@leifos.com>
29* @version $Id$
30*
31*
32* @ingroup ServicesLDAP
33*/
34class ilLDAPRoleGroupMapping
35{
36    /**
37     * @var ilLogger
38     */
39    private $log = null;
40    private static $instance = null;
41    private $servers = null;
42    private $mappings = array();
43    private $mapping_members = array();
44    private $query = array();
45    private $active_servers = false;
46
47    /**
48     * @var array
49     */
50    private $users = [];
51
52    /**
53     * Singleton contructor
54     *
55     * @access private
56     *
57     */
58    private function __construct()
59    {
60        global $DIC;
61
62        $this->log = $DIC->logger()->auth();
63
64        $this->initServers();
65    }
66
67    /**
68     * Get singleton instance of this class
69     *
70     * @access public
71     *
72     */
73    public static function _getInstance()
74    {
75        if (is_object(self::$instance)) {
76            return self::$instance;
77        }
78        return self::$instance = new ilLDAPRoleGroupMapping();
79    }
80
81    /**
82     * Get info string for object
83     * If check info type is enabled this function will check if the info string is visible in the repository.
84     *
85     * @access public
86     * @param int object id
87     * @param bool check info type
88     *
89     */
90    public function getInfoStrings($a_obj_id, $a_check_type = false)
91    {
92        if (!$this->active_servers) {
93            return false;
94        }
95        if ($a_check_type) {
96            if (isset($this->mapping_info_strict[$a_obj_id]) and is_array($this->mapping_info_strict[$a_obj_id])) {
97                return $this->mapping_info_strict[$a_obj_id];
98            }
99        } else {
100            if (isset($this->mapping_info[$a_obj_id]) and is_array($this->mapping_info[$a_obj_id])) {
101                return $this->mapping_info[$a_obj_id];
102            }
103        }
104        return false;
105    }
106
107
108    /**
109     * This method is typically called from class RbacAdmin::assignUser()
110     * It checks if there is a role mapping and if the user has auth mode LDAP
111     * After these checks the user is assigned to the LDAP group
112     *
113     * @access public
114     * @param
115     *
116     */
117    public function assign($a_role_id, $a_usr_id)
118    {
119        // return if there nothing to do
120        if (!$this->active_servers) {
121            return false;
122        }
123
124        if (!$this->isHandledRole($a_role_id)) {
125            return false;
126        }
127        if (!$this->isHandledUser($a_usr_id)) {
128            $this->log->info('LDAP assign: User ID: ' . $a_usr_id . ' has no LDAP account');
129            return false;
130        }
131        $this->log->info('LDAP assigned: User ID: ' . $a_usr_id . ' Role Id: ' . $a_role_id);
132        $this->assignToGroup($a_role_id, $a_usr_id);
133
134        return true;
135    }
136
137    /**
138     * Delete role.
139     * This function triggered from ilRbacAdmin::deleteRole
140     * It deassigns all user from the mapped ldap group.
141     *
142     * @access public
143     * @param int role id
144     *
145     */
146    public function deleteRole($a_role_id)
147    {
148        global $DIC;
149
150        $rbacreview = $DIC['rbacreview'];
151
152        // return if there nothing to do
153        if (!$this->active_servers) {
154            return false;
155        }
156
157        if (!$this->isHandledRole($a_role_id)) {
158            return false;
159        }
160
161        foreach ($rbacreview->assignedUsers($a_role_id) as $usr_id) {
162            $this->deassign($a_role_id, $usr_id);
163        }
164        return true;
165    }
166
167
168    /**
169     * This method is typically called from class RbacAdmin::deassignUser()
170     * It checks if there is a role mapping and if the user has auth mode LDAP
171     * After these checks the user is deassigned from the LDAP group
172     *
173     * @access public
174     * @param
175     *
176     */
177    public function deassign($a_role_id, $a_usr_id)
178    {
179        // return if there notzing to do
180        if (!$this->active_servers) {
181            return false;
182        }
183        if (!$this->isHandledRole($a_role_id)) {
184            return false;
185        }
186        if (!$this->isHandledUser($a_usr_id)) {
187            return false;
188        }
189        $this->log->info('LDAP deassigned: User ID: ' . $a_usr_id . ' Role Id: ' . $a_role_id);
190        $this->deassignFromGroup($a_role_id, $a_usr_id);
191
192        return true;
193    }
194
195    /**
196     * Delete user => deassign from all ldap groups
197     *
198     * @access public
199     * @param int user id
200     */
201    public function deleteUser($a_usr_id)
202    {
203        foreach ($this->mappings as $role_id => $data) {
204            $this->deassign($role_id, $a_usr_id);
205        }
206        return true;
207    }
208
209
210    /**
211     * Check if there is any active server with
212     *
213     * @access private
214     * @param
215     *
216     */
217    private function initServers()
218    {
219        $server_ids = ilLDAPServer::_getRoleSyncServerIds();
220
221        if (!count($server_ids)) {
222            return false;
223        }
224
225        // Init servers
226        include_once('Services/LDAP/classes/class.ilLDAPRoleGroupMappingSettings.php');
227
228        $this->active_servers = true;
229        $this->mappings = array();
230        $this->users = [];
231        foreach ($server_ids as $server_id) {
232            $this->servers[$server_id] = new ilLDAPServer($server_id);
233            $this->mappings = ilLDAPRoleGroupMappingSettings::_getAllActiveMappings();
234            $this->users[$server_id] = ilObjUser::_getExternalAccountsByAuthMode(
235                'ldap_' . $server_id,
236                true
237            );
238        }
239        $this->mapping_info = array();
240        $this->mapping_info_strict = array();
241        foreach ($this->mappings as $mapping) {
242            foreach ($mapping as $key => $data) {
243                if (strlen($data['info']) and $data['object_id']) {
244                    $this->mapping_info[$data['object_id']][] = $data['info'];
245                }
246                if (strlen($data['info']) && ($data['info_type'] == ilLDAPRoleGroupMappingSettings::MAPPING_INFO_ALL)) {
247                    $this->mapping_info_strict[$data['object_id']][] = $data['info'];
248                }
249            }
250        }
251        return true;
252    }
253
254    /**
255     * Check if a role is handled or not
256     *
257     * @access private
258     * @param int role_id
259     * @return int server id or 0 if mapping exists
260     *
261     */
262    private function isHandledRole($a_role_id)
263    {
264        return array_key_exists($a_role_id, $this->mappings);
265    }
266
267    /**
268     * Check if user is ldap user
269     *
270     * @access private
271     */
272    private function isHandledUser($a_usr_id)
273    {
274        foreach ($this->users as $server_id => $users) {
275            if (array_key_exists($a_usr_id, $users)) {
276                return true;
277            }
278        }
279        return false;
280    }
281
282
283    /**
284     * Assign user to group
285     *
286     * @access private
287     * @param int role_id
288     * @param int user_id
289     */
290    private function assignToGroup($a_role_id, $a_usr_id)
291    {
292        foreach ($this->mappings[$a_role_id] as $data) {
293            try {
294                if ($data['isdn']) {
295                    $external_account = $this->readDN($a_usr_id, $data['server_id']);
296                } else {
297                    $external_account = $this->users[$data['server_id']][$a_usr_id];
298                }
299                // Forcing modAdd since Active directory is too slow and i cannot check if a user is member or not.
300                $query_obj = $this->getLDAPQueryInstance($data['server_id'], $data['url']);
301                $query_obj->modAdd($data['dn'], array($data['member'] => $external_account));
302                $this->log->info('LDAP assign: Assigned ' . $external_account . ' to group ' . $data['dn']);
303            } catch (ilLDAPQueryException $exc) {
304                $this->log->warning($exc->getMessage());
305                // try next mapping
306                continue;
307            }
308        }
309    }
310
311    /**
312     * Deassign user from group
313     *
314     * @access private
315     * @param int role_id
316     * @param int user_id
317     *
318     */
319    private function deassignFromGroup($a_role_id, $a_usr_id)
320    {
321        foreach ($this->mappings[$a_role_id] as $data) {
322            try {
323                if ($data['isdn']) {
324                    $external_account = $this->readDN($a_usr_id, $data['server_id']);
325                } else {
326                    $external_account = $this->users[$data['server_id']][$a_usr_id];
327                }
328
329                // Check for other role membership
330                if ($role_id = $this->checkOtherMembership($a_usr_id, $a_role_id, $data)) {
331                    $this->log->info('LDAP deassign: User is still assigned to role "' . $role_id . '".');
332                    continue;
333                }
334                // Deassign user
335                $query_obj = $this->getLDAPQueryInstance($data['server_id'], $data['url']);
336                $query_obj->modDelete($data['dn'], array($data['member'] => $external_account));
337                $this->log->info('LDAP deassign: Deassigned ' . $external_account . ' from group ' . $data['dn']);
338
339                // Delete from cache
340                if (is_array($this->mapping_members[$data['mapping_id']])) {
341                    $key = array_search($external_account, $this->mapping_members[$data['mapping_id']]);
342                    if ($key or $key === 0) {
343                        unset($this->mapping_members[$data['mapping_id']]);
344                    }
345                }
346            } catch (ilLDAPQueryException $exc) {
347                $this->log->warning($exc->getMessage());
348                // try next mapping
349                continue;
350            }
351        }
352    }
353
354
355    /**
356     * Check other membership
357     *
358     * @access private
359     * @return string role name
360     *
361     */
362    private function checkOtherMembership($a_usr_id, $a_role_id, $a_data)
363    {
364        global $DIC;
365
366        $rbacreview = $DIC['rbacreview'];
367        $ilObjDataCache = $DIC['ilObjDataCache'];
368
369        foreach ($this->mappings as $role_id => $tmp_data) {
370            foreach ($tmp_data as $data) {
371                if ($role_id == $a_role_id) {
372                    continue;
373                }
374                if ($data['server_id'] != $a_data['server_id']) {
375                    continue;
376                }
377                if ($data['dn'] != $a_data['dn']) {
378                    continue;
379                }
380                if ($rbacreview->isAssigned($a_usr_id, $role_id)) {
381                    return $ilObjDataCache->lookupTitle($role_id);
382                }
383            }
384        }
385        return false;
386    }
387
388    /**
389     * Store Members
390     *
391     * @access private
392     *
393     */
394    private function storeMembers($a_mapping_id, $a_data)
395    {
396        $this->mapping_members[$a_mapping_id] = array();
397        foreach ($a_data as $field => $value) {
398            if (strtolower($field) == 'dn') {
399                continue;
400            }
401
402            if (!is_array($value)) {
403                $this->mapping_members[$a_mapping_id][] = $value;
404                continue;
405            }
406            foreach ($value as $external_account) {
407                $this->mapping_members[$a_mapping_id][] = $external_account;
408            }
409        }
410        return true;
411    }
412
413    /**
414     * Read DN of user
415     *
416     * @access private
417     * @param int user id
418     * @param int server id
419     * @throws ilLDAPQueryException
420     */
421    private function readDN($a_usr_id, $a_server_id)
422    {
423        if (isset($this->user_dns[$a_usr_id])) {
424            return $this->user_dns[$a_usr_id];
425        }
426
427        $external_account = $this->users[$a_server_id][$a_usr_id];
428
429        try {
430            $server = $this->servers[$a_server_id];
431            $query_obj = $this->getLDAPQueryInstance($a_server_id, $server->getUrl());
432
433            if ($search_base = $server->getSearchBase()) {
434                $search_base .= ',';
435            }
436            $search_base .= $server->getBaseDN();
437
438            // try optional group user filter first
439            if ($server->isMembershipOptional() and $server->getGroupUserFilter()) {
440                $userFilter = $server->getGroupUserFilter();
441            } else {
442                $userFilter = $server->getFilter();
443            }
444
445            $filter = sprintf(
446                '(&(%s=%s)%s)',
447                $server->getUserAttribute(),
448                $external_account,
449                $userFilter
450            );
451
452            $res = $query_obj->query($search_base, $filter, $server->getUserScope(), array('dn'));
453
454            if (!$res->numRows()) {
455                include_once('Services/LDAP/classes/class.ilLDAPQueryException.php');
456                throw new ilLDAPQueryException(__METHOD__ . ' cannot find dn for user ' . $external_account);
457            }
458            if ($res->numRows() > 1) {
459                include_once('Services/LDAP/classes/class.ilLDAPQueryException.php');
460                throw new ilLDAPQueryException(__METHOD__ . ' found multiple distinguished name for: ' . $external_account);
461            }
462
463            $data = $res->get();
464            return $this->user_dns[$a_usr_id] = $data['dn'];
465        } catch (ilLDAPQueryException $exc) {
466            throw $exc;
467        }
468    }
469
470    /**
471     * Get LDAPQueryInstance
472     *
473     * @access private
474     * @param
475     * @throws ilLDAPQueryException
476     */
477    private function getLDAPQueryInstance($a_server_id, $a_url)
478    {
479        include_once 'Services/LDAP/classes/class.ilLDAPQuery.php';
480
481        if (array_key_exists($a_server_id, $this->query) and
482            array_key_exists($a_url, $this->query[$a_server_id]) and
483            is_object($this->query[$a_server_id][$a_url])) {
484            return $this->query[$a_server_id][$a_url];
485        }
486        try {
487            $tmp_query = new ilLDAPQuery($this->servers[$a_server_id], $a_url);
488            $tmp_query->bind(IL_LDAP_BIND_ADMIN);
489        } catch (ilLDAPQueryException $exc) {
490            throw $exc;
491        }
492        return $this->query[$a_server_id][$a_url] = $tmp_query;
493    }
494}
495