Browse Source

Merge ../BusinessAppTemplate

# Conflicts:
#	README.md
#	api.php
#	composer.lock
#	index.php
#	lang/en_us.php
#	lib/login.php
#	required.php
#	settings.template.php
master
Skylar Ittner 7 months ago
parent
commit
47540e57d2
59 changed files with 1639 additions and 2079 deletions
  1. 1
    1
      LICENSE.md
  2. 4
    5
      README.md
  3. 23
    27
      action.php
  4. 2
    33
      api.php
  5. 5
    0
      api/.htaccess
  6. 9
    0
      api/actions/ping.php
  7. 15
    0
      api/apisettings.php
  8. 144
    0
      api/functions.php
  9. 79
    0
      api/index.php
  10. 40
    34
      app.php
  11. 12
    12
      composer.lock
  12. 112
    145
      index.php
  13. 0
    140
      lang/en_us.php
  14. 7
    0
      langs/en/core.json
  15. 8
    0
      langs/en/index.json
  16. 9
    0
      langs/en/titles.json
  17. 0
    0
      langs/messages.php
  18. 56
    0
      lib/AccountHubApi.lib.php
  19. 13
    0
      lib/Exceptions.lib.php
  20. 275
    0
      lib/FormBuilder.lib.php
  21. 135
    0
      lib/IPUtils.lib.php
  22. 80
    0
      lib/Login.lib.php
  23. 53
    0
      lib/Notifications.lib.php
  24. 19
    0
      lib/Session.lib.php
  25. 122
    0
      lib/Strings.lib.php
  26. 0
    18
      lib/authlog.php
  27. 1
    1
      lib/getlogtable.php
  28. 2
    2
      lib/getmanagetable.php
  29. 2
    2
      lib/getpermtable.php
  30. 0
    130
      lib/getusertable.php
  31. 0
    131
      lib/iputils.php
  32. 0
    402
      lib/login.php
  33. 44
    136
      lib/reports.php
  34. 0
    127
      lib/userinfo.php
  35. 19
    45
      mobile/index.php
  36. 6
    6
      pages.php
  37. 1
    1
      pages/404.php
  38. 12
    12
      pages/authlog.php
  39. 4
    4
      pages/clearlog.php
  40. 11
    19
      pages/deluser.php
  41. 34
    119
      pages/edituser.php
  42. 11
    11
      pages/export.php
  43. 25
    16
      pages/groups.php
  44. 6
    6
      pages/home.php
  45. 39
    14
      pages/managers.php
  46. 28
    39
      pages/permissions.php
  47. 59
    23
      pages/users.php
  48. 27
    119
      required.php
  49. 60
    50
      settings.template.php
  50. 3
    3
      static/css/bootstrap.min.css
  51. 0
    15
      static/css/index.css
  52. 3
    3
      static/css/svg-with-js.min.css
  53. 7
    0
      static/js/bootstrap.bundle.min.js
  54. 0
    7
      static/js/bootstrap.min.js
  55. 3
    3
      static/js/fontawesome-all.min.js
  56. 2
    47
      static/js/groups.js
  57. 2
    86
      static/js/managers.js
  58. 4
    52
      static/js/permissions.js
  59. 1
    33
      static/js/users.js

+ 1
- 1
LICENSE.md View File

@@ -1,4 +1,4 @@
1
-Copyright (c) 2018 Netsyms Technologies.
1
+Copyright (c) 2018-2019 Netsyms Technologies.
2 2
 
3 3
 If you modify and redistribute this project, you must replace the branding
4 4
 assets with your own.

+ 4
- 5
README.md View File

@@ -1,7 +1,7 @@
1 1
 ManagePanel
2 2
 ===========
3 3
 
4
-System administration tool.  Manage user accounts, permissions, and other data 
4
+System administration tool.  Manage user accounts, permissions, and other data
5 5
 shared between the apps.
6 6
 
7 7
 https://netsyms.biz/apps/managepanel
@@ -12,8 +12,7 @@ Installing
12 12
 0. Follow the installation directions for [AccountHub](https://source.netsyms.com/Business/AccountHub), then download this app somewhere.
13 13
 1. Copy `settings.template.php` to `settings.php`
14 14
 2. Import `database.sql` into your database server
15
-3. Edit `settings.php` and fill in your DB info ("DB_*" for the AccountHub database, "DB2_*" for the ManagePanel one you just installed)
16
-4. Set the location of the AccountHub API in `settings.php` (see "PORTAL_API") and enter an API key ("PORTAL_KEY")
17
-5. Set the location of the AccountHub home page ("PORTAL_URL")
18
-6. Set the URL of this app ("URL")
15
+3. Edit `settings.php` and fill in your DB info ("database" for the AccountHub database, "database2" for the ManagePanel one you just installed)
16
+4. Set the location of the AccountHub API in `settings.php`, enter an API key, and set the home page
17
+6. Set the URL of this app
19 18
 7. Run `composer install` (or `composer.phar install`) to install dependency libraries.

+ 23
- 27
action.php View File

@@ -8,14 +8,12 @@
8 8
  * Make things happen when buttons are pressed and forms submitted.
9 9
  */
10 10
 require_once __DIR__ . "/required.php";
11
-require_once __DIR__ . "/lib/login.php";
12
-require_once __DIR__ . "/lib/authlog.php";
13 11
 
14 12
 if ($VARS['action'] !== "signout") {
15 13
     dieifnotloggedin();
16 14
 }
17 15
 
18
-if (account_has_permission($_SESSION['username'], "ADMIN") == FALSE) {
16
+if ((new User($_SESSION['uid']))->hasPermission("ADMIN") == FALSE) {
19 17
     die("You don't have permission to be here.");
20 18
 }
21 19
 
@@ -44,7 +42,7 @@ function returnToSender($msg, $arg = "", $additional = []) {
44 42
 
45 43
 switch ($VARS['action']) {
46 44
     case "edituser":
47
-        if (is_empty($VARS['id'])) {
45
+        if (empty($VARS['id'])) {
48 46
             $insert = true;
49 47
         } else {
50 48
             if ($database->has('accounts', ['uid' => $VARS['id']])) {
@@ -53,7 +51,7 @@ switch ($VARS['action']) {
53 51
                 returnToSender("invalid_userid");
54 52
             }
55 53
         }
56
-        if (is_empty($VARS['name']) || is_empty($VARS['username']) || is_empty($VARS['status'])) {
54
+        if (empty($VARS['name']) || empty($VARS['username']) || empty($VARS['status'])) {
57 55
             returnToSender('invalid_parameters');
58 56
         }
59 57
 
@@ -69,7 +67,7 @@ switch ($VARS['action']) {
69 67
             'deleted' => 0
70 68
         ];
71 69
 
72
-        if (!is_empty($VARS['pass'])) {
70
+        if (!empty($VARS['pass'])) {
73 71
             $data['password'] = password_hash($VARS['pass'], PASSWORD_BCRYPT);
74 72
         }
75 73
 
@@ -78,11 +76,11 @@ switch ($VARS['action']) {
78 76
             $data['phone2'] = "";
79 77
             $data['accttype'] = 1;
80 78
             $database->insert('accounts', $data);
81
-            insertAuthLog(17, $_SESSION['uid'], $data['username'] . ", " . $data['realname'] . ", " . $data['email'] . ", " . $data['acctstatus']);
79
+            Log::insert(LogType::USER_ADDED, $_SESSION['uid'], $data['username'] . ", " . $data['realname'] . ", " . $data['email'] . ", " . $data['acctstatus']);
82 80
         } else {
83 81
             $olddata = $database->select('accounts', '*', ['uid' => $VARS['id']])[0];
84 82
             $database->update('accounts', $data, ['uid' => $VARS['id']]);
85
-            insertAuthLog(18, $_SESSION['uid'], "OLD: " . $olddata['username'] . ", " . $olddata['realname'] . ", " . $olddata['email'] . ", " . $olddata['acctstatus'] . "; NEW: " . $data['username'] . ", " . $data['realname'] . ", " . $data['email'] . ", " . $data['acctstatus']);
83
+            Log::insert(LogType::USER_EDITED, $_SESSION['uid'], "OLD: " . $olddata['username'] . ", " . $olddata['realname'] . ", " . $olddata['email'] . ", " . $olddata['acctstatus'] . "; NEW: " . $data['username'] . ", " . $data['realname'] . ", " . $data['email'] . ", " . $data['acctstatus']);
86 84
         }
87 85
 
88 86
         returnToSender("user_saved");
@@ -97,7 +95,7 @@ switch ($VARS['action']) {
97 95
             // we will flag it as deleted and set the status to LOCKED_OR_DISABLED.
98 96
             $database->update('accounts', ['acctstatus' => 2, 'deleted' => 1], ['uid' => $VARS['id']]);
99 97
         }
100
-        insertAuthLog(16, $_SESSION['uid'], $olddata['username'] . ", " . $olddata['realname'] . ", " . $olddata['email'] . ", " . $olddata['acctstatus']);
98
+        Log::insert(LogType::USER_REMOVED, $_SESSION['uid'], $olddata['username'] . ", " . $olddata['realname'] . ", " . $olddata['email'] . ", " . $olddata['acctstatus']);
101 99
         returnToSender("user_deleted");
102 100
     case "rmtotp":
103 101
         if ($database->has('accounts', ['uid' => $VARS['id']]) !== TRUE) {
@@ -105,28 +103,27 @@ switch ($VARS['action']) {
105 103
         }
106 104
         $u = $database->get('accounts', 'username', ['uid' => $VARS['id']]);
107 105
         $database->update('accounts', ["authsecret" => null], ['uid' => $VARS['id']]);
108
-        insertAuthLog(10, $_SESSION['uid'], $u);
106
+        Log::insert(LogType::REMOVED_2FA, $_SESSION['uid'], $u);
109 107
         returnToSender("2fa_removed");
110 108
     case "clearlog":
111 109
         $rows = $database->count('authlog');
112 110
         $database->delete('authlog', []);
113
-        insertAuthLog(15, $_SESSION['uid'], lang2("removed n entries", ['n' => $rows], false));
111
+        Log::insert(LogType::LOG_CLEARED, $_SESSION['uid'], $Strings->build("removed n entries", ['n' => $rows], false));
114 112
         returnToSender("log_cleared");
115 113
     case "editmanager":
116
-        require_once __DIR__ . "/lib/userinfo.php";
117 114
         if (!$database->has('accounts', ['username' => $VARS['manager']])) {
118 115
             returnToSender("invalid_manager");
119 116
         }
120
-        $manager = getUserByUsername($VARS['manager'])['uid'];
117
+        $manager = User::byUsername($VARS['manager'])->getUID();
121 118
         $already_assigned = $database->select('managers', 'employeeid', ['managerid' => $manager]);
122 119
 
123 120
         foreach ($VARS['employees'] as $u) {
124
-            if (!user_exists($u)) {
125
-                returnToSender("user_not_exists", htmlentities($u));
121
+            $emp = User::byUsername($u);
122
+            if (!$emp->exists()) {
123
+                returnToSender("user_not_exists", htmlentities($emp->getUsername()));
126 124
             }
127
-            $uid = getUserByUsername($u)['uid'];
128
-            $database->insert('managers', ['employeeid' => $uid, 'managerid' => $manager]);
129
-            $already_assigned = array_diff($already_assigned, [$uid]); // Remove user from old list
125
+            $database->insert('managers', ['employeeid' => $emp->getUID(), 'managerid' => $manager]);
126
+            $already_assigned = array_diff($already_assigned, [$emp->getUID()]); // Remove user from old list
130 127
         }
131 128
         foreach ($already_assigned as $uid) {
132 129
             $database->delete('managers', ["AND" => ['employeeid' => $uid, 'managerid' => $manager]]);
@@ -198,14 +195,14 @@ switch ($VARS['action']) {
198 195
         returnToSender("permission_deleted");
199 196
     case "autocomplete_user":
200 197
         header("Content-Type: application/json");
201
-        if (is_empty($VARS['q']) || strlen($VARS['q']) < 3) {
198
+        if (empty($VARS['q']) || strlen($VARS['q']) < 3) {
202 199
             exit(json_encode([]));
203 200
         }
204 201
         $data = $database->select('accounts', ['uid', 'username', 'realname (name)'], ["OR" => ['username[~]' => $VARS['q'], 'realname[~]' => $VARS['q']], "LIMIT" => 10]);
205 202
         exit(json_encode($data));
206 203
     case "autocomplete_permission":
207 204
         header("Content-Type: application/json");
208
-        if (is_empty($VARS['q'])) {
205
+        if (empty($VARS['q'])) {
209 206
             exit(json_encode([]));
210 207
         }
211 208
         $data = $database->select('permissions', ['permcode (name)', 'perminfo (info)'], ["OR" => ['permcode[~]' => $VARS['q'], 'perminfo[~]' => $VARS['q']], "LIMIT" => 10]);
@@ -217,14 +214,13 @@ switch ($VARS['action']) {
217 214
         $gid = $VARS['gid'];
218 215
         $already_assigned = $database->select('assigned_groups', 'uid', ['groupid' => $gid]);
219 216
 
220
-        require_once __DIR__ . "/lib/userinfo.php";
221 217
         foreach ($VARS['users'] as $u) {
222
-            if (!user_exists($u)) {
223
-                returnToSender("user_not_exists", htmlentities($u));
218
+            $user = User::byUsername($u);
219
+            if (!$user->exists()) {
220
+                returnToSender("user_not_exists", htmlentities($user->getUsername()));
224 221
             }
225
-            $uid = getUserByUsername($u)['uid'];
226
-            $database->insert('assigned_groups', ['groupid' => $gid, 'uid' => $uid]);
227
-            $already_assigned = array_diff($already_assigned, [$uid]); // Remove user from old list
222
+            $database->insert('assigned_groups', ['groupid' => $gid, 'uid' => $user->getUID()]);
223
+            $already_assigned = array_diff($already_assigned, [$user->getUID()]); // Remove user from old list
228 224
         }
229 225
         foreach ($already_assigned as $uid) {
230 226
             $database->delete('assigned_groups', ["AND" => ['uid' => $uid, 'groupid' => $gid]]);
@@ -251,7 +247,7 @@ switch ($VARS['action']) {
251 247
         break;
252 248
     case "signout":
253 249
         session_destroy();
254
-        header('Location: index.php');
250
+        header('Location: index.php?logout=1');
255 251
         die("Logged out.");
256 252
     default:
257 253
         die("Invalid action");

+ 2
- 33
api.php View File

@@ -4,37 +4,6 @@
4 4
  * License, v. 2.0. If a copy of the MPL was not distributed with this
5 5
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 6
 
7
-/**
8
- * Simple JSON API to allow other apps to access data from this app.
9
- *
10
- * Requests can be sent via either GET or POST requests.  POST is recommended
11
- * as it has a lower chance of being logged on the server, exposing unencrypted
12
- * user passwords.
13
- */
14
-require __DIR__ . '/required.php';
15
-require_once __DIR__ . '/lib/login.php';
16
-require_once __DIR__ . '/lib/userinfo.php';
17
-header("Content-Type: application/json");
18 7
 
19
-$username = $VARS['username'];
20
-$password = $VARS['password'];
21
-if (user_exists($username) !== true || authenticate_user($username, $password, $errmsg) !== true || account_has_permission($username, "ADMIN") !== true) {
22
-    header("HTTP/1.1 403 Unauthorized");
23
-    die("\"403 Unauthorized\"");
24
-}
25
-$userinfo = getUserByUsername($username);
26
-
27
-// query max results
28
-$max = 20;
29
-if (preg_match("/^[0-9]+$/", $VARS['max']) === 1 && $VARS['max'] <= 1000) {
30
-    $max = (int) $VARS['max'];
31
-}
32
-
33
-switch ($VARS['action']) {
34
-    case "ping":
35
-        $out = ["status" => "OK", "maxresults" => $max, "pong" => true];
36
-        exit(json_encode($out));
37
-    default:
38
-        header("HTTP/1.1 400 Bad Request");
39
-        die("\"400 Bad Request\"");
40
-}
8
+// Load in new API from legacy location (a.k.a. here)
9
+require __DIR__ . "/api/index.php";

+ 5
- 0
api/.htaccess View File

@@ -0,0 +1,5 @@
1
+# Rewrite for Nextcloud Notes API
2
+<IfModule mod_rewrite.c>
3
+    RewriteEngine on
4
+    RewriteRule ([a-zA-Z0-9]+) index.php?action=$1 [PT]
5
+</IfModule>

+ 9
- 0
api/actions/ping.php View File

@@ -0,0 +1,9 @@
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
+sendJsonResp();

+ 15
- 0
api/apisettings.php View File

@@ -0,0 +1,15 @@
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
+$APIS = [
10
+    "ping" => [
11
+        "load" => "ping.php",
12
+        "vars" => [
13
+        ]
14
+    ]
15
+];

+ 144
- 0
api/functions.php View File

@@ -0,0 +1,144 @@
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
+ * Build and send a simple JSON response.
11
+ * @param string $msg A message
12
+ * @param string $status "OK" or "ERROR"
13
+ * @param array $data More JSON data
14
+ */
15
+function sendJsonResp(string $msg = null, string $status = "OK", array $data = null) {
16
+    $resp = [];
17
+    if (!is_null($data)) {
18
+        $resp = $data;
19
+    }
20
+    if (!is_null($msg)) {
21
+        $resp["msg"] = $msg;
22
+    }
23
+    $resp["status"] = $status;
24
+    header("Content-Type: application/json");
25
+    exit(json_encode($resp));
26
+}
27
+
28
+function exitWithJson(array $json) {
29
+    header("Content-Type: application/json");
30
+    exit(json_encode($json));
31
+}
32
+
33
+/**
34
+ * Get the API key with most of the characters replaced with *s.
35
+ * @global string $key
36
+ * @return string
37
+ */
38
+function getCensoredKey() {
39
+    global $key;
40
+    $resp = $key;
41
+    if (strlen($key) > 5) {
42
+        for ($i = 2; $i < strlen($key) - 2; $i++) {
43
+            $resp[$i] = "*";
44
+        }
45
+    }
46
+    return $resp;
47
+}
48
+
49
+/**
50
+ * Check if the request is allowed
51
+ * @global array $VARS
52
+ * @return bool true if the request should continue, false if the request is bad
53
+ */
54
+function authenticate(): bool {
55
+    global $VARS;
56
+    // HTTP basic auth
57
+    if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) {
58
+        $user = User::byUsername($_SERVER['PHP_AUTH_USER']);
59
+        if (!$user->checkPassword($_SERVER['PHP_AUTH_PW'])) {
60
+            return false;
61
+        }
62
+        return true;
63
+    }
64
+    // Form auth
65
+    if (empty($VARS['username']) || empty($VARS['password'])) {
66
+        return false;
67
+    } else {
68
+        $username = $VARS['username'];
69
+        $password = $VARS['password'];
70
+        $user = User::byUsername($username);
71
+        if ($user->exists() !== true || Login::auth($username, $password) !== Login::LOGIN_OK) {
72
+            return false;
73
+        }
74
+    }
75
+    return true;
76
+}
77
+
78
+/**
79
+ * Get the User whose credentials were used to make the request.
80
+ */
81
+function getRequestUser(): User {
82
+    global $VARS;
83
+    if (!empty($_SERVER['PHP_AUTH_USER'])) {
84
+        return User::byUsername($_SERVER['PHP_AUTH_USER']);
85
+    } else {
86
+        return User::byUsername($VARS['username']);
87
+    }
88
+}
89
+
90
+function checkVars($vars, $or = false) {
91
+    global $VARS;
92
+    $ok = [];
93
+    foreach ($vars as $key => $val) {
94
+        if (strpos($key, "OR") === 0) {
95
+            checkVars($vars[$key], true);
96
+            continue;
97
+        }
98
+
99
+        // Only check type of optional variables if they're set, and don't
100
+        // mark them as bad if they're not set
101
+        if (strpos($key, " (optional)") !== false) {
102
+            $key = str_replace(" (optional)", "", $key);
103
+            if (empty($VARS[$key])) {
104
+                continue;
105
+            }
106
+        } else {
107
+            if (empty($VARS[$key])) {
108
+                $ok[$key] = false;
109
+                continue;
110
+            }
111
+        }
112
+
113
+        if (strpos($val, "/") === 0) {
114
+            // regex
115
+            $ok[$key] = preg_match($val, $VARS[$key]) === 1;
116
+        } else {
117
+            $checkmethod = "is_$val";
118
+            $ok[$key] = !($checkmethod($VARS[$key]) !== true);
119
+        }
120
+    }
121
+    if ($or) {
122
+        $success = false;
123
+        $bad = "";
124
+        foreach ($ok as $k => $v) {
125
+            if ($v) {
126
+                $success = true;
127
+                break;
128
+            } else {
129
+                $bad = $k;
130
+            }
131
+        }
132
+        if (!$success) {
133
+            http_response_code(400);
134
+            die("400 Bad request: variable $bad is missing or invalid");
135
+        }
136
+    } else {
137
+        foreach ($ok as $key => $bool) {
138
+            if (!$bool) {
139
+                http_response_code(400);
140
+                die("400 Bad request: variable $key is missing or invalid");
141
+            }
142
+        }
143
+    }
144
+}

+ 79
- 0
api/index.php View File

@@ -0,0 +1,79 @@
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
+require __DIR__ . '/../required.php';
10
+require __DIR__ . '/functions.php';
11
+require __DIR__ . '/apisettings.php';
12
+
13
+$VARS = $_GET;
14
+if ($_SERVER['REQUEST_METHOD'] != "GET") {
15
+    $VARS = array_merge($VARS, $_POST);
16
+}
17
+
18
+$requestbody = file_get_contents('php://input');
19
+$requestjson = json_decode($requestbody, TRUE);
20
+if (json_last_error() == JSON_ERROR_NONE) {
21
+    $VARS = array_merge($VARS, $requestjson);
22
+}
23
+
24
+// If we're not using the old api.php file, allow more flexible requests
25
+if (strpos($_SERVER['REQUEST_URI'], "/api.php") === FALSE) {
26
+    $route = explode("/", substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], "api/") + 4));
27
+
28
+    if (count($route) >= 1) {
29
+        $VARS["action"] = $route[0];
30
+    }
31
+    if (count($route) >= 2 && strpos($route[1], "?") !== 0) {
32
+        for ($i = 1; $i < count($route); $i++) {
33
+            if (empty($route[$i]) || strpos($route[$i], "=") === false) {
34
+                continue;
35
+            }
36
+            $key = explode("=", $route[$i], 2)[0];
37
+            $val = explode("=", $route[$i], 2)[1];
38
+            $VARS[$key] = $val;
39
+        }
40
+    }
41
+
42
+    if (strpos($route[count($route) - 1], "?") === 0) {
43
+        $morevars = explode("&", substr($route[count($route) - 1], 1));
44
+        foreach ($morevars as $var) {
45
+            $key = explode("=", $var, 2)[0];
46
+            $val = explode("=", $var, 2)[1];
47
+            $VARS[$key] = $val;
48
+        }
49
+    }
50
+}
51
+
52
+if (!authenticate()) {
53
+    header('WWW-Authenticate: Basic realm="' . $SETTINGS['site_title'] . '"');
54
+    header('HTTP/1.1 401 Unauthorized');
55
+    die("401 Unauthorized: you need to supply valid credentials.");
56
+}
57
+
58
+if (empty($VARS['action'])) {
59
+    http_response_code(404);
60
+    die("404 No action specified");
61
+}
62
+
63
+if (!isset($APIS[$VARS['action']])) {
64
+    http_response_code(404);
65
+    die("404 Action not defined");
66
+}
67
+
68
+$APIACTION = $APIS[$VARS["action"]];
69
+
70
+if (!file_exists(__DIR__ . "/actions/" . $APIACTION["load"])) {
71
+    http_response_code(404);
72
+    die("404 Action not found");
73
+}
74
+
75
+if (!empty($APIACTION["vars"])) {
76
+    checkVars($APIACTION["vars"]);
77
+}
78
+
79
+require_once __DIR__ . "/actions/" . $APIACTION["load"];

+ 40
- 34
app.php View File

@@ -1,5 +1,4 @@
1 1
 <?php
2
-
3 2
 /* This Source Code Form is subject to the terms of the Mozilla Public
4 3
  * License, v. 2.0. If a copy of the MPL was not distributed with this
5 4
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@@ -14,7 +13,7 @@ if ($_SESSION['loggedin'] != true) {
14 13
 require_once __DIR__ . "/pages.php";
15 14
 
16 15
 $pageid = "home";
17
-if (isset($_GET['page']) && !is_empty($_GET['page'])) {
16
+if (!empty($_GET['page'])) {
18 17
     $pg = strtolower($_GET['page']);
19 18
     $pg = preg_replace('/[^0-9a-z_]/', "", $pg);
20 19
     if (array_key_exists($pg, PAGES) && file_exists(__DIR__ . "/pages/" . $pg . ".php")) {
@@ -28,10 +27,10 @@ header("Link: <static/fonts/Roboto.css>; rel=preload; as=style", false);
28 27
 header("Link: <static/css/bootstrap.min.css>; rel=preload; as=style", false);
29 28
 header("Link: <static/css/material-color/material-color.min.css>; rel=preload; as=style", false);
30 29
 header("Link: <static/css/app.css>; rel=preload; as=style", false);
31
-header("Link: <static/css/fa-svg-with-js.css>; rel=preload; as=style", false);
30
+header("Link: <static/css/svg-with-js.min.css>; rel=preload; as=style", false);
32 31
 header("Link: <static/js/fontawesome-all.min.js>; rel=preload; as=script", false);
33 32
 header("Link: <static/js/jquery-3.3.1.min.js>; rel=preload; as=script", false);
34
-header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
33
+header("Link: <static/js/bootstrap.bundle.min.js>; rel=preload; as=script", false);
35 34
 ?>
36 35
 <!DOCTYPE html>
37 36
 <html>
@@ -40,14 +39,14 @@ header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
40 39
         <meta http-equiv="X-UA-Compatible" content="IE=edge">
41 40
         <meta name="viewport" content="width=device-width, initial-scale=1">
42 41
 
43
-        <title><?php echo SITE_TITLE; ?></title>
42
+        <title><?php echo $SETTINGS['site_title']; ?></title>
44 43
 
45 44
         <link rel="icon" href="static/img/logo.svg">
46 45
 
47 46
         <link href="static/css/bootstrap.min.css" rel="stylesheet">
48 47
         <link href="static/css/material-color/material-color.min.css" rel="stylesheet">
49 48
         <link href="static/css/app.css" rel="stylesheet">
50
-        <link href="static/css/fa-svg-with-js.css" rel="stylesheet">
49
+        <link href="static/css/svg-with-js.min.css" rel="stylesheet">
51 50
         <script nonce="<?php echo $SECURE_NONCE; ?>">
52 51
             FontAwesomeConfig = {autoAddCss: false}
53 52
         </script>
@@ -66,28 +65,35 @@ header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
66 65
 
67 66
         <?php
68 67
 // Alert messages
69
-        if (isset($_GET['msg']) && !is_empty($_GET['msg']) && array_key_exists($_GET['msg'], MESSAGES)) {
70
-            // optional string generation argument
71
-            if (!isset($_GET['arg']) || is_empty($_GET['arg'])) {
72
-                $alertmsg = lang(MESSAGES[$_GET['msg']]['string'], false);
68
+        if (!empty($_GET['msg'])) {
69
+            if (array_key_exists($_GET['msg'], MESSAGES)) {
70
+                // optional string generation argument
71
+                if (empty($_GET['arg'])) {
72
+                    $alertmsg = $Strings->get(MESSAGES[$_GET['msg']]['string'], false);
73
+                } else {
74
+                    $alertmsg = $Strings->build(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false);
75
+                }
76
+                $alerttype = MESSAGES[$_GET['msg']]['type'];
77
+                $alerticon = "square-o";
78
+                switch (MESSAGES[$_GET['msg']]['type']) {
79
+                    case "danger":
80
+                        $alerticon = "times";
81
+                        break;
82
+                    case "warning":
83
+                        $alerticon = "exclamation-triangle";
84
+                        break;
85
+                    case "info":
86
+                        $alerticon = "info-circle";
87
+                        break;
88
+                    case "success":
89
+                        $alerticon = "check";
90
+                        break;
91
+                }
73 92
             } else {
74
-                $alertmsg = lang2(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false);
75
-            }
76
-            $alerttype = MESSAGES[$_GET['msg']]['type'];
77
-            $alerticon = "square-o";
78
-            switch (MESSAGES[$_GET['msg']]['type']) {
79
-                case "danger":
80
-                    $alerticon = "times";
81
-                    break;
82
-                case "warning":
83
-                    $alerticon = "exclamation-triangle";
84
-                    break;
85
-                case "info":
86
-                    $alerticon = "info-circle";
87
-                    break;
88
-                case "success":
89
-                    $alerticon = "check";
90
-                    break;
93
+                // We don't have a message for this, so just assume an error and escape stuff.
94
+                $alertmsg = htmlentities($Strings->get($_GET['msg'], false));
95
+                $alerticon = "times";
96
+                $alerttype = "danger";
91 97
             }
92 98
             echo <<<END
93 99
             <div class="row justify-content-center" id="msg-alert-box">
@@ -121,7 +127,7 @@ END;
121 127
             </button>
122 128
             <a class="navbar-brand py-0 mr-auto" href="app.php">
123 129
                 <img src="static/img/logo.svg" alt="" class="d-none d-<?php echo $navbar_breakpoint; ?>-inline brand-img py-0" />
124
-                <?php echo SITE_TITLE; ?>
130
+                <?php echo $SETTINGS['site_title']; ?>
125 131
             </a>
126 132
 
127 133
             <div class="collapse navbar-collapse py-0" id="navbar-collapse">
@@ -146,7 +152,7 @@ END;
146 152
                                         if (isset($pg['icon'])) {
147 153
                                             ?><i class="<?php echo $pg['icon']; ?> fa-fw"></i> <?php
148 154
                                         }
149
-                                        lang($pg['title']);
155
+                                        $Strings->get($pg['title']);
150 156
                                         ?>
151 157
                                     </a>
152 158
                                 </span>
@@ -157,13 +163,13 @@ END;
157 163
                 </div>
158 164
                 <div class="navbar-nav ml-auto py-0" id="navbar-right">
159 165
                     <span class="nav-item py-<?php echo $navbar_breakpoint; ?>-0">
160
-                        <a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="<?php echo PORTAL_URL; ?>">
166
+                        <a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="<?php echo $SETTINGS['accounthub']['home']; ?>">
161 167
                             <i class="fas fa-user fa-fw"></i><span>&nbsp;<?php echo $_SESSION['realname'] ?></span>
162 168
                         </a>
163 169
                     </span>
164 170
                     <span class="nav-item mr-auto py-<?php echo $navbar_breakpoint; ?>-0">
165 171
                         <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>
172
+                            <i class="fas fa-sign-out-alt fa-fw"></i><span>&nbsp;<?php $Strings->get("sign out") ?></span>
167 173
                         </a>
168 174
                     </span>
169 175
                 </div>
@@ -177,12 +183,12 @@ END;
177 183
                 ?>
178 184
             </div>
179 185
             <div class="footer">
180
-                <?php echo FOOTER_TEXT; ?><br />
181
-                Copyright &copy; <?php echo date('Y'); ?> <?php echo COPYRIGHT_NAME; ?>
186
+                <?php echo $SETTINGS['footer_text']; ?><br />
187
+                Copyright &copy; <?php echo date('Y'); ?> <?php echo $SETTINGS['copyright']; ?>
182 188
             </div>
183 189
         </div>
184 190
         <script src="static/js/jquery-3.3.1.min.js"></script>
185
-        <script src="static/js/bootstrap.min.js"></script>
191
+        <script src="static/js/bootstrap.bundle.min.js"></script>
186 192
         <script src="static/js/app.js"></script>
187 193
         <?php
188 194
 // custom page scripts

+ 12
- 12
composer.lock View File

@@ -9,16 +9,16 @@
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",

+ 112
- 145
index.php View File

@@ -1,164 +1,131 @@
1 1
 <?php
2
-/* This Source Code Form is subject to the terms of the Mozilla Public
2
+/*
3
+ * This Source Code Form is subject to the terms of the Mozilla Public
3 4
  * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
+ */
5 7
 
6 8
 require_once __DIR__ . "/required.php";
7 9
 
8
-require_once __DIR__ . "/lib/login.php";
9
-
10 10
 // if we're logged in, we don't need to be here.
11 11
 if (!empty($_SESSION['loggedin']) && $_SESSION['loggedin'] === true && !isset($_GET['permissionerror'])) {
12 12
     header('Location: app.php');
13
+    die();
13 14
 }
14 15
 
15
-if (isset($_GET['permissionerror'])) {
16
-    $alert = lang("no access permission", false);
17
-}
16
+/**
17
+ * Show a simple HTML page with a line of text and a button.  Matches the UI of
18
+ * the AccountHub login flow.
19
+ *
20
+ * @global type $SETTINGS
21
+ * @global type $SECURE_NONCE
22
+ * @global type $Strings
23
+ * @param string $title Text to show, passed through i18n
24
+ * @param string $button Button text, passed through i18n
25
+ * @param string $url URL for the button
26
+ */
27
+function showHTML(string $title, string $button, string $url) {
28
+    global $SETTINGS, $SECURE_NONCE, $Strings;
29
+    ?>
30
+    <!DOCTYPE html>
31
+    <meta charset="UTF-8">
32
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
33
+    <meta name="viewport" content="width=device-width, initial-scale=1">
18 34
 
19
-/* Authenticate user */
20
-$userpass_ok = false;
21
-$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'])) {
28
-                    case "LOCKED_OR_DISABLED":
29
-                        $alert = lang("account locked", false);
30
-                        break;
31
-                    case "TERMINATED":
32
-                        $alert = lang("account terminated", false);
33
-                        break;
34
-                    case "CHANGE_PASSWORD":
35
-                        $alert = lang("password expired", false);
36
-                    case "NORMAL":
37
-                        $userpass_ok = true;
38
-                        break;
39
-                    case "ALERT_ON_ACCESS":
40
-                        sendLoginAlertEmail($VARS['username']);
41
-                        $userpass_ok = true;
42
-                        break;
43
-                }
44
-                if ($userpass_ok) {
45
-                    $_SESSION['passok'] = true; // stop logins using only username and authcode
46
-                    if (userHasTOTP($VARS['username'])) {
47
-                        $multiauth = true;
48
-                    } else {
49
-                        doLoginUser($VARS['username'], $VARS['password']);
50
-                        header('Location: app.php');
51
-                        die("Logged in, go to app.php");
52
-                    }
53
-                }
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
-                }
60
-            }
61
-        } else {
62
-            $alert = lang("captcha error", false);
63
-        }
64
-    } else if (!empty($VARS['progress']) && $VARS['progress'] == "2") {
65
-        if ($_SESSION['passok'] !== true) {
66
-            // stop logins using only username and authcode
67
-            sendError("Password integrity check failed!");
35
+    <title><?php echo $SETTINGS['site_title']; ?></title>
36
+
37
+    <link rel="icon" href="static/img/logo.svg">
38
+
39
+    <link href="static/css/bootstrap.min.css" rel="stylesheet">
40
+    <style nonce="<?php echo $SECURE_NONCE; ?>">
41
+        .display-5 {
42
+            font-size: 2.5rem;
43
+            font-weight: 300;
44
+            line-height: 1.2;
68 45
         }
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
-            }
76
-        } else {
77
-            $alert = lang("2fa incorrect", false);
46
+
47
+        .banner-image {
48
+            max-height: 100px;
49
+            margin: 2em auto;
50
+            border: 1px solid grey;
51
+            border-radius: 15%;
78 52
         }
79
-    }
80
-} else {
81
-    $alert = lang("login server unavailable", false);
82
-}
83
-header("Link: <static/fonts/Roboto.css>; rel=preload; as=style", false);
84
-header("Link: <static/css/bootstrap.min.css>; rel=preload; as=style", false);
85
-header("Link: <static/css/material-color/material-color.min.css>; rel=preload; as=style", false);
86
-header("Link: <static/css/index.css>; rel=preload; as=style", false);
87
-header("Link: <static/js/jquery-3.3.1.min.js>; rel=preload; as=script", false);
88
-header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
89
-?>
90
-<!DOCTYPE html>
91
-<html>
92
-    <head>
93
-        <meta charset="UTF-8">
94
-        <meta http-equiv="X-UA-Compatible" content="IE=edge">
95
-        <meta name="viewport" content="width=device-width, initial-scale=1">
96
-
97
-        <title><?php echo SITE_TITLE; ?></title>
98
-
99
-        <link rel="icon" href="static/img/logo.svg">
100
-
101
-        <link href="static/css/bootstrap.min.css" rel="stylesheet">
102
-        <link href="static/css/material-color/material-color.min.css" rel="stylesheet">
103
-        <link href="static/css/index.css" rel="stylesheet">
104
-        <?php if (CAPTCHA_ENABLED) { ?>
105
-            <script src="<?php echo CAPTCHA_SERVER ?>/captcheck.dist.js"></script>
106
-        <?php } ?>
107
-    </head>
108
-    <body>
53
+    </style>
54
+
55
+    <div class="container mt-4">
109 56
         <div class="row justify-content-center">
110
-            <div class="col-auto">
111
-                <img class="banner-image" src="static/img/logo.svg" />
57
+            <div class="col-12 text-center">
58
+                <img class="banner-image" src="./static/img/logo.svg" />
112 59
             </div>
113
-        </div>
114
-        <div class="row justify-content-center">
115
-            <div class="card col-11 col-xs-11 col-sm-8 col-md-6 col-lg-4">
116
-                <div class="card-body">
117
-                    <h5 class="card-title"><?php lang("sign in"); ?></h5>
118
-                    <form action="" method="POST">
119
-                        <?php
120
-                        if (!empty($alert)) {
121
-                            ?>
122
-                            <div class="alert alert-danger">
123
-                                <i class="fa fa-fw fa-exclamation-triangle"></i> <?php echo $alert; ?>
124
-                            </div>
125
-                            <?php
126
-                        }
127
-
128
-                        if ($multiauth != true) {
129
-                            ?>
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 />
132
-                            <?php if (CAPTCHA_ENABLED) { ?>
133
-                                <div class="captcheck_container" data-stylenonce="<?php echo $SECURE_NONCE; ?>"></div>
134
-                                <br />
135
-                            <?php } ?>
136
-                            <input type="hidden" name="progress" value="1" />
137
-                            <?php
138
-                        } else if ($multiauth) {
139
-                            ?>
140
-                            <div class="alert alert-info">
141
-                                <?php lang("2fa prompt"); ?>
142
-                            </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 />
144
-                            <input type="hidden" name="progress" value="2" />
145
-                            <input type="hidden" name="username" value="<?php echo $VARS['username']; ?>" />
146
-                            <?php
147
-                        }
148
-                        ?>
149
-                        <button type="submit" class="btn btn-primary">
150
-                            <?php lang("continue"); ?>
151
-                        </button>
152
-                    </form>
60
+
61
+            <div class="col-12 text-center">
62
+                <h1 class="display-5 mb-4"><?php $Strings->get($title); ?></h1>
63
+            </div>
64
+
65
+            <div class="col-12 col-sm-8 col-lg-6">
66
+                <div class="card mt-4">
67
+                    <div class="card-body">
68
+                        <a href="<?php echo $url; ?>" class="btn btn-primary btn-block"><?php $Strings->get($button); ?></a>
69
+                    </div>
153 70
                 </div>
154 71
             </div>
155 72
         </div>
156
-        <div class="footer">
157
-            <?php echo FOOTER_TEXT; ?><br />
158
-            Copyright &copy; <?php echo date('Y'); ?> <?php echo COPYRIGHT_NAME; ?>
159
-        </div>
160 73
     </div>
161
-    <script src="static/js/jquery-3.3.1.min.js"></script>
162
-    <script src="static/js/bootstrap.min.js"></script>
163
-</body>
164
-</html>
74
+    <?php
75
+}
76
+
77
+if (!empty($_GET['logout'])) {
78
+    showHTML("You have been logged out.", "Log in again", "./index.php");
79
+    die();
80
+}
81
+if (empty($_SESSION["login_code"])) {
82
+    $redirecttologin = true;
83
+} else {
84
+    try {
85
+        $uidinfo = AccountHubApi::get("checkloginkey", ["code" => $_SESSION["login_code"]]);
86
+        if ($uidinfo["status"] == "ERROR") {
87
+            throw new Exception();
88
+        }
89
+        if (is_numeric($uidinfo['uid'])) {
90
+            $user = new User($uidinfo['uid'] * 1);
91
+            foreach ($SETTINGS['permissions'] as $perm) {
92
+                if (!$user->hasPermission($perm)) {
93
+                    showHTML("no access permission", "sign out", "./action.php?action=signout");
94
+                    die();
95
+                }
96
+            }
97
+            Session::start($user);
98
+            $_SESSION["login_code"] = null;
99
+            header('Location: app.php');
100
+            showHTML("Logged in", "Continue", "./app.php");
101
+            die();
102
+        } else {
103
+            throw new Exception();
104
+        }
105
+    } catch (Exception $ex) {
106
+        $redirecttologin = true;
107
+    }
108
+}
109
+
110
+if ($redirecttologin) {
111
+    try {
112
+        $urlbase = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . (($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) ? ":" . $_SERVER['SERVER_PORT'] : "");
113
+        $iconurl = $urlbase . str_replace("index.php", "", $_SERVER["REQUEST_URI"]) . "static/img/logo.svg";
114
+        $codedata = AccountHubApi::get("getloginkey", ["appname" => $SETTINGS["site_title"], "appicon" => $iconurl]);
115
+
116
+        if ($codedata['status'] != "OK") {
117
+            throw new Exception($Strings->get("login server unavailable", false));
118
+        }
119
+
120
+        $redirecturl = $urlbase . $_SERVER['REQUEST_URI'];
121
+
122
+        $_SESSION["login_code"] = $codedata["code"];
123
+
124
+        $locationurl = $codedata["loginurl"] . "?code=" . htmlentities($codedata["code"]) . "&redirect=" . htmlentities($redirecturl);
125
+        header("Location: $locationurl");
126
+        showHTML("Continue", "Continue", $locationurl);
127
+        die();
128
+    } catch (Exception $ex) {
129
+        sendError($ex->getMessage());
130
+    }
131
+}

+ 0
- 140
lang/en_us.php View File

@@ -1,140 +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
-    "no admin permission" => "You do not have permission to access this system.",
17
-    "login server unavailable" => "Login server unavailable.  Try again later or contact technical support.",
18
-    "account locked" => "This account has been disabled. Contact technical support.",
19
-    "password expired" => "You must change your password before continuing.",
20
-    "account terminated" => "Account terminated.  Access denied.",
21
-    "account state error" => "Your account state is not stable.  Log out, restart your browser, and try again.",
22
-    "welcome user" => "Welcome, {user}!",
23
-    "sign out" => "Sign out",
24
-    "settings" => "Settings",
25
-    "options" => "Options",
26
-    "404 error" => "404 Error",
27
-    "page not found" => "Page not found.",
28
-    "invalid parameters" => "Invalid request parameters.",
29
-    "login server error" => "The login server returned an error: {arg}",
30
-    "login server user data error" => "The login server refused to provide account information.  Try again or contact technical support.",
31
-    "captcha error" => "There was a problem with the CAPTCHA (robot test).  Try again.",
32
-    "no access permission" => "You do not have permission to access this system.",
33
-    "home" => "Home",
34
-    "users" => "Users",
35
-    "more" => "More",
36
-    "actions" => "Actions",
37
-    "name" => "Name",
38
-    "email" => "Email",
39
-    "status" => "Status",
40
-    "type" => "Type",
41
-    "new user" => "New User",
42
-    "total users" => "Total Users",
43
-    "view users" => "View Users",
44
-    "normal accounts" => "Normal Accounts",
45
-    "locked accounts" => "Locked Accounts",
46
-    "editing user" => "Editing {user}",
47
-    "invalid userid" => "Invalid user ID.",
48
-    "user saved" => "User saved.",
49
-    "adding user" => "Adding new user",
50
-    "placeholder name" => "John Doe",
51
-    "placeholder username" => "jdoe",
52
-    "placeholder email address" => "jdoe@example.com",
53
-    "placeholder password" => "swordfish",
54
-    "new password" => "New Password",
55
-    "non-local account warning" => "This account is not locally managed.  Changes made here will not synchronize to the directory server and some attributes cannot be edited.",
56
-    "delete user" => "Delete User",
57
-    "really delete user" => "Are you sure you want to delete this user?  This action cannot be reversed.",
58
-    "user deleted" => "User account deleted.",
59
-    "user does not exist" => "User does not exist.",
60
-    "logtime" => "Date/Time",
61
-    "logtype" => "Event Type",
62
-    "ip address" => "IP Address",
63
-    "other data" => "Other",
64
-    "security log" => "Security Log",
65
-    "event type reference" => "Event Type Reference",
66
-    "clear log" => "Clear Log",
67
-    "really clear log" => "Are you sure you want to purge the security log?  This action cannot be reversed.",
68
-    "log cleared" => "Security log cleared.",
69
-    "removed n entries" => "Removed {n} entries",
70
-    "security log entries" => "Security Log Entries",
71
-    "view security log" => "View Security Log",
72
-    "managers" => "Managers",
73
-    "manager" => "Manager",
74
-    "employee" => "Employee",
75
-    "delete relationship" => "Delete Relationship",
76
-    "really delete relationship" => "Are you sure you want to remove this manager-employee relationship?  This action cannot be reversed.",
77
-    "relationship deleted" => "Relationship deleted.",
78
-    "edit relationship" => "Edit Relationship",
79
-    "adding relationship" => "Adding Relationship",
80
-    "relationship added" => "Relationship added.",
81
-    "permissions" => "Permissions",
82
-    "permission" => "Permission",
83
-    "new permission" => "New Permission",
84
-    "delete permission" => "Delete Permission",
85
-    "adding permission" => "Adding Permission",
86
-    "user" => "User",
87
-    "permission does not exist" => "Permission does not exist: {arg}",
88
-    "really delete permission" => "Are you sure you want to revoke this permission?",
89
-    "permission added" => "Permission assigned.",
90
-    "permission deleted" => "Permission deleted.",
91
-    "remove 2fa" => "Reset 2FA",
92
-    "action performed by" => "Action performed by {user}",
93
-    "2fa removed" => "2-factor authentication removed.",
94
-    "2fa" => "2FA",
95
-    "show deleted" => "Show deleted",
96
-    "editing deleted account" => "You are editing an account marked as deleted.  The account will be undeleted if you press Save.",
97
-    "manager assigned" => "Manager relationships saved.",
98
-    "manager does not exist" => "The selected manager username does not exist.",
99
-    "type to add a person" => "Type to add a person",
100
-    "employees" => "Employees",
101
-    "type to select a manager" => "Type to select a manager",
102
-    "select a manager to view or edit employees" => "Select a manager to view or edit the assigned employees.",
103
-    "report export" => "Reports/Export",
104
-    "report type" => "Report type",
105
-    "format" => "Format",
106
-    "generate report" => "Generate report",
107
-    "choose an option" => "Choose an option",
108
-    "csv file" => "CSV text file",
109
-    "ods file" => "ODS spreadsheet",
110
-    "html file" => "HTML web page",
111
-    "uid" => "User ID",
112
-    "manager name" => "Manager",
113
-    "manager username" => "Mgr. Username",
114
-    "employee name" => "Employee",
115
-    "employee username" => "Emp. Username",
116
-    "permission id" => "Perm. ID",
117
-    "permissions assigned" => "Permissions assigned.",
118
-    "type to select a user" => "Type to select a user",
119
-    "type to add a permission" => "Type to add a permission",
120
-    "Choose a permission" => "Choose a permission",
121
-    "select a user to view or edit permissions" => "Select a user to view or edit the assigned permissions.",
122
-    "group" => "Group",
123
-    "groups" => "Groups",
124
-    "group does not exist" => "That group does not exist.",
125
-    "group members updated" => "Group members updated.",
126
-    "group added" => "Group added.",
127
-    "group deleted" => "Group deleted.",
128
-    "group already exists" => "A group with that name already exists.",
129
-    "save" => "Save",
130
-    "next" => "Next",
131
-    "add" => "Add",
132
-    "delete" => "Delete",
133
-    "new group" => "New group",
134
-    "delete group" => "Delete group",
135
-    "enter group name" => "Group name",
136
-    "group management" => "Group Management",
137
-    "group assignments" => "Group Assignments",
138
-    "group id" => "Group ID",
139
-    "group name" => "Group Name"
140
-]);

+ 7
- 0
langs/en/core.json View File

@@ -0,0 +1,7 @@
1
+{
2
+    "sign out": "Sign out",
3
+    "404 error": "404 Error",
4
+    "page not found": "Page not found.",
5
+    "invalid parameters": "Invalid request parameters.",
6
+    "login server error": "The login server returned an error: {arg}"
7
+}

+ 8
- 0
langs/en/index.json View File

@@ -0,0 +1,8 @@
1
+{
2
+    "You have been logged out.": "You have been logged out.",
3
+    "Log in again": "Log in again",
4
+    "login server unavailable": "Login server unavailable.  Try again later or contact technical support.",
5
+    "no access permission": "You do not have permission to access this system.",
6
+    "Logged in": "Logged in",
7
+    "Continue": "Continue"
8
+}

+ 9
- 0
langs/en/titles.json View File

@@ -0,0 +1,9 @@
1
+{
2
+    "Home": "Home",
3
+    "Users": "Users",
4
+    "Groups": "Groups",
5
+    "Security": "Security",
6
+    "Security Log": "Security Log",
7
+    "Managers": "Managers",
8
+    "Permissions": "Permissions"
9
+}

lang/messages.php → langs/messages.php View File


+ 56
- 0
lib/AccountHubApi.lib.php View File

@@ -0,0 +1,56 @@
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 AccountHubApi {
10
+
11
+    public static function get(string $action, array $data = null, bool $throwex = false) {
12
+        global $SETTINGS;
13
+
14
+        $content = [
15
+            "action" => $action,
16
+            "key" => $SETTINGS['accounthub']['key']
17
+        ];
18
+        if (!is_null($data)) {
19
+            $content = array_merge($content, $data);
20
+        }
21
+        $options = [
22
+            'http' => [
23
+                'method' => 'POST',
24
+                'content' => json_encode($content),
25
+                'header' => "Content-Type: application/json\r\n" .
26
+                "Accept: application/json\r\n",
27
+                "ignore_errors" => true
28
+            ]
29
+        ];
30
+
31
+        $context = stream_context_create($options);
32
+        $result = file_get_contents($SETTINGS['accounthub']['api'], false, $context);
33
+        $response = json_decode($result, true);
34
+        if ($result === false || !AccountHubApi::checkHttpRespCode($http_response_header) || json_last_error() != JSON_ERROR_NONE) {
35
+            if ($throwex) {
36
+                throw new Exception($result);
37
+            } else {
38
+                sendError($result);
39
+            }
40
+        }
41
+        return $response;
42
+    }
43
+
44
+    private static function checkHttpRespCode(array $headers): bool {
45
+        foreach ($headers as $header) {
46
+            if (preg_match("/HTTP\/[0-9]\.[0-9] [0-9]{3}.*/", $header)) {
47
+                $respcode = explode(" ", $header)[1] * 1;
48
+                if ($respcode >= 200 && $respcode < 300) {
49
+                    return true;
50
+                }
51
+            }
52
+        }
53
+        return false;
54
+    }
55
+
56
+}

+ 13
- 0
lib/Exceptions.lib.php View File

@@ -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
+}

+ 275
- 0
lib/FormBuilder.lib.php View File

@@ -0,0 +1,275 @@
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 FormBuilder {
10
+
11
+    private $items = [];
12
+    private $hiddenitems = [];
13
+    private $title = "";
14
+    private $icon = "";
15
+    private $buttons = [];
16
+    private $action = "action.php";
17
+    private $method = "POST";
18
+    private $id = "editform";
19
+
20
+    /**
21
+     * Create a form with autogenerated HTML.
22
+     *
23
+     * @param string $title Form title/heading
24
+     * @param string $icon FontAwesone icon next to the title.
25
+     * @param string $action URL to submit the form to.
26
+     * @param string $method Form submission method (POST, GET, etc.)
27
+     */
28
+    public function __construct(string $title = "Untitled Form", string $icon = "fas fa-file-alt", string $action = "action.php", string $method = "POST") {
29
+        $this->title = $title;
30
+        $this->icon = $icon;
31
+        $this->action = $action;
32
+        $this->method = $method;
33
+    }
34
+
35
+    /**
36
+     * Set the title of the form.
37
+     * @param string $title
38
+     */
39
+    public function setTitle(string $title) {
40
+        $this->title = $title;
41
+    }
42
+
43
+    /**
44
+     * Set the icon for the form.
45
+     * @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
46
+     */
47
+    public function setIcon(string $icon) {
48
+        $this->icon = $icon;
49
+    }
50
+
51
+    /**
52
+     * Set the URL the form will submit to.
53
+     * @param string $action
54
+     */
55
+    public function setAction(string $action) {
56
+        $this->action = $action;
57
+    }
58
+
59
+    /**
60
+     * Set the form submission method (GET, POST, etc)
61
+     * @param string $method
62
+     */
63
+    public function setMethod(string $method = "POST") {
64
+        $this->method = $method;
65
+    }
66
+
67
+    /**
68
+     * Set the form ID.
69
+     * @param string $id
70
+     */
71
+    public function setID(string $id = "editform") {
72
+        $this->id = $id;
73
+    }
74
+
75
+    /**
76
+     * Add an input to the form.
77
+     *
78
+     * @param string $name Element name
79
+     * @param string $value Element value
80
+     * @param string $type Input type (text, number, date, select, tel...)
81
+     * @param bool $required If the element is required for form submission.
82
+     * @param string $id Element ID
83
+     * @param array $options Array of [value => text] pairs for a select element
84
+     * @param string $label Text label to display near the input
85
+     * @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
86
+     * @param int $width Bootstrap column width for the input, out of 12.
87
+     * @param int $minlength Minimum number of characters for the input.
88
+     * @param int $maxlength Maximum number of characters for the input.
89
+     * @param string $pattern Regex pattern for custom client-side validation.
90
+     * @param string $error Message to show if the input doesn't validate.
91
+     */
92
+    public function addInput(string $name, string $value = "", string $type = "text", bool $required = true, string $id = null, array $options = null, string $label = "", string $icon = "", int $width = 4, int $minlength = 1, int $maxlength = 100, string $pattern = "", string $error = "") {
93
+        $item = [
94
+            "name" => $name,
95
+            "value" => $value,
96
+            "type" => $type,
97
+            "required" => $required,
98
+            "label" => $label,
99
+            "icon" => $icon,
100
+            "width" => $width,
101
+            "minlength" => $minlength,
102
+            "maxlength" => $maxlength
103
+        ];
104
+        if (!empty($id)) {
105
+            $item["id"] = $id;
106
+        }
107
+        if (!empty($options) && $type == "select") {
108
+            $item["options"] = $options;
109
+        }
110
+        if (!empty($pattern)) {
111
+            $item["pattern"] = $pattern;
112
+        }
113
+        if (!empty($error)) {
114
+            $item["error"] = $error;
115
+        }
116
+        $this->items[] = $item;
117
+    }
118
+
119
+    /**
120
+     * Add a button to the form.
121
+     *
122
+     * @param string $text Text string to show on the button.
123
+     * @param string $icon FontAwesome icon to show next to the text.
124
+     * @param string $href If not null, the button will actually be a hyperlink.
125
+     * @param string $type Usually "button" or "submit".  Ignored if $href is set.
126
+     * @param string $id The element ID.
127
+     * @param string $name The element name for the button.
128
+     * @param string $value The form value for the button. Ignored if $name is null.
129
+     * @param string $class The CSS classes for the button, if a standard success-colored one isn't right.
130
+     */
131
+    public function addButton(string $text, string $icon = "", string $href = null, string $type = "button", string $id = null, string $name = null, string $value = "", string $class = "btn btn-success") {
132
+        $button = [
133
+            "text" => $text,
134
+            "icon" => $icon,
135
+            "class" => $class,
136
+            "type" => $type,
137
+            "id" => $id,
138
+            "href" => $href,
139
+            "name" => $name,
140
+            "value" => $value
141
+        ];
142
+        $this->buttons[] = $button;
143
+    }
144
+
145
+    /**
146
+     * Add a hidden input.
147
+     * @param string $name
148
+     * @param string $value
149
+     */
150
+    public function addHiddenInput(string $name, string $value) {
151
+        $this->hiddenitems[$name] = $value;
152
+    }
153
+
154
+    /**
155
+     * Generate the form HTML.
156
+     * @param bool $echo If false, returns HTML string instead of outputting it.
157
+     */
158
+    public function generate(bool $echo = true) {
159
+        $html = <<<HTMLTOP
160
+<form action="$this->action" method="$this->method" id="$this->id">
161
+    <div class="card">
162
+         <h3 class="card-header d-flex">
163
+            <div>
164
+                <i class="$this->icon"></i> $this->title
165
+            </div>
166
+        </h3>
167
+
168
+        <div class="card-body">
169
+            <div class="row">
170
+HTMLTOP;
171
+
172
+        foreach ($this->items as $item) {
173
+            $required = $item["required"] ? "required" : "";
174
+            $id = empty($item["id"]) ? "" : "id=\"$item[id]\"";
175
+            $pattern = empty($item["pattern"]) ? "" : "pattern=\"$item[pattern]\"";
176
+            if (empty($item['type'])) {
177
+                $item['type'] = "text";
178
+            }
179
+            $itemhtml = "";
180
+            $itemlabel = "";
181
+            if ($item['type'] != "checkbox") {
182
+                $itemlabel = "<label class=\"mb-0\">$item[label]:</label>";
183
+            }
184
+            $strippedlabel = strip_tags($item['label']);
185
+            $itemhtml .= <<<ITEMTOP
186
+\n\n                <div class="col-12 col-md-$item[width]">
187
+                    <div class="form-group mb-3">
188
+                        $itemlabel
189
+                        <div class="input-group">
190
+                            <div class="input-group-prepend">
191
+                                <span class="input-group-text"><i class="$item[icon]"></i></span>
192
+                            </div>
193
+ITEMTOP;
194
+            switch ($item['type']) {
195
+                case "select":
196
+                    $itemhtml .= <<<SELECT
197
+\n                            <select class="form-control" name="$item[name]" aria-label="$strippedlabel" $required>
198
+SELECT;
199
+                    foreach ($item['options'] as $value => $label) {
200
+                        $selected = "";
201
+                        if (!empty($item['value']) && $value == $item['value']) {
202
+                            $selected = " selected";
203
+                        }
204
+                        $itemhtml .= "\n                                <option value=\"$value\"$selected>$label</option>";
205
+                    }
206
+                    $itemhtml .= "\n                            </select>";
207
+                    break;
208
+                case "checkbox":
209
+                    $itemhtml .= <<<CHECKBOX
210
+\n                            <div class="form-group form-check">
211
+                                <input type="checkbox" name="$item[name]" $id class="form-check-input" value="$item[value]" $required aria-label="$strippedlabel">
212
+                                <label class="form-check-label">$item[label]</label>
213
+                              </div>
214
+CHECKBOX;
215
+                    break;
216
+                default:
217
+                    $itemhtml .= <<<INPUT
218
+\n                            <input type="$item[type]" name="$item[name]" $id class="form-control" aria-label="$strippedlabel" minlength="$item[minlength]" maxlength="$item[maxlength]" $pattern value="$item[value]" $required />
219
+INPUT;
220
+                    break;
221
+            }
222
+
223
+            if (!empty($item["error"])) {
224
+                $itemhtml .= <<<ERROR
225
+\n                            <div class="invalid-feedback">
226
+                                $item[error]
227
+                            </div>
228
+ERROR;
229
+            }
230
+            $itemhtml .= <<<ITEMBOTTOM
231
+\n                        </div>
232
+                    </div>
233
+                </div>\n
234
+ITEMBOTTOM;
235
+            $html .= $itemhtml;
236
+        }
237
+
238
+        $html .= <<<HTMLBOTTOM
239
+
240
+            </div>
241
+        </div>
242
+HTMLBOTTOM;
243
+
244
+        if (!empty($this->buttons)) {
245
+            $html .= "\n        <div class=\"card-footer\">";
246
+            foreach ($this->buttons as $btn) {
247
+                $btnhtml = "";
248
+                $inner = "<i class=\"$btn[icon]\"></i> $btn[text]";
249
+                $id = empty($btn['id']) ? "" : "id=\"$btn[id]\"";
250
+                if (!empty($btn['href'])) {
251
+                    $btnhtml = "<a href=\"$btn[href]\" class=\"$btn[class]\" $id>$inner</a>";
252
+                } else {
253
+                    $name = empty($btn['name']) ? "" : "name=\"$btn[name]\"";
254
+                    $value = (!empty($btn['name']) && !empty($btn['value'])) ? "value=\"$btn[value]\"" : "";
255
+                    $btnhtml = "<button type=\"$btn[type]\" class=\"$btn[class]\" $id $name $value>$inner</button>";
256
+                }
257
+                $html .= "\n            $btnhtml";
258
+            }
259
+            $html .= "\n        </div>";
260
+        }
261
+
262
+        $html .= "\n    </div>";
263
+        foreach ($this->hiddenitems as $name => $value) {
264
+            $value = htmlentities($value);
265
+            $html .= "\n    <input type=\"hidden\" name=\"$name\" value=\"$value\" />";
266
+        }
267
+        $html .= "\n</form>\n";
268
+
269
+        if ($echo) {
270
+            echo $html;
271
+        }
272
+        return $html;
273
+    }
274
+
275
+}

+ 135
- 0
lib/IPUtils.lib.php View File

@@ -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
+}

+ 80
- 0
lib/Login.lib.php View File

@@ -0,0 +1,80 @@
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
+    /**
49
+     * Check the login server API for sanity
50
+     * @return boolean true if OK, else false
51
+     */
52
+    public static function checkLoginServer() {
53
+        try {
54
+            $resp = AccountHubApi::get("ping");
55
+            if ($resp['status'] == "OK") {
56
+                return true;
57
+            } else {
58
+                return false;
59
+            }
60
+        } catch (Exception $e) {
61
+            return false;
62
+        }
63
+    }
64
+
65
+    /**
66
+     * Checks if the given AccountHub API key is valid by attempting to
67
+     * access the API with it.
68
+     * @param String $key The API key to check
69
+     * @return boolean TRUE if the key is valid, FALSE if invalid or something went wrong
70
+     */
71
+    function checkAPIKey($key) {
72
+        try {
73
+            $resp = AccountHubApi::get("ping", null, true);
74
+            return false;
75
+        } catch (Exception $e) {
76
+            return false;
77
+        }
78
+    }
79
+
80
+}

+ 53
- 0
lib/Notifications.lib.php View File

@@ -0,0 +1,53 @@
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
+            $resp = AccountHubApi::get("addnotification", [
36
+                        'uid' => $user->getUID(),
37
+                        'title' => $title,
38
+                        'content' => $content,
39
+                        'timestamp' => $timestamp,
40
+                        'url' => $url,
41
+                        'sensitive' => $sensitive
42
+                            ]
43
+            );
44
+            if ($resp['status'] == "OK") {
45
+                return $resp['id'] * 1;
46
+            } else {
47
+                return false;
48
+            }
49
+        }
50
+        throw new Exception($Strings->get("user does not exist", false));
51
+    }
52
+
53
+}

+ 19
- 0
lib/Session.lib.php View File

@@ -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
+}

+ 122
- 0
lib/Strings.lib.php View File

@@ -0,0 +1,122 @@
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 ($language == "en") {
25
+            return;
26
+        }
27
+
28
+        if (file_exists(__DIR__ . "/../langs/$language/")) {
29
+            $this->language = $language;
30
+            $this->load($language);
31
+        } else {
32
+            trigger_error("Language $language could not be found.", E_USER_WARNING);
33
+        }
34
+    }
35
+
36
+    /**
37
+     * Load all JSON files for the specified language.
38
+     * @param string $language
39
+     */
40
+    private function load(string $language) {
41
+        $files = glob(__DIR__ . "/../langs/$language/*.json");
42
+        foreach ($files as $file) {
43
+            $strings = json_decode(file_get_contents($file), true);
44
+            foreach ($strings as $key => $val) {
45
+                if (array_key_exists($key, $this->strings)) {
46
+                    trigger_error("Language key \"$key\" is defined more than once.", E_USER_WARNING);
47
+                }
48
+                $this->strings[$key] = $val;
49
+            }
50
+        }
51
+    }
52
+
53
+    /**
54
+     * Add language strings dynamically.
55
+     * @param array $strings ["key" => "value", ...]
56
+     */
57
+    public function addStrings(array $strings) {
58
+        foreach ($strings as $key => $val) {
59
+            $this->strings[$key] = $val;
60
+        }
61
+    }
62
+
63
+    /**
64
+     * I18N string getter.  If the key isn't found, it outputs the key itself.
65
+     * @param string $key
66
+     * @param bool $echo True to echo the result, false to return it.  Default is true.
67
+     * @return string
68
+     */
69
+    public function get(string $key, bool $echo = true): string {
70
+        $str = $key;
71
+        if (array_key_exists($key, $this->strings)) {
72
+            $str = $this->strings[$key];
73
+        } else {
74
+            trigger_error("Language key \"$key\" does not exist in " . $this->language, E_USER_WARNING);
75
+        }
76
+
77
+        if ($echo) {
78
+            echo $str;
79
+        }
80
+        return $str;
81
+    }
82
+
83
+    /**
84
+     * I18N string getter (with builder).    If the key doesn't exist, outputs the key itself.
85
+     * @param string $key
86
+     * @param array $replace key-value array of replacements.
87
+     * If the string value is "hello {abc}" and you give ["abc" => "123"], the
88
+     * result will be "hello 123".
89
+     * @param bool $echo True to echo the result, false to return it.  Default is true.
90
+     * @return string
91
+     */
92
+    public function build(string $key, array $replace, bool $echo = true): string {
93
+        $str = $key;
94
+        if (array_key_exists($key, $this->strings)) {
95
+            $str = $this->strings[$key];
96
+        } else {
97
+            trigger_error("Language key \"$key\" does not exist in " . $this->language, E_USER_WARNING);
98
+        }
99
+
100
+        foreach ($replace as $find => $repl) {
101
+            $str = str_replace("{" . $find . "}", $repl, $str);
102
+        }
103
+
104
+        if ($echo) {
105
+            echo $str;
106
+        }
107
+        return $str;
108
+    }
109
+
110
+    /**
111
+     * Builds and returns a JSON key:value string for the supplied array of keys.
112
+     * @param array $keys ["key1", "key2", ...]
113
+     */
114
+    public function getJSON(array $keys): string {
115
+        $strings = [];
116
+        foreach ($keys as $k) {
117
+            $strings[$k] = $this->get($k, false);
118
+        }
119
+        return json_encode($strings);
120
+    }
121
+
122
+}

+ 0
- 18
lib/authlog.php View File

@@ -1,18 +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
-require_once __DIR__ . "/../required.php";
9
-require_once __DIR__ . "/iputils.php";
10
-
11
-dieifnotloggedin();
12
-
13
-function insertAuthLog($type, $uid = null, $data = "") {
14
-    global $database;
15
-    // find IP address
16
-    $ip = getClientIP();
17
-    $database->insert("authlog", ['logtime' => date("Y-m-d H:i:s"), 'logtype' => $type, 'uid' => $uid, 'ip' => $ip, 'otherdata' => $data]);
18
-}

+ 1
- 1
lib/getlogtable.php View File

@@ -43,7 +43,7 @@ switch ($VARS['order'][0]['column']) {
43 43
 }
44 44
 
45 45
 // search
46
-if (!is_empty($VARS['search']['value'])) {
46
+if (!empty($VARS['search']['value'])) {
47 47
     $filter = true;
48 48
     $wherenolimit = [
49 49
         "OR" => [

+ 2
- 2
lib/getmanagetable.php View File

@@ -34,7 +34,7 @@ switch ($VARS['order'][0]['column']) {
34 34
 }
35 35
 
36 36
 // search
37
-if (!is_empty($VARS['search']['value'])) {
37
+if (!empty($VARS['search']['value'])) {
38 38
     $filter = true;
39 39
     $wherenolimit = [
40 40
         "OR" => [
@@ -78,7 +78,7 @@ if ($filter) {
78 78
 }
79 79
 $out['recordsFiltered'] = $recordsFiltered;
80 80
 for ($i = 0; $i < count($managers); $i++) {
81
-    $managers[$i]["delbtn"] = '<a class="btn btn-danger btn-xs" href="app.php?page=delmanager&mid=' . $managers[$i]['managerid'] . '&eid=' . $managers[$i]['employeeid'] . '"><i class="fa fa-trash"></i> ' . lang("delete", false) . '</a>';
81
+    $managers[$i]["delbtn"] = '<a class="btn btn-danger btn-xs" href="app.php?page=delmanager&mid=' . $managers[$i]['managerid'] . '&eid=' . $managers[$i]['employeeid'] . '"><i class="fa fa-trash"></i> ' . $Strings->get("delete", false) . '</a>';
82 82
 }
83 83
 $out['managers'] = $managers;
84 84
 

+ 2
- 2
lib/getpermtable.php View File

@@ -34,7 +34,7 @@ switch ($VARS['order'][0]['column']) {
34 34
 }
35 35
 
36 36
 // search
37
-if (!is_empty($VARS['search']['value'])) {
37
+if (!empty($VARS['search']['value'])) {
38 38
     $filter = true;
39 39
     $wherenolimit = [
40 40
         "OR" => [
@@ -76,7 +76,7 @@ if ($filter) {
76 76
 }
77 77
 $out['recordsFiltered'] = $recordsFiltered;
78 78
 for ($i = 0; $i < count($data); $i++) {
79
-    $data[$i]["delbtn"] = '<a class="btn btn-danger btn-xs" href="app.php?page=delpermission&uid=' . $data[$i]['uid'] . '&pid=' . $data[$i]['permid'] . '"><i class="fa fa-trash"></i> ' . lang("delete", false) . '</a>';
79
+    $data[$i]["delbtn"] = '<a class="btn btn-danger btn-xs" href="app.php?page=delpermission&uid=' . $data[$i]['uid'] . '&pid=' . $data[$i]['permid'] . '"><i class="fa fa-trash"></i> ' . $Strings->get("delete", false) . '</a>';
80 80
 }
81 81
 $out['perms'] = $data;
82 82
 

+ 0
- 130
lib/getusertable.php View File

@@ -1,130 +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
-require __DIR__ . '/../required.php';
9
-
10
-dieifnotloggedin();
11
-
12
-header("Content-Type: application/json");
13
-
14
-$show_deleted = false;
15
-if ($VARS['show_deleted'] == 1) {
16
-    $show_deleted = true;
17
-}
18
-
19
-$out = [];
20
-
21
-$out['draw'] = intval($VARS['draw']);
22
-
23
-if ($show_deleted) {
24
-    $out['recordsTotal'] = $database->count('accounts');
25
-} else {
26
-    $out['recordsTotal'] = $database->count('accounts', ['deleted' => 0]);
27
-}
28
-$filter = false;
29
-
30
-// sort
31
-$order = null;
32
-$sortby = "DESC";
33
-if ($VARS['order'][0]['dir'] == 'asc') {
34
-    $sortby = "ASC";
35
-}
36
-switch ($VARS['order'][0]['column']) {
37
-    case 2:
38
-        $order = ["realname" => $sortby];
39
-        break;
40
-    case 3:
41
-        $order = ["username" => $sortby];
42
-        break;
43
-    case 4:
44
-        $order = ["email" => $sortby];
45
-        break;
46
-    case 5:
47
-        $order = ["authsecret" => $sortby];
48
-        break;
49
-    case 6:
50
-        $order = ["statuscode" => $sortby];
51
-        break;
52
-    case 7:
53
-        $order = ["typecode" => $sortby];
54
-        break;
55
-}
56
-
57
-// search
58
-if (!is_empty($VARS['search']['value'])) {
59
-    $filter = true;
60
-    if ($show_deleted) {
61
-        $wherenolimit = [
62
-            "OR" => [
63
-                "username[~]" => $VARS['search']['value'],
64
-                "realname[~]" => $VARS['search']['value'],
65
-                "email[~]" => $VARS['search']['value'],
66
-                "statuscode[~]" => $VARS['search']['value'],
67
-                "typecode[~]" => $VARS['search']['value']
68
-            ]
69
-        ];
70
-    } else {
71
-        $wherenolimit = [
72
-            "AND" => [
73
-                "OR" => [
74
-                    "username[~]" => $VARS['search']['value'],
75
-                    "realname[~]" => $VARS['search']['value'],
76
-                    "email[~]" => $VARS['search']['value'],
77
-                    "statuscode[~]" => $VARS['search']['value'],
78
-                    "typecode[~]" => $VARS['search']['value']
79
-                ],
80
-                "deleted" => 0
81
-            ]
82
-        ];
83
-    }
84
-    $where = $wherenolimit;
85
-    $where["LIMIT"] = [$VARS['start'], $VARS['length']];
86
-} else {
87
-    $where = ["LIMIT" => [$VARS['start'], $VARS['length']]];
88
-    if (!$show_deleted) {
89
-        $where["deleted"] = 0;
90
-    }
91
-}
92
-if (!is_null($order)) {
93
-    $where["ORDER"] = $order;
94
-}
95
-
96
-
97
-$users = $database->select('accounts', [
98
-    "[>]acctstatus" => ['acctstatus' => 'statusid'],
99
-    "[>]accttypes" => ['accttype' => 'typeid']
100
-        ], [
101
-    'uid',
102
-    'username',
103
-    'realname',
104
-    'email',
105
-    'authsecret (2fa)',
106
-    'acctstatus',
107
-    'statuscode',
108
-    'accttype',
109
-    'typecode',
110
-    'deleted'
111
-        ], $where);
112
-
113
-
114
-$out['status'] = "OK";
115
-if ($filter) {
116
-    $recordsFiltered = $database->count('accounts', [
117
-        "[>]acctstatus" => ['acctstatus' => 'statusid'],
118
-        "[>]accttypes" => ['accttype' => 'typecode']
119
-            ], 'uid', $wherenolimit);
120
-} else {
121
-    $recordsFiltered = $out['recordsTotal'];
122
-}
123
-$out['recordsFiltered'] = $recordsFiltered;
124
-for ($i = 0; $i < count($users); $i++) {
125
-    $users[$i]["2fa"] = (is_empty($users[$i]["2fa"]) ? false : true);
126
-    $users[$i]["editbtn"] = '<a class="btn btn-blue btn-sm" href="app.php?page=edituser&id=' . $users[$i]['uid'] . '"><i class="far fa-edit"></i> ' . lang("edit", false) . '</a>';
127
-}
128
-$out['users'] = $users;
129
-
130
-echo json_encode($out);

+ 0
- 131
lib/iputils.php View File

@@ -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 View File

@@ -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 a Portal 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
<