User::authenticate()   B
last analyzed

Complexity

Conditions 10
Paths 40

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
nc 40
nop 3
dl 0
loc 38
rs 7.6666
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
*
5
*  User class to work with current user, authentication etc
6
*
7
*/
8
class User extends Common_functions {
9
10
11
    /**
12
     * Current username
13
     *
14
     * @var string
15
     */
16
    public $username;
17
18
    /**
19
     * from api flag
20
     *
21
     * (default value: false)
22
     *
23
     * @var bool
24
     */
25
    public $api = false;
26
27
    /**
28
     * flag if user is authenticated
29
     *
30
     * (default value: false)
31
     *
32
     * @var bool
33
     */
34
    protected $authenticated = false;
35
36
    /**
37
     * timeout flag - is timeout reached
38
     *
39
     * (default value: false)
40
     *
41
     * @var bool
42
     */
43
    protected $timeout = false;
44
45
    /**
46
     * user details
47
     *
48
     * (default value: null)
49
     *
50
     * @var object
51
     */
52
    public $user = null;
53
54
    /**
55
     * flag if user is admin
56
     *
57
     * (default value: false)
58
     *
59
     * @var bool
60
     */
61
    protected $isadmin = false;
62
63
    /**
64
     * limit for IP block - after how many attampts user is blocked
65
     *
66
     * (default value: 5)
67
     *
68
     * @var int
69
     */
70
    public $blocklimit = 5;
71
72
    /**
73
     * authentication method id for user
74
     *
75
     * (default value: 1)
76
     *
77
     * @var int
78
     */
79
    private $authmethodid = 1;
80
81
    /**
82
     * authentication method type
83
     *
84
     * (default value: "local")
85
     *
86
     * @var string
87
     */
88
    private $authmethodtype = "local";
89
90
    /**
91
     * ldap is used flag
92
     *
93
     * (default value: false)
94
     *
95
     * @var bool
96
     */
97
    private $ldap = false;
98
99
    /**
100
     * Users IP address
101
     *
102
     * @var mixed
103
     */
104
    private $ip;
105
106
    /**
107
     * Set allowed themes
108
     *
109
     * @var array
110
     */
111
    public $themes = array("white", "dark");
112
113
    /**
114
     * (json) parameters for authentication
115
     *
116
     * @var mixed
117
     */
118
    protected $authmethodparams;
119
120
    /**
121
     *  debugging flag
122
     *
123
     * (default value: false)
124
     *
125
     * @var bool
126
     */
127
    protected $debugging = false;
128
129
    /**
130
     * Result object
131
     *
132
     * @var object
133
     */
134
    public $Result;
135
136
    /**
137
     * for Database connection
138
     *
139
     * @var mixed
140
     */
141
    protected $Database;
142
143
    /**
144
     * for Logging connection
145
     *
146
     * @var object
147
     */
148
    public $Log;
149
150
    /**
151
     * Cryptographic functions
152
     * @var Crypto
153
     */
154
    public $Crypto;
155
156
157
    /**
158
     * __construct function.
159
     *
160
     * @access public
161
     * @param Database_PDO $database
162
     * @param bool $api (default: false)
163
     */
164
    public function __construct (Database_PDO $database, $api = false) {
165
166
        # Save database object
167
        $this->Database = $database;
168
        # set api
169
        $this->api = $api;
170
        # initialize Result
171
        $this->Result = new Result ();
172
173
        # get settings
174
        $this->get_settings ();
175
176
        # Log object
177
        $this->Log = new Logging ($this->Database, $this->settings);
178
179
        # initialize Crypto
180
        $this->Crypto = new Crypto ();
181
182
        # register new session
183
        $this->register_session ();
184
        # check timeut
185
        $this->check_timeout ();
186
        # set authenticated flag
187
        $this->is_authenticated ();
188
        # get users IP address
189
        $this->block_get_ip ();
190
        # set theme
191
        $this->set_user_theme ();
192
    }
193
194
195
196
197
198
199
200
201
202
203
    /**
204
     * @session management functions
205
     * ------------------------------
206
     */
207
208
    /**
209
     * registers new session
210
     *
211
     * @access private
212
     * @return void
213
     */
214
    private function register_session () {
215
        // not for api
216
        if ($this->api !== true) {
217
            if (@$_SESSION===NULL && !isset($_SESSION)) {
218
                //set session name
219
                $this->set_session_name();
220
                //set debugging
221
                $this->set_debugging();
222
                //set default params
223
                $this->set_session_ini_params ();
224
                //register session
225
                $this->start_session ();
226
            }
227
        }
228
    }
229
230
    /**
231
     * Start session - files or use database handler
232
     * @method start_session
233
     * @return [type]
0 ignored issues
show
Documentation introduced by
The doc-type [type] could not be parsed: Unknown type name "" at position 0. [(view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
234
     */
235
    private function start_session () {
236
        // check if database should be set for sessions
237
        include( dirname(__FILE__).'/../../config.php' );
238
        // db
239
        if ($session_storage == "database") {
0 ignored issues
show
Bug introduced by
The variable $session_storage does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
240
            new Session_db ($this->Database);
241
        }
242
        // local
243
        else {
244
            session_start ();
245
        }
246
    }
247
248
    /**
249
     * destroys session
250
     *
251
     * @access public
252
     * @return void
253
     */
254
    public function destroy_session () {
255
        session_destroy();
256
    }
257
258
    /**
259
     * sets session name if specified in config file
260
     *
261
     * @access private
262
     * @return void
263
     */
264
    private function set_session_name () {
265
        include( dirname(__FILE__).'/../../config.php' );
266
        $sessname = strlen(@$phpsessname)>0 ? $phpsessname : "phpipam";
267
        // check old name
268
        $old_name = session_name();
269
        if ($sessname != $old_name) {
270
          // save
271
          session_name($sessname);
272
        }
273
    }
274
275
    /**
276
     * Default session parameters for phpipam - MAX
277
     *
278
     *  gc_maxlifetime  : time for server to keep data parameters for (at least 24 hours)
279
     *  cookie_lifetime : time for client browser to keep cookies
280
     *
281
     * @access private
282
     * @return void
283
     */
284
    private function set_session_ini_params () {
285
        if(!isset($_SESSION)) {
286
            ini_set('session.gc_maxlifetime', 86400);
287
            ini_set('session.cookie_lifetime', 86400);
288
        }
289
    }
290
291
    /**
292
     * saves parameters to session after authentication succeeds
293
     *
294
     * @access private
295
     * @return void
296
     */
297
    private function write_session_parameters () {
298
        // not for api
299
        if ($this->api !== true) {
300
            $_SESSION['ipamusername'] = $this->user->username;
301
            $_SESSION['ipamlanguage'] = $this->fetch_lang_details ();
302
            $_SESSION['lastactive']   = time();
303
            // 2fa required ?
304
            if ($this->twofa) {
0 ignored issues
show
Bug introduced by
The property twofa does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
305
                $_SESSION['2fa_required'] = true;
306
            }
307
        }
308
    }
309
310
    /**
311
     * Update users language
312
     *
313
     * @access public
314
     * @return void
315
     */
316
    public function update_session_language () {
317
        // not for api
318
        if ($this->api !== true) {
319
            # update user object
320
            $this->fetch_user_details ($this->username, true);
321
            $_SESSION['ipamlanguage'] = $this->fetch_lang_details ();
322
        }
323
    }
324
325
    /**
326
     * Checks if user is authenticated - session is set
327
     *
328
     * @access public
329
     * @return bool
330
     */
331
    public function is_authenticated () {
332
        # if checked for subpages first check if $user is array
333
        if(!is_array($this->user)) {
334
            if( strlen(@$_SESSION['ipamusername'])>0 ) {
335
                # save username
336
                $this->username = $_SESSION['ipamusername'];
337
                # check for timeout
338
                if($this->timeout === true) {
339
                    $this->authenticated = false;
340
                }
341
                else {
342
                    # fetch user profile and save it
343
                    $this->fetch_user_details ($this->username);
344
345
                    $this->authenticated = true;
346
                    $this->reset_inactivity_time();
347
                    $this->update_activity_time ();
348
                    # bind language
349
                    $this->set_ui_language();
350
                }
351
            }
352
        }
353
354
        # return
355
        return $this->authenticated;
356
    }
357
358
    /**
359
     * Check if 2fa is required for user
360
     * @method twofa_required
361
     * @return bool
362
     */
363
    public function twofa_required () {
364
        return isset($_SESSION['2fa_required']) ? true : false;
365
    }
366
367
    /**
368
     * Checks if current user is admin or not
369
     *
370
     * @access public
371
     * @param bool $die (default: true)
372
     * @return string|bool
373
     */
374
    public function is_admin ($die = true) {
375
        if($this->isadmin)      { return true; }
376
        else {
377
            if($die)            { $this->Result->show("danger", _('Administrator level privileges required'), true); }
378
            else                { return false; }
379
        }
380
    }
381
382
    /**
383
     * checks if user is authenticated, if not redirects to login page
384
     *
385
     * @access public
386
     * @param bool $redirect (default: true)
387
     * @return string|false
388
     */
389
    public function check_user_session ($redirect = true, $ignore_2fa = false) {
390
        # not authenticated
391
        if($this->authenticated===false) {
392
            # set url
393
            $url = $this->createURL();
394
395
            # error print for AJAX
396
            if(@$_SERVER['HTTP_X_REQUESTED_WITH'] == "XMLHttpRequest") {
397
                # for AJAX always check origin
398
                $this->check_referrer ();
399
                # kill session
400
                $this->destroy_session ();
401
                # error
402
                $this->Result->show("danger", _('Please login first')."!<hr><a class='btn btn-sm btn-default' href='".$url.create_link ("login")."'>"._('Login')."</a>", true, true);
403
                die();
404
            }
405
            # timeout
406
            elseif ($this->timeout) {
407
                # set redirect cookie
408
                $this->set_redirect_cookie ();
409
                # redirect
410
                if ($redirect)
411
                header("Location:".$url.create_link ("login","timeout"));
0 ignored issues
show
Security Response Splitting introduced by
'Location:' . $url . cre...ink('login', 'timeout') can contain request data and is used in response header context(s) leading to a potential security vulnerability.

4 paths for user data to reach this point

  1. Path: Fetching key HTTP_HOST from $_SERVER, and $url is assigned in functions/classes/class.Common.php on line 846
  1. Fetching key HTTP_HOST from $_SERVER, and $url is assigned
    in functions/classes/class.Common.php on line 846
  2. Common_functions::createURL() returns tainted data, and $url is assigned
    in functions/classes/class.User.php on line 393
  2. Path: Fetching key HTTP_X_FORWARDED_HOST from $_SERVER, and $url is assigned in functions/classes/class.Common.php on line 851
  1. Fetching key HTTP_X_FORWARDED_HOST from $_SERVER, and $url is assigned
    in functions/classes/class.Common.php on line 851
  2. Common_functions::createURL() returns tainted data, and $url is assigned
    in functions/classes/class.User.php on line 393
  3. Path: Fetching key HTTP_HOST from $_SERVER, and $url is assigned in functions/classes/class.Common.php on line 854
  1. Fetching key HTTP_HOST from $_SERVER, and $url is assigned
    in functions/classes/class.Common.php on line 854
  2. Common_functions::createURL() returns tainted data, and $url is assigned
    in functions/classes/class.User.php on line 393
  4. Path: Fetching key HTTP_HOST from $_SERVER, and $url is assigned in functions/classes/class.Common.php on line 866
  1. Fetching key HTTP_HOST from $_SERVER, and $url is assigned
    in functions/classes/class.Common.php on line 866
  2. Common_functions::createURL() returns tainted data, and $url is assigned
    in functions/classes/class.User.php on line 393

Response Splitting Attacks

Allowing an attacker to set a response header, opens your application to response splitting attacks; effectively allowing an attacker to send any response, he would like.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
412
                die();
413
            }
414
            else {
415
                # set redirect cookie
416
                $this->set_redirect_cookie ();
417
                # redirect
418
                if ($redirect)
419
                header("Location:".$url.create_link ("login"));
0 ignored issues
show
Security Response Splitting introduced by
'Location:' . $url . create_link('login') can contain request data and is used in response header context(s) leading to a potential security vulnerability.

4 paths for user data to reach this point

  1. Path: Fetching key HTTP_HOST from $_SERVER, and $url is assigned in functions/classes/class.Common.php on line 846
  1. Fetching key HTTP_HOST from $_SERVER, and $url is assigned
    in functions/classes/class.Common.php on line 846
  2. Common_functions::createURL() returns tainted data, and $url is assigned
    in functions/classes/class.User.php on line 393
  2. Path: Fetching key HTTP_X_FORWARDED_HOST from $_SERVER, and $url is assigned in functions/classes/class.Common.php on line 851
  1. Fetching key HTTP_X_FORWARDED_HOST from $_SERVER, and $url is assigned
    in functions/classes/class.Common.php on line 851
  2. Common_functions::createURL() returns tainted data, and $url is assigned
    in functions/classes/class.User.php on line 393
  3. Path: Fetching key HTTP_HOST from $_SERVER, and $url is assigned in functions/classes/class.Common.php on line 854
  1. Fetching key HTTP_HOST from $_SERVER, and $url is assigned
    in functions/classes/class.Common.php on line 854
  2. Common_functions::createURL() returns tainted data, and $url is assigned
    in functions/classes/class.User.php on line 393
  4. Path: Fetching key HTTP_HOST from $_SERVER, and $url is assigned in functions/classes/class.Common.php on line 866
  1. Fetching key HTTP_HOST from $_SERVER, and $url is assigned
    in functions/classes/class.Common.php on line 866
  2. Common_functions::createURL() returns tainted data, and $url is assigned
    in functions/classes/class.User.php on line 393

Response Splitting Attacks

Allowing an attacker to set a response header, opens your application to response splitting attacks; effectively allowing an attacker to send any response, he would like.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
420
                die();
421
            }
422
        }
423
        # authenticated, do we need to do 2fa ?
424
        elseif (isset($_SESSION['2fa_required']) && $ignore_2fa!==true) {
425
            header("Location:".$url.create_link ("2fa"));
0 ignored issues
show
Bug introduced by
The variable $url seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
426
            die();
427
        }
428
        else {
429
            return true;
430
        }
431
    }
432
433
    /**
434
     * Sets UI theme for user
435
     *
436
     * @method set_user_theme
437
     * @return void
438
     */
439
    private function set_user_theme () {
440
        // set defaukt theme if field is missing
441
        if(!isset($this->settings->theme)) {
442
            $this->settings->theme = "dark";
443
        }
444
        // set user
445
        if(is_object($this->user)) {
446
            // use default theme from general settings
447
            if(!isset($this->user->theme) || @$this->user->theme=="") {
448
                $this->user->ui_theme = $this->settings->theme;
449
            }
450
            else {
451
                $this->user->ui_theme = $this->user->theme;
452
            }
453
            // validate
454
            if(!in_array($this->user->ui_theme, $this->themes)) {
455
                $this->user->ui_theme = "white";
456
            }
457
        }
458
    }
459
460
    /**
461
     * Check if users timeout expired
462
     *     if yes set timeout flag
463
     *
464
     * @access private
465
     * @return void
466
     */
467
    private function check_timeout () {
468
        //session set
469
        if(isset($_SESSION['lastactive'])) {
470
            if( strlen($this->settings->inactivityTimeout)>0 && (time()-@$_SESSION['lastactive']) > $this->settings->inactivityTimeout) {
471
                $this->timeout = true;
472
                unset($_SESSION['lastactive']);
473
            }
474
        }
475
    }
476
477
    /**
478
     * resets inactivity time after each succesfull login
479
     *
480
     * @access private
481
     * @return void
482
     */
483
    private function reset_inactivity_time () {
484
        if($this->timeout!==true) {
485
            $_SESSION['lastactive'] = time();
486
        }
487
    }
488
489
    /**
490
     * Saves redirect cookie if session times out
491
     *
492
     * @access private
493
     * @return void
494
     */
495
    private function set_redirect_cookie () {
496
        # save current redirect vaule
497
        if($_SERVER['SCRIPT_URL']!="/login/" && $_SERVER['SCRIPT_URL']!="logout" && $_SERVER['SCRIPT_URL']!="?page=login" && $_SERVER['SCRIPT_URL']!="?page=logout" && $_SERVER['SCRIPT_URL']!="index.php?page=login" && $_SERVER['SCRIPT_URL']!="index.php?page=logout" && $_SERVER['SCRIPT_URL']!="/" && $_SERVER['SCRIPT_URL']!="%2f");
498
        setcookie("phpipamredirect", preg_replace('/^\/+/', '/', $_SERVER['REQUEST_URI']), time()+10, "/", null, null, true);
499
    }
500
501
    /**
502
     * Sets translation for logged in user
503
     *
504
     * @access private
505
     * @return void
506
     */
507
    private function set_ui_language () {
508
        if(strlen($_SESSION['ipamlanguage'])>0)     {
509
            putenv("LC_ALL=$_SESSION[ipamlanguage]");
510
            bindtextdomain("phpipam", dirname(__FILE__)."/../locale");    // Specify location of translation tables
511
            setlocale(LC_ALL, $_SESSION['ipamlanguage']);        // set language
512
            textdomain("phpipam");                                // Choose domain
513
        }
514
    }
515
516
    /**
517
     * Checks if system is in maintaneance mode and exits if it is
518
     *
519
     * @method check_maintaneance_mode
520
     * @param  bool    $is_popup (default: false)
521
     * @return void
522
     */
523
    public function check_maintaneance_mode ($is_popup = false) {
524
        if($this->settings->maintaneanceMode == "1" && $this->user->username!="Admin") {
525
            if($is_popup) {
526
                $this->Result->show("warning", "<i class='fa fa-info'></i> "._("System is running in maintenance mode")." !", true, true);
527
            }
528
            else {
529
                $this->Result->show("warning text-center nomargin", "<i class='fa fa-info'></i> "._("System is running in maintenance mode")." !", true);
530
            }
531
        }
532
    }
533
534
    /**
535
     * Sets maintaneance mode
536
     *
537
     * @method set_maintaneance_mode
538
     * @param  bool $on (default: false)
539
     */
540
    public function set_maintaneance_mode ($on = false) {
541
        # set mode status
542
        $maintaneance_mode = $on ? "1" : "0";
543
        # execute
544
        try { $this->Database->updateObject("settings", array("id"=>1, "maintaneanceMode"=>$maintaneance_mode), "id"); }
545
        catch (Exception $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
546
    }
547
548
    /**
549
     * Migrate resolve_subnets from config.php to database
550
     * for versions older than 1.31
551
     *
552
     * @method migrate_resolve_subnets
553
     *
554
     * @return void
555
     */
556
    public function migrate_resolve_subnets () {
557
        // read config.php
558
        include( dirname(__FILE__).'/../../config.php' );
559
        // check for array and values
560
        if(isset($config['resolve_subnets'])) {
0 ignored issues
show
Bug introduced by
The variable $config seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
561
            if(is_array($config['resolve_subnets'])) {
562
                if (sizeof($config['resolve_subnets'])>0) {
563
                    foreach ($config['resolve_subnets'] as $subnetId) {
564
                        $update = array (
565
                                         "id"         => $subnetId,
566
                                         "resolveDNS" => 1
567
                                         );
568
                        // update
569
                        try {
570
                            $this->Database->updateObject("subnets", $update);
571
                        } catch (Exception $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
572
                    }
573
                    // print that is can be deleted
574
                    $this->Result->show ("warning", '$config[resolve_subnets] '._('was migrated to database. It can be deleted from config.php'), false);
575
                }
576
            }
577
        }
578
    }
579
580
581
582
583
584
585
586
587
    /**
588
     * @miscalaneous methods
589
     * ------------------------------
590
     */
591
592
    /**
593
     * Checks AJAX loaded pages for proper origin
594
     *
595
     * @access private
596
     * @return void
597
     */
598
    private function check_referrer () {
599
        if ( ($_SERVER['HTTP_X_REQUESTED_WITH'] != "XMLHttpRequest") && ($_SERVER['HTTP_ORIGIN'] != $_SERVER['HTTP_HOST'] ) ) {
600
            # write log and die
601
            $this->Log->write ("referrer_check", _('Page not referred properly'), 0 );
602
            $this->Result->show ("danger", _('Page not referred properly'), true);
603
        }
604
    }
605
606
    /**
607
     * fetches default language
608
     *
609
     * @access public
610
     * @return object
611
     */
612
    public function get_default_lang () {
613
        try { $lang = $this->Database->findObject("lang","l_id",$this->settings->defaultLang); }
614
        catch (Exception $e) { $this->debugging ? : $this->Result->show("danger", _("Database error: ").$e->getMessage()); }
615
616
        return $lang;
617
    }
618
619
    /**
620
     * Sets available authentication methods
621
     *
622
     *    Can be extended by reading set properties from set field options
623
     *
624
     * @access public
625
     * @return array
626
     */
627
    public function fetch_available_auth_method_types () {
628
		return array("AD", "LDAP", "NetIQ", "Radius", "SAML2");
629
	}
630
631
632
633
634
635
636
637
638
639
640
    /**
641
     * @favourite methods
642
     * ------------------------------
643
     */
644
645
    /**
646
     * Fetches details for users favourite subnets
647
     *
648
     * @access public
649
     * @return array|false
650
     */
651
    public function fetch_favourite_subnets () {
652
        # none
653
        if(strlen($this->user->favourite_subnets)==0) {
654
            return false;
655
        }
656
        # ok
657
        else {
658
            # store to array
659
            $subnets = explode(";", $this->user->favourite_subnets);
660
            $subnets = array_filter($subnets);
661
662
            if(sizeof($subnets)>0) {
663
                // init
664
                $fsubnets = array();
665
                # fetch details for each subnet
666
                foreach($subnets as $id) {
667
                    $query = "select `su`.`id` as `subnetId`,`se`.`id` as `sectionId`, `subnet`, `mask`,`isFull`,`su`.`description`,`se`.`description` as `section`, `vlanId`, `isFolder`
668
                              from `subnets` as `su`, `sections` as `se` where `su`.`id` = ? and `su`.`sectionId` = `se`.`id` limit 1;";
669
670
                    try { $fsubnet = $this->Database->getObjectQuery($query, array($id)); }
671
                    catch (Exception $e) {
672
                        $this->Result->show("danger", _("Error: ").$e->getMessage());
673
                        return false;
674
                    }
675
676
                    # out array
677
                    $fsubnets[] = (array) $fsubnet;
678
                }
679
                return $fsubnets;
680
            } else {
681
                return false;
682
            }
683
        }
684
    }
685
686
    /**
687
     * Edit users favourites
688
     *
689
     * @access public
690
     * @param mixed $action
691
     * @param mixed $subnetId
692
     * @return bool
693
     */
694
    public function edit_favourite($action, $subnetId) {
695
        # execute
696
        if($action=="remove")    { return $this->remove_favourite ($subnetId); }
697
        elseif($action=="add")   { return $this->add_favourite ($subnetId); }
698
        else                     { return false; }
699
    }
700
701
    /**
702
     * Remove subnet from user favourite subnets
703
     *
704
     * @access private
705
     * @param mixed $subnetId
706
     * @return bool
707
     */
708
    private function remove_favourite ($subnetId) {
709
        # set old favourite subnets
710
        $old_favourites = explode(";", $this->user->favourite_subnets);
711
        # set new
712
        $new_favourites = implode(";", array_diff($old_favourites, array($subnetId)));
713
        # update
714
        try { $this->Database->updateObject("users", array("favourite_subnets"=>$new_favourites, "id"=>$this->user->id), "id"); }
715
        catch (Exception $e) {
716
            return false;
717
        }
718
        return true;
719
    }
720
721
    /**
722
     * Add subnet to user favourite subnets
723
     *
724
     * @access private
725
     * @param int $subnetId
726
     * @return bool
727
     */
728
    private function add_favourite ($subnetId) {
729
        # set old favourite subnets
730
        $old_favourites = explode(";", $this->user->favourite_subnets);
731
        $old_favourites = is_array($old_favourites) ? $old_favourites : array();
732
        # set new
733
        $new_favourites = implode(";",array_merge(array($subnetId), $old_favourites));
734
        # update
735
        try { $this->Database->updateObject("users", array("favourite_subnets"=>$new_favourites, "id"=>$this->user->id), "id"); }
736
        catch (Exception $e) {
737
            return false;
738
        }
739
        return true;
740
    }
741
742
    /**
743
     * Checks if subnet is in users favourite subnets
744
     *
745
     * @access public
746
     * @param int $subnetId
747
     * @return boolean
748
     */
749
    public function is_subnet_favourite ($subnetId) {
750
        $this->fetch_favourite_subnets ();
751
        # check if in array
752
        $subnets = explode(";", $this->user->favourite_subnets);
753
        $subnets = array_filter($subnets);
754
        # result
755
        return in_array($subnetId, $subnets) ? true : false;
756
    }
757
758
    /**
759
     * Checks if folder is favourite - alias for is subnet favourite
760
     *
761
     * @access public
762
     * @param mixed $subnetId
763
     * @return bool
764
     */
765
    public function is_folder_favourite ($subnetId) {
766
        return $this->is_subnet_favourite ($subnetId);
767
    }
768
769
770
771
772
773
774
775
776
777
778
779
    /**
780
    * @authentication functions
781
    * -------------------------------
782
    */
783
784
    /**
785
     * Main function for authenticating users
786
     *
787
     *    > tries to fetch user details from database by username
788
     *    > sets authentication method and checks validity
789
     *    > authenticates
790
     *
791
     * @access public
792
     * @param string $username
793
     * @param string $password
794
     * @param bool $saml
795
     * @return void
796
     */
797
    public function authenticate ($username, $password, $saml = false) {
798
        if(($saml !== false ) && (defined('MAP_SAML_USER')) && (MAP_SAML_USER !== false)) {
799
            $username = SAML_USERNAME;
800
        }
801
        # first we need to check if username exists
802
        $this->fetch_user_details ($username);
803
        # set method type if set, otherwise presume local auth
804
        $this->authmethodid = strlen(@$this->user->authMethod)>0 ? $this->user->authMethod : 1;
805
806
        # 2fa
807
        if ($this->user->{'2fa'}==1) {
808
            $this->twofa = true;
809
        }
810
811
        # get authentication method details
812
        $this->get_auth_method_type ();
813
814
        # authenticate based on name of auth method
815
        if(!method_exists($this, $this->authmethodtype))    {
816
            $this->Log->write ("User login", _('Error: Invalid authentication method'), 2 );
817
            $this->Result->show("danger", _("Error: Invalid authentication method"), true);
818
        }
819
        else {
820
            # set method name variable
821
            $authmethodtype = $this->authmethodtype;
822
            if($saml !== false) {
823
                $authmethodtype = 'auth_SAML2';
824
            }
825
            # is auth_SAML and $saml == false throw error
826
            if ($authmethodtype=="auth_SAML2" && $saml===false) {
827
                $this->Result->show("danger", "Please use <a href='".create_link('saml2')."'>login</a>!", true);
828
            }
829
            else {
830
                # authenticate
831
                $this->{$authmethodtype} ($username, $password);
832
            }
833
        }
834
    }
835
836
    /**
837
     * tries to fetch user datails from database by username if not already existing locally
838
     *
839
     * @access private
840
     * @param string $username
841
     * @param bool $force
842
     * @return void
843
     */
844
    private function fetch_user_details ($username, $force = false) {
845
        # only if not already active
846
        if(!is_object($this->user) || $force) {
847
            try { $user = $this->Database->findObject("users", "username", $username); }
848
            catch (Exception $e)     { $this->Result->show("danger", _("Error: ").$e->getMessage(), true);}
849
850
            # if not result return false
851
            $usert = (array) $user;
852
853
            # admin?
854
            if($user->role == "Administrator")    { $this->isadmin = true; }
855
856
            if(sizeof($usert)==0)    { $this->block_ip (); $this->Log->write ("User login", _('Invalid username'), 2, $username ); $this->Result->show("danger", _("Invalid username or password"), true);}
857
            else                     { $this->user = $user; }
858
859
            // register permissions
860
            $this->register_user_module_permissions ();
861
        }
862
    }
863
864
    /**
865
     * Fetch all languages from database.
866
     *
867
     * @access public
868
     * @return array
869
     */
870
    public function fetch_langs () {
871
        try { $langs = $this->Database->getObjects("lang", "l_id"); }
872
        catch (Exception $e) {
873
            $this->Result->show("danger", _("Error: ").$e->getMessage());
874
            return false;
875
        }
876
        # return
877
        return $langs;
878
    }
879
880
    /**
881
     * fetches language details from database
882
     *
883
     * @access private
884
     * @return string
885
     */
886
    private function fetch_lang_details () {
887
        // fetch from db
888
        try { $lang = $this->Database->findObject("lang", "l_id", $this->user->lang); }
889
        catch (Exception $e) {
890
            $this->Result->show("danger", _("Error: ").$e->getMessage(), true);
891
            return false;
892
        }
893
        // return code
894
        return $lang->l_code;
895
    }
896
897
    /**
898
     * Fetches name and details of authentication method (local, AD, LDAP, ...) from DB and saves them to var
899
     *
900
     * @access private
901
     * @return void
902
     */
903
    private function get_auth_method_type () {
904
        # for older versions - only local is available!
905
        if($this->settings->version=="1.1") {
906
            $this->authmethodtype = "auth_local";
907
        }
908
        else {
909
            try { $method = $this->Database->getObject("usersAuthMethod", $this->authmethodid); }
910
            catch (Exception $e) {
911
                $this->Result->show("danger", _("Error: ").$e->getMessage(), true);
912
            }
913
            # save method name if existing
914
            if($method!==false) {
915
                $this->authmethodtype   = "auth_".$method->type;
916
                $this->authmethodparams = $method->params;
917
            }
918
        }
919
    }
920
921
    /**
922
     * local user authentication method, authenticates users through local DB entry
923
     * we provide user object from DB, and username/password entered by users
924
     *
925
     * @access private
926
     * @param mixed $username
927
     * @param mixed $password
928
     * @return void
929
     */
930
    private function auth_local ($username, $password) {
931
        # auth ok
932
        if($this->user->password == crypt($password, $this->user->password)) {
933
            # save to session
934
            $this->write_session_parameters ();
935
936
            $this->Result->show("success", _("Login successful"));
937
            $this->Log->write( "User login", "User ".$this->user->real_name." logged in", 0, $username );
938
939
            # write last logintime
940
            $this->update_login_time ();
941
942
            # remove possible blocked IP
943
            $this->block_remove_entry ();
944
        }
945
        # auth failed
946
        else {
947
            # add blocked count
948
            $this->block_ip ();
949
950
            $this->Log->write( "User login", "Invalid username or password", 2, $username );
951
952
            # apache
953
            if (!empty($_SERVER['PHP_AUTH_USER']) && $this->api!==true) { $this->show_http_login(); }
954
            else                                                        { $this->Result->show("danger", _("Invalid username or password"), true); }
955
        }
956
    }
957
958
    /**
959
     * HTTP REMOTE_USER authentication, the user is already authenticated
960
     * by the web server so just create the session
961
     *
962
     * @access private
963
     * @param mixed $username
964
     * @param mixed $password
965
     * @return void
966
     */
967 View Code Duplication
    public function auth_http ($username, $password) {
0 ignored issues
show
Unused Code introduced by
The parameter $password is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
968
        # save to session
969
        $this->write_session_parameters ();
970
971
        $this->Result->show("success", _("Login successful"));
972
        $this->Log->write( "User login", "User ".$this->user->real_name." logged in", 0, $username );
973
974
        # write last logintime
975
        $this->update_login_time ();
976
977
        # remove possible blocked IP
978
        $this->block_remove_entry ();
979
    }
980
981
    /**
982
     * Shows login prompt for apache logins
983
     *
984
     * @access private
985
     * @return void
986
     */
987
    private function show_http_login () {
988
        header('WWW-Authenticate: Basic realm="phpIPAM authentication"');
989
        header('HTTP/1.0 401 Unauthorized');
990
        echo 'Authentication failed';
991
        exit;
992
    }
993
994
    /**
995
     * Connect to a directory given our auth method settings
996
     *
997
     *Connect using adLDAP
998
     *
999
     * @access private
1000
     * @param mixed $authparams
1001
     * @return adLDAP object
1002
     */
1003
    private function directory_connect ($authparams) {
1004
        # adLDAP script
1005
        require(dirname(__FILE__) . "/../adLDAP/src/adLDAP.php");
1006
        $dirparams = Array();
1007
        $dirparams['base_dn'] = @$authparams['base_dn'];
1008
        $dirparams['ad_port'] = @$authparams['ad_port'];
1009
        $dirparams['account_suffix'] = @$authparams['account_suffix'];
1010
        $dirparams['domain_controllers'] = explode(";", str_replace(" ", "", $authparams['domain_controllers']));
1011
        // set ssl and tls separate for ldap and AD
1012
        if ($this->ldap) {
1013
            // set ssl and tls
1014
            $dirparams['use_ssl'] = false;
1015
            $dirparams['use_tls'] = false;
1016
            // Support the pre-1.2 auth settings as well as the current version
1017
            // TODO: remove legacy support at some point
1018
            if ($authparams['ldap_security'] == 'tls' || $authparams['use_tls'] == 1)         { $dirparams['use_tls'] = true; }
1019
            elseif ($authparams['ldap_security'] == 'ssl' || $authparams['use_ssl'] == 1)     { $dirparams['use_ssl'] = true; }
1020
            if (isset($authparams['admin_username']) && isset($authparams['admin_password'])) {
1021
                $dirparams['admin_username'] = $authparams['adminUsername'];
1022
                $dirparams['admin_password'] = $authparams['adminPassword'];
1023
            }
1024
        }
1025
        else {
1026
            $dirparams['use_ssl'] = @$authparams['use_ssl'];
1027
            $dirparams['use_tls'] = @$authparams['use_tls'];
1028
        }
1029
        # open connection
1030
        try {
1031
            # Initialize adLDAP
1032
            $dirconn = new adLDAP($dirparams);
1033
        } catch (adLDAPException $e) {
1034
            $this->Log->write("Directory connection error", "Failed to connect: " . $e->getMessage(), 2, null);
1035
            $this->Result->show("danger", _("Error: ") . $e->getMessage(), true);
1036
        }
1037
        return $dirconn;
1038
    }
1039
1040
    /**
1041
     *    Authenticate against a directory
1042
     *
1043
     *    Authenticates users against a directory - AD or LDAP
1044
     *    Using library > adLDAP - LDAP Authentication with PHP for Active Directory
1045
     *    http://adldap.sourceforge.net
1046
     *
1047
     * @access private
1048
     * @param array $authparams
1049
     * @param string $username
1050
     * @param string $password
1051
     * @return void
1052
     */
1053
    private function directory_authenticate ($authparams, $username, $password) {
1054
        // set method
1055
        $method = $this->ldap ? "LDAP" : "AD";
1056
        // connect
1057
        $adldap = $this->directory_connect($authparams);
1058
1059
        # authenticate
1060
        try {
1061
            if ($adldap->authenticate($username, $password)) {
1062
                # save to session
1063
                $this->write_session_parameters();
1064
1065
                $this->Log->write($method . " login", "User " . $this->user->real_name . " logged in via " . $method, 0, $username);
1066
                $this->Result->show("success", _($method . " Login successful"));
1067
1068
                # write last logintime
1069
                $this->update_login_time();
1070
                # remove possible blocked IP
1071
                $this->block_remove_entry();
1072
            } # wrong user/pass by default
1073 View Code Duplication
            else {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1074
                # add blocked count
1075
                $this->block_ip();
1076
                $this->Log->write($method . " login", "User $username failed to authenticate against " . $method, 1, $username);
1077
                $this->Result->show("danger", _("Invalid username or password"), true);
1078
1079
            }
1080
        } catch (adLDAPException $e) {
1081
            $this->Log->write("Error", "Something went wrong during auth: " . $e->getMessage(), 2, $username);
1082
            $this->Result->show("danger", _("Error: ") . $e->getMessage(), true);
1083
        }
1084
    }
1085
1086
    /**
1087
     * AD (Active directory) authentication function
1088
     *
1089
     *
1090
     * @access private
1091
     * @param mixed $username
1092
     * @param mixed $password
1093
     * @return void
1094
     */
1095
    private function auth_AD ($username, $password) {
1096
        // parse settings for LDAP connection and store them to array
1097
        $authparams = json_decode($this->authmethodparams, true);
1098
        // authenticate
1099
        $this->directory_authenticate($authparams, $username, $password);
1100
    }
1101
1102
    /**
1103
     *    LDAP authentication
1104
     *    same as AD authentication, only set the LDAP flag to true
1105
     *
1106
     * @access private
1107
     * @param mixed $username
1108
     * @param mixed $password
1109
     * @return void
1110
     */
1111
    private function auth_LDAP ($username, $password) {
1112
        // parse settings for LDAP connection and store them to array
1113
        $authparams = json_decode($this->authmethodparams, true);
1114
        $this->ldap = true;                            //set ldap flag
1115
1116
        // set uid
1117
        if (!empty($authparams['uid_attr'])) { $udn = $authparams['uid_attr'] . '=' . $username; }
1118
        else                                 { $udn = 'uid=' . $username; }
1119
        // set DN
1120
        if (!empty($authparams['users_base_dn'])) { $udn = $udn . "," . $authparams['users_base_dn']; }
1121
        else                                      { $udn = $udn . "," . $authparams['base_dn']; }
1122
        // authenticate
1123
        $this->directory_authenticate($authparams, $udn, $password);
1124
    }
1125
1126
    /**
1127
     * NetIQ authentication
1128
     * same as AD authentication, only add cn= before username
1129
     *
1130
     * @access private
1131
     * @param mixed $username
1132
     * @param mixed $password
1133
     * @return void
1134
     */
1135
    private function auth_NetIQ ($username, $password) {
1136
        $this->auth_AD ("cn=".$username, $password);
1137
    }
1138
1139
    /**
1140
     * Authenticates user on radius server
1141
     *
1142
     * @access private
1143
     * @param mixed $username
1144
     * @param mixed $password
1145
     * @return void
1146
     */
1147
    private function auth_radius ($username, $password) {
1148
        # decode radius parameters
1149
        $params = json_decode($this->authmethodparams);
1150
1151
        # check for socket support !
1152
        if(!in_array("sockets", get_loaded_extensions())) {
1153
            $this->Log->write( "Radius login", "php Socket extension missing", 2 );
1154
            $this->Result->show("danger", _("php Socket extension missing"), true);
1155
        }
1156
1157
        # initialize radius class
1158
        require( dirname(__FILE__) . '/class.Radius.php' );
1159
        $Radius = new Radius ($params->hostname, $params->secret, $params->suffix, $params->timeout, $params->port);
1160
        //debugging
1161
        $this->debugging!==true ? : $Radius->SetDebugMode(TRUE);
1162
1163
        # authenticate
1164
        $auth = $Radius->AccessRequest($username, $password);
1165
        # debug?
1166
        if($this->debugging) {
1167
            print "<pre style='width:700px;margin:auto;margin-top:10px;'>";
1168
            print(implode("<br>", $Radius->debug_text));
1169
            print "</pre>";
1170
        }
1171
1172
        # authenticate user
1173
        if($auth) {
1174
            # save to session
1175
            $this->write_session_parameters ();
1176
1177
            $this->Log->write( "Radius login", "User ".$this->user->real_name." logged in via radius", 0, $username );
1178
            $this->Result->show("success", _("Radius login successful"));
1179
1180
            # write last logintime
1181
            $this->update_login_time ();
1182
            # remove possible blocked IP
1183
            $this->block_remove_entry ();
1184
        }
1185
        else {
1186
            # add blocked count
1187
            $this->block_ip ();
1188
            $this->Log->write( "Radius login", "Failed to authenticate user on radius server", 2, $username );
1189
            $this->Result->show("danger", _("Invalid username or password"), true);
1190
        }
1191
    }
1192
1193
    /**
1194
     * SAML2 auth
1195
     *
1196
     * @access private
1197
     * @param mixed $username
1198
     * @param mixed $password (default: null)
1199
     * @return void
1200
     */
1201 View Code Duplication
    private function auth_SAML2 ($username, $password = null) {
0 ignored issues
show
Unused Code introduced by
The parameter $password is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1202
        # save to session
1203
        $this->write_session_parameters ();
1204
1205
        $this->Log->write( "SAML2 login", "User ".$this->user->real_name." logged in via SAML2", 0, $username );
1206
        $this->Result->show("success", _("SAML2 login successful"));
1207
1208
        # write last logintime
1209
        $this->update_login_time ();
1210
        # remove possible blocked IP
1211
        $this->block_remove_entry ();
1212
    }
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
    /**
1224
     *    @crypt functions
1225
     *    ------------------------------
1226
     */
1227
1228
1229
    /**
1230
     *    function to crypt user pass, randomly generates salt. Use sha256 if possible, otherwise Blowfish or md5 as fallback
1231
     *
1232
     *        types:
1233
     *            CRYPT_MD5 == 1           (Salt starting with $1$, 12 characters )
1234
     *            CRYPT_BLOWFISH == 1        (Salt starting with $2a$. The two digit cost parameter: 09. 22 characters )
1235
     *            CRYPT_SHA256 == 1        (Salt starting with $5$rounds=5000$, 16 character salt.)
1236
     *            CRYPT_SHA512 == 1        (Salt starting with $6$rounds=5000$, 16 character salt.)
1237
     *
1238
     * @access public
1239
     * @param mixed $input
1240
     * @return string
1241
     */
1242
    public function crypt_user_pass ($input) {
1243
        # initialize salt
1244
        $salt = "";
1245
        # set possible salt characters in array
1246
        $salt_chars = array_merge(range('A','Z'), range('a','z'), range(0,9));
1247
        # loop to create salt
1248
        for($i=0; $i < 22; $i++) { $salt .= $salt_chars[array_rand($salt_chars)]; }
1249
        # get prefix
1250
        $prefix = $this->detect_crypt_type ();
1251
        # return crypted variable
1252
        return crypt($input, $prefix.$salt);
1253
    }
1254
1255
    /**
1256
     *    this function will detect highest crypt type to use for system
1257
     *
1258
     * @access public
1259
     * @return string
1260
     */
1261
    private function detect_crypt_type () {
1262
        if(CRYPT_SHA512 == 1)        { return '$6$rounds=3000$'; }
1263
        elseif(CRYPT_SHA256 == 1)    { return '$5$rounds=3000$'; }
1264
        elseif(CRYPT_BLOWFISH == 1)  { return '$2y$'.str_pad(rand(4,31),2,0, STR_PAD_LEFT).'$'; }
1265
        elseif(CRYPT_MD5 == 1)       { return '$5$rounds=3000$'; }
1266
        else                         { $this->Result->show("danger", _("No crypt types supported"), true); }
1267
    }
1268
1269
    /**
1270
     * Returns crypt type used to encrypt password
1271
     *
1272
     * @access public
1273
     * @return string
1274
     */
1275
    public function return_crypt_type () {
1276
        if(CRYPT_SHA512 == 1)        { return 'CRYPT_SHA512'; }
1277
        elseif(CRYPT_SHA256 == 1)    { return 'CRYPT_SHA256'; }
1278
        elseif(CRYPT_BLOWFISH == 1)  { return 'CRYPT_BLOWFISH'; }
1279
        elseif(CRYPT_MD5 == 1)       { return 'CRYPT_MD5'; }
1280
        else                         { return "No crypt types supported"; }
1281
    }
1282
1283
    /**
1284
     * Updates users password
1285
     *
1286
     * @access public
1287
     * @param mixed $password
1288
     * @return void
1289
     */
1290
    public function update_user_pass ($password) {
1291
        try { $this->Database->updateObject("users", array("password"=>$this->crypt_user_pass ($password), "passChange"=>"No", "id"=>$this->user->id), "id"); }
1292
        catch (Exception $e) { $this->Result->show("danger", $e->getMessage(), true); }
1293
1294
        $this->Result->show("success", "Hi, ".$this->user->real_name.", "._("your password was updated").". <a class='btn btn-sm btn-default' href='".create_link("dashboard")."'>Dashboard</a>", false);
1295
    }
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
    /**
1307
     *    @updating user methods
1308
     *    ------------------------------
1309
     */
1310
1311
    /**
1312
     * User self update method
1313
     *
1314
     * @access public
1315
     * @param array|object $post //posted user details
1316
     * @return bool
1317
     */
1318
    public function self_update($post) {
1319
        # remove theme
1320
        if($post['theme'] == "default") { $post['theme'] = ""; }
1321
        # set items to update
1322
        $items  = array("real_name"        => escape_input(strip_tags($post['real_name'])),
1323
                        "mailNotify"       => $post['mailNotify'] == "Yes" ? "Yes" : "No",
1324
                        "mailChangelog"    => $post['mailChangelog'] == "Yes" ? "Yes" : "No",
1325
                        "email"            => $this->validate_email($post['email']) ? escape_input($post['email']) : '',
1326
                        "lang"             => escape_input(strip_tags($post['lang'])),
1327
                        "id"               => $this->user->id,
1328
                        //display
1329
                        "compressOverride" => escape_input(strip_tags($post['compressOverride'])),
1330
                        "hideFreeRange"    => $this->verify_checkbox(@$post['hideFreeRange']),
1331
                        "menuType"         => $this->verify_checkbox(@$post['menuType']),
1332
                        "menuCompact"      => $this->verify_checkbox(@$post['menuCompact']),
1333
                        "theme"            => $post['theme'],
1334
                        "2fa"              => $this->verify_checkbox(@$post['2fa']),
1335
                        );
1336
        if(strlen($post['password1'])>0) {
1337
        $items['password'] = $this->crypt_user_pass ($post['password1']);
1338
        }
1339
1340
        # prepare log file
1341
        $log = $this->array_to_log ($post);
1342
1343
        # update
1344
        try { $this->Database->updateObject("users", $items); }
1345
        catch (Exception $e) {
1346
            $this->Result->show("danger", _("Error: ").$e->getMessage(), false);
1347
            $this->Log->write( "User self update", "User self update failed!<br>".$log, 2 );
1348
            return false;
1349
        }
1350
        # update language
1351
        $this->update_session_language ();
1352
1353
        # ok, update log table
1354
        $this->Log->write( "User self update", "User self update suceeded!", 0 );
1355
        return true;
1356
    }
1357
1358
    /**
1359
     * User self update widgets.
1360
     *
1361
     * @access public
1362
     * @param mixed $widgets
1363
     * @return bool
1364
     */
1365 View Code Duplication
    public function self_update_widgets ($widgets) {
1366
        # update
1367
        try { $this->Database->updateObject("users", array("widgets"=>$widgets, "id"=>$this->user->id)); }
1368
        catch (Exception $e) {
1369
            $this->Result->show("danger", _("Error: ").$e->getMessage(), false);
1370
            return false;
1371
        }
1372
        # ok, update log table
1373
        return true;
1374
    }
1375
1376
    /**
1377
     * Updates last users login time
1378
     *
1379
     * @access public
1380
     * @return bool
1381
     */
1382
    public function update_login_time () {
1383
        # fix for older versions
1384
        if($this->settings->version!="1.1") {
1385
            # update
1386
            try { $this->Database->updateObject("users", array("lastLogin"=>date("Y-m-d H:i:s"), "id"=>$this->user->id)); }
1387
            catch (Exception $e) {
1388
                $this->Result->show("danger", _("Error: ").$e->getMessage(), false);
1389
                return false;
1390
            }
1391
        }
1392
    }
1393
1394
    /**
1395
     * Updates last users activity time
1396
     *
1397
     * @access public
1398
     * @return void
1399
     */
1400
    public function update_activity_time () {
1401
        # update
1402
        try { $this->Database->updateObject("users", array("lastActivity"=>date("Y-m-d H:i:s"), "id"=>$this->user->id)); }
1403
        catch (Exception $e) { }
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1404
    }
1405
1406
1407
1408
1409
1410
1411
1412
1413
    /**
1414
     *    @blocking IP functions
1415
     *    ------------------------------
1416
     */
1417
1418
1419
    /**
1420
     * sets limit for failed login attempts
1421
     *
1422
     * @access public
1423
     * @param int $limit
1424
     * @return none
1425
     */
1426
    public function set_block_limit ($limit) {
1427
        $this->blocklimit = $limit;
1428
    }
1429
1430
    /**
1431
     * checks if IP is blocked and returns count for entries
1432
     *
1433
     * @access public
1434
     * @param none
1435
     * @return int|false
1436
     */
1437
    public function block_check_ip () {
1438
        # first purge
1439
        $this->purge_blocked_entries ();
1440
        $this->block_get_ip ();
1441
        # set date and query
1442
        $now = date("Y-m-d H:i:s", time() - 5*60);
1443
        $query = "select count from `loginAttempts` where `ip` = ? and `datetime` > ?;";
1444
        # fetch
1445
        try { $cnt = $this->Database->getObjectQuery($query, array($this->ip, $now)); }
1446
        catch (Exception $e) { !$this->debugging ? : $this->Result->show("danger", $e->getMessage(), false); }
1447
1448
        # verify
1449
        return @$cnt->count>0 ? $cnt->count : false;
1450
    }
1451
1452
    /**
1453
     * adds new IP to block or updates count if already present
1454
     *
1455
     * @access public
1456
     * @return bool
1457
     */
1458
    public function block_ip () {
1459
        # validate IP
1460
        if(!filter_var($this->ip, FILTER_VALIDATE_IP))    { return false; }
1461
1462
        # first check if already in
1463
        if($this->block_check_ip ())         { $this->block_update_count(); }
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->block_check_ip() of type integer|false is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1464
        # if not in add first entry
1465
        else                                 { $this->block_add_entry(); }
1466
    }
1467
1468
    /**
1469
     * sets IP address to block
1470
     * needed for proxy access to block end user not whole proxy
1471
     *
1472
     * @access private
1473
     * @return void
1474
     */
1475
    private function block_get_ip () {
1476
        # set IP
1477
        if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { $this->ip = @$_SERVER['HTTP_X_FORWARDED_FOR']; }
1478
        else                                        { $this->ip = @$_SERVER['REMOTE_ADDR']; }
1479
    }
1480
1481
    /**
1482
     * purges login attampts more than 5 minutes old (since last attempt)
1483
     *
1484
     * @access private
1485
     * @return void
1486
     */
1487
    private function purge_blocked_entries () {
1488
        # set date 5 min ago and query
1489
        $ago = date("Y-m-d H:i:s", time() - 5*60);
1490
        $query = "delete from `loginAttempts` where `datetime` < ?; ";
1491
1492
        try { $this->Database->runQuery($query, array($ago)); }
1493
        catch (Exception $e) { !$this->debugging ? : $this->Result->show("danger", $e->getMessage(), false); }
1494
    }
1495
1496
    /**
1497
     * updates existing log attampt count
1498
     *
1499
     * @access private
1500
     * @return void
1501
     */
1502
    private function block_update_count() {
1503
        # query
1504
        $query = "update `loginAttempts` set `count`=`count`+1 where `ip` = ?; ";
1505
        try { $this->Database->runQuery($query, array($this->ip)); }
1506
        catch (Exception $e) { !$this->debugging ? : $this->Result->show("danger", $e->getMessage(), false); }
1507
    }
1508
1509
    /**
1510
     * adds new IP entry to block with count 1
1511
     *
1512
     * @access private
1513
     * @return void
1514
     */
1515
    private function block_add_entry() {
1516
        try { $this->Database->insertObject("loginAttempts", array("ip"=>$this->ip, "count"=>1)); }
1517
        catch (Exception $e) { !$this->debugging ? : $this->Result->show("danger", $e->getMessage(), false); }
1518
    }
1519
1520
    /**
1521
     * removes blocked IP entry if it exists on successfull login
1522
     *
1523
     * @access private
1524
     * @return void
1525
     */
1526
    private function block_remove_entry() {
1527
        try { $this->Database->deleteRow("loginAttempts", "ip", $this->ip); }
1528
        catch (Exception $e) { !$this->debugging ? : $this->Result->show("danger", $e->getMessage(), false); }
1529
    }
1530
1531
1532
1533
	/* @users and groups -------------------- */
1534
1535
    /**
1536
     * From json {"2":"2","3":"1"}, get user list + perm
1537
     *
1538
     * @method get_user_permissions_from_json
1539
     * @param  json     $json
1540
     * @return array
1541
     */
1542
    public function get_user_permissions_from_json ($json) {
1543
        $groups = array();
1544
        foreach((array) json_decode($json, true) as $group_id => $perm) {
1545
            $group_details = $this->groups_parse (array($group_id));
1546
1547
            $tmp = array();
1548
            $tmp['group_id'] = $group_id;
1549
            $tmp['permission'] = $perm;
1550
            $tmp['name'] = $group_details[$group_id]['g_name'];
1551
            $tmp['desc'] = $group_details[$group_id]['g_desc'];
1552
            $tmp['members'] = $group_details[$group_id]['members'];
1553
1554
            $groups[] = $tmp;
1555
        }
1556
        return $groups;
1557
    }
1558
1559
	/**
1560
	 * Parse user groups
1561
	 *
1562
	 *	input:  array of group ids
1563
	 *	output: array of groups ( "id"=>array($group) )
1564
	 *
1565
     * @method groups_parse
1566
	 * @param array  $group_ids
1567
	 * @return array
1568
	 */
1569
	private function groups_parse ($group_ids) {
1570
		if(sizeof($group_ids)>0) {
1571
	    	foreach($group_ids as $g_id) {
1572
	    		// group details
1573
	    		$group = $this->fetch_object ("userGroups", "g_id", $g_id);
1574
	    		$out[$group->g_id] = (array) $group;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$out was never initialized. Although not strictly required by PHP, it is generally a good practice to add $out = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
1575
	    		$out[$group->g_id]['members'] = $this->fetch_multiple_objects("users", "groups", "%\"$g_id\"%", "real_name", true, true, array("username"));
1576
	    	}
1577
	    }
1578
	    # return array of groups
1579
	    return isset($out) ? $out : array();
1580
	}
1581
1582
    /**
1583
     * Get user l2domain access permissions
1584
     *
1585
     * Result can be the following:
1586
     *     - 0 : no access
1587
     *     - 1 : read-only
1588
     *     - 2 ; read-write
1589
     *     - 3 : admin
1590
     *
1591
     * @method get_l2domain_permissions
1592
     * @param  object $l2domain
1593
     * @return int
1594
     */
1595
    public function get_l2domain_permissions ($l2domain) {
1596
        if ($this->is_admin(false))
1597
            return 3;
1598
1599
        // Default l2domain is assigned to all sections
1600
        if ($l2domain->id == 1) {
1601
            $sections_ids = [];
1602
            $all_sections = $this->fetch_all_objects("sections");
1603
            if (is_array($all_sections)) {
1604
                foreach($all_sections as $section){
1605
                    $sections_ids[] = $section->id;
1606
                }
1607
            }
1608
            $valid_sections = implode(';', $sections_ids);
1609
        } else {
1610
            $valid_sections = $l2domain->permissions;
1611
        }
1612
1613
        $cached_item = $this->cache_check('l2domain_permissions', $valid_sections);
1614
        if(is_object($cached_item)) return $cached_item->result;
1615
1616
        if (empty($valid_sections)) {
1617
            $this->cache_write('l2domain_permissions', $valid_sections, (object)["result" => 0]);
1618
            return 0;
1619
        }
1620
1621
        $max_permission = 0;
1622
1623
        $ids = explode(";", $valid_sections);
1624
        foreach($ids as $id) {
1625
            $section = $this->fetch_object("sections", "id", $id);
1626
1627
            if (!is_object($section)) continue;
1628
1629
            # Get Section permissions
1630
            $sectionP = json_decode($section->permissions, true);
1631
1632
            # ok, user has section access, check also for any higher access from subnet
1633
            if(!is_array($sectionP)) continue;
1634
1635
            # get all user groups
1636
            $groups = json_decode($this->user->groups, true);
1637
1638 View Code Duplication
            foreach($sectionP as $sk=>$sp) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1639
                # check each group if user is in it and if so check for permissions for that group
1640
                foreach($groups as $uk=>$up) {
1641
                    if($uk == $sk) {
1642
                        if($sp > $max_permission) { $max_permission = $sp; }
1643
                    }
1644
                }
1645
            }
1646
        }
1647
1648
        # return result
1649
        $this->cache_write('l2domain_permissions', $valid_sections, (object)["result" => $max_permission]);
1650
        return $max_permission;
1651
    }
1652
1653
    /**
1654
     * Check if user has l2domain permissions for specific access level
1655
     *
1656
     * @method check_l2domain_permissions
1657
     * @param  object $l2domain
1658
     * @param  int $required_level
1659
     * @param  bool $die
1660
     * @param  bool $popup
1661
     * @return bool|void
1662
     */
1663
    public function check_l2domain_permissions($l2domain, $required_level = 1, $die = true, $popup = false) {
1664
        // check if valid
1665
        $valid = $this->get_l2domain_permissions($l2domain)>=$required_level;
1666
        // return or die ?
1667
        if ($die===true && !$valid) {
1668
            $this->Result->show ("danger", _("You do not have permissions to access this object"), true, $popup);
1669
        }
1670
        else {
1671
            return $valid;
1672
        }
1673
    }
1674
1675
    /**
1676
     * Register use module permissions from json
1677
     *
1678
     * @method register_user_module_permissions
1679
     * @return void
1680
     */
1681
    private function register_user_module_permissions () {
1682
        // decode
1683
        $permissions = json_decode($this->user->module_permissions, true);
1684
        // check for each module
1685
        foreach ($this->get_modules_with_permissions() as $m) {
1686
            if (!is_array($permissions)) {
1687
                $this->user->{'perm_'.$m} = 0;
1688
            }
1689
            elseif(array_key_exists($m, $permissions)) {
1690
                $this->user->{'perm_'.$m} = $permissions[$m];
1691
            }
1692
            else {
1693
                $this->user->{'perm_'.$m} = 0;
1694
            }
1695
        }
1696
    }
1697
1698
    /**
1699
     * Get module permissions for user
1700
     *
1701
     * Result can be the following:
1702
     *     - 0 : no access
1703
     *     - 1 : read-only
1704
     *     - 2 ; read-write
1705
     *     - 3 : admin
1706
     *
1707
     * @method get_module_permissions
1708
     * @param  string $module_name
1709
     * @return int
1710
     */
1711
    public function get_module_permissions ($module_name = "") {
1712
        if(in_array($module_name, $this->get_modules_with_permissions())) {
1713
            // admin
1714
            if($this->is_admin(false)) {
1715
                return 3;
1716
            }
1717
            else {
1718
                return $this->user->{'perm_'.$module_name};
1719
            }
1720
        }
1721
        else {
1722
            return 0;
1723
        }
1724
    }
1725
1726
    /**
1727
     * Check if user has module permissions for specific access level
1728
     *
1729
     * @method check_module_permissions
1730
     * @param  string $module_name
1731
     * @param  int $required_level
1732
     * @param  bool $die
1733
     * @param  bool $popup
1734
     * @return bool|void
1735
     */
1736
    public function check_module_permissions ($module_name = "", $required_level = 1, $die = true, $popup = false) {
1737
        // check if valid
1738
        $valid = $this->get_module_permissions($module_name)>=$required_level;
1739
        // return or die ?
1740
        if ($die===true && !$valid) {
1741
            $this->Result->show ("danger", _("You do not have permissions to access this module"), true, $popup);
1742
        }
1743
        else {
1744
            return $valid;
1745
        }
1746
    }
1747
1748
    /**
1749
     * Return array of all modules with permissions
1750
     *
1751
     * @method get_modules_with_permissions
1752
     * @return array
1753
     */
1754
    public function get_modules_with_permissions () {
1755
        return [
1756
                "vlan",
1757
                "vrf",
1758
                "pdns",
1759
                "circuits",
1760
                "racks",
1761
                "nat",
1762
                "pstn",
1763
                "customers",
1764
                "locations",
1765
                "devices",
1766
                "dhcp"
1767
            ];
1768
    }
1769
1770
    /**
1771
     * Prints permission badge
1772
     *
1773
     * @method print_permission_badge
1774
     * @param  int $level
1775
     * @return string
1776
     */
1777
    public function print_permission_badge ($level) {
1778
        // null level
1779
        if(is_null($level)) $level = 0;
1780
        // return
1781
        return $level=="0" ? "<span class='badge badge1 badge5 alert-danger'>"._($this->parse_permissions ($level))."</span>" : "<span class='badge badge1 badge5 alert-success'>"._($this->parse_permissions ($level))."</span>";
1782
    }
1783
}