Просмотр исходного кода

Rewrite to use classes, aligning with AccountHub 2.0

Skylar Ittner 7 месяцев назад
Родитель
Сommit
1271317eb9
22 измененных файлов: 1051 добавлений и 822 удалений
  1. 2
    4
      api.php
  2. 4
    4
      app.php
  3. 14
    14
      composer.lock
  4. 56
    45
      index.php
  5. 0
    35
      lang/en_us.php
  6. 26
    0
      langs/en/core.json
  7. 4
    0
      langs/en/titles.json
  8. 0
    0
      langs/messages.php
  9. 13
    0
      lib/Exceptions.lib.php
  10. 135
    0
      lib/IPUtils.lib.php
  11. 129
    0
      lib/Login.lib.php
  12. 65
    0
      lib/Notifications.lib.php
  13. 19
    0
      lib/Session.lib.php
  14. 118
    0
      lib/Strings.lib.php
  15. 352
    0
      lib/User.lib.php
  16. 0
    131
      lib/iputils.php
  17. 0
    402
      lib/login.php
  18. 0
    127
      lib/userinfo.php
  19. 9
    10
      mobile/index.php
  20. 1
    1
      pages/404.php
  21. 8
    49
      required.php
  22. 96
    0
      tests/User.test.php

+ 2
- 4
api.php Просмотреть файл

@@ -12,17 +12,15 @@
12 12
  * user passwords.
13 13
  */
14 14
 require __DIR__ . '/required.php';
15
-require_once __DIR__ . '/lib/login.php';
16
-require_once __DIR__ . '/lib/userinfo.php';
17 15
 header("Content-Type: application/json");
18 16
 
19 17
 $username = $VARS['username'];
20 18
 $password = $VARS['password'];
21
-if (user_exists($username) !== true || authenticate_user($username, $password, $errmsg) !== true) {
19
+$user = User::byUsername($username);
20
+if ($user->exists() !== true || Login::auth($username, $password) !== Login::LOGIN_OK) {
22 21
     header("HTTP/1.1 403 Unauthorized");
23 22
     die("\"403 Unauthorized\"");
24 23
 }
25
-$userinfo = getUserByUsername($username);
26 24
 
27 25
 // query max results
28 26
 $max = 20;

+ 4
- 4
app.php Просмотреть файл

@@ -69,9 +69,9 @@ header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
69 69
         if (isset($_GET['msg']) && !is_empty($_GET['msg']) && array_key_exists($_GET['msg'], MESSAGES)) {
70 70
             // optional string generation argument
71 71
             if (!isset($_GET['arg']) || is_empty($_GET['arg'])) {
72
-                $alertmsg = lang(MESSAGES[$_GET['msg']]['string'], false);
72
+                $alertmsg = $Strings->get(MESSAGES[$_GET['msg']]['string'], false);
73 73
             } else {
74
-                $alertmsg = lang2(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false);
74
+                $alertmsg = $Strings->build(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false);
75 75
             }
76 76
             $alerttype = MESSAGES[$_GET['msg']]['type'];
77 77
             $alerticon = "square-o";
@@ -146,7 +146,7 @@ END;
146 146
                                         if (isset($pg['icon'])) {
147 147
                                             ?><i class="<?php echo $pg['icon']; ?> fa-fw"></i> <?php
148 148
                                         }
149
-                                        lang($pg['title']);
149
+                                        $Strings->get($pg['title']);
150 150
                                         ?>
151 151
                                     </a>
152 152
                                 </span>
@@ -163,7 +163,7 @@ END;
163 163
                     </span>
164 164
                     <span class="nav-item mr-auto py-<?php echo $navbar_breakpoint; ?>-0">
165 165
                         <a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="action.php?action=signout">
166
-                            <i class="fas fa-sign-out-alt fa-fw"></i><span>&nbsp;<?php lang("sign out") ?></span>
166
+                            <i class="fas fa-sign-out-alt fa-fw"></i><span>&nbsp;<?php $Strings->get("sign out") ?></span>
167 167
                         </a>
168 168
                     </span>
169 169
                 </div>

+ 14
- 14
composer.lock Просмотреть файл

@@ -4,21 +4,21 @@
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
-    "hash": "577921e9d14ff39571692f88476151ee",
8
-    "content-hash": "1c8b61c5d506ae016285b99b20040cf0",
7
+    "hash": "5c7439c6e041764f2f6b0270a95ab3ae",
8
+    "content-hash": "e4e700119f47d2f68b0ed82abaf8c5c6",
9 9
     "packages": [
10 10
         {
11 11
             "name": "catfan/medoo",
12
-            "version": "v1.5.3",
12
+            "version": "v1.5.7",
13 13
             "source": {
14 14
                 "type": "git",
15 15
                 "url": "https://github.com/catfan/Medoo.git",
16
-                "reference": "1aa25a4001e0cfb739ba2996f00f4a3d2a7fdf07"
16
+                "reference": "8d90cba0e8ff176028847527d0ea76fe41a06ecf"
17 17
             },
18 18
             "dist": {
19 19
                 "type": "zip",
20
-                "url": "https://api.github.com/repos/catfan/Medoo/zipball/1aa25a4001e0cfb739ba2996f00f4a3d2a7fdf07",
21
-                "reference": "1aa25a4001e0cfb739ba2996f00f4a3d2a7fdf07",
20
+                "url": "https://api.github.com/repos/catfan/Medoo/zipball/8d90cba0e8ff176028847527d0ea76fe41a06ecf",
21
+                "reference": "8d90cba0e8ff176028847527d0ea76fe41a06ecf",
22 22
                 "shasum": ""
23 23
             },
24 24
             "require": {
@@ -64,20 +64,20 @@
64 64
                 "sql",
65 65
                 "sqlite"
66 66
             ],
67
-            "time": "2017-12-25 17:02:41"
67
+            "time": "2018-06-14 18:59:08"
68 68
         },
69 69
         {
70 70
             "name": "guzzlehttp/guzzle",
71
-            "version": "6.3.0",
71
+            "version": "6.3.3",
72 72
             "source": {
73 73
                 "type": "git",
74 74
                 "url": "https://github.com/guzzle/guzzle.git",
75
-                "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699"
75
+                "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba"
76 76
             },
77 77
             "dist": {
78 78
                 "type": "zip",
79
-                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699",
80
-                "reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699",
79
+                "url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba",
80
+                "reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba",
81 81
                 "shasum": ""
82 82
             },
83 83
             "require": {
@@ -87,7 +87,7 @@
87 87
             },
88 88
             "require-dev": {
89 89
                 "ext-curl": "*",
90
-                "phpunit/phpunit": "^4.0 || ^5.0",
90
+                "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
91 91
                 "psr/log": "^1.0"
92 92
             },
93 93
             "suggest": {
@@ -96,7 +96,7 @@
96 96
             "type": "library",
97 97
             "extra": {
98 98
                 "branch-alias": {
99
-                    "dev-master": "6.2-dev"
99
+                    "dev-master": "6.3-dev"
100 100
                 }
101 101
             },
102 102
             "autoload": {
@@ -129,7 +129,7 @@
129 129
                 "rest",
130 130
                 "web service"
131 131
             ],
132
-            "time": "2017-06-22 18:50:49"
132
+            "time": "2018-04-22 15:46:56"
133 133
         },
134 134
         {
135 135
             "name": "guzzlehttp/promises",

+ 56
- 45
index.php Просмотреть файл

@@ -5,80 +5,91 @@
5 5
 
6 6
 require_once __DIR__ . "/required.php";
7 7
 
8
-require_once __DIR__ . "/lib/login.php";
9
-
10 8
 // if we're logged in, we don't need to be here.
11 9
 if (!empty($_SESSION['loggedin']) && $_SESSION['loggedin'] === true && !isset($_GET['permissionerror'])) {
12 10
     header('Location: app.php');
13 11
 }
14 12
 
15 13
 if (isset($_GET['permissionerror'])) {
16
-    $alert = lang("no access permission", false);
14
+    $alert = $Strings->get("no access permission", false);
17 15
 }
18 16
 
19 17
 /* Authenticate user */
20 18
 $userpass_ok = false;
21 19
 $multiauth = false;
22
-if (checkLoginServer()) {
23
-    if (!empty($VARS['progress']) && $VARS['progress'] == "1") {
24
-        if (!CAPTCHA_ENABLED || (CAPTCHA_ENABLED && verifyCaptcheck($VARS['captcheck_session_code'], $VARS['captcheck_selected_answer'], CAPTCHA_SERVER . "/api.php"))) {
25
-            $errmsg = "";
26
-            if (authenticate_user($VARS['username'], $VARS['password'], $errmsg)) {
27
-                switch (get_account_status($VARS['username'])) {
20
+if (Login::checkLoginServer()) {
21
+    if (empty($VARS['progress'])) {
22
+        // Easy way to remove "undefined" warnings.
23
+    } else if ($VARS['progress'] == "1") {
24
+        if (!CAPTCHA_ENABLED || (CAPTCHA_ENABLED && Login::verifyCaptcha($VARS['captcheck_session_code'], $VARS['captcheck_selected_answer'], CAPTCHA_SERVER . "/api.php"))) {
25
+            $autherror = "";
26
+            $user = User::byUsername($VARS['username']);
27
+            if ($user->exists()) {
28
+                $status = $user->getStatus()->getString();
29
+                switch ($status) {
28 30
                     case "LOCKED_OR_DISABLED":
29
-                        $alert = lang("account locked", false);
31
+                        $alert = $Strings->get("account locked", false);
30 32
                         break;
31 33
                     case "TERMINATED":
32
-                        $alert = lang("account terminated", false);
34
+                        $alert = $Strings->get("account terminated", false);
33 35
                         break;
34 36
                     case "CHANGE_PASSWORD":
35
-                        $alert = lang("password expired", false);
37
+                        $alert = $Strings->get("password expired", false);
38
+                        break;
36 39
                     case "NORMAL":
37
-                        $userpass_ok = true;
40
+                        $username_ok = true;
38 41
                         break;
39 42
                     case "ALERT_ON_ACCESS":
40
-                        sendLoginAlertEmail($VARS['username']);
41
-                        $userpass_ok = true;
43
+                        $mail_resp = $user->sendAlertEmail();
44
+                        if (DEBUG) {
45
+                            var_dump($mail_resp);
46
+                        }
47
+                        $username_ok = true;
48
+                        break;
49
+                    default:
50
+                        if (!is_empty($error)) {
51
+                            $alert = $error;
52
+                        } else {
53
+                            $alert = $Strings->get("login error", false);
54
+                        }
42 55
                         break;
43 56
                 }
44
-                if ($userpass_ok) {
45
-                    $_SESSION['passok'] = true; // stop logins using only username and authcode
46
-                    if (userHasTOTP($VARS['username'])) {
47
-                        $multiauth = true;
57
+                if ($username_ok) {
58
+                    if ($user->checkPassword($VARS['password'])) {
59
+                        $_SESSION['passok'] = true; // stop logins using only username and authcode
60
+                        if ($user->has2fa()) {
61
+                            $multiauth = true;
62
+                        } else {
63
+                            Session::start($user);
64
+                            header('Location: app.php');
65
+                            die("Logged in, go to app.php");
66
+                        }
48 67
                     } else {
49
-                        doLoginUser($VARS['username'], $VARS['password']);
50
-                        header('Location: app.php');
51
-                        die("Logged in, go to app.php");
68
+                        $alert = $Strings->get("login incorrect", false);
52 69
                     }
53 70
                 }
54
-            } else {
55
-                if (!is_empty($errmsg)) {
56
-                    $alert = lang2("login server error", ['arg' => $errmsg], false);
57
-                } else {
58
-                    $alert = lang("login incorrect", false);
59
-                }
71
+            } else { // User does not exist anywhere
72
+                $alert = $Strings->get("login incorrect", false);
60 73
             }
61 74
         } else {
62
-            $alert = lang("captcha error", false);
75
+            $alert = $Strings->get("captcha error", false);
63 76
         }
64
-    } else if (!empty($VARS['progress']) && $VARS['progress'] == "2") {
77
+    } else if ($VARS['progress'] == "2") {
78
+        $user = User::byUsername($VARS['username']);
65 79
         if ($_SESSION['passok'] !== true) {
66 80
             // stop logins using only username and authcode
67 81
             sendError("Password integrity check failed!");
68 82
         }
69
-        if (verifyTOTP($VARS['username'], $VARS['authcode'])) {
70
-            if (doLoginUser($VARS['username'])) {
71
-                header('Location: app.php');
72
-                die("Logged in, go to app.php");
73
-            } else {
74
-                $alert = lang("login server user data error", false);
75
-            }
83
+        if ($user->check2fa($VARS['authcode'])) {
84
+            Session::start($user);
85
+            header('Location: app.php');
86
+            die("Logged in, go to app.php");
76 87
         } else {
77
-            $alert = lang("2fa incorrect", false);
88
+            $alert = $Strings->get("2fa incorrect", false);
78 89
         }
79 90
     }
80 91
 } else {
81
-    $alert = lang("login server unavailable", false);
92
+    $alert = $Strings->get("login server unavailable", false);
82 93
 }
83 94
 header("Link: <static/fonts/Roboto.css>; rel=preload; as=style", false);
84 95
 header("Link: <static/css/bootstrap.min.css>; rel=preload; as=style", false);
@@ -114,7 +125,7 @@ header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
114 125
         <div class="row justify-content-center">
115 126
             <div class="card col-11 col-xs-11 col-sm-8 col-md-6 col-lg-4">
116 127
                 <div class="card-body">
117
-                    <h5 class="card-title"><?php lang("sign in"); ?></h5>
128
+                    <h5 class="card-title"><?php $Strings->get("sign in"); ?></h5>
118 129
                     <form action="" method="POST">
119 130
                         <?php
120 131
                         if (!empty($alert)) {
@@ -127,8 +138,8 @@ header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
127 138
 
128 139
                         if ($multiauth != true) {
129 140
                             ?>
130
-                            <input type="text" class="form-control" name="username" placeholder="<?php lang("username"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /><br />
131
-                            <input type="password" class="form-control" name="password" placeholder="<?php lang("password"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" /><br />
141
+                            <input type="text" class="form-control" name="username" placeholder="<?php $Strings->get("username"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /><br />
142
+                            <input type="password" class="form-control" name="password" placeholder="<?php $Strings->get("password"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" /><br />
132 143
                             <?php if (CAPTCHA_ENABLED) { ?>
133 144
                                 <div class="captcheck_container" data-stylenonce="<?php echo $SECURE_NONCE; ?>"></div>
134 145
                                 <br />
@@ -138,16 +149,16 @@ header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
138 149
                         } else if ($multiauth) {
139 150
                             ?>
140 151
                             <div class="alert alert-info">
141
-                                <?php lang("2fa prompt"); ?>
152
+                                <?php $Strings->get("2fa prompt"); ?>
142 153
                             </div>
143
-                            <input type="text" class="form-control" name="authcode" placeholder="<?php lang("authcode"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /><br />
154
+                            <input type="text" class="form-control" name="authcode" placeholder="<?php $Strings->get("authcode"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /><br />
144 155
                             <input type="hidden" name="progress" value="2" />
145 156
                             <input type="hidden" name="username" value="<?php echo $VARS['username']; ?>" />
146 157
                             <?php
147 158
                         }
148 159
                         ?>
149 160
                         <button type="submit" class="btn btn-primary">
150
-                            <?php lang("continue"); ?>
161
+                            <?php $Strings->get("continue"); ?>
151 162
                         </button>
152 163
                     </form>
153 164
                 </div>

+ 0
- 35
lang/en_us.php Просмотреть файл

@@ -1,35 +0,0 @@
1
-<?php
2
-
3
-/* This Source Code Form is subject to the terms of the Mozilla Public
4
- * License, v. 2.0. If a copy of the MPL was not distributed with this
5
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
-
7
-define("STRINGS", [
8
-    "sign in" => "Sign In",
9
-    "username" => "Username",
10
-    "password" => "Password",
11
-    "continue" => "Continue",
12
-    "authcode" => "Authentication code",
13
-    "2fa prompt" => "Enter the six-digit code from your mobile authenticator app.",
14
-    "2fa incorrect" => "Authentication code incorrect.",
15
-    "login incorrect" => "Login incorrect.",
16
-    "login server unavailable" => "Login server unavailable.  Try again later or contact technical support.",
17
-    "account locked" => "This account has been disabled. Contact technical support.",
18
-    "password expired" => "You must change your password before continuing.",
19
-    "account terminated" => "Account terminated.  Access denied.",
20
-    "account state error" => "Your account state is not stable.  Log out, restart your browser, and try again.",
21
-    "welcome user" => "Welcome, {user}!",
22
-    "sign out" => "Sign out",
23
-    "settings" => "Settings",
24
-    "options" => "Options",
25
-    "404 error" => "404 Error",
26
-    "page not found" => "Page not found.",
27
-    "invalid parameters" => "Invalid request parameters.",
28
-    "login server error" => "The login server returned an error: {arg}",
29
-    "login server user data error" => "The login server refused to provide account information.  Try again or contact technical support.",
30
-    "captcha error" => "There was a problem with the CAPTCHA (robot test).  Try again.",
31
-    "no access permission" => "You do not have permission to access this system.",
32
-    "home" => "Home",
33
-    "more" => "More",
34
-    "test" => "Test"
35
-]);

+ 26
- 0
langs/en/core.json Просмотреть файл

@@ -0,0 +1,26 @@
1
+{
2
+    "sign in": "Sign In",
3
+    "username": "Username",
4
+    "password": "Password",
5
+    "continue": "Continue",
6
+    "authcode": "Authentication code",
7
+    "2fa prompt": "Enter the six-digit code from your mobile authenticator app.",
8
+    "2fa incorrect": "Authentication code incorrect.",
9
+    "login incorrect": "Login incorrect.",
10
+    "login server unavailable": "Login server unavailable.  Try again later or contact technical support.",
11
+    "account locked": "This account has been disabled. Contact technical support.",
12
+    "password expired": "You must change your password before continuing.",
13
+    "account terminated": "Account terminated.  Access denied.",
14
+    "account state error": "Your account state is not stable.  Log out, restart your browser, and try again.",
15
+    "welcome user": "Welcome, {user}!",
16
+    "sign out": "Sign out",
17
+    "settings": "Settings",
18
+    "options": "Options",
19
+    "404 error": "404 Error",
20
+    "page not found": "Page not found.",
21
+    "invalid parameters": "Invalid request parameters.",
22
+    "login server error": "The login server returned an error: {arg}",
23
+    "login server user data error": "The login server refused to provide account information.  Try again or contact technical support.",
24
+    "captcha error": "There was a problem with the CAPTCHA (robot test).  Try again.",
25
+    "no access permission": "You do not have permission to access this system."
26
+}

+ 4
- 0
langs/en/titles.json Просмотреть файл

@@ -0,0 +1,4 @@
1
+{
2
+    "home": "Home",
3
+    "test": "Test"
4
+}

lang/messages.php → langs/messages.php Просмотреть файл


+ 13
- 0
lib/Exceptions.lib.php Просмотреть файл

@@ -0,0 +1,13 @@
1
+<?php
2
+
3
+/*
4
+ * This Source Code Form is subject to the terms of the Mozilla Public
5
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
6
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
+ */
8
+
9
+class IncorrectPasswordException extends Exception {
10
+    public function __construct(string $message = "Incorrect password.", int $code = 0, \Throwable $previous = null) {
11
+        parent::__construct($message, $code, $previous);
12
+    }
13
+}

+ 135
- 0
lib/IPUtils.lib.php Просмотреть файл

@@ -0,0 +1,135 @@
1
+<?php
2
+
3
+/* This Source Code Form is subject to the terms of the Mozilla Public
4
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
5
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
+
7
+class IPUtils {
8
+
9
+    /**
10
+     * Check if a given ipv4 address is in a given cidr
11
+     * @param  string $ip    IP to check in IPV4 format eg. 127.0.0.1
12
+     * @param  string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed
13
+     * @return boolean true if the ip is in this range / false if not.
14
+     * @author Thorsten Ott <https://gist.github.com/tott/7684443>
15
+     */
16
+    public static function ip4_in_cidr($ip, $cidr) {
17
+        if (strpos($cidr, '/') == false) {
18
+            $cidr .= '/32';
19
+        }
20
+        // $range is in IP/CIDR format eg 127.0.0.1/24
21
+        list( $cidr, $netmask ) = explode('/', $cidr, 2);
22
+        $range_decimal = ip2long($cidr);
23
+        $ip_decimal = ip2long($ip);
24
+        $wildcard_decimal = pow(2, ( 32 - $netmask)) - 1;
25
+        $netmask_decimal = ~ $wildcard_decimal;
26
+        return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) );
27
+    }
28
+
29
+    /**
30
+     * Check if a given ipv6 address is in a given cidr
31
+     * @param string $ip IP to check in IPV6 format
32
+     * @param string $cidr CIDR netmask
33
+     * @return boolean true if the IP is in this range, false otherwise.
34
+     * @author MW. <https://stackoverflow.com/a/7952169>
35
+     */
36
+    public static function ip6_in_cidr($ip, $cidr) {
37
+        $address = inet_pton($ip);
38
+        $subnetAddress = inet_pton(explode("/", $cidr)[0]);
39
+        $subnetMask = explode("/", $cidr)[1];
40
+
41
+        $addr = str_repeat("f", $subnetMask / 4);
42
+        switch ($subnetMask % 4) {
43
+            case 0:
44
+                break;
45
+            case 1:
46
+                $addr .= "8";
47
+                break;
48
+            case 2:
49
+                $addr .= "c";
50
+                break;
51
+            case 3:
52
+                $addr .= "e";
53
+                break;
54
+        }
55
+        $addr = str_pad($addr, 32, '0');
56
+        $addr = pack("H*", $addr);
57
+
58
+        $binMask = $addr;
59
+        return ($address & $binMask) == $subnetAddress;
60
+    }
61
+
62
+    /**
63
+     * Check if the REMOTE_ADDR is on Cloudflare's network.
64
+     * @return boolean true if it is, otherwise false
65
+     */
66
+    public static function validateCloudflare() {
67
+        if (filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
68
+            // Using IPv6
69
+            $cloudflare_ips_v6 = [
70
+                "2400:cb00::/32",
71
+                "2405:8100::/32",
72
+                "2405:b500::/32",
73
+                "2606:4700::/32",
74
+                "2803:f800::/32",
75
+                "2c0f:f248::/32",
76
+                "2a06:98c0::/29"
77
+            ];
78
+            $valid = false;
79
+            foreach ($cloudflare_ips_v6 as $cidr) {
80
+                if (ip6_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
81
+                    $valid = true;
82
+                    break;
83
+                }
84
+            }
85
+        } else {
86
+            // Using IPv4
87
+            $cloudflare_ips_v4 = [
88
+                "103.21.244.0/22",
89
+                "103.22.200.0/22",
90
+                "103.31.4.0/22",
91
+                "104.16.0.0/12",
92
+                "108.162.192.0/18",
93
+                "131.0.72.0/22",
94
+                "141.101.64.0/18",
95
+                "162.158.0.0/15",
96
+                "172.64.0.0/13",
97
+                "173.245.48.0/20",
98
+                "188.114.96.0/20",
99
+                "190.93.240.0/20",
100
+                "197.234.240.0/22",
101
+                "198.41.128.0/17"
102
+            ];
103
+            $valid = false;
104
+            foreach ($cloudflare_ips_v4 as $cidr) {
105
+                if (ip4_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
106
+                    $valid = true;
107
+                    break;
108
+                }
109
+            }
110
+        }
111
+        return $valid;
112
+    }
113
+
114
+    /**
115
+     * Makes a good guess at the client's real IP address.
116
+     *
117
+     * @return string Client IP or `0.0.0.0` if we can't find anything
118
+     */
119
+    public static function getClientIP() {
120
+        // If CloudFlare is in the mix, we should use it.
121
+        // Check if the request is actually from CloudFlare before trusting it.
122
+        if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
123
+            if (validateCloudflare()) {
124
+                return $_SERVER["HTTP_CF_CONNECTING_IP"];
125
+            }
126
+        }
127
+
128
+        if (isset($_SERVER["REMOTE_ADDR"])) {
129
+            return $_SERVER["REMOTE_ADDR"];
130
+        }
131
+
132
+        return "0.0.0.0"; // This will not happen unless we aren't a web server
133
+    }
134
+
135
+}

+ 129
- 0
lib/Login.lib.php Просмотреть файл

@@ -0,0 +1,129 @@
1
+<?php
2
+
3
+/*
4
+ * This Source Code Form is subject to the terms of the Mozilla Public
5
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
6
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
+ */
8
+
9
+class Login {
10
+
11
+    const BAD_USERPASS = 1;
12
+    const BAD_2FA = 2;
13
+    const ACCOUNT_DISABLED = 3;
14
+    const LOGIN_OK = 4;
15
+
16
+    public static function auth(string $username, string $password, string $twofa = ""): int {
17
+        global $database;
18
+        $username = strtolower($username);
19
+
20
+        $user = User::byUsername($username);
21
+
22
+        if (!$user->exists()) {
23
+            return Login::BAD_USERPASS;
24
+        }
25
+        if (!$user->checkPassword($password)) {
26
+            return Login::BAD_USERPASS;
27
+        }
28
+
29
+        if ($user->has2fa()) {
30
+            if (!$user->check2fa($twofa)) {
31
+                return Login::BAD_2FA;
32
+            }
33
+        }
34
+
35
+        switch ($user->getStatus()->get()) {
36
+            case AccountStatus::TERMINATED:
37
+                return Login::BAD_USERPASS;
38
+            case AccountStatus::LOCKED_OR_DISABLED:
39
+                return Login::ACCOUNT_DISABLED;
40
+            case AccountStatus::NORMAL:
41
+            default:
42
+                return Login::LOGIN_OK;
43
+        }
44
+
45
+        return Login::LOGIN_OK;
46
+    }
47
+
48
+    public static function verifyCaptcha(string $session, string $answer, string $url): bool {
49
+        $data = [
50
+            'session_id' => $session,
51
+            'answer_id' => $answer,
52
+            'action' => "verify"
53
+        ];
54
+        $options = [
55
+            'http' => [
56
+                'header' => "Content-type: application/x-www-form-urlencoded\r\n",
57
+                'method' => 'POST',
58
+                'content' => http_build_query($data)
59
+            ]
60
+        ];
61
+        $context = stream_context_create($options);
62
+        $result = file_get_contents($url, false, $context);
63
+        $resp = json_decode($result, TRUE);
64
+        if (!$resp['result']) {
65
+            return false;
66
+        } else {
67
+            return true;
68
+        }
69
+    }
70
+
71
+    /**
72
+     * Check the login server API for sanity
73
+     * @return boolean true if OK, else false
74
+     */
75
+    public static function checkLoginServer() {
76
+        try {
77
+            $client = new GuzzleHttp\Client();
78
+
79
+            $response = $client
80
+                    ->request('POST', PORTAL_API, [
81
+                'form_params' => [
82
+                    'key' => PORTAL_KEY,
83
+                    'action' => "ping"
84
+                ]
85
+            ]);
86
+
87
+            if ($response->getStatusCode() != 200) {
88
+                return false;
89
+            }
90
+
91
+            $resp = json_decode($response->getBody(), TRUE);
92
+            if ($resp['status'] == "OK") {
93
+                return true;
94
+            } else {
95
+                return false;
96
+            }
97
+        } catch (Exception $e) {
98
+            return false;
99
+        }
100
+    }
101
+
102
+    /**
103
+     * Checks if the given AccountHub API key is valid by attempting to
104
+     * access the API with it.
105
+     * @param String $key The API key to check
106
+     * @return boolean TRUE if the key is valid, FALSE if invalid or something went wrong
107
+     */
108
+    function checkAPIKey($key) {
109
+        try {
110
+            $client = new GuzzleHttp\Client();
111
+
112
+            $response = $client
113
+                    ->request('POST', PORTAL_API, [
114
+                'form_params' => [
115
+                    'key' => $key,
116
+                    'action' => "ping"
117
+                ]
118
+            ]);
119
+
120
+            if ($response->getStatusCode() === 200) {
121
+                return true;
122
+            }
123
+            return false;
124
+        } catch (Exception $e) {
125
+            return false;
126
+        }
127
+    }
128
+
129
+}

+ 65
- 0
lib/Notifications.lib.php Просмотреть файл

@@ -0,0 +1,65 @@
1
+<?php
2
+
3
+/*
4
+ * This Source Code Form is subject to the terms of the Mozilla Public
5
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
6
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
+ */
8
+
9
+class Notifications {
10
+
11
+    /**
12
+     * Add a new notification.
13
+     * @global $database
14
+     * @param User $user
15
+     * @param string $title
16
+     * @param string $content
17
+     * @param string $timestamp If left empty, the current date and time will be used.
18
+     * @param string $url
19
+     * @param bool $sensitive If true, the notification is marked as containing sensitive content, and the $content might be hidden on lockscreens and other non-secure places.
20
+     * @return int The newly-created notification ID.
21
+     * @throws Exception
22
+     */
23
+    public static function add(User $user, string $title, string $content, string $timestamp = "", string $url = "", bool $sensitive = false): int {
24
+        global $Strings;
25
+        if ($user->exists()) {
26
+            if (empty($title) || empty($content)) {
27
+                throw new Exception($Strings->get("invalid parameters", false));
28
+            }
29
+
30
+            $timestamp = date("Y-m-d H:i:s");
31
+            if (!empty($timestamp)) {
32
+                $timestamp = date("Y-m-d H:i:s", strtotime($timestamp));
33
+            }
34
+
35
+            $client = new GuzzleHttp\Client();
36
+
37
+            $response = $client
38
+                    ->request('POST', PORTAL_API, [
39
+                'form_params' => [
40
+                    'key' => PORTAL_KEY,
41
+                    'action' => "addnotification",
42
+                    'uid' => $user->getUID(),
43
+                    'title' => $title,
44
+                    'content' => $content,
45
+                    'timestamp' => $timestamp,
46
+                    'url' => $url,
47
+                    'sensitive' => $sensitive
48
+                ]
49
+            ]);
50
+
51
+            if ($response->getStatusCode() > 299) {
52
+                sendError("Login server error: " . $response->getBody());
53
+            }
54
+
55
+            $resp = json_decode($response->getBody(), TRUE);
56
+            if ($resp['status'] == "OK") {
57
+                return $resp['id'] * 1;
58
+            } else {
59
+                return false;
60
+            }
61
+        }
62
+        throw new Exception($Strings->get("user does not exist", false));
63
+    }
64
+
65
+}

+ 19
- 0
lib/Session.lib.php Просмотреть файл

@@ -0,0 +1,19 @@
1
+<?php
2
+
3
+/*
4
+ * This Source Code Form is subject to the terms of the Mozilla Public
5
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
6
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
+ */
8
+
9
+class Session {
10
+
11
+    public static function start(User $user) {
12
+        $_SESSION['username'] = $user->getUsername();
13
+        $_SESSION['uid'] = $user->getUID();
14
+        $_SESSION['email'] = $user->getEmail();
15
+        $_SESSION['realname'] = $user->getName();
16
+        $_SESSION['loggedin'] = true;
17
+    }
18
+
19
+}

+ 118
- 0
lib/Strings.lib.php Просмотреть файл

@@ -0,0 +1,118 @@
1
+<?php
2
+
3
+/*
4
+ * This Source Code Form is subject to the terms of the Mozilla Public
5
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
6
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
+ */
8
+
9
+/**
10
+ * Provides translated language strings.
11
+ */
12
+class Strings {
13
+
14
+    private $language = "en";
15
+    private $strings = [];
16
+
17
+    public function __construct($language = "en") {
18
+        if (!preg_match("/[a-zA-Z\_\-]+/", $language)) {
19
+            throw new Exception("Invalid language code $language");
20
+        }
21
+
22
+        $this->load("en");
23
+
24
+        if (file_exists(__DIR__ . "/../langs/$language/")) {
25
+            $this->language = $language;
26
+            $this->load($language);
27
+        } else {
28
+            trigger_error("Language $language could not be found.", E_USER_WARNING);
29
+        }
30
+    }
31
+
32
+    /**
33
+     * Load all JSON files for the specified language.
34
+     * @param string $language
35
+     */
36
+    private function load(string $language) {
37
+        $files = glob(__DIR__ . "/../langs/$language/*.json");
38
+        foreach ($files as $file) {
39
+            $strings = json_decode(file_get_contents($file), true);
40
+            foreach ($strings as $key => $val) {
41
+                if (array_key_exists($key, $this->strings)) {
42
+                    trigger_error("Language key \"$key\" is defined more than once.", E_USER_WARNING);
43
+                }
44
+                $this->strings[$key] = $val;
45
+            }
46
+        }
47
+    }
48
+
49
+    /**
50
+     * Add language strings dynamically.
51
+     * @param array $strings ["key" => "value", ...]
52
+     */
53
+    public function addStrings(array $strings) {
54
+        foreach ($strings as $key => $val) {
55
+            $this->strings[$key] = $val;
56
+        }
57
+    }
58
+
59
+    /**
60
+     * I18N string getter.  If the key isn't found, it outputs the key itself.
61
+     * @param string $key
62
+     * @param bool $echo True to echo the result, false to return it.  Default is true.
63
+     * @return string
64
+     */
65
+    public function get(string $key, bool $echo = true): string {
66
+        $str = $key;
67
+        if (array_key_exists($key, $this->strings)) {
68
+            $str = $this->strings[$key];
69
+        } else {
70
+            trigger_error("Language key \"$key\" does not exist in " . $this->language, E_USER_WARNING);
71
+        }
72
+
73
+        if ($echo) {
74
+            echo $str;
75
+        }
76
+        return $str;
77
+    }
78
+
79
+    /**
80
+     * I18N string getter (with builder).    If the key doesn't exist, outputs the key itself.
81
+     * @param string $key
82
+     * @param array $replace key-value array of replacements.
83
+     * If the string value is "hello {abc}" and you give ["abc" => "123"], the
84
+     * result will be "hello 123".
85
+     * @param bool $echo True to echo the result, false to return it.  Default is true.
86
+     * @return string
87
+     */
88
+    public function build(string $key, array $replace, bool $echo = true): string {
89
+        $str = $key;
90
+        if (array_key_exists($key, $this->strings)) {
91
+            $str = $this->strings[$key];
92
+        } else {
93
+            trigger_error("Language key \"$key\" does not exist in " . $this->language, E_USER_WARNING);
94
+        }
95
+
96
+        foreach ($replace as $find => $repl) {
97
+            $str = str_replace("{" . $find . "}", $repl, $str);
98
+        }
99
+
100
+        if ($echo) {
101
+            echo $str;
102
+        }
103
+        return $str;
104
+    }
105
+
106
+    /**
107
+     * Builds and returns a JSON key:value string for the supplied array of keys.
108
+     * @param array $keys ["key1", "key2", ...]
109
+     */
110
+    public function getJSON(array $keys): string {
111
+        $strings = [];
112
+        foreach ($keys as $k) {
113
+            $strings[$k] = $this->get($k, false);
114
+        }
115
+        return json_encode($strings);
116
+    }
117
+
118
+}

+ 352
- 0
lib/User.lib.php Просмотреть файл

@@ -0,0 +1,352 @@
1
+<?php
2
+
3
+/*
4
+ * This Source Code Form is subject to the terms of the Mozilla Public
5
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
6
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
+ */
8
+
9
+class User {
10
+
11
+    private $uid = null;
12
+    private $username;
13
+    private $email;
14
+    private $realname;
15
+    private $has2fa = false;
16
+    private $exists = false;
17
+
18
+    public function __construct(int $uid, string $username = "") {
19
+        // Check if user exists
20
+        $client = new GuzzleHttp\Client();
21
+
22
+        $response = $client
23
+                ->request('POST', PORTAL_API, [
24
+            'form_params' => [
25
+                'key' => PORTAL_KEY,
26
+                'action' => "userexists",
27
+                'uid' => $uid
28
+            ]
29
+        ]);
30
+
31
+        if ($response->getStatusCode() > 299) {
32
+            sendError("Login server error: " . $response->getBody());
33
+        }
34
+
35
+        $resp = json_decode($response->getBody(), TRUE);
36
+        if ($resp['status'] == "OK" && $resp['exists'] === true) {
37
+            $this->exists = true;
38
+        } else {
39
+            $this->uid = $uid;
40
+            $this->username = $username;
41
+            $this->exists = false;
42
+        }
43
+
44
+        if ($this->exists) {
45
+            // Get user info
46
+            $client = new GuzzleHttp\Client();
47
+
48
+            $response = $client
49
+                    ->request('POST', PORTAL_API, [
50
+                'form_params' => [
51
+                    'key' => PORTAL_KEY,
52
+                    'action' => "userinfo",
53
+                    'uid' => $uid
54
+                ]
55
+            ]);
56
+
57
+            if ($response->getStatusCode() > 299) {
58
+                sendError("Login server error: " . $response->getBody());
59
+            }
60
+
61
+            $resp = json_decode($response->getBody(), TRUE);
62
+            if ($resp['status'] == "OK") {
63
+                $this->uid = $resp['data']['uid'] * 1;
64
+                $this->username = $resp['data']['username'];
65
+                $this->email = $resp['data']['email'];
66
+                $this->realname = $resp['data']['name'];
67
+            } else {
68
+                sendError("Login server error: " . $resp['msg']);
69
+            }
70
+        }
71
+    }
72
+
73
+    public static function byUsername(string $username): User {
74
+        $client = new GuzzleHttp\Client();
75
+
76
+        $response = $client
77
+                ->request('POST', PORTAL_API, [
78
+            'form_params' => [
79
+                'key' => PORTAL_KEY,
80
+                'username' => $username,
81
+                'action' => "userinfo"
82
+            ]
83
+        ]);
84
+
85
+        if ($response->getStatusCode() > 299) {
86
+            sendError("Login server error: " . $response->getBody());
87
+        }
88
+
89
+        $resp = json_decode($response->getBody(), TRUE);
90
+        if (!isset($resp['status'])) {
91
+            sendError("Login server error: " . $resp);
92
+        }
93
+        if ($resp['status'] == "OK") {
94
+            return new self($resp['data']['uid'] * 1);
95
+        } else {
96
+            return new self(-1, $username);
97
+        }
98
+    }
99
+
100
+    public function exists(): bool {
101
+        return $this->exists;
102
+    }
103
+
104
+    public function has2fa(): bool {
105
+        if (!$this->exists) {
106
+            return false;
107
+        }
108
+        $client = new GuzzleHttp\Client();
109
+
110
+        $response = $client
111
+                ->request('POST', PORTAL_API, [
112
+            'form_params' => [
113
+                'key' => PORTAL_KEY,
114
+                'action' => "hastotp",
115
+                'username' => $this->username
116
+            ]
117
+        ]);
118
+
119
+        if ($response->getStatusCode() > 299) {
120
+            sendError("Login server error: " . $response->getBody());
121
+        }
122
+
123
+        $resp = json_decode($response->getBody(), TRUE);
124
+        if ($resp['status'] == "OK") {
125
+            return $resp['otp'] == true;
126
+        } else {
127
+            return false;
128
+        }
129
+    }
130
+
131
+    function getUsername() {
132
+        return $this->username;
133
+    }
134
+
135
+    function getUID() {
136
+        return $this->uid;
137
+    }
138
+
139
+    function getEmail() {
140
+        return $this->email;
141
+    }
142
+
143
+    function getName() {
144
+        return $this->realname;
145
+    }
146
+
147
+    /**
148
+     * Check the given plaintext password against the stored hash.
149
+     * @param string $password
150
+     * @return bool
151
+     */
152
+    function checkPassword(string $password): bool {
153
+        $client = new GuzzleHttp\Client();
154
+
155
+        $response = $client
156
+                ->request('POST', PORTAL_API, [
157
+            'form_params' => [
158
+                'key' => PORTAL_KEY,
159
+                'action' => "auth",
160
+                'username' => $this->username,
161
+                'password' => $password
162
+            ]
163
+        ]);
164
+
165
+        if ($response->getStatusCode() > 299) {
166
+            sendError("Login server error: " . $response->getBody());
167
+        }
168
+
169
+        $resp = json_decode($response->getBody(), TRUE);
170
+        if ($resp['status'] == "OK") {
171
+            return true;
172
+        } else {
173
+            return false;
174
+        }
175
+    }
176
+
177
+    function check2fa(string $code): bool {
178
+        if (!$this->has2fa) {
179
+            return true;
180
+        }
181
+        $client = new GuzzleHttp\Client();
182
+
183
+        $response = $client
184
+                ->request('POST', PORTAL_API, [
185
+            'form_params' => [
186
+                'key' => PORTAL_KEY,
187
+                'action' => "verifytotp",
188
+                'username' => $this->username,
189
+                'code' => $code
190
+            ]
191
+        ]);
192
+
193
+        if ($response->getStatusCode() > 299) {
194
+            sendError("Login server error: " . $response->getBody());
195
+        }
196
+
197
+        $resp = json_decode($response->getBody(), TRUE);
198
+        if ($resp['status'] == "OK") {
199
+            return $resp['valid'];
200
+        } else {
201
+            return false;
202
+        }
203
+    }
204
+
205
+    /**
206
+     * Check if the given username has the given permission (or admin access)
207
+     * @global $database $database
208
+     * @param string $code
209
+     * @return boolean TRUE if the user has the permission (or admin access), else FALSE
210
+     */
211
+    function hasPermission(string $code): bool {
212
+        $client = new GuzzleHttp\Client();
213
+
214
+        $response = $client
215
+                ->request('POST', PORTAL_API, [
216
+            'form_params' => [
217
+                'key' => PORTAL_KEY,
218
+                'action' => "permission",
219
+                'username' => $this->username,
220
+                'code' => $code
221
+            ]
222
+        ]);
223
+
224
+        if ($response->getStatusCode() > 299) {
225
+            sendError("Login server error: " . $response->getBody());
226
+        }
227
+
228
+        $resp = json_decode($response->getBody(), TRUE);
229
+        if ($resp['status'] == "OK") {
230
+            return $resp['has_permission'];
231
+        } else {
232
+            return false;
233
+        }
234
+    }
235
+
236
+    /**
237
+     * Get the account status.
238
+     * @return \AccountStatus
239
+     */
240
+    function getStatus(): AccountStatus {
241
+
242
+        $client = new GuzzleHttp\Client();
243
+
244
+        $response = $client
245
+                ->request('POST', PORTAL_API, [
246
+            'form_params' => [
247
+                'key' => PORTAL_KEY,
248
+                'action' => "acctstatus",
249
+                'username' => $this->username
250
+            ]
251
+        ]);
252
+
253
+        if ($response->getStatusCode() > 299) {
254
+            sendError("Login server error: " . $response->getBody());
255
+        }
256
+
257
+        $resp = json_decode($response->getBody(), TRUE);
258
+        if ($resp['status'] == "OK") {
259
+            return AccountStatus::fromString($resp['account']);
260
+        } else {
261
+            return null;
262
+        }
263
+    }
264
+
265
+    function sendAlertEmail(string $appname = SITE_TITLE) {
266
+        $client = new GuzzleHttp\Client();
267
+
268
+        $response = $client
269
+                ->request('POST', PORTAL_API, [
270
+            'form_params' => [
271
+                'key' => PORTAL_KEY,
272
+                'action' => "alertemail",
273
+                'username' => $this->username,
274
+                'appname' => SITE_TITLE
275
+            ]
276
+        ]);
277
+
278
+        if ($response->getStatusCode() > 299) {
279
+            return "An unknown error occurred.";
280
+        }
281
+
282
+        $resp = json_decode($response->getBody(), TRUE);
283
+        if ($resp['status'] == "OK") {
284
+            return true;
285
+        } else {
286
+            return $resp['msg'];
287
+        }
288
+    }
289
+
290
+}
291
+
292
+class AccountStatus {
293
+
294
+    const NORMAL = 1;
295
+    const LOCKED_OR_DISABLED = 2;
296
+    const CHANGE_PASSWORD = 3;
297
+    const TERMINATED = 4;
298
+    const ALERT_ON_ACCESS = 5;
299
+
300
+    private $status;
301
+
302
+    public function __construct(int $status) {
303
+        $this->status = $status;
304
+    }
305
+
306
+    public static function fromString(string $status): AccountStatus {
307
+        switch ($status) {
308
+            case "NORMAL":
309
+                return new self(self::NORMAL);
310
+            case "LOCKED_OR_DISABLED":
311
+                return new self(self::LOCKED_OR_DISABLED);
312
+            case "CHANGE_PASSWORD":
313
+                return new self(self::CHANGE_PASSWORD);
314
+            case "TERMINATED":
315
+                return new self(self::TERMINATED);
316
+            case "ALERT_ON_ACCESS":
317
+                return new self(self::ALERT_ON_ACCESS);
318
+            default:
319
+                return new self(0);
320
+        }
321
+    }
322
+
323
+    /**
324
+     * Get the account status/state as an integer.
325
+     * @return int
326
+     */
327
+    public function get(): int {
328
+        return $this->status;
329
+    }
330
+
331
+    /**
332
+     * Get the account status/state as a string representation.
333
+     * @return string
334
+     */
335
+    public function getString(): string {
336
+        switch ($this->status) {
337
+            case self::NORMAL:
338
+                return "NORMAL";
339
+            case self::LOCKED_OR_DISABLED:
340
+                return "LOCKED_OR_DISABLED";
341
+            case self::CHANGE_PASSWORD:
342
+                return "CHANGE_PASSWORD";
343
+            case self::TERMINATED:
344
+                return "TERMINATED";
345
+            case self::ALERT_ON_ACCESS:
346
+                return "ALERT_ON_ACCESS";
347
+            default:
348
+                return "OTHER_" . $this->status;
349
+        }
350
+    }
351
+
352
+}

+ 0
- 131
lib/iputils.php Просмотреть файл

@@ -1,131 +0,0 @@
1
-<?php
2
-
3
-/* This Source Code Form is subject to the terms of the Mozilla Public
4
- * License, v. 2.0. If a copy of the MPL was not distributed with this
5
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
-
7
-/**
8
- * Check if a given ipv4 address is in a given cidr
9
- * @param  string $ip    IP to check in IPV4 format eg. 127.0.0.1
10
- * @param  string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed
11
- * @return boolean true if the ip is in this range / false if not.
12
- * @author Thorsten Ott <https://gist.github.com/tott/7684443>
13
- */
14
-function ip4_in_cidr($ip, $cidr) {
15
-    if (strpos($cidr, '/') == false) {
16
-        $cidr .= '/32';
17
-    }
18
-    // $range is in IP/CIDR format eg 127.0.0.1/24
19
-    list( $cidr, $netmask ) = explode('/', $cidr, 2);
20
-    $range_decimal = ip2long($cidr);
21
-    $ip_decimal = ip2long($ip);
22
-    $wildcard_decimal = pow(2, ( 32 - $netmask)) - 1;
23
-    $netmask_decimal = ~ $wildcard_decimal;
24
-    return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) );
25
-}
26
-
27
-/**
28
- * Check if a given ipv6 address is in a given cidr
29
- * @param string $ip IP to check in IPV6 format
30
- * @param string $cidr CIDR netmask
31
- * @return boolean true if the IP is in this range, false otherwise.
32
- * @author MW. <https://stackoverflow.com/a/7952169>
33
- */
34
-function ip6_in_cidr($ip, $cidr) {
35
-    $address = inet_pton($ip);
36
-    $subnetAddress = inet_pton(explode("/", $cidr)[0]);
37
-    $subnetMask = explode("/", $cidr)[1];
38
-
39
-    $addr = str_repeat("f", $subnetMask / 4);
40
-    switch ($subnetMask % 4) {
41
-        case 0:
42
-            break;
43
-        case 1:
44
-            $addr .= "8";
45
-            break;
46
-        case 2:
47
-            $addr .= "c";
48
-            break;
49
-        case 3:
50
-            $addr .= "e";
51
-            break;
52
-    }
53
-    $addr = str_pad($addr, 32, '0');
54
-    $addr = pack("H*", $addr);
55
-
56
-    $binMask = $addr;
57
-    return ($address & $binMask) == $subnetAddress;
58
-}
59
-
60
-/**
61
- * Check if the REMOTE_ADDR is on Cloudflare's network.
62
- * @return boolean true if it is, otherwise false
63
- */
64
-function validateCloudflare() {
65
-    if (filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
66
-        // Using IPv6
67
-        $cloudflare_ips_v6 = [
68
-            "2400:cb00::/32",
69
-            "2405:8100::/32",
70
-            "2405:b500::/32",
71
-            "2606:4700::/32",
72
-            "2803:f800::/32",
73
-            "2c0f:f248::/32",
74
-            "2a06:98c0::/29"
75
-        ];
76
-        $valid = false;
77
-        foreach ($cloudflare_ips_v6 as $cidr) {
78
-            if (ip6_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
79
-                $valid = true;
80
-                break;
81
-            }
82
-        }
83
-    } else {
84
-        // Using IPv4
85
-        $cloudflare_ips_v4 = [
86
-            "103.21.244.0/22",
87
-            "103.22.200.0/22",
88
-            "103.31.4.0/22",
89
-            "104.16.0.0/12",
90
-            "108.162.192.0/18",
91
-            "131.0.72.0/22",
92
-            "141.101.64.0/18",
93
-            "162.158.0.0/15",
94
-            "172.64.0.0/13",
95
-            "173.245.48.0/20",
96
-            "188.114.96.0/20",
97
-            "190.93.240.0/20",
98
-            "197.234.240.0/22",
99
-            "198.41.128.0/17"
100
-        ];
101
-        $valid = false;
102
-        foreach ($cloudflare_ips_v4 as $cidr) {
103
-            if (ip4_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
104
-                $valid = true;
105
-                break;
106
-            }
107
-        }
108
-    }
109
-    return $valid;
110
-}
111
-
112
-/**
113
- * Makes a good guess at the client's real IP address.
114
- *
115
- * @return string Client IP or `0.0.0.0` if we can't find anything
116
- */
117
-function getClientIP() {
118
-    // If CloudFlare is in the mix, we should use it.
119
-    // Check if the request is actually from CloudFlare before trusting it.
120
-    if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
121
-        if (validateCloudflare()) {
122
-            return $_SERVER["HTTP_CF_CONNECTING_IP"];
123
-        }
124
-    }
125
-
126
-    if (isset($_SERVER["REMOTE_ADDR"])) {
127
-        return $_SERVER["REMOTE_ADDR"];
128
-    }
129
-
130
-    return "0.0.0.0"; // This will not happen unless we aren't a web server
131
-}

+ 0
- 402
lib/login.php Просмотреть файл

@@ -1,402 +0,0 @@
1
-<?php
2
-
3
-/* This Source Code Form is subject to the terms of the Mozilla Public
4
- * License, v. 2.0. If a copy of the MPL was not distributed with this
5
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
-
7
-/**
8
- * Authentication and account functions.  Connects to an AccountHub instance.
9
- */
10
-
11
-/**
12
- * Check the login server API for sanity
13
- * @return boolean true if OK, else false
14
- */
15
-function checkLoginServer() {
16
-    try {
17
-        $client = new GuzzleHttp\Client();
18
-
19
-        $response = $client
20
-                ->request('POST', PORTAL_API, [
21
-            'form_params' => [
22
-                'key' => PORTAL_KEY,
23
-                'action' => "ping"
24
-            ]
25
-        ]);
26
-
27
-        if ($response->getStatusCode() != 200) {
28
-            return false;
29
-        }
30
-
31
-        $resp = json_decode($response->getBody(), TRUE);
32
-        if ($resp['status'] == "OK") {
33
-            return true;
34
-        } else {
35
-            return false;
36
-        }
37
-    } catch (Exception $e) {
38
-        return false;
39
-    }
40
-}
41
-
42
-/**
43
- * Checks if the given AccountHub API key is valid by attempting to
44
- * access the API with it.
45
- * @param String $key The API key to check
46
- * @return boolean TRUE if the key is valid, FALSE if invalid or something went wrong
47
- */
48
-function checkAPIKey($key) {
49
-    try {
50
-        $client = new GuzzleHttp\Client();
51
-
52
-        $response = $client
53
-                ->request('POST', PORTAL_API, [
54
-            'form_params' => [
55
-                'key' => $key,
56
-                'action' => "ping"
57
-            ]
58
-        ]);
59
-
60
-        if ($response->getStatusCode() === 200) {
61
-            return true;
62
-        }
63
-        return false;
64
-    } catch (Exception $e) {
65
-        return false;
66
-    }
67
-}
68
-
69
-////////////////////////////////////////////////////////////////////////////////
70
-//                           Account handling                                 //
71
-////////////////////////////////////////////////////////////////////////////////
72
-
73
-/**
74
- * Checks the given credentials against the API.
75
- * @param string $username
76
- * @param string $password
77
- * @return boolean True if OK, else false
78
- */
79
-function authenticate_user($username, $password, &$errmsg) {
80
-    $client = new GuzzleHttp\Client();
81
-
82
-    $response = $client
83
-            ->request('POST', PORTAL_API, [
84
-        'form_params' => [
85
-            'key' => PORTAL_KEY,
86
-            'action' => "auth",
87
-            'username' => $username,
88
-            'password' => $password
89
-        ]
90
-    ]);
91
-
92
-    if ($response->getStatusCode() > 299) {
93
-        sendError("Login server error: " . $response->getBody());
94
-    }
95
-
96
-    $resp = json_decode($response->getBody(), TRUE);
97
-    if ($resp['status'] == "OK") {
98
-        return true;
99
-    } else {
100
-        $errmsg = $resp['msg'];
101
-        return false;
102
-    }
103
-}
104
-
105
-/**
106
- * Check if a username exists.
107
- * @param String $username
108
- */
109
-function user_exists($username) {
110
-    $client = new GuzzleHttp\Client();
111
-
112
-    $response = $client
113
-            ->request('POST', PORTAL_API, [
114
-        'form_params' => [
115
-            'key' => PORTAL_KEY,
116
-            'action' => "userexists",
117
-            'username' => $username
118
-        ]
119
-    ]);
120
-
121
-    if ($response->getStatusCode() > 299) {
122
-        sendError("Login server error: " . $response->getBody());
123
-    }
124
-
125
-    $resp = json_decode($response->getBody(), TRUE);
126
-    if ($resp['status'] == "OK" && $resp['exists'] === true) {
127
-        return true;
128
-    } else {
129
-        return false;
130
-    }
131
-}
132
-
133
-/**
134
- * Check if a UID exists.
135
- * @param String $uid
136
- */
137
-function uid_exists($uid) {
138
-    $client = new GuzzleHttp\Client();
139
-
140
-    $response = $client
141
-            ->request('POST', PORTAL_API, [
142
-        'form_params' => [
143
-            'key' => PORTAL_KEY,
144
-            'action' => "userexists",
145
-            'uid' => $uid
146
-        ]
147
-    ]);
148
-
149
-    if ($response->getStatusCode() > 299) {
150
-        sendError("Login server error: " . $response->getBody());
151
-    }
152
-
153
-    $resp = json_decode($response->getBody(), TRUE);
154
-    if ($resp['status'] == "OK" && $resp['exists'] === true) {
155
-        return true;
156
-    } else {
157
-        return false;
158
-    }
159
-}
160
-
161
-/**
162
- * Get the account status: NORMAL, TERMINATED, LOCKED_OR_DISABLED,
163
- * CHANGE_PASSWORD, or ALERT_ON_ACCESS
164
- * @param string $username
165
- * @return string
166
- */
167
-function get_account_status($username) {
168
-    $client = new GuzzleHttp\Client();
169
-
170
-    $response = $client
171
-            ->request('POST', PORTAL_API, [
172
-        'form_params' => [
173
-            'key' => PORTAL_KEY,
174
-            'action' => "acctstatus",
175
-            'username' => $username
176
-        ]
177
-    ]);
178
-
179
-    if ($response->getStatusCode() > 299) {
180
-        sendError("Login server error: " . $response->getBody());
181
-    }
182
-
183
-    $resp = json_decode($response->getBody(), TRUE);
184
-    if ($resp['status'] == "OK") {
185
-        return $resp['account'];
186
-    } else {
187
-        return false;
188
-    }
189
-}
190
-
191
-/**
192
- * Check if the given username has the given permission (or admin access)
193
- * @param string $username
194
- * @param string $permcode
195
- * @return boolean TRUE if the user has the permission (or admin access), else FALSE
196
- */
197
-function account_has_permission($username, $permcode) {
198
-    $client = new GuzzleHttp\Client();
199
-
200
-    $response = $client
201
-            ->request('POST', PORTAL_API, [
202
-        'form_params' => [
203
-            'key' => PORTAL_KEY,
204
-            'action' => "permission",
205
-            'username' => $username,
206
-            'code' => $permcode
207
-        ]
208
-    ]);
209
-
210
-    if ($response->getStatusCode() > 299) {
211
-        sendError("Login server error: " . $response->getBody());
212
-    }
213
-
214
-    $resp = json_decode($response->getBody(), TRUE);
215
-    if ($resp['status'] == "OK") {
216
-        return $resp['has_permission'];
217
-    } else {
218
-        return false;
219
-    }
220
-}
221
-
222
-////////////////////////////////////////////////////////////////////////////////
223
-//                              Login handling                                //
224
-////////////////////////////////////////////////////////////////////////////////
225
-
226
-/**
227
- * Setup $_SESSION values with user data and set loggedin flag to true
228
- * @param string $username
229
- */
230
-function doLoginUser($username) {
231
-    $client = new GuzzleHttp\Client();
232
-
233
-    $response = $client
234
-            ->request('POST', PORTAL_API, [
235
-        'form_params' => [
236
-            'key' => PORTAL_KEY,
237
-            'action' => "userinfo",
238
-            'username' => $username
239
-        ]
240
-    ]);
241
-
242
-    if ($response->getStatusCode() > 299) {
243
-        sendError("Login server error: " . $response->getBody());
244
-    }
245
-
246
-    $resp = json_decode($response->getBody(), TRUE);
247
-
248
-    if ($resp['status'] == "OK") {
249
-        $userinfo = $resp['data'];
250
-        session_regenerate_id(true);
251
-        $newSession = session_id();
252
-        session_write_close();
253
-        session_id($newSession);
254
-        session_start();
255
-        $_SESSION['username'] = $username;
256
-        $_SESSION['uid'] = $userinfo['uid'];
257
-        $_SESSION['email'] = $userinfo['email'];
258
-        $_SESSION['realname'] = $userinfo['name'];
259
-        $_SESSION['loggedin'] = true;
260
-        return true;
261
-    } else {
262
-        return false;
263
-    }
264
-}
265
-
266
-function sendLoginAlertEmail($username) {
267
-    $client = new GuzzleHttp\Client();
268
-
269
-    $response = $client
270
-            ->request('POST', PORTAL_API, [
271
-        'form_params' => [
272
-            'key' => PORTAL_KEY,
273
-            'action' => "alertemail",
274
-            'username' => $username,
275
-            'appname' => SITE_TITLE
276
-        ]
277
-    ]);
278
-
279
-    if ($response->getStatusCode() > 299) {
280
-        return "An unknown error occurred.";
281
-    }
282
-
283
-    $resp = json_decode($response->getBody(), TRUE);
284
-    if ($resp['status'] == "OK") {
285
-        return true;
286
-    } else {
287
-        return $resp['msg'];
288
-    }
289
-}
290
-
291
-function simLogin($username, $password) {
292
-    $client = new GuzzleHttp\Client();
293
-
294
-    $response = $client
295
-            ->request('POST', PORTAL_API, [
296
-        'form_params' => [
297
-            'key' => PORTAL_KEY,
298
-            'action' => "login",
299
-            'username' => $username,
300
-            'password' => $password
301
-        ]
302
-    ]);
303
-
304
-    if ($response->getStatusCode() > 299) {
305
-        sendError("Login server error: " . $response->getBody());
306
-    }
307
-
308
-    $resp = json_decode($response->getBody(), TRUE);
309
-    if ($resp['status'] == "OK") {
310
-        return true;
311
-    } else {
312
-        return $resp['msg'];
313
-    }
314
-}
315
-
316
-function verifyCaptcheck($session, $answer, $url) {
317
-    $data = [
318
-        'session_id' => $session,
319
-        'answer_id' => $answer,
320
-        'action' => "verify"
321
-    ];
322
-    $options = [
323
-        'http' => [
324
-            'header' => "Content-type: application/x-www-form-urlencoded\r\n",
325
-            'method' => 'POST',
326
-            'content' => http_build_query($data)
327
-        ]
328
-    ];
329
-    $context = stream_context_create($options);
330
-    $result = file_get_contents($url, false, $context);
331
-    $resp = json_decode($result, TRUE);
332
-    if (!$resp['result']) {
333
-        return false;
334
-    } else {
335
-        return true;
336
-    }
337
-}
338
-
339
-////////////////////////////////////////////////////////////////////////////////
340
-//                          2-factor authentication                           //
341
-////////////////////////////////////////////////////////////////////////////////
342
-
343
-/**
344
- * Check if a user has TOTP setup
345
- * @param string $username
346
- * @return boolean true if TOTP secret exists, else false
347
- */
348
-function userHasTOTP($username) {
349
-    $client = new GuzzleHttp\Client();
350
-
351
-    $response = $client
352
-            ->request('POST', PORTAL_API, [
353
-        'form_params' => [
354
-            'key' => PORTAL_KEY,
355
-            'action' => "hastotp",
356
-            'username' => $username
357
-        ]
358
-    ]);
359
-
360
-    if ($response->getStatusCode() > 299) {
361
-        sendError("Login server error: " . $response->getBody());
362
-    }
363
-
364
-    $resp = json_decode($response->getBody(), TRUE);
365
-    if ($resp['status'] == "OK") {
366
-        return $resp['otp'];
367
-    } else {
368
-        return false;
369
-    }
370
-}
371
-
372
-/**
373
- * Verify a TOTP multiauth code
374
- * @global $database
375
- * @param string $username
376
- * @param int $code
377
- * @return boolean true if it's legit, else false
378
- */
379
-function verifyTOTP($username, $code) {
380
-    $client = new GuzzleHttp\Client();
381
-
382
-    $response = $client
383
-            ->request('POST', PORTAL_API, [
384
-        'form_params' => [
385
-            'key' => PORTAL_KEY,
386
-            'action' => "verifytotp",
387
-            'username' => $username,
388
-            'code' => $code
389
-        ]
390
-    ]);
391
-
392
-    if ($response->getStatusCode() > 299) {
393
-        sendError("Login server error: " . $response->getBody());
394
-    }
395
-
396
-    $resp = json_decode($response->getBody(), TRUE);
397
-    if ($resp['status'] == "OK") {
398
-        return $resp['valid'];
399
-    } else {
400
-        return false;
401
-    }
402
-}

+ 0
- 127
lib/userinfo.php Просмотреть файл

@@ -1,127 +0,0 @@
1
-<?php
2
-
3
-/* This Source Code Form is subject to the terms of the Mozilla Public
4
- * License, v. 2.0. If a copy of the MPL was not distributed with this
5
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
-
7
-/**
8
- * Get user info for the given username.
9
- * @param int $u username
10
- * @return [string] Array of [uid, username, name]
11
- */
12
-function getUserByUsername($u) {
13
-    $client = new GuzzleHttp\Client();
14
-
15
-    $response = $client
16
-            ->request('POST', PORTAL_API, [
17
-        'form_params' => [
18
-            'key' => PORTAL_KEY,
19
-            'action' => "userinfo",
20
-            'username' => $u
21
-        ]
22
-    ]);
23
-
24
-    if ($response->getStatusCode() > 299) {
25
-        sendError("Login server error: " . $response->getBody());
26
-    }
27
-
28
-    $resp = json_decode($response->getBody(), TRUE);
29
-    if ($resp['status'] == "OK") {
30
-        return $resp['data'];
31
-    } else {
32
-        // this shouldn't happen, but in case it does just fake it.
33
-        return ["name" => $u, "username" => $u, "uid" => $u];
34
-    }
35
-}
36
-
37
-/**
38
- * Get user info for the given UID.
39
- * @param int $u user ID
40
- * @return [string] Array of [uid, username, name]
41
- */
42
-function getUserByID($u) {
43
-    $client = new GuzzleHttp\Client();
44
-
45
-    $response = $client
46
-            ->request('POST', PORTAL_API, [
47
-        'form_params' => [
48
-            'key' => PORTAL_KEY,
49
-            'action' => "userinfo",
50
-            'uid' => $u
51
-        ]
52
-    ]);
53
-
54
-    if ($response->getStatusCode() > 299) {
55
-        sendError("Login server error: " . $response->getBody());
56
-    }
57
-
58
-    $resp = json_decode($response->getBody(), TRUE);
59
-    if ($resp['status'] == "OK") {
60
-        return $resp['data'];
61
-    } else {
62
-        // this shouldn't happen, but in case it does just fake it.
63
-        return ["name" => $u, "username" => $u, "uid" => $u];
64
-    }
65
-}
66
-
67
-/**
68
- * Check if the first UID is a manager of the second UID.
69
- * @param int $m Manager UID
70
- * @param int $e Employee UID
71
- * @return boolean
72
- */
73
-function isManagerOf($m, $e) {
74
-    $client = new GuzzleHttp\Client();
75
-
76
-    $response = $client
77
-            ->request('POST', PORTAL_API, [
78
-        'form_params' => [
79
-            'key' => PORTAL_KEY,
80
-            'action' => "ismanagerof",
81
-            'manager' => $m,
82
-            'employee' => $e,
83
-            'uid' => 1
84
-        ]
85
-    ]);
86
-
87
-    if ($response->getStatusCode() > 299) {
88
-        sendError("Login server error: " . $response->getBody());
89
-    }
90
-
91
-    $resp = json_decode($response->getBody(), TRUE);
92
-    if ($resp['status'] == "OK") {
93
-        return $resp['managerof'] === true;
94
-    } else {
95
-        // this shouldn't happen, but in case it does just fake it.
96
-        return false;
97
-    }
98
-}
99
-
100
-/**
101
- * Get an array of UIDs the given UID is a manager of.
102
- * @param int $manageruid The UID of the manager to find employees for.
103
- * @return [int]
104
- */
105
-function getManagedUIDs($manageruid) {
106
-    $client = new GuzzleHttp\Client();
107
-
108
-    $response = $client
109
-            ->request('POST', PORTAL_API, [
110
-        'form_params' => [
111
-            'key' => PORTAL_KEY,
112
-            'action' => "getmanaged",
113
-            'uid' => $manageruid
114
-        ]
115
-    ]);
116
-
117
-    if ($response->getStatusCode() > 299) {
118
-        sendError("Login server error: " . $response->getBody());
119
-    }
120
-
121
-    $resp = json_decode($response->getBody(), TRUE);
122
-    if ($resp['status'] == "OK") {
123
-        return $resp['employees'];
124
-    } else {
125
-        return [];
126
-    }
127
-}

+ 9
- 10
mobile/index.php Просмотреть файл

@@ -14,8 +14,6 @@ $access_permission = null;
14 14
 
15 15
 require __DIR__ . "/../required.php";
16 16
 
17
-require __DIR__ . "/../lib/login.php";
18
-
19 17
 header('Content-Type: application/json');
20 18
 header('Access-Control-Allow-Origin: *');
21 19
 
@@ -73,7 +71,7 @@ function mobile_valid($username, $code) {
73 71
 }
74 72
 
75 73
 if (mobile_enabled() !== TRUE) {
76
-    exit(json_encode(["status" => "ERROR", "msg" => lang("mobile login disabled", false)]));
74
+    exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("mobile login disabled", false)]));
77 75
 }
78 76
 
79 77
 // Make sure we have a username and access key
@@ -93,20 +91,21 @@ if (!mobile_valid($VARS['username'], $VARS['key'])) {
93 91
 switch ($VARS['action']) {
94 92
     case "start_session":
95 93
         // Do a web login.
96
-        if (user_exists($VARS['username'])) {
97
-            if (get_account_status($VARS['username']) == "NORMAL") {
98
-                if (authenticate_user($VARS['username'], $VARS['password'], $autherror)) {
99
-                    if (is_null($access_permission) || account_has_permission($VARS['username'], $access_permission)) {
100
-                        doLoginUser($VARS['username'], $VARS['password']);
94
+        $user = User::byUsername($VARS['username']);
95
+        if ($user->exists()) {
96
+            if ($user->getStatus()->getString() == "NORMAL") {
97
+                if ($user->checkPassword($VARS['password'])) {
98
+                    if (is_null($access_permission) || $user->hasPermission($access_permission)) {
99
+                        Session::start($user);
101 100
                         $_SESSION['mobile'] = true;
102 101
                         exit(json_encode(["status" => "OK"]));
103 102
                     } else {
104
-                        exit(json_encode(["status" => "ERROR", "msg" => lang("no admin permission", false)]));
103
+                        exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("no admin permission", false)]));
105 104
                     }
106 105
                 }
107 106
             }
108 107
         }
109
-        exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
108
+        exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
110 109
     default:
111 110
         http_response_code(404);
112 111
         die(json_encode(["status" => "ERROR", "msg" => "The requested action is not available."]));

+ 1
- 1
pages/404.php Просмотреть файл

@@ -5,6 +5,6 @@
5 5
 ?>
6 6
 <div class="row justify-content-center">
7 7
     <div class="col-12 col-sm-10 col-md-8 col-lg-6">
8
-        <div class="alert alert-warning"><b><?php lang("404 error");?></b><br /> <?php lang("page not found"); ?></div>
8
+        <div class="alert alert-warning"><b><?php $Strings->get("404 error");?></b><br /> <?php $Strings->get("page not found"); ?></div>
9 9
     </div>
10 10
 </div>

+ 8
- 49
required.php Просмотреть файл

@@ -62,9 +62,14 @@ if ($_SESSION['mobile'] === TRUE) {
62 62
 require __DIR__ . '/vendor/autoload.php';
63 63
 
64 64
 // List of alert messages
65
-require __DIR__ . '/lang/messages.php';
66
-// text strings (i18n)
67
-require __DIR__ . '/lang/' . LANGUAGE . ".php";
65
+require __DIR__ . '/langs/messages.php';
66
+
67
+$libs = glob(__DIR__ . "/lib/*.lib.php");
68
+foreach ($libs as $lib) {
69
+    require_once $lib;
70
+}
71
+
72
+$Strings = new Strings(LANGUAGE);
68 73
 
69 74
 /**
70 75
  * Kill off the running process and spit out an error message
@@ -136,52 +141,6 @@ function is_empty($str) {
136 141
     return (is_null($str) || !isset($str) || $str == '');
137 142
 }
138 143
 
139
-/**
140
- * I18N string getter.  If the key doesn't exist, outputs the key itself.
141
- * @param string $key I18N string key
142
- * @param boolean $echo whether to echo the result or return it (default echo)
143
- */
144
-function lang($key, $echo = true) {
145
-    if (array_key_exists($key, STRINGS)) {
146
-        $str = STRINGS[$key];
147
-    } else {
148
-        trigger_error("Language key \"$key\" does not exist in " . LANGUAGE, E_USER_WARNING);
149
-        $str = $key;
150
-    }
151
-
152
-    if ($echo) {
153
-        echo $str;
154
-    } else {
155
-        return $str;
156
-    }
157
-}
158
-
159
-/**
160
- * I18N string getter (with builder).    If the key doesn't exist, outputs the key itself.
161
- * @param string $key I18N string key
162
- * @param array $replace key-value array of replacements.
163
- * If the string value is "hello {abc}" and you give ["abc" => "123"], the
164
- * result will be "hello 123".
165
- * @param boolean $echo whether to echo the result or return it (default echo)
166
- */
167
-function lang2($key, $replace, $echo = true) {
168
-    if (array_key_exists($key, STRINGS)) {
169
-        $str = STRINGS[$key];
170
-    } else {
171
-        trigger_error("Language key \"$key\" does not exist in " . LANGUAGE, E_USER_WARNING);
172
-        $str = $key;
173
-    }
174
-
175
-    foreach ($replace as $find => $repl) {
176
-        $str = str_replace("{" . $find . "}", $repl, $str);
177
-    }
178
-
179
-    if ($echo) {
180
-        echo $str;
181
-    } else {
182
-        return $str;
183
-    }
184
-}
185 144
 
186 145
 function dieifnotloggedin() {
187 146
     if ($_SESSION['loggedin'] != true) {

+ 96
- 0
tests/User.test.php Просмотреть файл

@@ -0,0 +1,96 @@
1
+<?php
2
+
3
+/*
4
+ * This Source Code Form is subject to the terms of the Mozilla Public
5
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
6
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7
+ */
8
+
9
+// Fill these in with valid credentials for an account with NORMAL status
10
+$valid_user = "";
11
+$valid_pass = "";
12
+
13
+require __DIR__ . "/../required.php";
14
+error_reporting(E_ALL);
15
+ini_set('display_errors', 'On');
16
+header("Content-Type: text/plain");
17
+
18
+// Test invalid user responses
19
+
20
+$user = new User(784587254);
21
+if ($user->exists()) {
22
+    echo "FAIL: Invalid user ID marked as existing\n";
23
+} else {
24
+    echo "OK\n";
25
+}
26
+if ($user->getUID() != 784587254) {
27
+    echo "FAIL: Invalid user has mismatched UID\n";
28
+} else {
29
+    echo "OK\n";
30
+}
31
+
32
+$user = User::byUsername("r9483yt8934t");
33
+if ($user->exists()) {
34
+    echo "FAIL: Invalid username marked as existing\n";
35
+} else {
36
+    echo "OK\n";
37
+}
38
+
39
+if ($user->checkPassword("gbirg4wre") != false) {
40
+    echo "FAIL: Invalid user and invalid password allowed\n";
41
+} else {
42
+    echo "OK\n";
43
+}
44
+
45
+if ($user->has2fa() != false) {
46
+    echo "FAIL: Invalid user has 2fa\n";
47
+} else {
48
+    echo "OK\n";
49
+}
50
+
51
+if ($user->getUsername() != "r9483yt8934t") {
52
+    echo "FAIL: Invalid user has mismatched username\n";
53
+} else {
54
+    echo "OK\n";
55
+}
56
+
57
+if ($user->getStatus()->get() != 0) {
58
+    echo "FAIL: Invalid user has real account status\n";
59
+} else {
60
+    echo "OK\n";
61
+}
62
+
63
+if ($user->getStatus()->getString() != "OTHER_0") {
64
+    echo "FAIL: Invalid user has wrong account status string\n";
65
+} else {
66
+    echo "OK\n";
67
+}
68
+
69
+// Test valid user responses
70
+
71
+$user = User::byUsername($valid_user);
72
+if (!$user->exists()) {
73
+    echo "FAIL: Valid user does not exist\n";
74
+} else {
75
+    echo "OK\n";
76
+}
77
+
78
+if ($user->checkPassword($valid_pass) !== true) {
79
+    echo "FAIL: Valid user and password not allowed\n";
80
+} else {
81
+    echo "OK\n";
82
+}
83
+
84
+if ($user->getUsername() != $valid_user) {
85
+    echo "FAIL: Valid user has mismatched username\n";
86
+} else {
87
+    echo "OK\n";
88
+}
89
+
90
+if ($user->getStatus()->getString() != "NORMAL") {
91
+    echo "FAIL: Valid user has wrong account status string\n";
92
+} else {
93
+    echo "OK\n";
94
+}
95
+
96
+exit("ALL OK");

Загрузка…
Отмена
Сохранить