Procházet zdrojové kódy

Add API, auth logging, AD support

TODO: Test changing AD passwords
tags/1.0beta
Skylar Ittner před 2 roky
rodič
revize
5929d13147
15 změnil soubory, kde provedl 1112 přidání a 156 odebrání
  1. 31
    4
      action.php
  2. 119
    0
      api.php
  3. 3
    1
      composer.json
  4. 486
    1
      composer.lock
  5. binární
      database.mwb
  6. 4
    3
      home.php
  7. 44
    28
      index.php
  8. 5
    0
      lang/en_us.php
  9. 8
    0
      lang/messages.php
  10. 298
    9
      lib/login.php
  11. 1
    1
      nbproject/project.xml
  12. 2
    103
      required.php
  13. 33
    6
      settings.template.php
  14. 78
    0
      static/img/logo.svg
  15. binární
      static/img/logo_512.png

+ 31
- 4
action.php Zobrazit soubor

@@ -3,10 +3,14 @@
3 3
 /**
4 4
  * Make things happen when buttons are pressed and forms submitted.
5 5
  */
6
+use LdapTools\LdapManager;
7
+use LdapTools\Object\LdapObjectType;
8
+
6 9
 require_once __DIR__ . "/required.php";
7 10
 
8 11
 dieifnotloggedin();
9 12
 
13
+require_once __DIR__ . "/lib/login.php";
10 14
 require_once __DIR__ . "/lib/worst_passwords.php";
11 15
 
12 16
 function returnToSender($msg, $arg = "") {
@@ -21,12 +25,12 @@ function returnToSender($msg, $arg = "") {
21 25
 
22 26
 switch ($VARS['action']) {
23 27
     case "signout":
28
+        insertAuthLog(11, $_SESSION['uid']);
24 29
         session_destroy();
25 30
         header('Location: index.php');
26 31
         die("Logged out.");
27 32
     case "chpasswd":
28
-        $oldmatch = comparePassword($VARS['oldpass'], $database->select('accounts', 'password', ['uid' => $_SESSION['uid']])[0]);
29
-        if ($oldmatch) {
33
+        if ($_SESSION['password'] == $VARS['oldpass']) {
30 34
             if ($VARS['newpass'] == $VARS['conpass']) {
31 35
                 $passrank = checkWorst500List($VARS['newpass']);
32 36
                 if ($passrank !== FALSE) {
@@ -35,8 +39,29 @@ switch ($VARS['action']) {
35 39
                 if (strlen($VARS['newpass']) < MIN_PASSWORD_LENGTH) {
36 40
                     returnToSender("weak_password");
37 41
                 }
38
-                $database->update('accounts', ['password' => encryptPassword($VARS['newpass'])], ['uid' => $_SESSION['uid']]);
39
-                returnToSender("password_updated");
42
+
43
+                $acctloc = account_location($_SESSION['username'], $_SESSION['password']);
44
+
45
+                if ($acctloc == "LOCAL") {
46
+                    $database->update('accounts', ['password' => encryptPassword($VARS['newpass'])], ['uid' => $_SESSION['uid']]);
47
+                    $_SESSION['password'] = $VARS['newpass'];
48
+                    insertAuthLog(3, $_SESSION['uid']);
49
+                    returnToSender("password_updated");
50
+                } else if ($acctloc == "LDAP") {
51
+                    $ldapManager = new LdapManager($ldap_config);
52
+                    $repository = $ldapManager->getRepository(LdapObjectType::USER);
53
+                    $user = $repository->findOneByUsername($_SESSION['username']);
54
+                    $user->setPassword($VARS['newpass']);
55
+                    try {
56
+                        $ldapManager->persist($user);
57
+                        insertAuthLog(3, $_SESSION['uid']);
58
+                        returnToSender("password_updated");
59
+                    } catch (\Exception $e) {
60
+                        returnToSender("ldap_error", $e->getMessage());
61
+                    }
62
+                } else {
63
+                    returnToSender("account_state_error");
64
+                }
40 65
             } else {
41 66
                 returnToSender("new_password_mismatch");
42 67
             }
@@ -49,9 +74,11 @@ switch ($VARS['action']) {
49 74
             returnToSender("invalid_parameters");
50 75
         }
51 76
         $database->update('accounts', ['authsecret' => $VARS['secret']], ['uid' => $_SESSION['uid']]);
77
+        insertAuthLog(9, $_SESSION['uid']);
52 78
         returnToSender("2fa_enabled");
53 79
     case "rm2fa":
54 80
         $database->update('accounts', ['authsecret' => ""], ['uid' => $_SESSION['uid']]);
81
+        insertAuthLog(10, $_SESSION['uid']);
55 82
         returnToSender("2fa_removed");
56 83
         break;
57 84
 }

+ 119
- 0
api.php Zobrazit soubor

@@ -0,0 +1,119 @@
1
+<?php
2
+
3
+/**
4
+ * Simple JSON API to allow other apps to access accounts in this system.
5
+ * 
6
+ * Requests can be sent via either GET or POST requests.  POST is recommended
7
+ * as it has a lower chance of being logged on the server, exposing unencrypted
8
+ * user passwords.
9
+ */
10
+require __DIR__ . '/required.php';
11
+require_once __DIR__ . '/lib/login.php';
12
+header("Content-Type: application/json");
13
+
14
+//try {
15
+$key = $VARS['key'];
16
+if ($database->has('apikeys', ['key' => $key]) !== TRUE) {
17
+    header("HTTP/1.1 403 Unauthorized");
18
+    die("\"403 Unauthorized\"");
19
+}
20
+
21
+switch ($VARS['action']) {
22
+    case "ping":
23
+        exit(json_encode(["status" => "OK"]));
24
+        break;
25
+    case "auth":
26
+        if (authenticate_user($VARS['username'], $VARS['password'])) {
27
+            insertAuthLog(12);
28
+            exit(json_encode(["status" => "OK", "msg" => lang("login successful", false)]));
29
+        } else {
30
+            insertAuthLog(13);
31
+            exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
32
+        }
33
+        break;
34
+    case "userinfo":
35
+        if (user_exists($VARS['username'])) {
36
+            $data = $database->select("accounts", ["uid", "realname (name)", "email", "phone" => ["phone1 (1)", "phone2 (2)"]], ["username" => $VARS['username']])[0];
37
+            exit(json_encode(["status" => "OK", "data" => $data]));
38
+        } else {
39
+            exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
40
+        }
41
+        break;
42
+    case "userexists":
43
+        if (user_exists($VARS['username'])) {
44
+            exit(json_encode(["status" => "OK", "exists" => true]));
45
+        } else {
46
+            exit(json_encode(["status" => "OK", "exists" => false]));
47
+        }
48
+        break;
49
+    case "hastotp":
50
+        if (userHasTOTP($VARS['username'])) {
51
+            exit(json_encode(["status" => "OK", "otp" => true]));
52
+        } else {
53
+            exit(json_encode(["status" => "OK", "otp" => false]));
54
+        }
55
+        break;
56
+    case "verifytotp":
57
+        if (verifyTOTP($VARS['username'], $VARS['code'])) {
58
+            exit(json_encode(["status" => "OK", "valid" => true]));
59
+        } else {
60
+            insertAuthLog(7);
61
+            exit(json_encode(["status" => "ERROR", "msg" => lang("2fa incorrect", false), "valid" => false]));
62
+        }
63
+        break;
64
+    case "acctstatus":
65
+        exit(json_encode(["status" => "OK", "account" => get_account_status($VARS['username'])]));
66
+    case "login":
67
+        // simulate a login, checking account status and alerts
68
+        if (authenticate_user($VARS['username'], $VARS['password'])) {
69
+            switch (get_account_status($VARS['username'])) {
70
+                case "LOCKED_OR_DISABLED":
71
+                    insertAuthLog(5);
72
+                    exit(json_encode(["status" => "ERROR", "msg" => lang("account locked", false)]));
73
+                case "TERMINATED":
74
+                    insertAuthLog(5);
75
+                    exit(json_encode(["status" => "ERROR", "msg" => lang("account terminated", false)]));
76
+                case "CHANGE_PASSWORD":
77
+                    insertAuthLog(5);
78
+                    exit(json_encode(["status" => "ERROR", "msg" => lang("password expired", false)]));
79
+                case "NORMAL":
80
+                    insertAuthLog(4);
81
+                    exit(json_encode(["status" => "OK"]));
82
+                case "ALERT_ON_ACCESS":
83
+                    sendLoginAlertEmail($VARS['username']);
84
+                    insertAuthLog(4);
85
+                    exit(json_encode(["status" => "OK", "alert" => true]));
86
+                default:
87
+                    insertAuthLog(5);
88
+                    exit(json_encode(["status" => "ERROR", "msg" => lang("account state error", false)]));
89
+            }
90
+        } else {
91
+            insertAuthLog(5);
92
+            exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
93
+        }
94
+        break;
95
+    case "ismanagerof":
96
+        if (user_exists($VARS['manager'])) {
97
+            if (user_exists($VARS['employee'])) {
98
+                $managerid = $database->select('accounts', 'uid', ['username' => $VARS['manager']]);
99
+                $employeeid = $database->select('accounts', 'uid', ['username' => $VARS['employee']]);
100
+                if ($database->has('managers', ['AND' => ['managerid' => $managerid, 'employeeid' => $employeeid]])) {
101
+                    exit(json_encode(["status" => "OK", "managerof" => true]));
102
+                } else {
103
+                    exit(json_encode(["status" => "OK", "managerof" => false]));
104
+                }
105
+            } else {
106
+                exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false), "user" => $VARS['employee']]));
107
+            }
108
+        } else {
109
+            exit(json_encode(["status" => "ERROR", "msg" => lang("user does not exist", false), "user" => $VARS['manager']]));
110
+        }
111
+        break;
112
+    default:
113
+        header("HTTP/1.1 400 Bad Request");
114
+        die("\"400 Bad Request\"");
115
+}
116
+    /* } catch (Exception $e) {
117
+      header("HTTP/1.1 500 Internal Server Error");
118
+      die("\"500 Internal Server Error\"");
119
+      } */    

+ 3
- 1
composer.json Zobrazit soubor

@@ -5,7 +5,9 @@
5 5
     "require": {
6 6
         "catfan/medoo": "^1.2",
7 7
         "spomky-labs/otphp": "^8.3",
8
-        "endroid/qrcode": "^1.9"
8
+        "endroid/qrcode": "^1.9",
9
+        "ldaptools/ldaptools": "^0.24.0",
10
+        "guzzlehttp/guzzle": "^6.2"
9 11
     },
10 12
     "authors": [
11 13
         {

+ 486
- 1
composer.lock Zobrazit soubor

@@ -4,7 +4,7 @@
4 4
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
5 5
         "This file is @generated automatically"
6 6
     ],
7
-    "content-hash": "3d5a548f8a7cbbd0c911987b1fab33a5",
7
+    "content-hash": "4965262916e04d361db07e7f14ed06d6",
8 8
     "packages": [
9 9
         {
10 10
             "name": "beberlei/assert",
@@ -230,6 +230,244 @@
230 230
             ],
231 231
             "time": "2017-04-08T09:13:59+00:00"
232 232
         },
233
+        {
234
+            "name": "guzzlehttp/guzzle",
235
+            "version": "6.2.3",
236
+            "source": {
237
+                "type": "git",
238
+                "url": "https://github.com/guzzle/guzzle.git",
239
+                "reference": "8d6c6cc55186db87b7dc5009827429ba4e9dc006"
240
+            },
241
+            "dist": {
242
+                "type": "zip",
243
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/8d6c6cc55186db87b7dc5009827429ba4e9dc006",
244
+                "reference": "8d6c6cc55186db87b7dc5009827429ba4e9dc006",
245
+                "shasum": ""
246
+            },
247
+            "require": {
248
+                "guzzlehttp/promises": "^1.0",
249
+                "guzzlehttp/psr7": "^1.4",
250
+                "php": ">=5.5"
251
+            },
252
+            "require-dev": {
253
+                "ext-curl": "*",
254
+                "phpunit/phpunit": "^4.0",
255
+                "psr/log": "^1.0"
256
+            },
257
+            "type": "library",
258
+            "extra": {
259
+                "branch-alias": {
260
+                    "dev-master": "6.2-dev"
261
+                }
262
+            },
263
+            "autoload": {
264
+                "files": [
265
+                    "src/functions_include.php"
266
+                ],
267
+                "psr-4": {
268
+                    "GuzzleHttp\\": "src/"
269
+                }
270
+            },
271
+            "notification-url": "https://packagist.org/downloads/",
272
+            "license": [
273
+                "MIT"
274
+            ],
275
+            "authors": [
276
+                {
277
+                    "name": "Michael Dowling",
278
+                    "email": "mtdowling@gmail.com",
279
+                    "homepage": "https://github.com/mtdowling"
280
+                }
281
+            ],
282
+            "description": "Guzzle is a PHP HTTP client library",
283
+            "homepage": "http://guzzlephp.org/",
284
+            "keywords": [
285
+                "client",
286
+                "curl",
287
+                "framework",
288
+                "http",
289
+                "http client",
290
+                "rest",
291
+                "web service"
292
+            ],
293
+            "time": "2017-02-28T22:50:30+00:00"
294
+        },
295
+        {
296
+            "name": "guzzlehttp/promises",
297
+            "version": "v1.3.1",
298
+            "source": {
299
+                "type": "git",
300
+                "url": "https://github.com/guzzle/promises.git",
301
+                "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646"
302
+            },
303
+            "dist": {
304
+                "type": "zip",
305
+                "url": "https://api.github.com/repos/guzzle/promises/zipball/a59da6cf61d80060647ff4d3eb2c03a2bc694646",
306
+                "reference": "a59da6cf61d80060647ff4d3eb2c03a2bc694646",
307
+                "shasum": ""
308
+            },
309
+            "require": {
310
+                "php": ">=5.5.0"
311
+            },
312
+            "require-dev": {
313
+                "phpunit/phpunit": "^4.0"
314
+            },
315
+            "type": "library",
316
+            "extra": {
317
+                "branch-alias": {
318
+                    "dev-master": "1.4-dev"
319
+                }
320
+            },
321
+            "autoload": {
322
+                "psr-4": {
323
+                    "GuzzleHttp\\Promise\\": "src/"
324
+                },
325
+                "files": [
326
+                    "src/functions_include.php"
327
+                ]
328
+            },
329
+            "notification-url": "https://packagist.org/downloads/",
330
+            "license": [
331
+                "MIT"
332
+            ],
333
+            "authors": [
334
+                {
335
+                    "name": "Michael Dowling",
336
+                    "email": "mtdowling@gmail.com",
337
+                    "homepage": "https://github.com/mtdowling"
338
+                }
339
+            ],
340
+            "description": "Guzzle promises library",
341
+            "keywords": [
342
+                "promise"
343
+            ],
344
+            "time": "2016-12-20T10:07:11+00:00"
345
+        },
346
+        {
347
+            "name": "guzzlehttp/psr7",
348
+            "version": "1.4.2",
349
+            "source": {
350
+                "type": "git",
351
+                "url": "https://github.com/guzzle/psr7.git",
352
+                "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c"
353
+            },
354
+            "dist": {
355
+                "type": "zip",
356
+                "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
357
+                "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c",
358
+                "shasum": ""
359
+            },
360
+            "require": {
361
+                "php": ">=5.4.0",
362
+                "psr/http-message": "~1.0"
363
+            },
364
+            "provide": {
365
+                "psr/http-message-implementation": "1.0"
366
+            },
367
+            "require-dev": {
368
+                "phpunit/phpunit": "~4.0"
369
+            },
370
+            "type": "library",
371
+            "extra": {
372
+                "branch-alias": {
373
+                    "dev-master": "1.4-dev"
374
+                }
375
+            },
376
+            "autoload": {
377
+                "psr-4": {
378
+                    "GuzzleHttp\\Psr7\\": "src/"
379
+                },
380
+                "files": [
381
+                    "src/functions_include.php"
382
+                ]
383
+            },
384
+            "notification-url": "https://packagist.org/downloads/",
385
+            "license": [
386
+                "MIT"
387
+            ],
388
+            "authors": [
389
+                {
390
+                    "name": "Michael Dowling",
391
+                    "email": "mtdowling@gmail.com",
392
+                    "homepage": "https://github.com/mtdowling"
393
+                },
394
+                {
395
+                    "name": "Tobias Schultze",
396
+                    "homepage": "https://github.com/Tobion"
397
+                }
398
+            ],
399
+            "description": "PSR-7 message implementation that also provides common utility methods",
400
+            "keywords": [
401
+                "http",
402
+                "message",
403
+                "request",
404
+                "response",
405
+                "stream",
406
+                "uri",
407
+                "url"
408
+            ],
409
+            "time": "2017-03-20T17:10:46+00:00"
410
+        },
411
+        {
412
+            "name": "ldaptools/ldaptools",
413
+            "version": "v0.24.0",
414
+            "source": {
415
+                "type": "git",
416
+                "url": "https://github.com/ldaptools/ldaptools.git",
417
+                "reference": "31e05ae6082fc7e61afc666e2c773ee8cb0e47b5"
418
+            },
419
+            "dist": {
420
+                "type": "zip",
421
+                "url": "https://api.github.com/repos/ldaptools/ldaptools/zipball/31e05ae6082fc7e61afc666e2c773ee8cb0e47b5",
422
+                "reference": "31e05ae6082fc7e61afc666e2c773ee8cb0e47b5",
423
+                "shasum": ""
424
+            },
425
+            "require": {
426
+                "ext-ldap": "*",
427
+                "php": ">=5.6",
428
+                "ramsey/uuid": ">=3.0",
429
+                "symfony/event-dispatcher": ">=2.0",
430
+                "symfony/yaml": ">=2.0"
431
+            },
432
+            "require-dev": {
433
+                "doctrine/cache": "~1.0",
434
+                "friendsofphp/php-cs-fixer": "~1.0",
435
+                "phpspec/phpspec": "~3.0",
436
+                "tedivm/stash": ">=0.14.1"
437
+            },
438
+            "suggest": {
439
+                "doctrine/cache": "Provides the cache_type 'doctrine' to help increase performance.",
440
+                "ext-intl": "Better UTF-8 handling.",
441
+                "ext-mbstring": "Better UTF-8 handling.",
442
+                "tedivm/stash": "Provides the cache_type 'stash' to help increase performance."
443
+            },
444
+            "type": "library",
445
+            "autoload": {
446
+                "psr-4": {
447
+                    "LdapTools\\": "src/LdapTools"
448
+                }
449
+            },
450
+            "notification-url": "https://packagist.org/downloads/",
451
+            "license": [
452
+                "MIT"
453
+            ],
454
+            "authors": [
455
+                {
456
+                    "name": "Chad Sikorra",
457
+                    "email": "Chad.Sikorra@gmail.com",
458
+                    "homepage": "http://www.chadsikorra.com"
459
+                }
460
+            ],
461
+            "description": "LdapTools is a feature-rich LDAP library for PHP 5.6+.",
462
+            "homepage": "http://www.phpldaptools.com",
463
+            "keywords": [
464
+                "Microsoft Exchange",
465
+                "active directory",
466
+                "ldap",
467
+                "openldap"
468
+            ],
469
+            "time": "2017-04-09T23:39:51+00:00"
470
+        },
233 471
         {
234 472
             "name": "paragonie/random_compat",
235 473
             "version": "v2.0.10",
@@ -278,6 +516,138 @@
278 516
             ],
279 517
             "time": "2017-03-13T16:27:32+00:00"
280 518
         },
519
+        {
520
+            "name": "psr/http-message",
521
+            "version": "1.0.1",
522
+            "source": {
523
+                "type": "git",
524
+                "url": "https://github.com/php-fig/http-message.git",
525
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
526
+            },
527
+            "dist": {
528
+                "type": "zip",
529
+                "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
530
+                "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
531
+                "shasum": ""
532
+            },
533
+            "require": {
534
+                "php": ">=5.3.0"
535
+            },
536
+            "type": "library",
537
+            "extra": {
538
+                "branch-alias": {
539
+                    "dev-master": "1.0.x-dev"
540
+                }
541
+            },
542
+            "autoload": {
543
+                "psr-4": {
544
+                    "Psr\\Http\\Message\\": "src/"
545
+                }
546
+            },
547
+            "notification-url": "https://packagist.org/downloads/",
548
+            "license": [
549
+                "MIT"
550
+            ],
551
+            "authors": [
552
+                {
553
+                    "name": "PHP-FIG",
554
+                    "homepage": "http://www.php-fig.org/"
555
+                }
556
+            ],
557
+            "description": "Common interface for HTTP messages",
558
+            "homepage": "https://github.com/php-fig/http-message",
559
+            "keywords": [
560
+                "http",
561
+                "http-message",
562
+                "psr",
563
+                "psr-7",
564
+                "request",
565
+                "response"
566
+            ],
567
+            "time": "2016-08-06T14:39:51+00:00"
568
+        },
569
+        {
570
+            "name": "ramsey/uuid",
571
+            "version": "3.6.1",
572
+            "source": {
573
+                "type": "git",
574
+                "url": "https://github.com/ramsey/uuid.git",
575
+                "reference": "4ae32dd9ab8860a4bbd750ad269cba7f06f7934e"
576
+            },
577
+            "dist": {
578
+                "type": "zip",
579
+                "url": "https://api.github.com/repos/ramsey/uuid/zipball/4ae32dd9ab8860a4bbd750ad269cba7f06f7934e",
580
+                "reference": "4ae32dd9ab8860a4bbd750ad269cba7f06f7934e",
581
+                "shasum": ""
582
+            },
583
+            "require": {
584
+                "paragonie/random_compat": "^1.0|^2.0",
585
+                "php": "^5.4 || ^7.0"
586
+            },
587
+            "replace": {
588
+                "rhumsaa/uuid": "self.version"
589
+            },
590
+            "require-dev": {
591
+                "apigen/apigen": "^4.1",
592
+                "codeception/aspect-mock": "^1.0 | ^2.0",
593
+                "doctrine/annotations": "~1.2.0",
594
+                "goaop/framework": "1.0.0-alpha.2 | ^1.0 | ^2.1",
595
+                "ircmaxell/random-lib": "^1.1",
596
+                "jakub-onderka/php-parallel-lint": "^0.9.0",
597
+                "mockery/mockery": "^0.9.4",
598
+                "moontoast/math": "^1.1",
599
+                "php-mock/php-mock-phpunit": "^0.3|^1.1",
600
+                "phpunit/phpunit": "^4.7|>=5.0 <5.4",
601
+                "satooshi/php-coveralls": "^0.6.1",
602
+                "squizlabs/php_codesniffer": "^2.3"
603
+            },
604
+            "suggest": {
605
+                "ext-libsodium": "Provides the PECL libsodium extension for use with the SodiumRandomGenerator",
606
+                "ext-uuid": "Provides the PECL UUID extension for use with the PeclUuidTimeGenerator and PeclUuidRandomGenerator",
607
+                "ircmaxell/random-lib": "Provides RandomLib for use with the RandomLibAdapter",
608
+                "moontoast/math": "Provides support for converting UUID to 128-bit integer (in string form).",
609
+                "ramsey/uuid-console": "A console application for generating UUIDs with ramsey/uuid",
610
+                "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type."
611
+            },
612
+            "type": "library",
613
+            "extra": {
614
+                "branch-alias": {
615
+                    "dev-master": "3.x-dev"
616
+                }
617
+            },
618
+            "autoload": {
619
+                "psr-4": {
620
+                    "Ramsey\\Uuid\\": "src/"
621
+                }
622
+            },
623
+            "notification-url": "https://packagist.org/downloads/",
624
+            "license": [
625
+                "MIT"
626
+            ],
627
+            "authors": [
628
+                {
629
+                    "name": "Marijn Huizendveld",
630
+                    "email": "marijn.huizendveld@gmail.com"
631
+                },
632
+                {
633
+                    "name": "Thibaud Fabre",
634
+                    "email": "thibaud@aztech.io"
635
+                },
636
+                {
637
+                    "name": "Ben Ramsey",
638
+                    "email": "ben@benramsey.com",
639
+                    "homepage": "https://benramsey.com"
640
+                }
641
+            ],
642
+            "description": "Formerly rhumsaa/uuid. A PHP 5.4+ library for generating RFC 4122 version 1, 3, 4, and 5 universally unique identifiers (UUID).",
643
+            "homepage": "https://github.com/ramsey/uuid",
644
+            "keywords": [
645
+                "guid",
646
+                "identifier",
647
+                "uuid"
648
+            ],
649
+            "time": "2017-03-26T20:37:53+00:00"
650
+        },
281 651
         {
282 652
             "name": "spomky-labs/otphp",
283 653
             "version": "v8.3.0",
@@ -342,6 +712,66 @@
342 712
             ],
343 713
             "time": "2016-12-08T10:46:02+00:00"
344 714
         },
715
+        {
716
+            "name": "symfony/event-dispatcher",
717
+            "version": "v3.2.7",
718
+            "source": {
719
+                "type": "git",
720
+                "url": "https://github.com/symfony/event-dispatcher.git",
721
+                "reference": "154bb1ef7b0e42ccc792bd53edbce18ed73440ca"
722
+            },
723
+            "dist": {
724
+                "type": "zip",
725
+                "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/154bb1ef7b0e42ccc792bd53edbce18ed73440ca",
726
+                "reference": "154bb1ef7b0e42ccc792bd53edbce18ed73440ca",
727
+                "shasum": ""
728
+            },
729
+            "require": {
730
+                "php": ">=5.5.9"
731
+            },
732
+            "require-dev": {
733
+                "psr/log": "~1.0",
734
+                "symfony/config": "~2.8|~3.0",
735
+                "symfony/dependency-injection": "~2.8|~3.0",
736
+                "symfony/expression-language": "~2.8|~3.0",
737
+                "symfony/stopwatch": "~2.8|~3.0"
738
+            },
739
+            "suggest": {
740
+                "symfony/dependency-injection": "",
741
+                "symfony/http-kernel": ""
742
+            },
743
+            "type": "library",
744
+            "extra": {
745
+                "branch-alias": {
746
+                    "dev-master": "3.2-dev"
747
+                }
748
+            },
749
+            "autoload": {
750
+                "psr-4": {
751
+                    "Symfony\\Component\\EventDispatcher\\": ""
752
+                },
753
+                "exclude-from-classmap": [
754
+                    "/Tests/"
755
+                ]
756
+            },
757
+            "notification-url": "https://packagist.org/downloads/",
758
+            "license": [
759
+                "MIT"
760
+            ],
761
+            "authors": [
762
+                {
763
+                    "name": "Fabien Potencier",
764
+                    "email": "fabien@symfony.com"
765
+                },
766
+                {
767
+                    "name": "Symfony Community",
768
+                    "homepage": "https://symfony.com/contributors"
769
+                }
770
+            ],
771
+            "description": "Symfony EventDispatcher Component",
772
+            "homepage": "https://symfony.com",
773
+            "time": "2017-04-04T07:26:27+00:00"
774
+        },
345 775
         {
346 776
             "name": "symfony/options-resolver",
347 777
             "version": "v3.2.7",
@@ -562,6 +992,61 @@
562 992
                 "shim"
563 993
             ],
564 994
             "time": "2016-11-14T01:06:16+00:00"
995
+        },
996
+        {
997
+            "name": "symfony/yaml",
998
+            "version": "v3.2.7",
999
+            "source": {
1000
+                "type": "git",
1001
+                "url": "https://github.com/symfony/yaml.git",
1002
+                "reference": "62b4cdb99d52cb1ff253c465eb1532a80cebb621"
1003
+            },
1004
+            "dist": {
1005
+                "type": "zip",
1006
+                "url": "https://api.github.com/repos/symfony/yaml/zipball/62b4cdb99d52cb1ff253c465eb1532a80cebb621",
1007
+                "reference": "62b4cdb99d52cb1ff253c465eb1532a80cebb621",
1008
+                "shasum": ""
1009
+            },
1010
+            "require": {
1011
+                "php": ">=5.5.9"
1012
+            },
1013
+            "require-dev": {
1014
+                "symfony/console": "~2.8|~3.0"
1015
+            },
1016
+            "suggest": {
1017
+                "symfony/console": "For validating YAML files using the lint command"
1018
+            },
1019
+            "type": "library",
1020
+            "extra": {
1021
+                "branch-alias": {
1022
+                    "dev-master": "3.2-dev"
1023
+                }
1024
+            },
1025
+            "autoload": {
1026
+                "psr-4": {
1027
+                    "Symfony\\Component\\Yaml\\": ""
1028
+                },
1029
+                "exclude-from-classmap": [
1030
+                    "/Tests/"
1031
+                ]
1032
+            },
1033
+            "notification-url": "https://packagist.org/downloads/",
1034
+            "license": [
1035
+                "MIT"
1036
+            ],
1037
+            "authors": [
1038
+                {
1039
+                    "name": "Fabien Potencier",
1040
+                    "email": "fabien@symfony.com"
1041
+                },
1042
+                {
1043
+                    "name": "Symfony Community",
1044
+                    "homepage": "https://symfony.com/contributors"
1045
+                }
1046
+            ],
1047
+            "description": "Symfony Yaml Component",
1048
+            "homepage": "https://symfony.com",
1049
+            "time": "2017-03-20T09:45:15+00:00"
565 1050
         }
566 1051
     ],
567 1052
     "packages-dev": [],

binární
database.mwb Zobrazit soubor


+ 4
- 3
home.php Zobrazit soubor

@@ -34,7 +34,7 @@ if (!is_empty($_GET['page'])) {
34 34
         <div class="container">
35 35
             <div class="row">
36 36
                 <div class="col-xs-12 col-sm-6 col-md-4 col-lg-4 col-sm-offset-3 col-md-offset-4 col-lg-offset-4">
37
-                    <img class="img-responsive banner-image" src="static/img/banner.png" />
37
+                    <img class="img-responsive banner-image" src="static/img/logo.svg" />
38 38
                 </div>
39 39
             </div>
40 40
             <nav class="navbar navbar-inverse">
@@ -130,17 +130,18 @@ END;
130 130
                 if ($appcount == 1) {
131 131
                     ?>
132 132
                     <div class="hidden-xs col-sm-3 col-md-4 col-lg-4">
133
-                        <!-- Placeholder column for nice center-align -->
133
+                        <!-- Empty placeholder column for nice center-align -->
134 134
                     </div>
135 135
                     <?php
136 136
                 } else if ($appcount == 2) {
137 137
                     ?>
138 138
                     <div class="hidden-xs hidden-sm col-md-2 col-lg-2">
139
-                        <!-- Placeholder column for nice center-align -->
139
+                        <!-- Empty placeholder column for nice center-align -->
140 140
                     </div>
141 141
                     <?php
142 142
                 }
143 143
 
144
+                // Load app widgets
144 145
                 foreach (APPS[$pageid] as $app) {
145 146
                     if (file_exists(__DIR__ . "/apps/" . $app . ".php")) {
146 147
                         include_once __DIR__ . "/apps/" . $app . ".php";

+ 44
- 28
index.php Zobrazit soubor

@@ -7,43 +7,52 @@ require_once __DIR__ . "/lib/login.php";
7 7
 $userpass_ok = false;
8 8
 $multiauth = false;
9 9
 if ($VARS['progress'] == "1") {
10
-    if (authenticate_user($VARS['username'], $VARS['password'])) {
11
-        switch (get_account_status($VARS['username'])) {
12
-            case "LOCKED_OR_DISABLED":
13
-                $alert = lang("account locked", false);
14
-                break;
15
-            case "TERMINATED":
16
-                $alert = lang("account terminated", false);
17
-                break;
18
-            case "CHANGE_PASSWORD":
19
-                $alert = lang("password expired", false);
20
-            case "NORMAL":
21
-                $userpass_ok = true;
22
-                break;
23
-            case "ALERT_ON_ACCESS":
24
-                sendAlertEmail($VARS['username']);
25
-                $userpass_ok = true;
26
-                break;
27
-        }
28
-        if ($userpass_ok) {
29
-            if (userHasTOTP($VARS['username'])) {
30
-                $multiauth = true;
31
-            } else {
32
-                doLoginUser($VARS['username']);
33
-                header('Location: home.php');
34
-                die("Logged in, go to home.php");
10
+    if (!RECAPTCHA_ENABLED || (RECAPTCHA_ENABLED && verifyReCaptcha($VARS['g-recaptcha-response']))) {
11
+        if (authenticate_user($VARS['username'], $VARS['password'])) {
12
+            switch (get_account_status($VARS['username'])) {
13
+                case "LOCKED_OR_DISABLED":
14
+                    $alert = lang("account locked", false);
15
+                    break;
16
+                case "TERMINATED":
17
+                    $alert = lang("account terminated", false);
18
+                    break;
19
+                case "CHANGE_PASSWORD":
20
+                    $alert = lang("password expired", false);
21
+                case "NORMAL":
22
+                    $userpass_ok = true;
23
+                    break;
24
+                case "ALERT_ON_ACCESS":
25
+                    sendLoginAlertEmail($VARS['username']);
26
+                    $userpass_ok = true;
27
+                    break;
28
+            }
29
+            if ($userpass_ok) {
30
+                if (userHasTOTP($VARS['username'])) {
31
+                    $multiauth = true;
32
+                } else {
33
+                    doLoginUser($VARS['username'], $VARS['password']);
34
+                    insertAuthLog(1, $_SESSION['uid']);
35
+                    header('Location: home.php');
36
+                    die("Logged in, go to home.php");
37
+                }
35 38
             }
39
+        } else {
40
+            $alert = lang("login incorrect", false);
41
+            insertAuthLog(2);
36 42
         }
37 43
     } else {
38
-        $alert = lang("login incorrect", false);
44
+        $alert = lang("captcha error", false);
45
+        insertAuthLog(8);
39 46
     }
40 47
 } else if ($VARS['progress'] == "2") {
41 48
     if (verifyTOTP($VARS['username'], $VARS['authcode'])) {
42
-        doLoginUser($VARS['username']);
49
+        doLoginUser($VARS['username'], $VARS['password']);
50
+        insertAuthLog(1, $_SESSION['uid']);
43 51
         header('Location: home.php');
44 52
         die("Logged in, go to home.php");
45 53
     } else {
46 54
         $alert = lang("2fa incorrect", false);
55
+        insertAuthLog(6);
47 56
     }
48 57
 }
49 58
 ?>
@@ -58,13 +67,16 @@ if ($VARS['progress'] == "1") {
58 67
 
59 68
         <link href="static/css/bootstrap.min.css" rel="stylesheet">
60 69
         <link href="static/css/app.css" rel="stylesheet">
70
+        <?php if (RECAPTCHA_ENABLED) { ?>
71
+            <script src='https://www.google.com/recaptcha/api.js'></script>
72
+        <?php } ?>
61 73
     </head>
62 74
     <body>
63 75
         <div class="container">
64 76
             <div class="row">
65 77
                 <div class="col-xs-12 col-sm-6 col-md-4 col-lg-4 col-sm-offset-3 col-md-offset-4 col-lg-offset-4">
66 78
                     <div>
67
-                        <img class="img-responsive banner-image" src="static/img/banner.png" />
79
+                        <img class="img-responsive banner-image" src="static/img/logo.svg" />
68 80
                     </div>
69 81
                     <div class="panel panel-primary">
70 82
                         <div class="panel-heading">
@@ -85,6 +97,10 @@ if ($VARS['progress'] == "1") {
85 97
                                     ?>
86 98
                                     <input type="text" class="form-control" name="username" placeholder="<?php lang("username"); ?>" required="required" autofocus /><br />
87 99
                                     <input type="password" class="form-control" name="password" placeholder="<?php lang("password"); ?>" required="required" /><br />
100
+                                    <?php if (RECAPTCHA_ENABLED) { ?>
101
+                                        <div class="g-recaptcha" data-sitekey="<?php echo RECAPTCHA_SITE_KEY; ?>"></div>
102
+                                        <br />
103
+                                    <?php } ?>
88 104
                                     <input type="hidden" name="progress" value="1" />
89 105
                                     <?php
90 106
                                 } else if ($multiauth) {

+ 5
- 0
lang/en_us.php Zobrazit soubor

@@ -9,9 +9,11 @@ define("STRINGS", [
9 9
     "2fa prompt" => "Enter the six-digit code from your mobile authenticator app.",
10 10
     "2fa incorrect" => "Authentication code incorrect.",
11 11
     "login incorrect" => "Login incorrect.",
12
+    "login successful" => "Login successful.",
12 13
     "account locked" => "This account has been disabled. Contact technical support.",
13 14
     "password expired" => "You must change your password before continuing.",
14 15
     "account terminated" => "Account terminated.  Access denied.",
16
+    "account state error" => "Your account state is not stable.  Log out, restart your browser, and try again.",
15 17
     "password on 500 list" => "The given password is ranked number {arg} out of the 500 most common passwords.  Try a different one.",
16 18
     "welcome user" => "Welcome, {user}!",
17 19
     "change password" => "Change password",
@@ -36,5 +38,8 @@ define("STRINGS", [
36 38
     "scan 2fa qrcode" => "Scan the QR Code with the authenticator app, or enter the secret key manually.",
37 39
     "confirm 2fa" => "Finish setup",
38 40
     "invalid parameters" => "Invalid request parameters.",
41
+    "ldap server error" => "The LDAP server returned an error: {arg}",
42
+    "user does not exist" => "User does not exist.",
43
+    "captcha error" => "There was a problem with the CAPTCHA (robot test).  Try again.",
39 44
     "home" => "Home",
40 45
 ]);

+ 8
- 0
lang/messages.php Zobrazit soubor

@@ -32,5 +32,13 @@ define("MESSAGES", [
32 32
     "password_500" => [
33 33
         "string" => "password on 500 list",
34 34
         "type" => "danger"
35
+    ],
36
+    "account_state_error" => [
37
+        "string" => "account state error",
38
+        "type" => "danger"
39
+    ],
40
+    "ldap_error" => [
41
+        "string" => "ldap server error",
42
+        "type" => "danger"
35 43
     ]
36 44
 ]);

+ 298
- 9
lib/login.php Zobrazit soubor

@@ -1,32 +1,317 @@
1 1
 <?php
2 2
 
3
+/**
4
+ * Authentication and account functions
5
+ */
3 6
 use Base32\Base32;
4 7
 use OTPHP\TOTP;
8
+use LdapTools\LdapManager;
9
+use LdapTools\Connection\ADResponseCodes;
10
+
11
+////////////////////////////////////////////////////////////////////////////////
12
+//                           Account handling                                 //
13
+////////////////////////////////////////////////////////////////////////////////
5 14
 
6 15
 /**
7
- * Send an alert email to the system admin
8
- * 
9
- * Used when an account with the status ALERT_ON_ACCESS logs in
10
- * @param String $username the account username
16
+ * Add a user to the system.  /!\ Assumes input is OK /!\
17
+ * @param string $username Username, saved in lowercase.
18
+ * @param string $password Password, will be hashed before saving.
19
+ * @param string $realname User's real legal name
20
+ * @param string $email User's email address.
21
+ * @param string $phone1 Phone number #1
22
+ * @param string $phone2 Phone number #2
23
+ * @param string $type Account type
24
+ * @return int The new user's ID number in the database.
11 25
  */
12
-function sendAlertEmail($username) {
13
-    // TODO: add email code
26
+function adduser($username, $password, $realname, $email = null, $phone1 = "", $phone2 = "", $type) {
27
+    global $database;
28
+    $database->insert('accounts', [
29
+        'username' => strtolower($username),
30
+        'password' => (is_null($password) ? null : encryptPassword($password)),
31
+        'realname' => $realname,
32
+        'email' => $email,
33
+        'phone1' => $phone1,
34
+        'phone2' => $phone2,
35
+        'acctstatus' => 1,
36
+        'accttype' => $type
37
+    ]);
38
+    var_dump($database->error());
39
+    return $database->id();
40
+}
41
+
42
+/**
43
+ * Get where a user's account actually is.
44
+ * @param string $username
45
+ * @return string "LDAP", "LOCAL", "LDAP_ONLY", or "NONE".
46
+ */
47
+function account_location($username, $password) {
48
+    global $database;
49
+    $username = strtolower($username);
50
+    $user_exists = user_exists($username);
51
+    if (!$user_exists && !LDAP_ENABLED) {
52
+        return false;
53
+    }
54
+    if ($user_exists) {
55
+        $userinfo = $database->select('accounts', ['password'], ['username' => $username])[0];
56
+        // if password empty, it's an LDAP user
57
+        if (is_empty($userinfo['password']) && LDAP_ENABLED) {
58
+            return "LDAP";
59
+        } else if (is_empty($userinfo['password']) && !LDAP_ENABLED) {
60
+            return "NONE";
61
+        } else {
62
+            return "LOCAL";
63
+        }
64
+    } else {
65
+        if (user_exists_ldap($username, $password)) {
66
+            return "LDAP_ONLY";
67
+        } else {
68
+            return "NONE";
69
+        }
70
+    }
71
+}
72
+
73
+/**
74
+ * Checks the given credentials against the database.
75
+ * @param string $username
76
+ * @param string $password
77
+ * @return boolean True if OK, else false
78
+ */
79
+function authenticate_user($username, $password) {
80
+    global $database;
81
+    global $ldap_config;
82
+    $username = strtolower($username);
83
+    if (is_empty($username) || is_empty($password)) {
84
+        return false;
85
+    }
86
+    $loc = account_location($username, $password);
87
+    if ($loc == "NONE") {
88
+        return false;
89
+    } else if ($loc == "LOCAL") {
90
+        $hash = $database->select('accounts', ['password'], ['username' => $username, "LIMIT" => 1])[0]['password'];
91
+        return (comparePassword($password, $hash));
92
+    } else if ($loc == "LDAP") {
93
+        return authenticate_user_ldap($username, $password) === TRUE;
94
+    } else if ($loc == "LDAP_ONLY") {
95
+        try {
96
+            if (authenticate_user_ldap($username, $password) === TRUE) {
97
+                $user = (new LdapManager($ldap_config))->getRepository('user')->findOneByUsername($username);
98
+                //var_dump($user);
99
+                adduser($user->getUsername(), null, $user->getName(), ($user->hasEmailAddress() ? $user->getEmailAddress() : null), "", "", 2);
100
+                return true;
101
+            } else {
102
+                return false;
103
+            }
104
+        } catch (Exception $e) {
105
+            sendError("LDAP error: " . $e->getMessage());
106
+        }
107
+    } else {
108
+        return false;
109
+    }
110
+}
111
+
112
+/**
113
+ * Check if a username exists in the local database.
114
+ * @param String $username
115
+ */
116
+function user_exists($username) {
117
+    global $database;
118
+    $username = strtolower($username);
119
+    return $database->has('accounts', ['username' => $username, "LIMIT" => QUERY_LIMIT]);
14 120
 }
15 121
 
122
+/**
123
+ * Get the account status: NORMAL, TERMINATED, LOCKED_OR_DISABLED,
124
+ * CHANGE_PASSWORD, or ALERT_ON_ACCESS
125
+ * @global $database $database
126
+ * @param string $username
127
+ * @return string
128
+ */
129
+function get_account_status($username) {
130
+    global $database;
131
+    $username = strtolower($username);
132
+    $loc = account_location($username);
133
+    if ($loc == "LOCAL") {
134
+        $statuscode = $database->select('accounts', [
135
+                    '[>]acctstatus' => [
136
+                        'acctstatus' => 'statusid'
137
+                    ]
138
+                        ], [
139
+                    'accounts.acctstatus',
140
+                    'acctstatus.statuscode'
141
+                        ], [
142
+                    'username' => $username,
143
+                    "LIMIT" => 1
144
+                        ]
145
+                )[0]['statuscode'];
146
+        return $statuscode;
147
+    } else if ($loc == "LDAP") {
148
+        // TODO: Read actual account status from AD servers
149
+        return "NORMAL";
150
+    } else {
151
+        // account isn't setup properly
152
+        return "LOCKED_OR_DISABLED";
153
+    }
154
+}
155
+
156
+////////////////////////////////////////////////////////////////////////////////
157
+//                              Login handling                                //
158
+////////////////////////////////////////////////////////////////////////////////
159
+
16 160
 /**
17 161
  * Setup $_SESSION values to log in a user
18 162
  * @param string $username
19 163
  */
20
-function doLoginUser($username) {
164
+function doLoginUser($username, $password) {
21 165
     global $database;
166
+    $username = strtolower($username);
22 167
     $userinfo = $database->select('accounts', ['email', 'uid', 'realname'], ['username' => $username])[0];
23 168
     $_SESSION['username'] = $username;
24 169
     $_SESSION['uid'] = $userinfo['uid'];
25 170
     $_SESSION['email'] = $userinfo['email'];
26 171
     $_SESSION['realname'] = $userinfo['realname'];
172
+    $_SESSION['password'] = $password; // needed for things like EWS
27 173
     $_SESSION['loggedin'] = true;
28 174
 }
29 175
 
176
+/**
177
+ * Send an alert email to the system admin
178
+ * 
179
+ * Used when an account with the status ALERT_ON_ACCESS logs in
180
+ * @param String $username the account username
181
+ */
182
+function sendLoginAlertEmail($username) {
183
+    // TODO: add email code
184
+}
185
+
186
+function insertAuthLog($type, $uid = null) {
187
+    global $database;
188
+    $ip = "";
189
+    if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
190
+        $ip = $_SERVER["HTTP_CF_CONNECTING_IP"];
191
+    } else if (isset($_SERVER["HTTP_CLIENT_IP"])) {
192
+        $ip = $_SERVER["HTTP_CLIENT_IP"];
193
+    } else if (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {
194
+        $ip = $_SERVER["HTTP_X_FORWARDED_FOR"];
195
+    } else if (isset($_SERVER["HTTP_X_FORWARDED"])) {
196
+        $ip = $_SERVER["HTTP_X_FORWARDED"];
197
+    } else if (isset($_SERVER["HTTP_FORWARDED_FOR"])) {
198
+        $ip = $_SERVER["HTTP_FORWARDED_FOR"];
199
+    } else if (isset($_SERVER["HTTP_FORWARDED"])) {
200
+        $ip = $_SERVER["HTTP_FORWARDED"];
201
+    } else if (isset($_SERVER["REMOTE_ADDR"])) {
202
+        $ip = $_SERVER["REMOTE_ADDR"];
203
+    } else {
204
+        $ip = "NOT FOUND";
205
+    }
206
+    $database->insert("authlog", ['#logtime' => 'NOW()', 'logtype' => $type, 'uid' => $uid, 'ip' => $ip]);
207
+}
208
+
209
+function verifyReCaptcha($response) {
210
+    try {
211
+        $client = new GuzzleHttp\Client();
212
+
213
+        $response = $client
214
+                ->request('POST', "https://www.google.com/recaptcha/api/siteverify", [
215
+            'form_params' => [
216
+                'secret' => RECAPTCHA_SECRET_KEY,
217
+                'response' => $response
218
+            ]
219
+        ]);
220
+
221
+        if ($response->getStatusCode() != 200) {
222
+            return false;
223
+        }
224
+
225
+        $resp = json_decode($response->getBody(), TRUE);
226
+        if ($resp['success'] === true) {
227
+            return true;
228
+        } else {
229
+            return false;
230
+        }
231
+    } catch (Exception $e) {
232
+        return false;
233
+    }
234
+}
235
+
236
+////////////////////////////////////////////////////////////////////////////////
237
+//                              LDAP handling                                 //
238
+////////////////////////////////////////////////////////////////////////////////
239
+
240
+/**
241
+ * Checks the given credentials against the LDAP server.
242
+ * @param string $username
243
+ * @param string $password
244
+ * @return mixed True if OK, else false or the error code from the server
245
+ */
246
+function authenticate_user_ldap($username, $password) {
247
+    global $ldap_config;
248
+    if (is_empty($username) || is_empty($password)) {
249
+        return false;
250
+    }
251
+    $username = strtolower($username);
252
+    try {
253
+        $ldapManager = new LdapManager($ldap_config);
254
+        $msg = "";
255
+        $code = 0;
256
+        if ($ldapManager->authenticate($username, $password, $msg, $code) === TRUE) {
257
+            return true;
258
+        } else {
259
+            return $code;
260
+        }
261
+    } catch (Exception $e) {
262
+        sendError("LDAP error: " . $e->getMessage());
263
+    }
264
+}
265
+
266
+/**
267
+ * Check if a username exists on the LDAP server.
268
+ * @global type $ldap_config
269
+ * @param type $username
270
+ * @return boolean true if yes, else false
271
+ */
272
+function user_exists_ldap($username, $password) {
273
+    global $ldap_config;
274
+    try {
275
+        $ldap = new LdapManager($ldap_config);
276
+        $username = strtolower($username);
277
+        if (!$ldap->authenticate($username, $password, $message, $code)) {
278
+            switch ($code) {
279
+                case ADResponseCodes::ACCOUNT_INVALID:
280
+                    return false;
281
+                case ADResponseCodes::ACCOUNT_CREDENTIALS_INVALID:
282
+                    return true;
283
+                case ADResponseCodes::ACCOUNT_RESTRICTIONS:
284
+                    return true;
285
+                case ADResponseCodes::ACCOUNT_RESTRICTIONS_TIME:
286
+                    return true;
287
+                case ADResponseCodes::ACCOUNT_RESTRICTIONS_DEVICE:
288
+                    return true;
289
+                case ADResponseCodes::ACCOUNT_PASSWORD_EXPIRED:
290
+                    return true;
291
+                case ADResponseCodes::ACCOUNT_DISABLED:
292
+                    return true;
293
+                case ADResponseCodes::ACCOUNT_CONTEXT_IDS:
294
+                    return true;
295
+                case ADResponseCodes::ACCOUNT_EXPIRED:
296
+                    return false;
297
+                case ADResponseCodes::ACCOUNT_PASSWORD_MUST_CHANGE:
298
+                    return true;
299
+                case ADResponseCodes::ACCOUNT_LOCKED:
300
+                    return true;
301
+                default:
302
+                    return false;
303
+            }
304
+        }
305
+        return true;
306
+    } catch (Exception $e) {
307
+        sendError("LDAP error: " . $e->getMessage());
308
+    }
309
+}
310
+
311
+////////////////////////////////////////////////////////////////////////////////
312
+//                          2-factor authentication                           //
313
+////////////////////////////////////////////////////////////////////////////////
314
+
30 315
 /**
31 316
  * Check if a user has TOTP setup
32 317
  * @global $database $database
@@ -35,6 +320,7 @@ function doLoginUser($username) {
35 320
  */
36 321
 function userHasTOTP($username) {
37 322
     global $database;
323
+    $username = strtolower($username);
38 324
     $secret = $database->select('accounts', 'authsecret', ['username' => $username])[0];
39 325
     if (is_empty($secret)) {
40 326
         return false;
@@ -49,10 +335,11 @@ function userHasTOTP($username) {
49 335
  */
50 336
 function newTOTP($username) {
51 337
     global $database;
338
+    $username = strtolower($username);
52 339
     $secret = random_bytes(20);
53 340
     $encoded_secret = Base32::encode($secret);
54
-    $userdata = $database->select('accounts', ['email', 'authsecret'], ['username' => $username])[0];
55
-    $totp = new TOTP($userdata['email'], $encoded_secret);
341
+    $userdata = $database->select('accounts', ['email', 'authsecret', 'realname'], ['username' => $username])[0];
342
+    $totp = new TOTP((is_null($userdata['email']) ? $userdata['realname'] : $userdata['email']), $encoded_secret);
56 343
     $totp->setIssuer(SYSTEM_NAME);
57 344
     return $totp->getProvisioningUri();
58 345
 }
@@ -65,6 +352,7 @@ function newTOTP($username) {
65 352
  */
66 353
 function saveTOTP($username, $secret) {
67 354
     global $database;
355
+    $username = strtolower($username);
68 356
     $database->update('accounts', ['authsecret' => $secret], ['username' => $username]);
69 357
 }
70 358
 
@@ -77,6 +365,7 @@ function saveTOTP($username, $secret) {
77 365
  */
78 366
 function verifyTOTP($username, $code) {
79 367
     global $database;
368
+    $username = strtolower($username);
80 369
     $userdata = $database->select('accounts', ['email', 'authsecret'], ['username' => $username])[0];
81 370
     if (is_empty($userdata['authsecret'])) {
82 371
         return false;

+ 1
- 1
nbproject/project.xml Zobrazit soubor

@@ -3,7 +3,7 @@
3 3
     <type>org.netbeans.modules.php.project</type>
4 4
     <configuration>
5 5
         <data xmlns="http://www.netbeans.org/ns/php-project/1">
6
-            <name>NetsymsBusinessSSO</name>
6
+            <name>BusinessPortal</name>
7 7
         </data>
8 8
     </configuration>
9 9
 </project>

+ 2
- 103
required.php Zobrazit soubor

@@ -16,9 +16,10 @@ $session_length = 60 * 60; // 1 hour
16 16
 session_set_cookie_params($session_length, "/", null, false, true);
17 17
 
18 18
 session_start(); // stick some cookies in it
19
-
19
+//
20 20
 // Composer
21 21
 require __DIR__ . '/vendor/autoload.php';
22
+
22 23
 // Settings file
23 24
 require __DIR__ . '/settings.php';
24 25
 // List of alert messages
@@ -123,27 +124,6 @@ function lang2($key, $replace, $echo = true) {
123 124
     }
124 125
 }
125 126
 
126
-/**
127
- * Add a user to the system.  /!\ Assumes input is OK /!\
128
- * @param string $username Username, saved in lowercase.
129
- * @param string $password Password, will be hashed before saving.
130
- * @param string $realname User's real legal name
131
- * @param string $email User's email address.
132
- * @return int The new user's ID number in the database.
133
- */
134
-function adduser($username, $password, $realname, $email = "NOEMAIL@EXAMPLE.COM", $phone1 = "", $phone2 = "") {
135
-    global $database;
136
-    $database->insert('accounts', [
137
-        'username' => strtolower($username),
138
-        'password' => encryptPassword($password),
139
-        'realname' => $realname,
140
-        'email' => $email,
141
-        'phone1' => $phone1,
142
-        'phone2' => $phone2
143
-    ]);
144
-    return $database->id();
145
-}
146
-
147 127
 /**
148 128
  * Checks if an email address is valid.
149 129
  * @param string $email Email to check
@@ -153,87 +133,6 @@ function isValidEmail($email) {
153 133
     return filter_var($email, FILTER_VALIDATE_EMAIL);
154 134
 }
155 135
 
156
-/**
157
- * Check if an email exists in the database.
158
- * @param String $email
159
- */
160
-function email_exists($email) {
161
-    global $database;
162
-    return $database->has('accounts', ['email' => $email, "LIMIT" => QUERY_LIMIT]);
163
-}
164
-
165
-/**
166
- * Check if a username exists in the database.
167
- * @param String $username
168
- */
169
-function user_exists($username) {
170
-    global $database;
171
-    return $database->has('accounts', ['username' => $username, "LIMIT" => QUERY_LIMIT]);
172
-}
173
-
174
-/**
175
- * Checks the given credentials against the database.
176
- * @param string $username
177
- * @param string $password
178
- * @return boolean True if OK, else false
179
- */
180
-function authenticate_user($username, $password) {
181
-    global $database;
182
-    if (is_empty($username) || is_empty($password)) {
183
-        return false;
184
-    }
185
-    if (!user_exists($username)) {
186
-        return false;
187
-    }
188
-    $hash = $database->select('accounts', ['password'], ['username' => $username, "LIMIT" => 1])[0]['password'];
189
-    return (comparePassword($password, $hash));
190
-}
191
-
192
-function get_account_status($username) {
193
-    global $database;
194
-    $statuscode = $database->select('accounts', [
195
-                '[>]acctstatus' => [
196
-                    'acctstatus' => 'statusid'
197
-                ]
198
-                    ], [
199
-                'accounts.acctstatus',
200
-                'acctstatus.statuscode'
201
-                    ], [
202
-                'username' => $username,
203
-                "LIMIT" => 1
204
-                    ]
205
-            )[0]['statuscode'];
206
-    return $statuscode;
207
-}
208
-
209
-/**
210
- * Checks the given credentials to see if they're legit.
211
- * @param string $username
212
- * @param string $password
213
- * @return boolean True if OK, else false
214
- */
215
-function authenticate_user_ldap($username, $password) {
216
-    $ds = ldap_connect(LDAP_SERVER);
217
-    if ($ds) {
218
-        $sr = ldap_search($ds, LDAP_BASEDN, "(|(uid=" . $username . ")(mail=" . $username . "))", ['cn', 'uid', 'mail']);
219
-        if (ldap_count_entries($ds, $sr) == 1) {
220
-            $info = ldap_get_entries($ds, $sr);
221
-            $name = $info[0]["cn"][0];
222
-            $uid = $info[0]["uid"][0];
223
-            $mail = $info[0]["mail"][0];
224
-            $_SESSION['uid'] = $uid;
225
-            $_SESSION['name'] = $name;
226
-            $_SESSION['mail'] = $mail;
227
-            return true;
228
-        } else if (ldap_count_entries($ds, $sr) > 1) {
229
-            sendError("Multiple users matched search criteria.  Unsure which one you are.");
230
-        } else {
231
-            return false;
232
-        }
233
-    } else {
234
-        sendError("Login server offline.");
235
-    }
236
-}
237 136
 
238 137
 /**
239 138
  * Hashes the given plaintext password

+ 33
- 6
settings.template.php Zobrazit soubor

@@ -13,8 +13,28 @@ define("DB_USER", "sso");
13 13
 define("DB_PASS", "");
14 14
 define("DB_CHARSET", "utf8");
15 15
 
16
-define("LDAP_SERVER", "example.com");
17
-define("LDAP_BASEDN", "ou=users,dc=example,dc=com");
16
+define("LDAP_ENABLED", TRUE);
17
+
18
+// See https://github.com/ldaptools/ldaptools/blob/master/docs/en/reference/Main-Configuration.md
19
+// for info on the LDAP config
20
+/*
21
+ * Begin LDAP Configuration
22
+ */
23
+use LdapTools\Configuration;
24
+use LdapTools\DomainConfiguration;
25
+
26
+$ldap_config = new Configuration();
27
+$ldap_config_domain = (new DomainConfiguration('example'))
28
+        ->setDomainName("example.com")
29
+        ->setServers(['192.168.25.131'])
30
+        ->setLazyBind(TRUE)
31
+        ->setUsername("readonly-bind")
32
+        ->setPassword("password")
33
+        ->setUseTls(TRUE);
34
+$ldap_config->addDomain($ldap_config_domain);
35
+/*
36
+ * End LDAP Configuration
37
+ */
18 38
 
19 39
 define("SITE_TITLE", "Netsyms Business Apps :: Single Sign On");
20 40
 
@@ -27,6 +47,12 @@ define("TIMEZONE", "America/Denver");
27 47
 // Base URL for site links.
28 48
 define('URL', 'http://localhost:8000/');
29 49
 
50
+// Use reCAPTCHA on login screen
51
+// https://www.google.com/recaptcha/
52
+define("RECAPTCHA_ENABLED", FALSE);
53
+define('RECAPTCHA_SITE_KEY', '');
54
+define('RECAPTCHA_SECRET_KEY', '');
55
+
30 56
 // See lang folder for language options
31 57
 define('LANGUAGE', "en_us");
32 58
 
@@ -46,9 +72,10 @@ define("QUERY_LIMIT", 1000);
46 72
 
47 73
 
48 74
 
49
-///////////////////////////////////////////////////////////////////////////////////////////////
50
-//  /!\ Warning: Changing these values may violate the terms of your license agreement! /!\  //
51
-///////////////////////////////////////////////////////////////////////////////////////////////
75
+//////////////////////////////////////////////////////////////
76
+//  /!\       Warning: Changing these values may       /!\  //
77
+//  /!\  violate the terms of your license agreement!  /!\  //
78
+//////////////////////////////////////////////////////////////
52 79
 define("LICENSE_TEXT", "<b>Unlicensed Demo: For Trial Use Only</b>");
53 80
 define("COPYRIGHT_NAME", "Netsyms Technologies");
54
-/////////////////////////////////////////////////////////////////////////////////////////////
81
+//////////////////////////////////////////////////////////////

+ 78
- 0
static/img/logo.svg Zobrazit soubor

@@ -0,0 +1,78 @@
1
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
3
+
4
+<svg
5
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
6
+   xmlns:cc="http://creativecommons.org/ns#"
7
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
8
+   xmlns:svg="http://www.w3.org/2000/svg"
9
+   xmlns="http://www.w3.org/2000/svg"
10
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
11
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
12
+   width="512"
13
+   height="512"
14
+   viewBox="0 0 512.00001 512.00001"
15
+   id="svg2"
16
+   version="1.1"
17
+   inkscape:version="0.91 r13725"
18
+   sodipodi:docname="logo.svg"
19
+   inkscape:export-filename="/home/skylar/Documents/Projects/Assets/BusinessPortal/logo_512.png"
20
+   inkscape:export-xdpi="90"
21
+   inkscape:export-ydpi="90">
22
+  <defs
23
+     id="defs4" />
24
+  <sodipodi:namedview
25
+     id="base"
26
+     pagecolor="#ffffff"
27
+     bordercolor="#666666"
28
+     borderopacity="1.0"
29
+     inkscape:pageopacity="0.0"
30
+     inkscape:pageshadow="2"
31
+     inkscape:zoom="0.49497475"
32
+     inkscape:cx="-135.9681"
33
+     inkscape:cy="352.66131"
34
+     inkscape:document-units="px"
35
+     inkscape:current-layer="layer1"
36
+     showgrid="false"
37
+     units="px" />
38
+  <metadata
39
+     id="metadata7">
40
+    <rdf:RDF>
41
+      <cc:Work
42
+         rdf:about="">
43
+        <dc:format>image/svg+xml</dc:format>
44
+        <dc:type
45
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
46
+        <dc:title />
47
+      </cc:Work>
48
+    </rdf:RDF>
49
+  </metadata>
50
+  <g
51
+     inkscape:label="Layer 1"
52
+     inkscape:groupmode="layer"
53
+     id="layer1"
54
+     transform="translate(0,-540.36216)">
55
+    <rect
56
+       style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:20;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:0.74509804"
57
+       id="rect4726"
58
+       width="512"
59
+       height="512"
60
+       x="0"
61
+       y="540.36218"
62
+       rx="50"
63
+       ry="50" />
64
+    <ellipse
65
+       style="opacity:1;fill:none;fill-opacity:1;stroke:#2196f3;stroke-width:50;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
66
+       id="path4155"
67
+       cx="901.47205"
68
+       cy="-256"
69
+       rx="68.690376"
70
+       ry="193.9493"
71
+       transform="matrix(0,1,-1,0,0,0)" />
72
+    <path
73
+       inkscape:connector-curvature="0"
74
+       d="m 257.45991,599.83707 c 9.94158,-3.2506 20.98401,-3.01096 30.77413,0.67362 8.13772,3.03356 15.37803,8.41343 20.65625,15.31019 5.7326,7.42341 9.11199,16.6258 9.55504,25.99551 0.63974,11.77034 -3.4201,23.71233 -11.14414,32.62095 6.11458,6.07391 10.75535,13.62837 13.37297,21.84299 3.28899,10.26035 3.40883,21.51078 0.35265,31.84111 -2.44585,8.32302 -6.96227,16.01318 -12.98644,22.24763 -5.15388,5.31434 -11.38153,9.60704 -18.23981,12.40548 -0.0129,14.28395 -0.007,28.56791 -0.004,42.85187 -0.50863,0 -1.01271,-0.0143 -1.51227,-0.0143 0.0129,30.01462 -10e-4,60.02924 0.009,90.04386 0.0543,0.47919 -0.14918,0.95395 -0.54929,1.23189 -4.56391,3.42459 -9.12781,6.85389 -13.69623,10.2762 -0.5787,0.4362 -1.43991,0.42506 -2.02089,0.0143 -5.08607,-3.42245 -10.18796,-6.8199 -15.26953,-10.24677 -0.44305,-0.26224 -0.65099,-0.76871 -0.58547,-1.26589 0.0159,-30.01462 -0.0129,-60.02696 0.0159,-90.04172 -2.60182,0 -5.20363,0 -7.80317,0 -0.0114,-4.06875 0.0339,-8.13992 -0.0181,-12.20652 -0.53574,1.44442 -1.03529,2.90698 -1.60493,4.3424 -0.43399,-0.18082 -0.86577,-0.34593 -1.29978,-0.50632 -9.53697,26.00443 -19.14397,51.98187 -28.70129,77.97974 -0.13112,0.4272 -0.38428,0.85668 -0.859,0.94937 -5.01825,1.51913 -10.04102,3.01768 -15.06381,4.52094 -0.74369,0.26452 -1.57784,-0.1014 -1.97343,-0.75727 -3.15789,-4.37183 -6.3248,-8.7368 -9.50077,-13.09491 -0.37071,-0.47476 -0.80472,-1.06922 -0.486,-1.6908 7.00975,-19.06727 14.04209,-38.12755 21.05862,-57.19253 2.5566,-6.99387 5.19005,-13.96288 7.69466,-20.97717 -2.25143,-0.78213 -4.48025,-1.62982 -6.71587,-2.45493 4.56389,-12.36478 9.10972,-24.73398 13.66911,-37.10119 -6.0965,-5.61043 -10.75082,-12.76712 -13.42272,-20.60638 -3.3568,-9.71789 -3.59191,-20.48227 -0.62388,-30.32899 3.38618,-11.61193 11.17807,-21.8587 21.46999,-28.21756 -5.26461,-8.55355 -7.54771,-18.87946 -6.50336,-28.85942 0.91548,-9.41942 4.851,-18.50206 11.03791,-25.65645 5.5314,-6.43108 12.84403,-11.32052 20.91847,-13.92679 m -13.13113,14.70443 c -6.2276,5.76422 -10.57903,13.53348 -12.16813,21.8746 -1.94174,9.85347 -0.052,20.40536 5.25335,28.94094 4.48253,-2.07972 9.27248,-3.49243 14.16418,-4.18643 0.74819,-0.10855 1.51225,-0.12855 2.24914,-0.3278 7.64496,-2.25365 15.79171,-2.74188 23.66271,-1.53254 9.18205,1.406 17.95044,5.28493 25.23598,11.04019 2.56112,-3.05396 4.71761,-6.46055 6.29543,-10.12481 4.19092,-9.57528 4.426,-20.79176 0.65329,-30.53671 -2.92507,-7.68337 -8.28467,-14.40606 -15.09097,-19.01291 -7.28325,-4.97078 -16.23022,-7.42788 -25.03026,-6.89669 -9.32448,0.49954 -18.40936,4.3808 -25.22472,10.76216 m 20.4596,59.08886 c -3.87446,1.01265 -7.05947,4.14802 -8.22589,7.9681 7.28328,2.99525 15.45491,3.74567 23.17444,2.22669 -0.36167,-3.1128 -1.94175,-6.08077 -4.4102,-8.02923 -2.90697,-2.33281 -6.94871,-3.20306 -10.53835,-2.16556 m -7.45051,14.82199 c 1.12344,2.37566 2.91375,4.45081 5.19004,5.78911 3.65069,2.2017 8.43386,2.41865 12.2337,0.46562 1.66825,-1.08506 2.90924,-2.7103 3.84961,-4.44181 -7.10695,1.04193 -14.43996,0.39321 -21.27335,-1.81292 z"
75
+       id="path3"
76
+       style="fill:#ff9100;fill-opacity:1" />
77
+  </g>
78
+</svg>

binární
static/img/logo_512.png Zobrazit soubor


Načítá se…
Zrušit
Uložit