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
24/**
25* Singleton class that stores all security settings
26*
27* @author Roland Küstermann <roland@kuestermann.com>
28* @version $Id$
29*
30*
31* @ingroup Services/PrivacySecurity
32*/
33
34class ilSecuritySettings
35{
36    public static $SECURITY_SETTINGS_ERR_CODE_AUTO_HTTPS = 1;
37    public static $SECURITY_SETTINGS_ERR_CODE_HTTP_NOT_AVAILABLE = 2;
38    public static $SECURITY_SETTINGS_ERR_CODE_HTTPS_NOT_AVAILABLE = 3;
39
40    const SECURITY_SETTINGS_ERR_CODE_INVALID_PASSWORD_MIN_LENGTH = 4;
41    const SECURITY_SETTINGS_ERR_CODE_INVALID_PASSWORD_MAX_LENGTH = 5;
42    const SECURITY_SETTINGS_ERR_CODE_INVALID_PASSWORD_MAX_AGE = 6;
43    const SECURITY_SETTINGS_ERR_CODE_INVALID_LOGIN_MAX_ATTEMPTS = 7;
44    const SECURITY_SETTINGS_ERR_CODE_PASSWORD_MIN_LENGTH_MIN1 = 11;
45    const SECURITY_SETTINGS_ERR_CODE_PASSWORD_MIN_LENGTH_MIN2 = 8;
46    const SECURITY_SETTINGS_ERR_CODE_PASSWORD_MIN_LENGTH_MIN3 = 9;
47    const SECURITY_SETTINGS_ERR_CODE_PASSWORD_MAX_LENGTH_LESS_MIN_LENGTH = 10;
48
49    private static $instance = null;
50    private $db;
51    private $settings;
52
53    private $https_enable;
54
55    const DEFAULT_PASSWORD_CHARS_AND_NUMBERS_ENABLED = true;
56    const DEFAULT_PASSWORD_SPECIAL_CHARS_ENABLED = false;
57    const DEFAULT_PASSWORD_MIN_LENGTH = 8;
58    const DEFAULT_PASSWORD_MAX_LENGTH = 0;
59    const DEFAULT_PASSWORD_MAX_AGE = 90;
60    const DEFAULT_LOGIN_MAX_ATTEMPTS = 5;
61
62    const DEFAULT_PASSWORD_CHANGE_ON_FIRST_LOGIN_ENABLED = false;
63    const DEFAULT_PREVENT_SIMULTANEOUS_LOGINS = false;
64
65    private $password_chars_and_numbers_enabled = self::DEFAULT_PASSWORD_CHARS_AND_NUMBERS_ENABLED;
66    private $password_special_chars_enabled = self::DEFAULT_PASSWORD_SPECIAL_CHARS_ENABLED;
67    private $password_min_length = self::DEFAULT_PASSWORD_MIN_LENGTH;
68    private $password_max_length = self::DEFAULT_PASSWORD_MAX_LENGTH;
69    private $password_max_age = self::DEFAULT_PASSWORD_MAX_AGE;
70    private $password_ucase_chars_num = 0;
71    private $password_lcase_chars_num = 0;
72    private $login_max_attempts = self::DEFAULT_LOGIN_MAX_ATTEMPTS;
73    private $password_must_not_contain_loginname = false;
74
75    private $password_change_on_first_login_enabled = self::DEFAULT_PASSWORD_CHANGE_ON_FIRST_LOGIN_ENABLED;
76    private $prevent_simultaneous_logins = self::DEFAULT_PREVENT_SIMULTANEOUS_LOGINS;
77
78    private $protect_admin_role = false;
79
80    /**
81     * Private constructor: use _getInstance()
82     *
83     * @access private
84     * @param
85     *
86     */
87    private function __construct()
88    {
89        global $DIC;
90
91        $ilSetting = $DIC['ilSetting'];
92        $ilDB = $DIC['ilDB'];
93
94        $this->db = $ilDB;
95        $this->settings = $ilSetting;
96
97        $this->read();
98    }
99
100    /**
101     * Get instance of ilSecuritySettings
102     *
103     * @return ilSecuritySettings  instance
104     * @access public
105     *
106     */
107    public static function _getInstance()
108    {
109        if (is_object(self::$instance)) {
110            return self::$instance;
111        }
112        return self::$instance = new ilSecuritySettings();
113    }
114
115    public function getSecuritySettingsRefId()
116    {
117        return $this->ref_id;
118    }
119
120    /**
121     * set if the passwords have to contain
122     * characters and numbers
123     *
124     * @param boolean $a_chars_and_numbers_enabled
125     *
126     */
127    public function setPasswordCharsAndNumbersEnabled($a_chars_and_numbers_enabled)
128    {
129        $this->password_chars_and_numbers_enabled = $a_chars_and_numbers_enabled;
130    }
131
132    /**
133     * get boolean if the passwords have to contain
134     * characters and numbers
135     *
136     * @return boolean	characters and numbers enabled
137     *
138     */
139    public function isPasswordCharsAndNumbersEnabled()
140    {
141        return $this->password_chars_and_numbers_enabled;
142    }
143
144    /**
145     * set if the passwords have to contain
146     * special characters
147     *
148     * @param boolean $a_password_special_chars_enabled
149     *
150     */
151    public function setPasswordSpecialCharsEnabled($a_password_special_chars_enabled)
152    {
153        $this->password_special_chars_enabled = $a_password_special_chars_enabled;
154    }
155
156    /**
157     * get boolean if the passwords have to contain
158     * special characters
159     *
160     * @return boolean	password special chars enabled
161     *
162     */
163    public function isPasswordSpecialCharsEnabled()
164    {
165        return $this->password_special_chars_enabled;
166    }
167
168    /**
169     * set the minimum length for passwords
170     *
171     * @param integer $a_password_min_length
172     */
173    public function setPasswordMinLength($a_password_min_length)
174    {
175        $this->password_min_length = $a_password_min_length;
176    }
177
178    /**
179     * get the minimum length for passwords
180     *
181     * @return integer  password min length
182     */
183    public function getPasswordMinLength()
184    {
185        return $this->password_min_length;
186    }
187
188    /**
189     * set the maximum length for passwords
190     *
191     * @param integer $a_password_max_length
192     */
193    public function setPasswordMaxLength($a_password_max_length)
194    {
195        $this->password_max_length = $a_password_max_length;
196    }
197
198    /**
199     * get the maximum length for passwords
200     *
201     * @return integer  password max length
202     */
203    public function getPasswordMaxLength()
204    {
205        return $this->password_max_length;
206    }
207
208    /**
209     * set the maximum password age
210     *
211     * @param integer $a_password_max_age
212     */
213    public function setPasswordMaxAge($a_password_max_age)
214    {
215        $this->password_max_age = $a_password_max_age;
216    }
217
218    /**
219     * get the maximum password age
220     *
221     * @return integer  password max age
222     */
223    public function getPasswordMaxAge()
224    {
225        return $this->password_max_age;
226    }
227
228    /**
229     * set the maximum count of login attempts
230     *
231     * @param integer $a_login_max_attempts
232     */
233    public function setLoginMaxAttempts($a_login_max_attempts)
234    {
235        $this->login_max_attempts = $a_login_max_attempts;
236    }
237
238    /**
239     * get the maximum count of login attempts
240     *
241     * @return integer  password max login attempts
242     */
243    public function getLoginMaxAttempts()
244    {
245        return $this->login_max_attempts;
246    }
247
248    /**
249     * Enable https for certain scripts
250     *
251     * @param boolean $value
252     */
253    public function setHTTPSEnabled($value)
254    {
255        $this->https_enable = $value;
256    }
257
258    /**
259     * read access to https enabled property
260     *
261     * @return boolean  true, if enabled, false otherwise
262     */
263    public function isHTTPSEnabled()
264    {
265        return $this->https_enable;
266    }
267
268    /**
269     * set if the passwords have to be changed by users
270     * on first login
271     *
272     * @param boolean $a_password_change_on_first_login_enabled
273     *
274     */
275    public function setPasswordChangeOnFirstLoginEnabled($a_password_change_on_first_login_enabled)
276    {
277        $this->password_change_on_first_login_enabled = $a_password_change_on_first_login_enabled;
278    }
279
280    /**
281     * get boolean if the passwords have to be changed by users
282     * on first login
283     *
284     * @return boolean	password change on first login enabled
285     *
286     */
287    public function isPasswordChangeOnFirstLoginEnabled()
288    {
289        return $this->password_change_on_first_login_enabled;
290    }
291
292    /**
293     * Check if admin role is protected
294     * @return type
295     */
296    public function isAdminRoleProtected()
297    {
298        return (bool) $this->protect_admin_role;
299    }
300
301    /**
302     * Set admin role protection status
303     * @param type $a_stat
304     */
305    public function protectedAdminRole($a_stat)
306    {
307        $this->protect_admin_role = $a_stat;
308    }
309
310    /**
311     * Check if the administrator role is accessible for a specific user
312     * @param int $a_usr_id
313     */
314    public function checkAdminRoleAccessible($a_usr_id)
315    {
316        global $DIC;
317
318        $rbacreview = $DIC['rbacreview'];
319
320        if (!$this->isAdminRoleProtected()) {
321            return true;
322        }
323        if ($rbacreview->isAssigned($a_usr_id, SYSTEM_ROLE_ID)) {
324            return true;
325        }
326        return false;
327    }
328
329    /**
330     * Save settings
331     *
332     *
333     */
334    public function save()
335    {
336        $this->settings->set('https', (int) $this->isHTTPSEnabled());
337
338        $this->settings->set('ps_password_chars_and_numbers_enabled', (bool) $this->isPasswordCharsAndNumbersEnabled());
339        $this->settings->set('ps_password_special_chars_enabled', (bool) $this->isPasswordSpecialCharsEnabled());
340        $this->settings->set('ps_password_min_length', (int) $this->getPasswordMinLength());
341        $this->settings->set('ps_password_max_length', (int) $this->getPasswordMaxLength());
342        $this->settings->set('ps_password_max_age', (int) $this->getPasswordMaxAge());
343        $this->settings->set('ps_login_max_attempts', (int) $this->getLoginMaxAttempts());
344        $this->settings->set('ps_password_uppercase_chars_num', (int) $this->getPasswordNumberOfUppercaseChars());
345        $this->settings->set('ps_password_lowercase_chars_num', (int) $this->getPasswordNumberOfLowercaseChars());
346        $this->settings->set('ps_password_must_not_contain_loginame', (int) $this->getPasswordMustNotContainLoginnameStatus());
347
348        $this->settings->set('ps_password_change_on_first_login_enabled', (bool) $this->isPasswordChangeOnFirstLoginEnabled());
349        $this->settings->set('ps_prevent_simultaneous_logins', (int) $this->isPreventionOfSimultaneousLoginsEnabled());
350        $this->settings->set('ps_protect_admin', (int) $this->isAdminRoleProtected());
351    }
352    /**
353     * read settings
354     *
355     * @access private
356     * @param
357     *
358     */
359    private function read()
360    {
361        global $DIC;
362
363        $ilDB = $DIC['ilDB'];
364
365        $query = "SELECT object_reference.ref_id FROM object_reference,tree,object_data " .
366                "WHERE tree.parent = " . $ilDB->quote(SYSTEM_FOLDER_ID, 'integer') . " " .
367                "AND object_data.type = 'ps' " .
368                "AND object_reference.ref_id = tree.child " .
369                "AND object_reference.obj_id = object_data.obj_id";
370        $res = $this->db->query($query);
371        $row = $res->fetchRow(ilDBConstants::FETCHMODE_ASSOC);
372        $this->ref_id = $row["ref_id"];
373
374        $this->https_enable = (boolean) $this->settings->get('https', false);
375
376        $this->password_chars_and_numbers_enabled = (bool) $this->settings->get('ps_password_chars_and_numbers_enabled', self::DEFAULT_PASSWORD_CHARS_AND_NUMBERS_ENABLED);
377        $this->password_special_chars_enabled = (bool) $this->settings->get('ps_password_special_chars_enabled', self::DEFAULT_PASSWORD_SPECIAL_CHARS_ENABLED);
378        $this->password_min_length = (int) $this->settings->get('ps_password_min_length', self::DEFAULT_PASSWORD_MIN_LENGTH);
379        $this->password_max_length = (int) $this->settings->get('ps_password_max_length', self::DEFAULT_PASSWORD_MAX_LENGTH);
380        $this->password_max_age = (int) $this->settings->get('ps_password_max_age', self::DEFAULT_PASSWORD_MAX_AGE);
381        $this->login_max_attempts = (int) $this->settings->get('ps_login_max_attempts', self::DEFAULT_LOGIN_MAX_ATTEMPTS);
382        $this->password_ucase_chars_num = (int) $this->settings->get('ps_password_uppercase_chars_num', 0);
383        $this->password_lcase_chars_num = (int) $this->settings->get('ps_password_lowercase_chars_num', 0);
384        $this->password_must_not_contain_loginname = $this->settings->get('ps_password_must_not_contain_loginame', 0) == '1' ? true : false;
385
386        $this->password_change_on_first_login_enabled = (bool) $this->settings->get('ps_password_change_on_first_login_enabled', self::DEFAULT_PASSWORD_CHANGE_ON_FIRST_LOGIN_ENABLED);
387        $this->prevent_simultaneous_logins = (bool) $this->settings->get('ps_prevent_simultaneous_logins', self::DEFAULT_PREVENT_SIMULTANEOUS_LOGINS);
388
389        $this->protect_admin_role = (bool) $this->settings->get('ps_protect_admin', $this->protect_admin_role);
390    }
391
392    /**
393     * validate settings
394     *
395     * @return 0, if everything is ok, an error code otherwise
396     */
397    public function validate(ilPropertyFormGUI $a_form = null)
398    {
399        $code = null;
400
401        if ($a_form) {
402            include_once "Services/PrivacySecurity/classes/class.ilObjPrivacySecurityGUI.php";
403        }
404
405        include_once './Services/Http/classes/class.ilHTTPS.php';
406
407        if ($this->isHTTPSEnabled()) {
408            if (!ilHTTPS::_checkHTTPS()) {
409                $code = ilSecuritySettings::$SECURITY_SETTINGS_ERR_CODE_HTTPS_NOT_AVAILABLE;
410                if (!$a_form) {
411                    return $code;
412                } else {
413                    $a_form->getItemByPostVar('https_enabled')
414                           ->setAlert(ilObjPrivacySecurityGUI::getErrorMessage($code));
415                }
416            }
417        }
418
419        if ($this->getPasswordMinLength() < 0) {
420            $code = self::SECURITY_SETTINGS_ERR_CODE_INVALID_PASSWORD_MIN_LENGTH;
421            if (!$a_form) {
422                return $code;
423            } else {
424                $a_form->getItemByPostVar('password_min_length')
425                        ->setAlert(ilObjPrivacySecurityGUI::getErrorMessage($code));
426            }
427        }
428
429        if ($this->getPasswordMaxLength() < 0) {
430            $code = self::SECURITY_SETTINGS_ERR_CODE_INVALID_PASSWORD_MAX_LENGTH;
431            if (!$a_form) {
432                return $code;
433            } else {
434                $a_form->getItemByPostVar('password_max_length')
435                        ->setAlert(ilObjPrivacySecurityGUI::getErrorMessage($code));
436            }
437        }
438
439        $password_min_length = 1;
440
441        if ($this->getPasswordNumberOfUppercaseChars() > 0 || $this->getPasswordNumberOfLowercaseChars() > 0) {
442            $password_min_length = 0;
443            if ($this->getPasswordNumberOfUppercaseChars() > 0) {
444                $password_min_length += $this->getPasswordNumberOfUppercaseChars();
445            }
446            if ($this->getPasswordNumberOfLowercaseChars() > 0) {
447                $password_min_length += $this->getPasswordNumberOfLowercaseChars();
448            }
449            $password_min_length_error_code = self::SECURITY_SETTINGS_ERR_CODE_PASSWORD_MIN_LENGTH_MIN1;
450        }
451
452        if ($this->isPasswordCharsAndNumbersEnabled()) {
453            $password_min_length++;
454            $password_min_length_error_code = self::SECURITY_SETTINGS_ERR_CODE_PASSWORD_MIN_LENGTH_MIN2;
455
456            if ($this->isPasswordSpecialCharsEnabled()) {
457                $password_min_length++;
458                $password_min_length_error_code = self::SECURITY_SETTINGS_ERR_CODE_PASSWORD_MIN_LENGTH_MIN3;
459            }
460        } elseif ($password_min_length > 1 && $this->isPasswordSpecialCharsEnabled()) {
461            $password_min_length++;
462            $password_min_length_error_code = self::SECURITY_SETTINGS_ERR_CODE_PASSWORD_MIN_LENGTH_MIN3;
463        }
464
465        if ($this->getPasswordMinLength() > 0 && $this->getPasswordMinLength() < $password_min_length) {
466            $code = $password_min_length_error_code;
467            if (!$a_form) {
468                return $code;
469            } else {
470                $a_form->getItemByPostVar('password_min_length')
471                        ->setAlert(sprintf(ilObjPrivacySecurityGUI::getErrorMessage($code), $password_min_length));
472            }
473        }
474        if ($this->getPasswordMaxLength() > 0 && $this->getPasswordMaxLength() < $this->getPasswordMinLength()) {
475            $code = self::SECURITY_SETTINGS_ERR_CODE_PASSWORD_MAX_LENGTH_LESS_MIN_LENGTH;
476            if (!$a_form) {
477                return $code;
478            } else {
479                $a_form->getItemByPostVar('password_max_length')
480                        ->setAlert(ilObjPrivacySecurityGUI::getErrorMessage($code));
481            }
482        }
483
484        if ($this->getPasswordMaxAge() < 0) {
485            $code = self::SECURITY_SETTINGS_ERR_CODE_INVALID_PASSWORD_MAX_AGE;
486            if (!$a_form) {
487                return $code;
488            } else {
489                $a_form->getItemByPostVar('password_max_age')
490                        ->setAlert(ilObjPrivacySecurityGUI::getErrorMessage($code));
491            }
492        }
493
494        if ($this->getLoginMaxAttempts() < 0) {
495            $code = self::SECURITY_SETTINGS_ERR_CODE_INVALID_LOGIN_MAX_ATTEMPTS;
496            if (!$a_form) {
497                return $code;
498            } else {
499                $a_form->getItemByPostVar('login_max_attempts')
500                        ->setAlert(ilObjPrivacySecurityGUI::getErrorMessage($code));
501            }
502        }
503
504        /*
505         * todo: have to check for local auth if first login password change is enabled??
506         * than: add errorcode
507         */
508
509        if (!$a_form) {
510            return 0;
511        } else {
512            return !(bool) $code;
513        }
514    }
515
516    /**
517     * Prevention of simultaneous logins with the same account
518     *
519     * @return boolean  true, if prevention of simultaneous logins with the same account is enabled, false otherwise
520     */
521    public function isPreventionOfSimultaneousLoginsEnabled()
522    {
523        return (bool) $this->prevent_simultaneous_logins;
524    }
525
526    /**
527     * Enable/Disable prevention of simultaneous logins with the same account
528     *
529     * @param boolean $value
530     */
531    public function setPreventionOfSimultaneousLogins($value)
532    {
533        $this->prevent_simultaneous_logins = (bool) $value;
534    }
535
536    /**
537     * Set number of uppercase characters required
538     * @param    integer
539     */
540    public function setPasswordNumberOfUppercaseChars($password_ucase_chars_num)
541    {
542        $this->password_ucase_chars_num = $password_ucase_chars_num;
543    }
544
545    /**
546     * Returns number of uppercase characters required
547     * @return    integer
548     */
549    public function getPasswordNumberOfUppercaseChars()
550    {
551        return $this->password_ucase_chars_num;
552    }
553
554    /**
555     * Set number of lowercase characters required
556     * @param    integer
557     */
558    public function setPasswordNumberOfLowercaseChars($password_lcase_chars_num)
559    {
560        $this->password_lcase_chars_num = $password_lcase_chars_num;
561    }
562
563    /**
564     * Returns number of lowercase characters required
565     * @return    integer
566     */
567    public function getPasswordNumberOfLowercaseChars()
568    {
569        return $this->password_lcase_chars_num;
570    }
571
572    /**
573     * Set whether the password must not contain the loginname or not
574     * @param	boolean
575     */
576    public function setPasswordMustNotContainLoginnameStatus($status)
577    {
578        $this->password_must_not_contain_loginname = $status;
579    }
580
581    /**
582     * Return whether the password must not contain the loginname or not
583     * @param	boolean
584     */
585    public function getPasswordMustNotContainLoginnameStatus()
586    {
587        return $this->password_must_not_contain_loginname;
588    }
589}
590