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 5 years ago
commit 47540e57d2

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

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

@ -8,14 +8,12 @@
* Make things happen when buttons are pressed and forms submitted.
*/
require_once __DIR__ . "/required.php";
require_once __DIR__ . "/lib/login.php";
require_once __DIR__ . "/lib/authlog.php";
if ($VARS['action'] !== "signout") {
dieifnotloggedin();
}
if (account_has_permission($_SESSION['username'], "ADMIN") == FALSE) {
if ((new User($_SESSION['uid']))->hasPermission("ADMIN") == FALSE) {
die("You don't have permission to be here.");
}
@ -44,7 +42,7 @@ function returnToSender($msg, $arg = "", $additional = []) {
switch ($VARS['action']) {
case "edituser":
if (is_empty($VARS['id'])) {
if (empty($VARS['id'])) {
$insert = true;
} else {
if ($database->has('accounts', ['uid' => $VARS['id']])) {
@ -53,7 +51,7 @@ switch ($VARS['action']) {
returnToSender("invalid_userid");
}
}
if (is_empty($VARS['name']) || is_empty($VARS['username']) || is_empty($VARS['status'])) {
if (empty($VARS['name']) || empty($VARS['username']) || empty($VARS['status'])) {
returnToSender('invalid_parameters');
}
@ -69,7 +67,7 @@ switch ($VARS['action']) {
'deleted' => 0
];
if (!is_empty($VARS['pass'])) {
if (!empty($VARS['pass'])) {
$data['password'] = password_hash($VARS['pass'], PASSWORD_BCRYPT);
}
@ -78,11 +76,11 @@ switch ($VARS['action']) {
$data['phone2'] = "";
$data['accttype'] = 1;
$database->insert('accounts', $data);
insertAuthLog(17, $_SESSION['uid'], $data['username'] . ", " . $data['realname'] . ", " . $data['email'] . ", " . $data['acctstatus']);
Log::insert(LogType::USER_ADDED, $_SESSION['uid'], $data['username'] . ", " . $data['realname'] . ", " . $data['email'] . ", " . $data['acctstatus']);
} else {
$olddata = $database->select('accounts', '*', ['uid' => $VARS['id']])[0];
$database->update('accounts', $data, ['uid' => $VARS['id']]);
insertAuthLog(18, $_SESSION['uid'], "OLD: " . $olddata['username'] . ", " . $olddata['realname'] . ", " . $olddata['email'] . ", " . $olddata['acctstatus'] . "; NEW: " . $data['username'] . ", " . $data['realname'] . ", " . $data['email'] . ", " . $data['acctstatus']);
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']);
}
returnToSender("user_saved");
@ -97,7 +95,7 @@ switch ($VARS['action']) {
// we will flag it as deleted and set the status to LOCKED_OR_DISABLED.
$database->update('accounts', ['acctstatus' => 2, 'deleted' => 1], ['uid' => $VARS['id']]);
}
insertAuthLog(16, $_SESSION['uid'], $olddata['username'] . ", " . $olddata['realname'] . ", " . $olddata['email'] . ", " . $olddata['acctstatus']);
Log::insert(LogType::USER_REMOVED, $_SESSION['uid'], $olddata['username'] . ", " . $olddata['realname'] . ", " . $olddata['email'] . ", " . $olddata['acctstatus']);
returnToSender("user_deleted");
case "rmtotp":
if ($database->has('accounts', ['uid' => $VARS['id']]) !== TRUE) {
@ -105,28 +103,27 @@ switch ($VARS['action']) {
}
$u = $database->get('accounts', 'username', ['uid' => $VARS['id']]);
$database->update('accounts', ["authsecret" => null], ['uid' => $VARS['id']]);
insertAuthLog(10, $_SESSION['uid'], $u);
Log::insert(LogType::REMOVED_2FA, $_SESSION['uid'], $u);
returnToSender("2fa_removed");
case "clearlog":
$rows = $database->count('authlog');
$database->delete('authlog', []);
insertAuthLog(15, $_SESSION['uid'], lang2("removed n entries", ['n' => $rows], false));
Log::insert(LogType::LOG_CLEARED, $_SESSION['uid'], $Strings->build("removed n entries", ['n' => $rows], false));
returnToSender("log_cleared");
case "editmanager":
require_once __DIR__ . "/lib/userinfo.php";
if (!$database->has('accounts', ['username' => $VARS['manager']])) {
returnToSender("invalid_manager");
}
$manager = getUserByUsername($VARS['manager'])['uid'];
$manager = User::byUsername($VARS['manager'])->getUID();
$already_assigned = $database->select('managers', 'employeeid', ['managerid' => $manager]);
foreach ($VARS['employees'] as $u) {
if (!user_exists($u)) {
returnToSender("user_not_exists", htmlentities($u));
$emp = User::byUsername($u);
if (!$emp->exists()) {
returnToSender("user_not_exists", htmlentities($emp->getUsername()));
}
$uid = getUserByUsername($u)['uid'];
$database->insert('managers', ['employeeid' => $uid, 'managerid' => $manager]);
$already_assigned = array_diff($already_assigned, [$uid]); // Remove user from old list
$database->insert('managers', ['employeeid' => $emp->getUID(), 'managerid' => $manager]);
$already_assigned = array_diff($already_assigned, [$emp->getUID()]); // Remove user from old list
}
foreach ($already_assigned as $uid) {
$database->delete('managers', ["AND" => ['employeeid' => $uid, 'managerid' => $manager]]);
@ -198,14 +195,14 @@ switch ($VARS['action']) {
returnToSender("permission_deleted");
case "autocomplete_user":
header("Content-Type: application/json");
if (is_empty($VARS['q']) || strlen($VARS['q']) < 3) {
if (empty($VARS['q']) || strlen($VARS['q']) < 3) {
exit(json_encode([]));
}
$data = $database->select('accounts', ['uid', 'username', 'realname (name)'], ["OR" => ['username[~]' => $VARS['q'], 'realname[~]' => $VARS['q']], "LIMIT" => 10]);
exit(json_encode($data));
case "autocomplete_permission":
header("Content-Type: application/json");
if (is_empty($VARS['q'])) {
if (empty($VARS['q'])) {
exit(json_encode([]));
}
$data = $database->select('permissions', ['permcode (name)', 'perminfo (info)'], ["OR" => ['permcode[~]' => $VARS['q'], 'perminfo[~]' => $VARS['q']], "LIMIT" => 10]);
@ -217,14 +214,13 @@ switch ($VARS['action']) {
$gid = $VARS['gid'];
$already_assigned = $database->select('assigned_groups', 'uid', ['groupid' => $gid]);
require_once __DIR__ . "/lib/userinfo.php";
foreach ($VARS['users'] as $u) {
if (!user_exists($u)) {
returnToSender("user_not_exists", htmlentities($u));
$user = User::byUsername($u);
if (!$user->exists()) {
returnToSender("user_not_exists", htmlentities($user->getUsername()));
}
$uid = getUserByUsername($u)['uid'];
$database->insert('assigned_groups', ['groupid' => $gid, 'uid' => $uid]);
$already_assigned = array_diff($already_assigned, [$uid]); // Remove user from old list
$database->insert('assigned_groups', ['groupid' => $gid, 'uid' => $user->getUID()]);
$already_assigned = array_diff($already_assigned, [$user->getUID()]); // Remove user from old list
}
foreach ($already_assigned as $uid) {
$database->delete('assigned_groups', ["AND" => ['uid' => $uid, 'groupid' => $gid]]);
@ -251,7 +247,7 @@ switch ($VARS['action']) {
break;
case "signout":
session_destroy();
header('Location: index.php');
header('Location: index.php?logout=1');
die("Logged out.");
default:
die("Invalid action");

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

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

@ -0,0 +1,9 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
sendJsonResp();

@ -0,0 +1,15 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
$APIS = [
"ping" => [
"load" => "ping.php",
"vars" => [
]
]
];

@ -0,0 +1,144 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/**
* Build and send a simple JSON response.
* @param string $msg A message
* @param string $status "OK" or "ERROR"
* @param array $data More JSON data
*/
function sendJsonResp(string $msg = null, string $status = "OK", array $data = null) {
$resp = [];
if (!is_null($data)) {
$resp = $data;
}
if (!is_null($msg)) {
$resp["msg"] = $msg;
}
$resp["status"] = $status;
header("Content-Type: application/json");
exit(json_encode($resp));
}
function exitWithJson(array $json) {
header("Content-Type: application/json");
exit(json_encode($json));
}
/**
* Get the API key with most of the characters replaced with *s.
* @global string $key
* @return string
*/
function getCensoredKey() {
global $key;
$resp = $key;
if (strlen($key) > 5) {
for ($i = 2; $i < strlen($key) - 2; $i++) {
$resp[$i] = "*";
}
}
return $resp;
}
/**
* Check if the request is allowed
* @global array $VARS
* @return bool true if the request should continue, false if the request is bad
*/
function authenticate(): bool {
global $VARS;
// HTTP basic auth
if (!empty($_SERVER['PHP_AUTH_USER']) && !empty($_SERVER['PHP_AUTH_PW'])) {
$user = User::byUsername($_SERVER['PHP_AUTH_USER']);
if (!$user->checkPassword($_SERVER['PHP_AUTH_PW'])) {
return false;
}
return true;
}
// Form auth
if (empty($VARS['username']) || empty($VARS['password'])) {
return false;
} else {
$username = $VARS['username'];
$password = $VARS['password'];
$user = User::byUsername($username);
if ($user->exists() !== true || Login::auth($username, $password) !== Login::LOGIN_OK) {
return false;
}
}
return true;
}
/**
* Get the User whose credentials were used to make the request.
*/
function getRequestUser(): User {
global $VARS;
if (!empty($_SERVER['PHP_AUTH_USER'])) {
return User::byUsername($_SERVER['PHP_AUTH_USER']);
} else {
return User::byUsername($VARS['username']);
}
}
function checkVars($vars, $or = false) {
global $VARS;
$ok = [];
foreach ($vars as $key => $val) {
if (strpos($key, "OR") === 0) {
checkVars($vars[$key], true);
continue;
}
// Only check type of optional variables if they're set, and don't
// mark them as bad if they're not set
if (strpos($key, " (optional)") !== false) {
$key = str_replace(" (optional)", "", $key);
if (empty($VARS[$key])) {
continue;
}
} else {
if (empty($VARS[$key])) {
$ok[$key] = false;
continue;
}
}
if (strpos($val, "/") === 0) {
// regex
$ok[$key] = preg_match($val, $VARS[$key]) === 1;
} else {
$checkmethod = "is_$val";
$ok[$key] = !($checkmethod($VARS[$key]) !== true);
}
}
if ($or) {
$success = false;
$bad = "";
foreach ($ok as $k => $v) {
if ($v) {
$success = true;
break;
} else {
$bad = $k;
}
}
if (!$success) {
http_response_code(400);
die("400 Bad request: variable $bad is missing or invalid");
}
} else {
foreach ($ok as $key => $bool) {
if (!$bool) {
http_response_code(400);
die("400 Bad request: variable $key is missing or invalid");
}
}
}
}

@ -0,0 +1,79 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
require __DIR__ . '/../required.php';
require __DIR__ . '/functions.php';
require __DIR__ . '/apisettings.php';
$VARS = $_GET;
if ($_SERVER['REQUEST_METHOD'] != "GET") {
$VARS = array_merge($VARS, $_POST);
}
$requestbody = file_get_contents('php://input');
$requestjson = json_decode($requestbody, TRUE);
if (json_last_error() == JSON_ERROR_NONE) {
$VARS = array_merge($VARS, $requestjson);
}
// If we're not using the old api.php file, allow more flexible requests
if (strpos($_SERVER['REQUEST_URI'], "/api.php") === FALSE) {
$route = explode("/", substr($_SERVER['REQUEST_URI'], strpos($_SERVER['REQUEST_URI'], "api/") + 4));
if (count($route) >= 1) {
$VARS["action"] = $route[0];
}
if (count($route) >= 2 && strpos($route[1], "?") !== 0) {
for ($i = 1; $i < count($route); $i++) {
if (empty($route[$i]) || strpos($route[$i], "=") === false) {
continue;
}
$key = explode("=", $route[$i], 2)[0];
$val = explode("=", $route[$i], 2)[1];
$VARS[$key] = $val;
}
}
if (strpos($route[count($route) - 1], "?") === 0) {
$morevars = explode("&", substr($route[count($route) - 1], 1));
foreach ($morevars as $var) {
$key = explode("=", $var, 2)[0];
$val = explode("=", $var, 2)[1];
$VARS[$key] = $val;
}
}
}
if (!authenticate()) {
header('WWW-Authenticate: Basic realm="' . $SETTINGS['site_title'] . '"');
header('HTTP/1.1 401 Unauthorized');
die("401 Unauthorized: you need to supply valid credentials.");
}
if (empty($VARS['action'])) {
http_response_code(404);
die("404 No action specified");
}
if (!isset($APIS[$VARS['action']])) {
http_response_code(404);
die("404 Action not defined");
}
$APIACTION = $APIS[$VARS["action"]];
if (!file_exists(__DIR__ . "/actions/" . $APIACTION["load"])) {
http_response_code(404);
die("404 Action not found");
}
if (!empty($APIACTION["vars"])) {
checkVars($APIACTION["vars"]);
}
require_once __DIR__ . "/actions/" . $APIACTION["load"];

@ -1,5 +1,4 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -14,7 +13,7 @@ if ($_SESSION['loggedin'] != true) {
require_once __DIR__ . "/pages.php";
$pageid = "home";
if (isset($_GET['page']) && !is_empty($_GET['page'])) {
if (!empty($_GET['page'])) {
$pg = strtolower($_GET['page']);
$pg = preg_replace('/[^0-9a-z_]/', "", $pg);
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);
header("Link: <static/css/bootstrap.min.css>; rel=preload; as=style", false);
header("Link: <static/css/material-color/material-color.min.css>; rel=preload; as=style", false);
header("Link: <static/css/app.css>; rel=preload; as=style", false);
header("Link: <static/css/fa-svg-with-js.css>; rel=preload; as=style", false);
header("Link: <static/css/svg-with-js.min.css>; rel=preload; as=style", false);
header("Link: <static/js/fontawesome-all.min.js>; rel=preload; as=script", false);
header("Link: <static/js/jquery-3.3.1.min.js>; rel=preload; as=script", false);
header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
header("Link: <static/js/bootstrap.bundle.min.js>; rel=preload; as=script", false);
?>
<!DOCTYPE html>
<html>
@ -40,14 +39,14 @@ header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?php echo SITE_TITLE; ?></title>
<title><?php echo $SETTINGS['site_title']; ?></title>
<link rel="icon" href="static/img/logo.svg">
<link href="static/css/bootstrap.min.css" rel="stylesheet">
<link href="static/css/material-color/material-color.min.css" rel="stylesheet">
<link href="static/css/app.css" rel="stylesheet">
<link href="static/css/fa-svg-with-js.css" rel="stylesheet">
<link href="static/css/svg-with-js.min.css" rel="stylesheet">
<script nonce="<?php echo $SECURE_NONCE; ?>">
FontAwesomeConfig = {autoAddCss: false}
</script>
@ -66,28 +65,35 @@ header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
<?php
// Alert messages
if (isset($_GET['msg']) && !is_empty($_GET['msg']) && array_key_exists($_GET['msg'], MESSAGES)) {
// optional string generation argument
if (!isset($_GET['arg']) || is_empty($_GET['arg'])) {
$alertmsg = lang(MESSAGES[$_GET['msg']]['string'], false);
if (!empty($_GET['msg'])) {
if (array_key_exists($_GET['msg'], MESSAGES)) {
// optional string generation argument
if (empty($_GET['arg'])) {
$alertmsg = $Strings->get(MESSAGES[$_GET['msg']]['string'], false);
} else {
$alertmsg = $Strings->build(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false);
}
$alerttype = MESSAGES[$_GET['msg']]['type'];
$alerticon = "square-o";
switch (MESSAGES[$_GET['msg']]['type']) {
case "danger":
$alerticon = "times";
break;
case "warning":
$alerticon = "exclamation-triangle";
break;
case "info":
$alerticon = "info-circle";
break;
case "success":
$alerticon = "check";
break;
}
} else {
$alertmsg = lang2(MESSAGES[$_GET['msg']]['string'], ["arg" => strip_tags($_GET['arg'])], false);
}
$alerttype = MESSAGES[$_GET['msg']]['type'];
$alerticon = "square-o";
switch (MESSAGES[$_GET['msg']]['type']) {
case "danger":
$alerticon = "times";
break;
case "warning":
$alerticon = "exclamation-triangle";
break;
case "info":
$alerticon = "info-circle";
break;
case "success":
$alerticon = "check";
break;
// We don't have a message for this, so just assume an error and escape stuff.
$alertmsg = htmlentities($Strings->get($_GET['msg'], false));
$alerticon = "times";
$alerttype = "danger";
}
echo <<<END
<div class="row justify-content-center" id="msg-alert-box">
@ -121,7 +127,7 @@ END;
</button>
<a class="navbar-brand py-0 mr-auto" href="app.php">
<img src="static/img/logo.svg" alt="" class="d-none d-<?php echo $navbar_breakpoint; ?>-inline brand-img py-0" />
<?php echo SITE_TITLE; ?>
<?php echo $SETTINGS['site_title']; ?>
</a>
<div class="collapse navbar-collapse py-0" id="navbar-collapse">
@ -146,7 +152,7 @@ END;
if (isset($pg['icon'])) {
?><i class="<?php echo $pg['icon']; ?> fa-fw"></i> <?php
}
lang($pg['title']);
$Strings->get($pg['title']);
?>
</a>
</span>
@ -157,13 +163,13 @@ END;
</div>
<div class="navbar-nav ml-auto py-0" id="navbar-right">
<span class="nav-item py-<?php echo $navbar_breakpoint; ?>-0">
<a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="<?php echo PORTAL_URL; ?>">
<a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="<?php echo $SETTINGS['accounthub']['home']; ?>">
<i class="fas fa-user fa-fw"></i><span>&nbsp;<?php echo $_SESSION['realname'] ?></span>
</a>
</span>
<span class="nav-item mr-auto py-<?php echo $navbar_breakpoint; ?>-0">
<a class="nav-link py-<?php echo $navbar_breakpoint; ?>-0" href="action.php?action=signout">
<i class="fas fa-sign-out-alt fa-fw"></i><span>&nbsp;<?php lang("sign out") ?></span>
<i class="fas fa-sign-out-alt fa-fw"></i><span>&nbsp;<?php $Strings->get("sign out") ?></span>
</a>
</span>
</div>
@ -177,12 +183,12 @@ END;
?>
</div>
<div class="footer">
<?php echo FOOTER_TEXT; ?><br />
Copyright &copy; <?php echo date('Y'); ?> <?php echo COPYRIGHT_NAME; ?>
<?php echo $SETTINGS['footer_text']; ?><br />
Copyright &copy; <?php echo date('Y'); ?> <?php echo $SETTINGS['copyright']; ?>
</div>
</div>
<script src="static/js/jquery-3.3.1.min.js"></script>
<script src="static/js/bootstrap.min.js"></script>
<script src="static/js/bootstrap.bundle.min.js"></script>
<script src="static/js/app.js"></script>
<?php
// custom page scripts

24
composer.lock generated

@ -9,16 +9,16 @@
"packages": [
{
"name": "catfan/medoo",
"version": "v1.5.3",
"version": "v1.5.7",
"source": {
"type": "git",
"url": "https://github.com/catfan/Medoo.git",
"reference": "1aa25a4001e0cfb739ba2996f00f4a3d2a7fdf07"
"reference": "8d90cba0e8ff176028847527d0ea76fe41a06ecf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/catfan/Medoo/zipball/1aa25a4001e0cfb739ba2996f00f4a3d2a7fdf07",
"reference": "1aa25a4001e0cfb739ba2996f00f4a3d2a7fdf07",
"url": "https://api.github.com/repos/catfan/Medoo/zipball/8d90cba0e8ff176028847527d0ea76fe41a06ecf",
"reference": "8d90cba0e8ff176028847527d0ea76fe41a06ecf",
"shasum": ""
},
"require": {
@ -64,20 +64,20 @@
"sql",
"sqlite"
],
"time": "2017-12-25 17:02:41"
"time": "2018-06-14 18:59:08"
},
{
"name": "guzzlehttp/guzzle",
"version": "6.3.0",
"version": "6.3.3",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699"
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/f4db5a78a5ea468d4831de7f0bf9d9415e348699",
"reference": "f4db5a78a5ea468d4831de7f0bf9d9415e348699",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba",
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba",
"shasum": ""
},
"require": {
@ -87,7 +87,7 @@
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "^4.0 || ^5.0",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
"psr/log": "^1.0"
},
"suggest": {
@ -96,7 +96,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.2-dev"
"dev-master": "6.3-dev"
}
},
"autoload": {
@ -129,7 +129,7 @@
"rest",
"web service"
],
"time": "2017-06-22 18:50:49"
"time": "2018-04-22 15:46:56"
},
{
"name": "guzzlehttp/promises",

@ -1,164 +1,131 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
require_once __DIR__ . "/required.php";
require_once __DIR__ . "/lib/login.php";
// if we're logged in, we don't need to be here.
if (!empty($_SESSION['loggedin']) && $_SESSION['loggedin'] === true && !isset($_GET['permissionerror'])) {
header('Location: app.php');
die();
}
if (isset($_GET['permissionerror'])) {
$alert = lang("no access permission", false);
}
/**
* Show a simple HTML page with a line of text and a button. Matches the UI of
* the AccountHub login flow.
*
* @global type $SETTINGS
* @global type $SECURE_NONCE
* @global type $Strings
* @param string $title Text to show, passed through i18n
* @param string $button Button text, passed through i18n
* @param string $url URL for the button
*/
function showHTML(string $title, string $button, string $url) {
global $SETTINGS, $SECURE_NONCE, $Strings;
?>
<!DOCTYPE html>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
/* Authenticate user */
$userpass_ok = false;
$multiauth = false;
if (checkLoginServer()) {
if (!empty($VARS['progress']) && $VARS['progress'] == "1") {
if (!CAPTCHA_ENABLED || (CAPTCHA_ENABLED && verifyCaptcheck($VARS['captcheck_session_code'], $VARS['captcheck_selected_answer'], CAPTCHA_SERVER . "/api.php"))) {
$errmsg = "";
if (authenticate_user($VARS['username'], $VARS['password'], $errmsg)) {
switch (get_account_status($VARS['username'])) {
case "LOCKED_OR_DISABLED":
$alert = lang("account locked", false);
break;
case "TERMINATED":
$alert = lang("account terminated", false);
break;
case "CHANGE_PASSWORD":
$alert = lang("password expired", false);
case "NORMAL":
$userpass_ok = true;
break;
case "ALERT_ON_ACCESS":
sendLoginAlertEmail($VARS['username']);
$userpass_ok = true;
break;
}
if ($userpass_ok) {
$_SESSION['passok'] = true; // stop logins using only username and authcode
if (userHasTOTP($VARS['username'])) {
$multiauth = true;
} else {
doLoginUser($VARS['username'], $VARS['password']);
header('Location: app.php');
die("Logged in, go to app.php");
}
}
} else {
if (!is_empty($errmsg)) {
$alert = lang2("login server error", ['arg' => $errmsg], false);
} else {
$alert = lang("login incorrect", false);
}
}
} else {
$alert = lang("captcha error", false);
}
} else if (!empty($VARS['progress']) && $VARS['progress'] == "2") {
if ($_SESSION['passok'] !== true) {
// stop logins using only username and authcode
sendError("Password integrity check failed!");
<title><?php echo $SETTINGS['site_title']; ?></title>
<link rel="icon" href="static/img/logo.svg">
<link href="static/css/bootstrap.min.css" rel="stylesheet">
<style nonce="<?php echo $SECURE_NONCE; ?>">
.display-5 {
font-size: 2.5rem;
font-weight: 300;
line-height: 1.2;
}
if (verifyTOTP($VARS['username'], $VARS['authcode'])) {
if (doLoginUser($VARS['username'])) {
header('Location: app.php');
die("Logged in, go to app.php");
} else {
$alert = lang("login server user data error", false);
}
} else {
$alert = lang("2fa incorrect", false);
.banner-image {
max-height: 100px;
margin: 2em auto;
border: 1px solid grey;
border-radius: 15%;
}
}
} else {
$alert = lang("login server unavailable", false);
}
header("Link: <static/fonts/Roboto.css>; rel=preload; as=style", false);
header("Link: <static/css/bootstrap.min.css>; rel=preload; as=style", false);
header("Link: <static/css/material-color/material-color.min.css>; rel=preload; as=style", false);
header("Link: <static/css/index.css>; rel=preload; as=style", false);
header("Link: <static/js/jquery-3.3.1.min.js>; rel=preload; as=script", false);
header("Link: <static/js/bootstrap.min.js>; rel=preload; as=script", false);
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title><?php echo SITE_TITLE; ?></title>
<link rel="icon" href="static/img/logo.svg">
<link href="static/css/bootstrap.min.css" rel="stylesheet">
<link href="static/css/material-color/material-color.min.css" rel="stylesheet">
<link href="static/css/index.css" rel="stylesheet">
<?php if (CAPTCHA_ENABLED) { ?>
<script src="<?php echo CAPTCHA_SERVER ?>/captcheck.dist.js"></script>
<?php } ?>
</head>
<body>
</style>
<div class="container mt-4">
<div class="row justify-content-center">
<div class="col-auto">
<img class="banner-image" src="static/img/logo.svg" />
<div class="col-12 text-center">
<img class="banner-image" src="./static/img/logo.svg" />
</div>
</div>
<div class="row justify-content-center">
<div class="card col-11 col-xs-11 col-sm-8 col-md-6 col-lg-4">
<div class="card-body">
<h5 class="card-title"><?php lang("sign in"); ?></h5>
<form action="" method="POST">
<?php
if (!empty($alert)) {
?>
<div class="alert alert-danger">
<i class="fa fa-fw fa-exclamation-triangle"></i> <?php echo $alert; ?>
</div>
<?php
}
if ($multiauth != true) {
?>
<input type="text" class="form-control" name="username" placeholder="<?php lang("username"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /><br />
<input type="password" class="form-control" name="password" placeholder="<?php lang("password"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" /><br />
<?php if (CAPTCHA_ENABLED) { ?>
<div class="captcheck_container" data-stylenonce="<?php echo $SECURE_NONCE; ?>"></div>
<br />
<?php } ?>
<input type="hidden" name="progress" value="1" />
<?php
} else if ($multiauth) {
?>
<div class="alert alert-info">
<?php lang("2fa prompt"); ?>
</div>
<input type="text" class="form-control" name="authcode" placeholder="<?php lang("authcode"); ?>" required="required" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autofocus /><br />
<input type="hidden" name="progress" value="2" />
<input type="hidden" name="username" value="<?php echo $VARS['username']; ?>" />
<?php
}
?>
<button type="submit" class="btn btn-primary">
<?php lang("continue"); ?>
</button>
</form>
<div class="col-12 text-center">
<h1 class="display-5 mb-4"><?php $Strings->get($title); ?></h1>
</div>
<div class="col-12 col-sm-8 col-lg-6">
<div class="card mt-4">
<div class="card-body">
<a href="<?php echo $url; ?>" class="btn btn-primary btn-block"><?php $Strings->get($button); ?></a>
</div>
</div>
</div>
</div>
<div class="footer">
<?php echo FOOTER_TEXT; ?><br />
Copyright &copy; <?php echo date('Y'); ?> <?php echo COPYRIGHT_NAME; ?>
</div>
</div>
<script src="static/js/jquery-3.3.1.min.js"></script>
<script src="static/js/bootstrap.min.js"></script>
</body>
</html>
<?php
}
if (!empty($_GET['logout'])) {
showHTML("You have been logged out.", "Log in again", "./index.php");
die();
}
if (empty($_SESSION["login_code"])) {
$redirecttologin = true;
} else {
try {
$uidinfo = AccountHubApi::get("checkloginkey", ["code" => $_SESSION["login_code"]]);
if ($uidinfo["status"] == "ERROR") {
throw new Exception();
}
if (is_numeric($uidinfo['uid'])) {
$user = new User($uidinfo['uid'] * 1);
foreach ($SETTINGS['permissions'] as $perm) {
if (!$user->hasPermission($perm)) {
showHTML("no access permission", "sign out", "./action.php?action=signout");
die();
}
}
Session::start($user);
$_SESSION["login_code"] = null;
header('Location: app.php');
showHTML("Logged in", "Continue", "./app.php");
die();
} else {
throw new Exception();
}
} catch (Exception $ex) {
$redirecttologin = true;
}
}
if ($redirecttologin) {
try {
$urlbase = (isset($_SERVER['HTTPS']) ? "https" : "http") . "://" . $_SERVER['HTTP_HOST'] . (($_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) ? ":" . $_SERVER['SERVER_PORT'] : "");
$iconurl = $urlbase . str_replace("index.php", "", $_SERVER["REQUEST_URI"]) . "static/img/logo.svg";
$codedata = AccountHubApi::get("getloginkey", ["appname" => $SETTINGS["site_title"], "appicon" => $iconurl]);
if ($codedata['status'] != "OK") {
throw new Exception($Strings->get("login server unavailable", false));
}
$redirecturl = $urlbase . $_SERVER['REQUEST_URI'];
$_SESSION["login_code"] = $codedata["code"];
$locationurl = $codedata["loginurl"] . "?code=" . htmlentities($codedata["code"]) . "&redirect=" . htmlentities($redirecturl);
header("Location: $locationurl");
showHTML("Continue", "Continue", $locationurl);
die();
} catch (Exception $ex) {
sendError($ex->getMessage());
}
}

@ -1,140 +0,0 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
define("STRINGS", [
"sign in" => "Sign In",
"username" => "Username",
"password" => "Password",
"continue" => "Continue",
"authcode" => "Authentication code",
"2fa prompt" => "Enter the six-digit code from your mobile authenticator app.",
"2fa incorrect" => "Authentication code incorrect.",
"login incorrect" => "Login incorrect.",
"no admin permission" => "You do not have permission to access this system.",
"login server unavailable" => "Login server unavailable. Try again later or contact technical support.",
"account locked" => "This account has been disabled. Contact technical support.",
"password expired" => "You must change your password before continuing.",
"account terminated" => "Account terminated. Access denied.",
"account state error" => "Your account state is not stable. Log out, restart your browser, and try again.",
"welcome user" => "Welcome, {user}!",
"sign out" => "Sign out",
"settings" => "Settings",
"options" => "Options",
"404 error" => "404 Error",
"page not found" => "Page not found.",
"invalid parameters" => "Invalid request parameters.",
"login server error" => "The login server returned an error: {arg}",
"login server user data error" => "The login server refused to provide account information. Try again or contact technical support.",
"captcha error" => "There was a problem with the CAPTCHA (robot test). Try again.",
"no access permission" => "You do not have permission to access this system.",
"home" => "Home",
"users" => "Users",
"more" => "More",
"actions" => "Actions",
"name" => "Name",
"email" => "Email",
"status" => "Status",
"type" => "Type",
"new user" => "New User",
"total users" => "Total Users",
"view users" => "View Users",
"normal accounts" => "Normal Accounts",
"locked accounts" => "Locked Accounts",
"editing user" => "Editing {user}",
"invalid userid" => "Invalid user ID.",
"user saved" => "User saved.",
"adding user" => "Adding new user",
"placeholder name" => "John Doe",
"placeholder username" => "jdoe",
"placeholder email address" => "jdoe@example.com",
"placeholder password" => "swordfish",
"new password" => "New Password",
"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.",
"delete user" => "Delete User",
"really delete user" => "Are you sure you want to delete this user? This action cannot be reversed.",
"user deleted" => "User account deleted.",
"user does not exist" => "User does not exist.",
"logtime" => "Date/Time",
"logtype" => "Event Type",
"ip address" => "IP Address",
"other data" => "Other",
"security log" => "Security Log",
"event type reference" => "Event Type Reference",
"clear log" => "Clear Log",
"really clear log" => "Are you sure you want to purge the security log? This action cannot be reversed.",
"log cleared" => "Security log cleared.",
"removed n entries" => "Removed {n} entries",
"security log entries" => "Security Log Entries",
"view security log" => "View Security Log",
"managers" => "Managers",
"manager" => "Manager",
"employee" => "Employee",
"delete relationship" => "Delete Relationship",
"really delete relationship" => "Are you sure you want to remove this manager-employee relationship? This action cannot be reversed.",
"relationship deleted" => "Relationship deleted.",
"edit relationship" => "Edit Relationship",
"adding relationship" => "Adding Relationship",
"relationship added" => "Relationship added.",
"permissions" => "Permissions",
"permission" => "Permission",
"new permission" => "New Permission",
"delete permission" => "Delete Permission",
"adding permission" => "Adding Permission",
"user" => "User",
"permission does not exist" => "Permission does not exist: {arg}",
"really delete permission" => "Are you sure you want to revoke this permission?",
"permission added" => "Permission assigned.",
"permission deleted" => "Permission deleted.",
"remove 2fa" => "Reset 2FA",
"action performed by" => "Action performed by {user}",
"2fa removed" => "2-factor authentication removed.",
"2fa" => "2FA",
"show deleted" => "Show deleted",
"editing deleted account" => "You are editing an account marked as deleted. The account will be undeleted if you press Save.",
"manager assigned" => "Manager relationships saved.",
"manager does not exist" => "The selected manager username does not exist.",
"type to add a person" => "Type to add a person",
"employees" => "Employees",
"type to select a manager" => "Type to select a manager",
"select a manager to view or edit employees" => "Select a manager to view or edit the assigned employees.",
"report export" => "Reports/Export",
"report type" => "Report type",
"format" => "Format",
"generate report" => "Generate report",
"choose an option" => "Choose an option",
"csv file" => "CSV text file",
"ods file" => "ODS spreadsheet",
"html file" => "HTML web page",
"uid" => "User ID",
"manager name" => "Manager",
"manager username" => "Mgr. Username",
"employee name" => "Employee",
"employee username" => "Emp. Username",
"permission id" => "Perm. ID",
"permissions assigned" => "Permissions assigned.",
"type to select a user" => "Type to select a user",
"type to add a permission" => "Type to add a permission",
"Choose a permission" => "Choose a permission",
"select a user to view or edit permissions" => "Select a user to view or edit the assigned permissions.",
"group" => "Group",
"groups" => "Groups",
"group does not exist" => "That group does not exist.",
"group members updated" => "Group members updated.",
"group added" => "Group added.",
"group deleted" => "Group deleted.",
"group already exists" => "A group with that name already exists.",
"save" => "Save",
"next" => "Next",
"add" => "Add",
"delete" => "Delete",
"new group" => "New group",
"delete group" => "Delete group",
"enter group name" => "Group name",
"group management" => "Group Management",
"group assignments" => "Group Assignments",
"group id" => "Group ID",
"group name" => "Group Name"
]);

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

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

@ -0,0 +1,9 @@
{
"Home": "Home",
"Users": "Users",
"Groups": "Groups",
"Security": "Security",
"Security Log": "Security Log",
"Managers": "Managers",
"Permissions": "Permissions"
}

@ -0,0 +1,56 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
class AccountHubApi {
public static function get(string $action, array $data = null, bool $throwex = false) {
global $SETTINGS;
$content = [
"action" => $action,
"key" => $SETTINGS['accounthub']['key']
];
if (!is_null($data)) {
$content = array_merge($content, $data);
}
$options = [
'http' => [
'method' => 'POST',
'content' => json_encode($content),
'header' => "Content-Type: application/json\r\n" .
"Accept: application/json\r\n",
"ignore_errors" => true
]
];
$context = stream_context_create($options);
$result = file_get_contents($SETTINGS['accounthub']['api'], false, $context);
$response = json_decode($result, true);
if ($result === false || !AccountHubApi::checkHttpRespCode($http_response_header) || json_last_error() != JSON_ERROR_NONE) {
if ($throwex) {
throw new Exception($result);
} else {
sendError($result);
}
}
return $response;
}
private static function checkHttpRespCode(array $headers): bool {
foreach ($headers as $header) {
if (preg_match("/HTTP\/[0-9]\.[0-9] [0-9]{3}.*/", $header)) {
$respcode = explode(" ", $header)[1] * 1;
if ($respcode >= 200 && $respcode < 300) {
return true;
}
}
}
return false;
}
}

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

@ -0,0 +1,275 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
class FormBuilder {
private $items = [];
private $hiddenitems = [];
private $title = "";
private $icon = "";
private $buttons = [];
private $action = "action.php";
private $method = "POST";
private $id = "editform";
/**
* Create a form with autogenerated HTML.
*
* @param string $title Form title/heading
* @param string $icon FontAwesone icon next to the title.
* @param string $action URL to submit the form to.
* @param string $method Form submission method (POST, GET, etc.)
*/
public function __construct(string $title = "Untitled Form", string $icon = "fas fa-file-alt", string $action = "action.php", string $method = "POST") {
$this->title = $title;
$this->icon = $icon;
$this->action = $action;
$this->method = $method;
}
/**
* Set the title of the form.
* @param string $title
*/
public function setTitle(string $title) {
$this->title = $title;
}
/**
* Set the icon for the form.
* @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
*/
public function setIcon(string $icon) {
$this->icon = $icon;
}
/**
* Set the URL the form will submit to.
* @param string $action
*/
public function setAction(string $action) {
$this->action = $action;
}
/**
* Set the form submission method (GET, POST, etc)
* @param string $method
*/
public function setMethod(string $method = "POST") {
$this->method = $method;
}
/**
* Set the form ID.
* @param string $id
*/
public function setID(string $id = "editform") {
$this->id = $id;
}
/**
* Add an input to the form.
*
* @param string $name Element name
* @param string $value Element value
* @param string $type Input type (text, number, date, select, tel...)
* @param bool $required If the element is required for form submission.
* @param string $id Element ID
* @param array $options Array of [value => text] pairs for a select element
* @param string $label Text label to display near the input
* @param string $icon FontAwesome icon (example: "fas fa-toilet-paper")
* @param int $width Bootstrap column width for the input, out of 12.
* @param int $minlength Minimum number of characters for the input.
* @param int $maxlength Maximum number of characters for the input.
* @param string $pattern Regex pattern for custom client-side validation.
* @param string $error Message to show if the input doesn't validate.
*/
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 = "") {
$item = [
"name" => $name,
"value" => $value,
"type" => $type,
"required" => $required,
"label" => $label,
"icon" => $icon,
"width" => $width,
"minlength" => $minlength,
"maxlength" => $maxlength
];
if (!empty($id)) {
$item["id"] = $id;
}
if (!empty($options) && $type == "select") {
$item["options"] = $options;
}
if (!empty($pattern)) {
$item["pattern"] = $pattern;
}
if (!empty($error)) {
$item["error"] = $error;
}
$this->items[] = $item;
}
/**
* Add a button to the form.
*
* @param string $text Text string to show on the button.
* @param string $icon FontAwesome icon to show next to the text.
* @param string $href If not null, the button will actually be a hyperlink.
* @param string $type Usually "button" or "submit". Ignored if $href is set.
* @param string $id The element ID.
* @param string $name The element name for the button.
* @param string $value The form value for the button. Ignored if $name is null.
* @param string $class The CSS classes for the button, if a standard success-colored one isn't right.
*/
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") {
$button = [
"text" => $text,
"icon" => $icon,
"class" => $class,
"type" => $type,
"id" => $id,
"href" => $href,
"name" => $name,
"value" => $value
];
$this->buttons[] = $button;
}
/**
* Add a hidden input.
* @param string $name
* @param string $value
*/
public function addHiddenInput(string $name, string $value) {
$this->hiddenitems[$name] = $value;
}
/**
* Generate the form HTML.
* @param bool $echo If false, returns HTML string instead of outputting it.
*/
public function generate(bool $echo = true) {
$html = <<<HTMLTOP
<form action="$this->action" method="$this->method" id="$this->id">
<div class="card">
<h3 class="card-header d-flex">
<div>
<i class="$this->icon"></i> $this->title
</div>
</h3>
<div class="card-body">
<div class="row">
HTMLTOP;
foreach ($this->items as $item) {
$required = $item["required"] ? "required" : "";
$id = empty($item["id"]) ? "" : "id=\"$item[id]\"";
$pattern = empty($item["pattern"]) ? "" : "pattern=\"$item[pattern]\"";
if (empty($item['type'])) {
$item['type'] = "text";
}
$itemhtml = "";
$itemlabel = "";
if ($item['type'] != "checkbox") {
$itemlabel = "<label class=\"mb-0\">$item[label]:</label>";
}
$strippedlabel = strip_tags($item['label']);
$itemhtml .= <<<ITEMTOP
\n\n <div class="col-12 col-md-$item[width]">
<div class="form-group mb-3">
$itemlabel
<div class="input-group">
<div class="input-group-prepend">
<span class="input-group-text"><i class="$item[icon]"></i></span>
</div>
ITEMTOP;
switch ($item['type']) {
case "select":
$itemhtml .= <<<SELECT
\n <select class="form-control" name="$item[name]" aria-label="$strippedlabel" $required>
SELECT;
foreach ($item['options'] as $value => $label) {
$selected = "";
if (!empty($item['value']) && $value == $item['value']) {
$selected = " selected";
}
$itemhtml .= "\n <option value=\"$value\"$selected>$label</option>";
}
$itemhtml .= "\n </select>";
break;
case "checkbox":
$itemhtml .= <<<CHECKBOX
\n <div class="form-group form-check">
<input type="checkbox" name="$item[name]" $id class="form-check-input" value="$item[value]" $required aria-label="$strippedlabel">
<label class="form-check-label">$item[label]</label>
</div>
CHECKBOX;
break;
default:
$itemhtml .= <<<INPUT
\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 />
INPUT;
break;
}
if (!empty($item["error"])) {
$itemhtml .= <<<ERROR
\n <div class="invalid-feedback">
$item[error]
</div>
ERROR;
}
$itemhtml .= <<<ITEMBOTTOM
\n </div>
</div>
</div>\n
ITEMBOTTOM;
$html .= $itemhtml;
}
$html .= <<<HTMLBOTTOM
</div>
</div>
HTMLBOTTOM;
if (!empty($this->buttons)) {
$html .= "\n <div class=\"card-footer\">";
foreach ($this->buttons as $btn) {
$btnhtml = "";
$inner = "<i class=\"$btn[icon]\"></i> $btn[text]";
$id = empty($btn['id']) ? "" : "id=\"$btn[id]\"";
if (!empty($btn['href'])) {
$btnhtml = "<a href=\"$btn[href]\" class=\"$btn[class]\" $id>$inner</a>";
} else {
$name = empty($btn['name']) ? "" : "name=\"$btn[name]\"";
$value = (!empty($btn['name']) && !empty($btn['value'])) ? "value=\"$btn[value]\"" : "";
$btnhtml = "<button type=\"$btn[type]\" class=\"$btn[class]\" $id $name $value>$inner</button>";
}
$html .= "\n $btnhtml";
}
$html .= "\n </div>";
}
$html .= "\n </div>";
foreach ($this->hiddenitems as $name => $value) {
$value = htmlentities($value);
$html .= "\n <input type=\"hidden\" name=\"$name\" value=\"$value\" />";
}
$html .= "\n</form>\n";
if ($echo) {
echo $html;
}
return $html;
}
}

@ -0,0 +1,135 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
class IPUtils {
/**
* Check if a given ipv4 address is in a given cidr
* @param string $ip IP to check in IPV4 format eg. 127.0.0.1
* @param string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed
* @return boolean true if the ip is in this range / false if not.
* @author Thorsten Ott <https://gist.github.com/tott/7684443>
*/
public static function ip4_in_cidr($ip, $cidr) {
if (strpos($cidr, '/') == false) {
$cidr .= '/32';
}
// $range is in IP/CIDR format eg 127.0.0.1/24
list( $cidr, $netmask ) = explode('/', $cidr, 2);
$range_decimal = ip2long($cidr);
$ip_decimal = ip2long($ip);
$wildcard_decimal = pow(2, ( 32 - $netmask)) - 1;
$netmask_decimal = ~ $wildcard_decimal;
return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) );
}
/**
* Check if a given ipv6 address is in a given cidr
* @param string $ip IP to check in IPV6 format
* @param string $cidr CIDR netmask
* @return boolean true if the IP is in this range, false otherwise.
* @author MW. <https://stackoverflow.com/a/7952169>
*/
public static function ip6_in_cidr($ip, $cidr) {
$address = inet_pton($ip);
$subnetAddress = inet_pton(explode("/", $cidr)[0]);
$subnetMask = explode("/", $cidr)[1];
$addr = str_repeat("f", $subnetMask / 4);
switch ($subnetMask % 4) {
case 0:
break;
case 1:
$addr .= "8";
break;
case 2:
$addr .= "c";
break;
case 3:
$addr .= "e";
break;
}
$addr = str_pad($addr, 32, '0');
$addr = pack("H*", $addr);
$binMask = $addr;
return ($address & $binMask) == $subnetAddress;
}
/**
* Check if the REMOTE_ADDR is on Cloudflare's network.
* @return boolean true if it is, otherwise false
*/
public static function validateCloudflare() {
if (filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
// Using IPv6
$cloudflare_ips_v6 = [
"2400:cb00::/32",
"2405:8100::/32",
"2405:b500::/32",
"2606:4700::/32",
"2803:f800::/32",
"2c0f:f248::/32",
"2a06:98c0::/29"
];
$valid = false;
foreach ($cloudflare_ips_v6 as $cidr) {
if (ip6_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
$valid = true;
break;
}
}
} else {
// Using IPv4
$cloudflare_ips_v4 = [
"103.21.244.0/22",
"103.22.200.0/22",
"103.31.4.0/22",
"104.16.0.0/12",
"108.162.192.0/18",
"131.0.72.0/22",
"141.101.64.0/18",
"162.158.0.0/15",
"172.64.0.0/13",
"173.245.48.0/20",
"188.114.96.0/20",
"190.93.240.0/20",
"197.234.240.0/22",
"198.41.128.0/17"
];
$valid = false;
foreach ($cloudflare_ips_v4 as $cidr) {
if (ip4_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
$valid = true;
break;
}
}
}
return $valid;
}
/**
* Makes a good guess at the client's real IP address.
*
* @return string Client IP or `0.0.0.0` if we can't find anything
*/
public static function getClientIP() {
// If CloudFlare is in the mix, we should use it.
// Check if the request is actually from CloudFlare before trusting it.
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
if (validateCloudflare()) {
return $_SERVER["HTTP_CF_CONNECTING_IP"];
}
}
if (isset($_SERVER["REMOTE_ADDR"])) {
return $_SERVER["REMOTE_ADDR"];
}
return "0.0.0.0"; // This will not happen unless we aren't a web server
}
}

@ -0,0 +1,80 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
class Login {
const BAD_USERPASS = 1;
const BAD_2FA = 2;
const ACCOUNT_DISABLED = 3;
const LOGIN_OK = 4;
public static function auth(string $username, string $password, string $twofa = ""): int {
global $database;
$username = strtolower($username);
$user = User::byUsername($username);
if (!$user->exists()) {
return Login::BAD_USERPASS;
}
if (!$user->checkPassword($password)) {
return Login::BAD_USERPASS;
}
if ($user->has2fa()) {
if (!$user->check2fa($twofa)) {
return Login::BAD_2FA;
}
}
switch ($user->getStatus()->get()) {
case AccountStatus::TERMINATED:
return Login::BAD_USERPASS;
case AccountStatus::LOCKED_OR_DISABLED:
return Login::ACCOUNT_DISABLED;
case AccountStatus::NORMAL:
default:
return Login::LOGIN_OK;
}
return Login::LOGIN_OK;
}
/**
* Check the login server API for sanity
* @return boolean true if OK, else false
*/
public static function checkLoginServer() {
try {
$resp = AccountHubApi::get("ping");
if ($resp['status'] == "OK") {
return true;
} else {
return false;
}
} catch (Exception $e) {
return false;
}
}
/**
* Checks if the given AccountHub API key is valid by attempting to
* access the API with it.
* @param String $key The API key to check
* @return boolean TRUE if the key is valid, FALSE if invalid or something went wrong
*/
function checkAPIKey($key) {
try {
$resp = AccountHubApi::get("ping", null, true);
return false;
} catch (Exception $e) {
return false;
}
}
}

@ -0,0 +1,53 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
class Notifications {
/**
* Add a new notification.
* @global $database
* @param User $user
* @param string $title
* @param string $content
* @param string $timestamp If left empty, the current date and time will be used.
* @param string $url
* @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.
* @return int The newly-created notification ID.
* @throws Exception
*/
public static function add(User $user, string $title, string $content, string $timestamp = "", string $url = "", bool $sensitive = false): int {
global $Strings;
if ($user->exists()) {
if (empty($title) || empty($content)) {
throw new Exception($Strings->get("invalid parameters", false));
}
$timestamp = date("Y-m-d H:i:s");
if (!empty($timestamp)) {
$timestamp = date("Y-m-d H:i:s", strtotime($timestamp));
}
$resp = AccountHubApi::get("addnotification", [
'uid' => $user->getUID(),
'title' => $title,
'content' => $content,
'timestamp' => $timestamp,
'url' => $url,
'sensitive' => $sensitive
]
);
if ($resp['status'] == "OK") {
return $resp['id'] * 1;
} else {
return false;
}
}
throw new Exception($Strings->get("user does not exist", false));
}
}

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

@ -0,0 +1,122 @@
<?php
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
/**
* Provides translated language strings.
*/
class Strings {
private $language = "en";
private $strings = [];
public function __construct($language = "en") {
if (!preg_match("/[a-zA-Z\_\-]+/", $language)) {
throw new Exception("Invalid language code $language");
}
$this->load("en");
if ($language == "en") {
return;
}
if (file_exists(__DIR__ . "/../langs/$language/")) {
$this->language = $language;
$this->load($language);
} else {
trigger_error("Language $language could not be found.", E_USER_WARNING);
}
}
/**
* Load all JSON files for the specified language.
* @param string $language
*/
private function load(string $language) {
$files = glob(__DIR__ . "/../langs/$language/*.json");
foreach ($files as $file) {
$strings = json_decode(file_get_contents($file), true);
foreach ($strings as $key => $val) {
if (array_key_exists($key, $this->strings)) {
trigger_error("Language key \"$key\" is defined more than once.", E_USER_WARNING);
}
$this->strings[$key] = $val;
}
}
}
/**
* Add language strings dynamically.
* @param array $strings ["key" => "value", ...]
*/
public function addStrings(array $strings) {
foreach ($strings as $key => $val) {
$this->strings[$key] = $val;
}
}
/**
* I18N string getter. If the key isn't found, it outputs the key itself.
* @param string $key
* @param bool $echo True to echo the result, false to return it. Default is true.
* @return string
*/
public function get(string $key, bool $echo = true): string {
$str = $key;
if (array_key_exists($key, $this->strings)) {
$str = $this->strings[$key];
} else {
trigger_error("Language key \"$key\" does not exist in " . $this->language, E_USER_WARNING);
}
if ($echo) {
echo $str;
}
return $str;
}
/**
* I18N string getter (with builder). If the key doesn't exist, outputs the key itself.
* @param string $key
* @param array $replace key-value array of replacements.
* If the string value is "hello {abc}" and you give ["abc" => "123"], the
* result will be "hello 123".
* @param bool $echo True to echo the result, false to return it. Default is true.
* @return string
*/
public function build(string $key, array $replace, bool $echo = true): string {
$str = $key;
if (array_key_exists($key, $this->strings)) {
$str = $this->strings[$key];
} else {
trigger_error("Language key \"$key\" does not exist in " . $this->language, E_USER_WARNING);
}
foreach ($replace as $find => $repl) {
$str = str_replace("{" . $find . "}", $repl, $str);
}
if ($echo) {
echo $str;
}
return $str;
}
/**
* Builds and returns a JSON key:value string for the supplied array of keys.
* @param array $keys ["key1", "key2", ...]
*/
public function getJSON(array $keys): string {
$strings = [];
foreach ($keys as $k) {
$strings[$k] = $this->get($k, false);
}
return json_encode($strings);
}
}

@ -1,18 +0,0 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
require_once __DIR__ . "/../required.php";
require_once __DIR__ . "/iputils.php";
dieifnotloggedin();
function insertAuthLog($type, $uid = null, $data = "") {
global $database;
// find IP address
$ip = getClientIP();
$database->insert("authlog", ['logtime' => date("Y-m-d H:i:s"), 'logtype' => $type, 'uid' => $uid, 'ip' => $ip, 'otherdata' => $data]);
}

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

@ -34,7 +34,7 @@ switch ($VARS['order'][0]['column']) {
}
// search
if (!is_empty($VARS['search']['value'])) {
if (!empty($VARS['search']['value'])) {
$filter = true;
$wherenolimit = [
"OR" => [
@ -78,7 +78,7 @@ if ($filter) {
}
$out['recordsFiltered'] = $recordsFiltered;
for ($i = 0; $i < count($managers); $i++) {
$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>';
$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>';
}
$out['managers'] = $managers;

@ -34,7 +34,7 @@ switch ($VARS['order'][0]['column']) {
}
// search
if (!is_empty($VARS['search']['value'])) {
if (!empty($VARS['search']['value'])) {
$filter = true;
$wherenolimit = [
"OR" => [
@ -76,7 +76,7 @@ if ($filter) {
}
$out['recordsFiltered'] = $recordsFiltered;
for ($i = 0; $i < count($data); $i++) {
$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>';
$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>';
}
$out['perms'] = $data;

@ -1,130 +0,0 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
require __DIR__ . '/../required.php';
dieifnotloggedin();
header("Content-Type: application/json");
$show_deleted = false;
if ($VARS['show_deleted'] == 1) {
$show_deleted = true;
}
$out = [];
$out['draw'] = intval($VARS['draw']);
if ($show_deleted) {
$out['recordsTotal'] = $database->count('accounts');
} else {
$out['recordsTotal'] = $database->count('accounts', ['deleted' => 0]);
}
$filter = false;
// sort
$order = null;
$sortby = "DESC";
if ($VARS['order'][0]['dir'] == 'asc') {
$sortby = "ASC";
}
switch ($VARS['order'][0]['column']) {
case 2:
$order = ["realname" => $sortby];
break;
case 3:
$order = ["username" => $sortby];
break;
case 4:
$order = ["email" => $sortby];
break;
case 5:
$order = ["authsecret" => $sortby];
break;
case 6:
$order = ["statuscode" => $sortby];
break;
case 7:
$order = ["typecode" => $sortby];
break;
}
// search
if (!is_empty($VARS['search']['value'])) {
$filter = true;
if ($show_deleted) {
$wherenolimit = [
"OR" => [
"username[~]" => $VARS['search']['value'],
"realname[~]" => $VARS['search']['value'],
"email[~]" => $VARS['search']['value'],
"statuscode[~]" => $VARS['search']['value'],
"typecode[~]" => $VARS['search']['value']
]
];
} else {
$wherenolimit = [
"AND" => [
"OR" => [
"username[~]" => $VARS['search']['value'],
"realname[~]" => $VARS['search']['value'],
"email[~]" => $VARS['search']['value'],
"statuscode[~]" => $VARS['search']['value'],
"typecode[~]" => $VARS['search']['value']
],
"deleted" => 0
]
];
}
$where = $wherenolimit;
$where["LIMIT"] = [$VARS['start'], $VARS['length']];
} else {
$where = ["LIMIT" => [$VARS['start'], $VARS['length']]];
if (!$show_deleted) {
$where["deleted"] = 0;
}
}
if (!is_null($order)) {
$where["ORDER"] = $order;
}
$users = $database->select('accounts', [
"[>]acctstatus" => ['acctstatus' => 'statusid'],
"[>]accttypes" => ['accttype' => 'typeid']
], [
'uid',
'username',
'realname',
'email',
'authsecret (2fa)',
'acctstatus',
'statuscode',
'accttype',
'typecode',
'deleted'
], $where);
$out['status'] = "OK";
if ($filter) {
$recordsFiltered = $database->count('accounts', [
"[>]acctstatus" => ['acctstatus' => 'statusid'],
"[>]accttypes" => ['accttype' => 'typecode']
], 'uid', $wherenolimit);
} else {
$recordsFiltered = $out['recordsTotal'];
}
$out['recordsFiltered'] = $recordsFiltered;
for ($i = 0; $i < count($users); $i++) {
$users[$i]["2fa"] = (is_empty($users[$i]["2fa"]) ? false : true);
$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>';
}
$out['users'] = $users;
echo json_encode($out);

@ -1,131 +0,0 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Check if a given ipv4 address is in a given cidr
* @param string $ip IP to check in IPV4 format eg. 127.0.0.1
* @param string $range IP/CIDR netmask eg. 127.0.0.0/24, also 127.0.0.1 is accepted and /32 assumed
* @return boolean true if the ip is in this range / false if not.
* @author Thorsten Ott <https://gist.github.com/tott/7684443>
*/
function ip4_in_cidr($ip, $cidr) {
if (strpos($cidr, '/') == false) {
$cidr .= '/32';
}
// $range is in IP/CIDR format eg 127.0.0.1/24
list( $cidr, $netmask ) = explode('/', $cidr, 2);
$range_decimal = ip2long($cidr);
$ip_decimal = ip2long($ip);
$wildcard_decimal = pow(2, ( 32 - $netmask)) - 1;
$netmask_decimal = ~ $wildcard_decimal;
return ( ( $ip_decimal & $netmask_decimal ) == ( $range_decimal & $netmask_decimal ) );
}
/**
* Check if a given ipv6 address is in a given cidr
* @param string $ip IP to check in IPV6 format
* @param string $cidr CIDR netmask
* @return boolean true if the IP is in this range, false otherwise.
* @author MW. <https://stackoverflow.com/a/7952169>
*/
function ip6_in_cidr($ip, $cidr) {
$address = inet_pton($ip);
$subnetAddress = inet_pton(explode("/", $cidr)[0]);
$subnetMask = explode("/", $cidr)[1];
$addr = str_repeat("f", $subnetMask / 4);
switch ($subnetMask % 4) {
case 0:
break;
case 1:
$addr .= "8";
break;
case 2:
$addr .= "c";
break;
case 3:
$addr .= "e";
break;
}
$addr = str_pad($addr, 32, '0');
$addr = pack("H*", $addr);
$binMask = $addr;
return ($address & $binMask) == $subnetAddress;
}
/**
* Check if the REMOTE_ADDR is on Cloudflare's network.
* @return boolean true if it is, otherwise false
*/
function validateCloudflare() {
if (filter_var($_SERVER["REMOTE_ADDR"], FILTER_VALIDATE_IP, FILTER_FLAG_IPV6)) {
// Using IPv6
$cloudflare_ips_v6 = [
"2400:cb00::/32",
"2405:8100::/32",
"2405:b500::/32",
"2606:4700::/32",
"2803:f800::/32",
"2c0f:f248::/32",
"2a06:98c0::/29"
];
$valid = false;
foreach ($cloudflare_ips_v6 as $cidr) {
if (ip6_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
$valid = true;
break;
}
}
} else {
// Using IPv4
$cloudflare_ips_v4 = [
"103.21.244.0/22",
"103.22.200.0/22",
"103.31.4.0/22",
"104.16.0.0/12",
"108.162.192.0/18",
"131.0.72.0/22",
"141.101.64.0/18",
"162.158.0.0/15",
"172.64.0.0/13",
"173.245.48.0/20",
"188.114.96.0/20",
"190.93.240.0/20",
"197.234.240.0/22",
"198.41.128.0/17"
];
$valid = false;
foreach ($cloudflare_ips_v4 as $cidr) {
if (ip4_in_cidr($_SERVER["REMOTE_ADDR"], $cidr)) {
$valid = true;
break;
}
}
}
return $valid;
}
/**
* Makes a good guess at the client's real IP address.
*
* @return string Client IP or `0.0.0.0` if we can't find anything
*/
function getClientIP() {
// If CloudFlare is in the mix, we should use it.
// Check if the request is actually from CloudFlare before trusting it.
if (isset($_SERVER["HTTP_CF_CONNECTING_IP"])) {
if (validateCloudflare()) {
return $_SERVER["HTTP_CF_CONNECTING_IP"];
}
}
if (isset($_SERVER["REMOTE_ADDR"])) {
return $_SERVER["REMOTE_ADDR"];
}
return "0.0.0.0"; // This will not happen unless we aren't a web server
}

@ -1,402 +0,0 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Authentication and account functions. Connects to a Portal instance.
*/
/**
* Check the login server API for sanity
* @return boolean true if OK, else false
*/
function checkLoginServer() {
try {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "ping"
]
]);
if ($response->getStatusCode() != 200) {
return false;
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return true;
} else {
return false;
}
} catch (Exception $e) {
return false;
}
}
/**
* Checks if the given AccountHub API key is valid by attempting to
* access the API with it.
* @param String $key The API key to check
* @return boolean TRUE if the key is valid, FALSE if invalid or something went wrong
*/
function checkAPIKey($key) {
try {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => $key,
'action' => "ping"
]
]);
if ($response->getStatusCode() === 200) {
return true;
}
return false;
} catch (Exception $e) {
return false;
}
}
////////////////////////////////////////////////////////////////////////////////
// Account handling //
////////////////////////////////////////////////////////////////////////////////
/**
* Checks the given credentials against the API.
* @param string $username
* @param string $password
* @return boolean True if OK, else false
*/
function authenticate_user($username, $password, &$errmsg) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "auth",
'username' => $username,
'password' => $password
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return true;
} else {
$errmsg = $resp['msg'];
return false;
}
}
/**
* Check if a username exists.
* @param String $username
*/
function user_exists($username) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "userexists",
'username' => $username
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK" && $resp['exists'] === true) {
return true;
} else {
return false;
}
}
/**
* Check if a UID exists.
* @param String $uid
*/
function uid_exists($uid) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "userexists",
'uid' => $uid
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK" && $resp['exists'] === true) {
return true;
} else {
return false;
}
}
/**
* Get the account status: NORMAL, TERMINATED, LOCKED_OR_DISABLED,
* CHANGE_PASSWORD, or ALERT_ON_ACCESS
* @param string $username
* @return string
*/
function get_account_status($username) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "acctstatus",
'username' => $username
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return $resp['account'];
} else {
return false;
}
}
/**
* Check if the given username has the given permission (or admin access)
* @param string $username
* @param string $permcode
* @return boolean TRUE if the user has the permission (or admin access), else FALSE
*/
function account_has_permission($username, $permcode) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "permission",
'username' => $username,
'code' => $permcode
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return $resp['has_permission'];
} else {
return false;
}
}
////////////////////////////////////////////////////////////////////////////////
// Login handling //
////////////////////////////////////////////////////////////////////////////////
/**
* Setup $_SESSION values with user data and set loggedin flag to true
* @param string $username
*/
function doLoginUser($username) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "userinfo",
'username' => $username
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
$userinfo = $resp['data'];
session_regenerate_id(true);
$newSession = session_id();
session_write_close();
session_id($newSession);
session_start();
$_SESSION['username'] = $username;
$_SESSION['uid'] = $userinfo['uid'];
$_SESSION['email'] = $userinfo['email'];
$_SESSION['realname'] = $userinfo['name'];
$_SESSION['loggedin'] = true;
return true;
} else {
return false;
}
}
function sendLoginAlertEmail($username) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "alertemail",
'username' => $username,
'appname' => SITE_TITLE
]
]);
if ($response->getStatusCode() > 299) {
return "An unknown error occurred.";
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return true;
} else {
return $resp['msg'];
}
}
function simLogin($username, $password) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "login",
'username' => $username,
'password' => $password
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return true;
} else {
return $resp['msg'];
}
}
function verifyCaptcheck($session, $answer, $url) {
$data = [
'session_id' => $session,
'answer_id' => $answer,
'action' => "verify"
];
$options = [
'http' => [
'header' => "Content-type: application/x-www-form-urlencoded\r\n",
'method' => 'POST',
'content' => http_build_query($data)
]
];
$context = stream_context_create($options);
$result = file_get_contents($url, false, $context);
$resp = json_decode($result, TRUE);
if (!$resp['result']) {
return false;
} else {
return true;
}
}
////////////////////////////////////////////////////////////////////////////////
// 2-factor authentication //
////////////////////////////////////////////////////////////////////////////////
/**
* Check if a user has TOTP setup
* @param string $username
* @return boolean true if TOTP secret exists, else false
*/
function userHasTOTP($username) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "hastotp",
'username' => $username
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return $resp['otp'];
} else {
return false;
}
}
/**
* Verify a TOTP multiauth code
* @global $database
* @param string $username
* @param int $code
* @return boolean true if it's legit, else false
*/
function verifyTOTP($username, $code) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "verifytotp",
'username' => $username,
'code' => $code
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return $resp['valid'];
} else {
return false;
}
}

@ -14,41 +14,20 @@ if (count(get_included_files()) == 1) {
require_once __DIR__ . "/../required.php";
use League\Csv\Writer;
use League\Csv\HTMLConverter;
use odsPhpGenerator\ods;
use odsPhpGenerator\odsTable;
use odsPhpGenerator\odsTableRow;
use odsPhpGenerator\odsTableColumn;
use odsPhpGenerator\odsTableCellString;
use odsPhpGenerator\odsStyleTableColumn;
use odsPhpGenerator\odsStyleTableCell;
// Allow access with a download code, for mobile app and stuff
$date = date("Y-m-d H:i:s");
if (isset($VARS['code']) && LOADED) {
if (!$database2->has('report_access_codes', ["AND" => ['code' => $VARS['code'], 'expires[>]' => $date]])) {
dieifnotloggedin();
}
} else {
dieifnotloggedin();
}
// Delete old DB entries
$database2->delete('report_access_codes', ['expires[<=]' => $date]);
dieifnotloggedin();
if (LOADED) {
if (isset($VARS['type']) && isset($VARS['format'])) {
generateReport($VARS['type'], $VARS['format']);
die();
} else {
lang("invalid parameters");
$Strings->get("invalid parameters");
die();
}
}
function getUserReport() {
global $database;
function getUserReport(): Report {
global $database, $Strings;
$users = $database->select(
"accounts", [
"[>]acctstatus" => ["acctstatus" => "statusid"],
@ -57,10 +36,21 @@ function getUserReport() {
"uid", "username", "realname", "email", "statuscode", "typecode", "authsecret"
]
);
$header = [lang("uid", false), lang("username", false), lang("name", false), lang("email", false), lang("status", false), lang("type", false), lang("2fa", false)];
$out = [$header];
$report = new Report($Strings->get("Users", false));
$report->setHeader([
$Strings->get("uid", false),
$Strings->get("username", false),
$Strings->get("name", false),
$Strings->get("email", false),
$Strings->get("status", false),
$Strings->get("type", false),
$Strings->get("2fa", false)
]);
for ($i = 0; $i < count($users); $i++) {
$out[] = [
$report->addDataRow([
$users[$i]["uid"],
$users[$i]["username"],
$users[$i]["realname"],
@ -68,13 +58,13 @@ function getUserReport() {
$users[$i]["statuscode"],
$users[$i]["typecode"],
is_null($users[$i]["authsecret"]) ? "0" : "1"
];
]);
}
return $out;
return $report;
}
function getGroupReport() {
global $database;
global $database, $Strings;
$groups = $database->select('assigned_groups', [
"[>]groups" => ['groupid'],
"[>]accounts" => ['uid']
@ -85,10 +75,10 @@ function getGroupReport() {
'groupname',
'groupid'
]);
$header = [lang("group id", false), lang("group name", false), lang("uid", false), lang("username", false), lang("name", false)];
$out = [$header];
$header = [$Strings->get("group id", false), $Strings->get("group name", false), $Strings->get("uid", false), $Strings->get("username", false), $Strings->get("name", false)];
$data = [];
for ($i = 0; $i < count($groups); $i++) {
$out[] = [
$data[] = [
$groups[$i]["groupid"],
$groups[$i]["groupname"],
$groups[$i]["uid"],
@ -96,11 +86,11 @@ function getGroupReport() {
$groups[$i]["realname"]
];
}
return $out;
return new Report($Strings->get("Groups", false), $header, $data);
}
function getManagerReport() {
global $database;
global $database, $Strings;
$managers = $database->select('managers', [
"[>]accounts (manager)" => ['managerid' => 'uid'],
"[>]accounts (employee)" => ['employeeid' => 'uid']
@ -112,21 +102,21 @@ function getManagerReport() {
'manager.realname (managername)',
'employee.realname (employeename)',
]);
$header = [lang("manager name", false), lang("manager username", false), lang("employee name", false), lang("employee username", false)];
$out = [$header];
$header = [$Strings->get("manager name", false), $Strings->get("manager username", false), $Strings->get("employee name", false), $Strings->get("employee username", false)];
$data = [];
for ($i = 0; $i < count($managers); $i++) {
$out[] = [
$data[] = [
$managers[$i]["managername"],
$managers[$i]["manageruser"],
$managers[$i]["employeename"],
$managers[$i]["employeeuser"]
];
}
return $out;
return new Report($Strings->get("Managers", false), $header, $data);
}
function getPermissionReport() {
global $database;
global $database, $Strings;
$permissions = $database->select('assigned_permissions', [
"[>]accounts" => ['uid' => 'uid'],
"[>]permissions" => ['permid' => 'permid']
@ -137,10 +127,10 @@ function getPermissionReport() {
'permissions.permid',
'permcode'
]);
$header = [lang("uid", false), lang("username", false), lang("name", false), lang("permission", false), lang("permission id", false)];
$out = [$header];
$header = [$Strings->get("uid", false), $Strings->get("username", false), $Strings->get("name", false), $Strings->get("permission", false), $Strings->get("permission id", false)];
$data = [];
for ($i = 0; $i < count($permissions); $i++) {
$out[] = [
$data[] = [
$permissions[$i]["uid"],
$permissions[$i]["username"],
$permissions[$i]["realname"],
@ -148,11 +138,11 @@ function getPermissionReport() {
$permissions[$i]["permid"],
];
}
return $out;
return new Report($Strings->get("Permissions", false), $header, $data);
}
function getSecurityReport() {
global $database;
global $database, $Strings;
$log = $database->select('authlog', [
"[>]logtypes" => ['logtype'],
"[>]accounts" => ['uid']
@ -165,10 +155,10 @@ function getSecurityReport() {
'ip',
'otherdata'
]);
$header = [lang("logtime", false), lang("logtype", false), lang("ip address", false), lang("uid", false), lang("username", false), lang("name", false), lang("other data", false)];
$out = [$header];
$header = [$Strings->get("logtime", false), $Strings->get("logtype", false), $Strings->get("ip address", false), $Strings->get("uid", false), $Strings->get("username", false), $Strings->get("name", false), $Strings->get("other data", false)];
$data = [];
for ($i = 0; $i < count($log); $i++) {
$out[] = [
$data[] = [
$log[$i]["logtime"],
$log[$i]["typename"],
$log[$i]["ip"],
@ -178,10 +168,10 @@ function getSecurityReport() {
$log[$i]["otherdata"]
];
}
return $out;
return new Report($Strings->get("Security", false), $header, $data);
}
function getReportData($type) {
function getReport($type): Report {
switch ($type) {
case "users":
return getUserReport();
@ -199,93 +189,11 @@ function getReportData($type) {
return getSecurityReport();
break;
default:
return [["error"]];
}
}
function dataToCSV($data, $name = "report") {
$csv = Writer::createFromString('');
$csv->insertAll($data);
header('Content-type: text/csv');
header('Content-Disposition: attachment; filename="' . $name . "_" . date("Y-m-d_Hi") . ".csv" . '"');
echo $csv;
die();
}
function dataToODS($data, $name = "report") {
$ods = new ods();
$styleColumn = new odsStyleTableColumn();
$styleColumn->setUseOptimalColumnWidth(true);
$headerstyle = new odsStyleTableCell();
$headerstyle->setFontWeight("bold");
$table = new odsTable($name);
for ($i = 0; $i < count($data[0]); $i++) {
$table->addTableColumn(new odsTableColumn($styleColumn));
}
$rowid = 0;
foreach ($data as $datarow) {
$row = new odsTableRow();
foreach ($datarow as $cell) {
if ($rowid == 0) {
$row->addCell(new odsTableCellString($cell, $headerstyle));
} else {
$row->addCell(new odsTableCellString($cell));
}
}
$table->addRow($row);
$rowid++;
}
$ods->addTable($table);
$ods->downloadOdsFile($name . "_" . date("Y-m-d_Hi") . ".ods");
}
function dataToHTML($data, $name = "report") {
global $SECURE_NONCE;
// HTML exporter doesn't like null values
for ($i = 0; $i < count($data); $i++) {
for ($j = 0; $j < count($data[$i]); $j++) {
if (is_null($data[$i][$j])) {
$data[$i][$j] = '';
}
}
}
header('Content-type: text/html');
$converter = new HTMLConverter();
$out = "<!DOCTYPE html>\n"
. "<meta charset=\"utf-8\">\n"
. "<meta name=\"viewport\" content=\"width=device-width\">\n"
. "<title>" . $name . "_" . date("Y-m-d_Hi") . "</title>\n"
. <<<STYLE
<style nonce="$SECURE_NONCE">
.table-csv-data {
border-collapse: collapse;
return new Report("error", ["ERROR"], ["Invalid report type."]);
}
.table-csv-data tr:first-child {
font-weight: bold;
}
.table-csv-data tr td {
border: 1px solid black;
}
</style>
STYLE
. $converter->convert($data);
echo $out;
}
function generateReport($type, $format) {
$data = getReportData($type);
switch ($format) {
case "ods":
dataToODS($data, $type);
break;
case "html":
dataToHTML($data, $type);
break;
case "csv":
default:
echo dataToCSV($data, $type);
break;
}
$report = getReport($type);
$report->output($format);
}

@ -1,127 +0,0 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* Get user info for the given username.
* @param int $u username
* @return [string] Array of [uid, username, name]
*/
function getUserByUsername($u) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "userinfo",
'username' => $u
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return $resp['data'];
} else {
// this shouldn't happen, but in case it does just fake it.
return ["name" => $u, "username" => $u, "uid" => $u];
}
}
/**
* Get user info for the given UID.
* @param int $u user ID
* @return [string] Array of [uid, username, name]
*/
function getUserByID($u) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "userinfo",
'uid' => $u
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return $resp['data'];
} else {
// this shouldn't happen, but in case it does just fake it.
return ["name" => $u, "username" => $u, "uid" => $u];
}
}
/**
* Check if the first UID is a manager of the second UID.
* @param int $m Manager UID
* @param int $e Employee UID
* @return boolean
*/
function isManagerOf($m, $e) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "ismanagerof",
'manager' => $m,
'employee' => $e,
'uid' => 1
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return $resp['managerof'] === true;
} else {
// this shouldn't happen, but in case it does just fake it.
return false;
}
}
/**
* Get an array of UIDs the given UID is a manager of.
* @param int $manageruid The UID of the manager to find employees for.
* @return [int]
*/
function getManagedUIDs($manageruid) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "getmanaged",
'uid' => $manageruid
]
]);
if ($response->getStatusCode() > 299) {
sendError("Login server error: " . $response->getBody());
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK") {
return $resp['employees'];
} else {
return [];
}
}

@ -14,8 +14,6 @@ $access_permission = "ADMIN";
require __DIR__ . "/../required.php";
require __DIR__ . "/../lib/login.php";
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
@ -25,21 +23,7 @@ if ($VARS['action'] == "ping") {
}
function mobile_enabled() {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
'action' => "mobileenabled"
]
]);
if ($response->getStatusCode() > 299) {
return false;
}
$resp = json_decode($response->getBody(), TRUE);
$resp = AccountHubApi::get("mobileenabled");
if ($resp['status'] == "OK" && $resp['mobile'] === TRUE) {
return true;
} else {
@ -48,36 +32,25 @@ function mobile_enabled() {
}
function mobile_valid($username, $code) {
$client = new GuzzleHttp\Client();
$response = $client
->request('POST', PORTAL_API, [
'form_params' => [
'key' => PORTAL_KEY,
"code" => $code,
"username" => $username,
'action' => "mobilevalid"
]
]);
try {
$resp = AccountHubApi::get("mobilevalid", ["code" => $code, "username" => $username], true);
if ($response->getStatusCode() > 299) {
return false;
}
$resp = json_decode($response->getBody(), TRUE);
if ($resp['status'] == "OK" && $resp['valid'] === TRUE) {
return true;
} else {
if ($resp['status'] == "OK" && $resp['valid'] === TRUE) {
return true;
} else {
return false;
}
} catch (Exception $ex) {
return false;
}
}
if (mobile_enabled() !== TRUE) {
exit(json_encode(["status" => "ERROR", "msg" => lang("mobile login disabled", false)]));
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("mobile login disabled", false)]));
}
// Make sure we have a username and access key
if (is_empty($VARS['username']) || is_empty($VARS['key'])) {
if (empty($VARS['username']) || empty($VARS['key'])) {
http_response_code(401);
die(json_encode(["status" => "ERROR", "msg" => "Missing username and/or access key."]));
}
@ -93,20 +66,21 @@ if (!mobile_valid($VARS['username'], $VARS['key'])) {
switch ($VARS['action']) {
case "start_session":
// Do a web login.
if (user_exists($VARS['username'])) {
if (get_account_status($VARS['username']) == "NORMAL") {
if (authenticate_user($VARS['username'], $VARS['password'], $autherror)) {
if (is_null($access_permission) || account_has_permission($VARS['username'], $access_permission)) {
doLoginUser($VARS['username'], $VARS['password']);
$user = User::byUsername($VARS['username']);
if ($user->exists()) {
if ($user->getStatus()->getString() == "NORMAL") {
if ($user->checkPassword($VARS['password'])) {
if (is_null($access_permission) || $user->hasPermission($access_permission)) {
Session::start($user);
$_SESSION['mobile'] = true;
exit(json_encode(["status" => "OK"]));
} else {
exit(json_encode(["status" => "ERROR", "msg" => lang("no admin permission", false)]));
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("no admin permission", false)]));
}
}
}
}
exit(json_encode(["status" => "ERROR", "msg" => lang("login incorrect", false)]));
exit(json_encode(["status" => "ERROR", "msg" => $Strings->get("login incorrect", false)]));
default:
http_response_code(404);
die(json_encode(["status" => "ERROR", "msg" => "The requested action is not available."]));

@ -7,12 +7,12 @@
// List of pages and metadata
define("PAGES", [
"home" => [
"title" => "home",
"title" => "Home",
"navbar" => true,
"icon" => "fas fa-home"
],
"users" => [
"title" => "users",
"title" => "Users",
"navbar" => true,
"icon" => "fas fa-users",
"styles" => [
@ -36,7 +36,7 @@ define("PAGES", [
"navbar" => false
],
"groups" => [
"title" => "groups",
"title" => "Groups",
"navbar" => true,
"icon" => "fas fa-object-group",
"styles" => [
@ -48,7 +48,7 @@ define("PAGES", [
],
],
"authlog" => [
"title" => "security log",
"title" => "Security Log",
"navbar" => true,
"icon" => "fas fa-list",
"styles" => [
@ -65,7 +65,7 @@ define("PAGES", [
"navbar" => false
],
"managers" => [
"title" => "managers",
"title" => "Managers",
"navbar" => true,
"icon" => "fas fa-id-card",
"styles" => [
@ -77,7 +77,7 @@ define("PAGES", [
]
],
"permissions" => [
"title" => "permissions",
"title" => "Permissions",
"navbar" => true,
"icon" => "fas fa-key",
"styles" => [

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

@ -8,17 +8,17 @@ require_once __DIR__ . '/../required.php';
redirectifnotloggedin();
?>
<div class="btn-group mgn-btm-10px">
<a href="app.php?page=clearlog" class="btn btn-warning"><i class="fa fa-times"></i> <?php lang("clear log"); ?></a>
<a href="app.php?page=clearlog" class="btn btn-warning"><i class="fa fa-times"></i> <?php $Strings->get("clear log"); ?></a>
</div>
<table id="logtable" class="table table-bordered table-hover table-sm">
<thead>
<tr>
<th data-priority="0"></th>
<th data-priority="1"><i class="fas fa-fw fa-calendar d-none d-md-inline"></i> <?php lang('logtime'); ?></th>
<th data-priority="1"><i class="fas fa-fw fa-server d-none d-md-inline"></i> <?php lang('logtype'); ?></th>
<th data-priority="2"><i class="fas fa-fw fa-id-badge d-none d-md-inline"></i> <?php lang('username'); ?></th>
<th data-priority="3"><i class="fas fa-fw fa-globe d-none d-md-inline"></i> <?php lang('ip address'); ?></th>
<th data-priority="3"><i class="fas fa-fw fa-info-circle d-none d-md-inline"></i> <?php lang('other data'); ?></th>
<th data-priority="1"><i class="fas fa-fw fa-calendar d-none d-md-inline"></i> <?php $Strings->get('logtime'); ?></th>
<th data-priority="1"><i class="fas fa-fw fa-server d-none d-md-inline"></i> <?php $Strings->get('logtype'); ?></th>
<th data-priority="2"><i class="fas fa-fw fa-id-badge d-none d-md-inline"></i> <?php $Strings->get('username'); ?></th>
<th data-priority="3"><i class="fas fa-fw fa-globe d-none d-md-inline"></i> <?php $Strings->get('ip address'); ?></th>
<th data-priority="3"><i class="fas fa-fw fa-info-circle d-none d-md-inline"></i> <?php $Strings->get('other data'); ?></th>
</tr>
</thead>
<tbody>
@ -27,11 +27,11 @@ redirectifnotloggedin();
<tfoot>
<tr>
<th data-priority="0"></th>
<th data-priority="1"><i class="fas fa-fw fa-calendar d-none d-md-inline"></i> <?php lang('logtime'); ?></th>
<th data-priority="1"><i class="fas fa-fw fa-server d-none d-md-inline"></i> <?php lang('logtype'); ?></th>
<th data-priority="2"><i class="fas fa-fw fa-id-badge d-none d-md-inline"></i> <?php lang('username'); ?></th>
<th data-priority="3"><i class="fas fa-fw fa-globe d-none d-md-inline"></i> <?php lang('ip address'); ?></th>
<th data-priority="3"><i class="fas fa-fw fa-info-circle d-none d-md-inline"></i> <?php lang('other data'); ?></th>
<th data-priority="1"><i class="fas fa-fw fa-calendar d-none d-md-inline"></i> <?php $Strings->get('logtime'); ?></th>
<th data-priority="1"><i class="fas fa-fw fa-server d-none d-md-inline"></i> <?php $Strings->get('logtype'); ?></th>
<th data-priority="2"><i class="fas fa-fw fa-id-badge d-none d-md-inline"></i> <?php $Strings->get('username'); ?></th>
<th data-priority="3"><i class="fas fa-fw fa-globe d-none d-md-inline"></i> <?php $Strings->get('ip address'); ?></th>
<th data-priority="3"><i class="fas fa-fw fa-info-circle d-none d-md-inline"></i> <?php $Strings->get('other data'); ?></th>
</tfoot>
</table>
<br />
@ -39,7 +39,7 @@ redirectifnotloggedin();
<div class="col-12 col-sm-6 col-md-4">
<div class="card">
<h4 class="card-header">
<?php lang('event type reference'); ?>
<?php $Strings->get('event type reference'); ?>
</h4>
<div class="list-group">
<?php

@ -11,15 +11,15 @@ redirectifnotloggedin();
<div class="col-12 col-sm-6 col-sm-offset-3">
<div class="card border-red text-center">
<h3 class="card-header text-red">
<?php lang("clear log") ?>
<?php $Strings->get("clear log") ?>
</h3>
<div class="card-body">
<p><i class="fas fa-exclamation-triangle fa-10x"></i></p>
<h4><?php lang("really clear log") ?></h4>
<h4><?php $Strings->get("really clear log") ?></h4>
</div>
<div class="card-footer d-flex">
<a href="app.php?page=authlog" class="btn btn-primary mr-auto"><i class="fa fa-arrow-left"></i> <?php lang('cancel'); ?></a>
<a href="action.php?action=clearlog&source=authlog" class="btn btn-danger"><i class="fa fa-times"></i> <?php lang('delete'); ?></a>
<a href="app.php?page=authlog" class="btn btn-primary mr-auto"><i class="fa fa-arrow-left"></i> <?php $Strings->get('cancel'); ?></a>
<a href="action.php?action=clearlog&source=authlog" class="btn btn-danger"><i class="fa fa-times"></i> <?php $Strings->get('delete'); ?></a>
</div>
</div>
</div>

@ -7,17 +7,9 @@ require_once __DIR__ . "/../required.php";
redirectifnotloggedin();
if (!is_empty($VARS['id'])) {
if ($database->has('accounts', ['uid' => $VARS['id']])) {
$userdata = $database->select('accounts', ['[>]accttypes' => ['accttype' => 'typeid']], [
'uid',
'username',
'realname',
'email'
], [
'uid' => $VARS['id']
])[0];
} else {
if (!empty($VARS['id']) && preg_match("/[0-9]+/", $VARS['id'])) {
$user = new User($VARS['id']);
if (!$user->exists()) {
// user id is invalid
header('Location: app.php?page=users&msg=user_not_exists');
die();
@ -32,23 +24,23 @@ if (!is_empty($VARS['id'])) {
<div class="col-12 col-sm-6 col-sm-offset-3">
<div class="card border-red text-center">
<h3 class="card-header text-red">
<?php lang("delete user") ?>
<?php $Strings->get("delete user") ?>
</h3>
<div class="card-body">
<p><i class="fas fa-exclamation-triangle fa-10x"></i></p>
<h4><?php lang("really delete user") ?></h4>
<h4><?php $Strings->get("really delete user") ?></h4>
<div class="list-group">
<div class="list-group-item">
<i class="fas fa-fw fa-user"></i> <?php echo $userdata['realname']; ?>
<i class="fas fa-fw fa-user"></i> <?php echo $user->getName(); ?>
</div>
<div class="list-group-item">
<i class="fas fa-fw fa-id-badge"></i> <?php echo $userdata['username']; ?>
<i class="fas fa-fw fa-id-badge"></i> <?php echo $user->getUsername(); ?>
</div>
<?php
if (!is_empty($userdata['email'])) {
if (!empty($user->getEmail())) {
?>
<div class="list-group-item">
<i class="fas fa-fw fa-envelope"></i> <?php echo $userdata['email']; ?>
<i class="fas fa-fw fa-envelope"></i> <?php echo $user->getEmail(); ?>
</div>
<?php
}
@ -56,8 +48,8 @@ if (!is_empty($VARS['id'])) {
</div>
</div>
<div class="card-footer d-flex">
<a href="app.php?page=users" class="btn btn-primary mr-auto"><i class="fas fa-arrow-left"></i> <?php lang('cancel'); ?></a>
<a href="action.php?action=deleteuser&source=users&id=<?php echo htmlspecialchars($VARS['id']); ?>" class="btn btn-danger"><i class="fas fa-times"></i> <?php lang('delete'); ?></a>
<a href="app.php?page=users" class="btn btn-primary mr-auto"><i class="fas fa-arrow-left"></i> <?php $Strings->get('cancel'); ?></a>
<a href="action.php?action=deleteuser&source=users&id=<?php echo $user->getUID(); ?>" class="btn btn-danger"><i class="fas fa-times"></i> <?php $Strings->get('delete'); ?></a>
</div>
</div>
</div>

@ -1,11 +1,10 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
require_once __DIR__ . '/../required.php';
require_once __DIR__ . "/../lib/login.php";
require_once __DIR__ . "/../lib/userinfo.php";
redirectifnotloggedin();
@ -21,134 +20,50 @@ $userdata = [
];
$editing = false;
$user = new User(-1);
if (!is_empty($VARS['id'])) {
if ($database->has('accounts', ['uid' => $VARS['id']])) {
if (!empty($VARS['id']) && preg_match("/[0-9]+/", $VARS['id'])) {
$user = new User($VARS['id']);
if ($user->exists()) {
$editing = true;
$userdata = $database->select('accounts', ['[>]accttypes' => ['accttype' => 'typeid']], [
'uid',
'username',
'realname',
'email',
'authsecret',
'acctstatus',
'typecode',
'deleted'
], [
'uid' => $VARS['id']
])[0];
} else {
// user id is invalid, redirect to a page that won't cause an error when pressing Save
header('Location: app.php?page=edituser');
}
}
if ($userdata['typecode'] != "LOCAL") {
$localacct = false;
$form = new FormBuilder("", "far fa-edit");
if ($editing) {
$form->setTitle($Strings->build("editing user", ['user' => "<span id=\"name_title\">" . htmlspecialchars($user->getName()) . "</span>"], false));
} else {
$localacct = true;
$form->setTitle($Strings->get("adding user", false));
}
?>
<form role="form" action="action.php" method="POST">
<div class="card border-blue">
<h3 class="card-header text-blue">
<?php
if ($editing) {
?>
<i class="far fa-edit"></i> <?php lang2("editing user", ['user' => "<span id=\"name_title\">" . htmlspecialchars($userdata['realname']) . "</span>"]); ?>
<?php
} else {
?>
<i class="far fa-edit"></i> <?php lang("adding user"); ?>
<?php
}
?>
</h3>
<div class="card-body">
<?php
if (!$localacct) {
?>
<div class="alert alert-warning">
<?php lang("non-local account warning"); ?>
</div>
<?php
}
if ($userdata['deleted'] == 1) {
?>
<div class="alert alert-info">
<?php lang("editing deleted account"); ?>
</div>
<?php
}
?>
<div class="form-group">
<label for="name"><i class="fas fa-user"></i> <?php lang("name"); ?></label>
<input type="text" class="form-control" id="name" name="name" placeholder="<?php lang("placeholder name"); ?>" required="required" value="<?php echo htmlspecialchars($userdata['realname']); ?>" />
</div>
<div class="row">
<div class="col-xs-12 col-md-6">
<div class="form-group">
<label for="username"><i class="fas fa-id-badge"></i> <?php lang("username"); ?></label>
<input type="text" <?php if (!$localacct) echo "readonly=\"readonly\""; ?> class="form-control" name="username" id="username" placeholder="<?php lang("placeholder username"); ?>" required="required" value="<?php echo htmlspecialchars($userdata['username']); ?>" />
</div>
</div>
<div class="col-xs-12 col-md-6">
<div class="form-group">
<label for="email"><i class="fas fa-envelope"></i> <?php lang("email"); ?></label>
<input type="email" class="form-control" name="email" id="email" placeholder="<?php lang("placeholder email address"); ?>" value="<?php echo htmlspecialchars($userdata['email']); ?>" />
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12 col-md-6">
<div class="form-group">
<label for="pass"><i class="fas fa-lock"></i> <?php lang("new password"); ?></label>
<input type="text" <?php if (!$localacct) echo "readonly=\"readonly\""; ?> autocomplete="new-password" class="form-control" name="pass" id="pass" placeholder="<?php lang("placeholder password"); ?>" />
</div>
</div>
$form->addInput("name", (empty($user->getName()) ? "" : $user->getName()), "text", true, "name", null, $Strings->get("name", false), "fas fa-user");
$form->addInput("username", (empty($user->getUsername()) ? "" : $user->getUsername()), "text", true, "username", null, $Strings->get("username", false), "fas fa-id-badge");
$form->addInput("email", (empty($user->getEmail()) ? "" : $user->getEmail()), "email", false, "email", null, $Strings->get("email", false), "fas fa-envelope");
$form->addInput("pass", "", "text", false, "pass", null, $Strings->get("new password", false), "fas fa-lock");
$form->addInput("status", $user->getStatus()->get(), "select", true, "status", [
AccountStatus::NORMAL => "NORMAL",
AccountStatus::LOCKED_OR_DISABLED => "LOCKED_OR_DISABLED",
AccountStatus::CHANGE_PASSWORD => "CHANGE_PASSWORD",
AccountStatus::TERMINATED => "TERMINATED",
AccountStatus::ALERT_ON_ACCESS => "ALERT_ON_ACCESS"
], $Strings->get("status", false), "fas fa-check-circle");
<div class="col-xs-12 col-md-6">
<div class="form-group">
<label for="status"><i class="fas fa-check-circle"></i> <?php lang("status"); ?></label>
<select class="form-control" name="status" id="status" required="required">
<?php
$statuses = $database->select('acctstatus', ['statusid (id)', 'statuscode (code)'], ["ORDER" => "statusid"]);
foreach ($statuses as $s) {
echo "<option";
if ($s['id'] == $userdata['acctstatus']) {
echo " selected";
}
echo " value=\"" . $s['id'] . "\">" . $s['code'] . "</option>";
}
?>
</select>
</div>
</div>
</div>
</div>
if ($editing) {
$form->addHiddenInput("id", $user->getUID());
}
$form->addHiddenInput("action", "edituser");
$form->addHiddenInput("source", "users");
<input type="hidden" name="id" value="<?php echo htmlspecialchars($VARS['id']); ?>" />
<input type="hidden" name="action" value="edituser" />
<input type="hidden" name="source" value="users" />
$form->addButton($Strings->get("save", false), "fas fa-save", null, "submit", null, null, "", "btn btn-success mr-auto");
if ($editing) {
if (!empty($userdata['authsecret'])) {
$form->addButton($Strings->get("remove 2fa", false), "fas fa-unlock", "action.php?action=rmtotp&source=users&id=" . $user->getUID(), "", null, null, "", "btn btn-warning btn-sm");
}
$form->addButton($Strings->get("delete", false), "fas fa-times", "app.php?page=deluser&id=" . $user->getUID(), "", null, null, "", "btn btn-danger");
}
<div class="card-footer d-flex">
<button type="submit" class="btn btn-success mr-auto"><i class="fas fa-save"></i> <?php lang("save"); ?></button>
<?php
if ($editing) {
if (!is_empty($userdata['authsecret'])) {
?>
<a href="action.php?action=rmtotp&source=users&id=<?php echo htmlspecialchars($VARS['id']); ?>" class="btn btn-warning btn-sm"><i class="fas fa-unlock"></i> <?php lang('remove 2fa'); ?></a> &nbsp; &nbsp;
<?php
}
?>
<a href="app.php?page=deluser&id=<?php echo htmlspecialchars($VARS['id']); ?>" class="btn btn-danger"><i class="fas fa-times"></i> <?php lang('delete'); ?></a>
<?php
}
?>
</div>
</div>
</form>
$form->generate();

@ -13,24 +13,24 @@ redirectifnotloggedin();
<div class="row">
<div class="col-12 col-sm-6">
<div class="form-group">
<label for="type"><?php lang("report type"); ?></label>
<label for="type"><?php $Strings->get("report type"); ?></label>
<select name="type" class="form-control" required>
<option value="users"><?php lang("users") ?></option>
<option value="groups"><?php lang("groups") ?></option>
<option value="managers"><?php lang("managers") ?></option>
<option value="permissions"><?php lang("permissions") ?></option>
<option value="security"><?php lang("security log") ?></option>
<option value="users"><?php $Strings->get("users") ?></option>
<option value="groups"><?php $Strings->get("groups") ?></option>
<option value="managers"><?php $Strings->get("managers") ?></option>
<option value="permissions"><?php $Strings->get("permissions") ?></option>
<option value="security"><?php $Strings->get("security log") ?></option>
</select>
</div>
</div>
<div class="col-12 col-sm-6">
<div class="form-group">
<label for="type"><?php lang("format"); ?></label>
<label for="type"><?php $Strings->get("format"); ?></label>
<select name="format" class="form-control" required>
<option value="csv"><?php lang("csv file") ?></option>
<option value="ods"><?php lang("ods file") ?></option>
<option value="html"><?php lang("html file") ?></option>
<option value="csv"><?php $Strings->get("csv file") ?></option>
<option value="ods"><?php $Strings->get("ods file") ?></option>
<option value="html"><?php $Strings->get("html file") ?></option>
</select>
</div>
</div>
@ -43,7 +43,7 @@ redirectifnotloggedin();
<input type="hidden" name="code" value="<?php echo $code; ?>" />
<div class="card-footer d-flex">
<button type="submit" class="btn btn-success ml-auto" id="genrptbtn"><i class="fas fa-download"></i> <?php lang("generate report"); ?></button>
<button type="submit" class="btn btn-success ml-auto" id="genrptbtn"><i class="fas fa-download"></i> <?php $Strings->get("generate report"); ?></button>
</div>
</form>
</div>

@ -11,26 +11,26 @@ redirectifnotloggedin();
$groupselected = false;
$user = "";
$users = [];
if ($VARS['gid'] && $database->has('groups', ['groupid' => $VARS['gid']])) {
if (!empty($VARS['gid']) && $database->has('groups', ['groupid' => $VARS['gid']])) {
$gid = $VARS['gid'];
$users = $database->select('assigned_groups', ["[>]accounts" => ["uid" => "uid"]], 'username', ['groupid' => $gid]);
$groupselected = true;
}
?>
<div class="row">
<div class="col-12 col-xl-6">
<div class="col-12">
<div class="card border-brown">
<h4 class="card-header text-brown">
<i class="fas fa-object-group"></i> <?php lang("group management"); ?>
<i class="fas fa-object-group"></i> <?php $Strings->get("group management"); ?>
</h4>
<div class="card-body">
<div class="row">
<form role="form" action="action.php" method="POST" class="col-12 col-sm-6">
<label for="addgroupbox"><i class="fas fa-plus"></i> <?php lang("new group"); ?></label>
<label for="addgroupbox"><i class="fas fa-plus"></i> <?php $Strings->get("new group"); ?></label>
<div class="input-group">
<input type="text" name="group" placeholder="<?php lang("enter group name"); ?>" class="form-control" />
<input type="text" name="group" placeholder="<?php $Strings->get("enter group name"); ?>" class="form-control" />
<div class="input-group-append">
<button type="submit" class="btn btn-success"><i class="fas fa-plus"></i> <?php lang("add"); ?></button>
<button type="submit" class="btn btn-success"><i class="fas fa-plus"></i> <?php $Strings->get("add"); ?></button>
</div>
</div>
<input type="hidden" name="action" value="addgroup" />
@ -38,7 +38,7 @@ if ($VARS['gid'] && $database->has('groups', ['groupid' => $VARS['gid']])) {
</form>
<form role="form" action="action.php" method="POST" class="col-12 col-sm-6">
<label for="addgroupbox"><i class="fas fa-trash"></i> <?php lang("delete group"); ?></label>
<label for="addgroupbox"><i class="fas fa-trash"></i> <?php $Strings->get("delete group"); ?></label>
<div class="input-group">
<select name="gid" class="form-control">
<?php
@ -49,7 +49,7 @@ if ($VARS['gid'] && $database->has('groups', ['groupid' => $VARS['gid']])) {
?>
</select>
<div class="input-group-append">
<button type="submit" class="btn btn-danger"><i class="fas fa-times"></i> <?php lang("delete"); ?></button>
<button type="submit" class="btn btn-danger"><i class="fas fa-times"></i> <?php $Strings->get("delete"); ?></button>
</div>
</div>
<input type="hidden" name="action" value="rmgroup" />
@ -61,10 +61,10 @@ if ($VARS['gid'] && $database->has('groups', ['groupid' => $VARS['gid']])) {
<br />
</div>
<div class="col-12 col-xl-6">
<div class="col-12">
<div class="card border-brown">
<h4 class="card-header text-brown">
<i class="fas fa-users"></i> <?php lang("group assignments"); ?>
<i class="fas fa-users"></i> <?php $Strings->get("group assignments"); ?>
</h4>
<?php if ($groupselected !== false) { ?>
<form role="form" action="action.php" method="POST">
@ -73,7 +73,7 @@ if ($VARS['gid'] && $database->has('groups', ['groupid' => $VARS['gid']])) {
<div class="row">
<div class="col-12 col-md-6">
<div class="form-group">
<label for="group-box"><i class="fas fa-object-group"></i> <?php lang("group"); ?></label>
<label for="group-box"><i class="fas fa-object-group"></i> <?php $Strings->get("group"); ?></label>
<div class="input-group">
<select <?php if ($groupselected === false) { ?>id="group-box"<?php } ?> class="form-control" value="<?php echo $gid ?>" name="gid" <?php echo ($groupselected !== false ? "readonly" : ""); ?>>
<?php
@ -89,7 +89,7 @@ if ($VARS['gid'] && $database->has('groups', ['groupid' => $VARS['gid']])) {
</select>
<div class="input-group-append">
<?php if ($groupselected === false) { ?>
<button class="btn btn-default" type="button" id="selectgroupbtn"><i class="fas fa-chevron-right"></i> <?php lang("next") ?></button>
<button class="btn btn-default" type="button" id="selectgroupbtn"><i class="fas fa-chevron-right"></i> <?php $Strings->get("next") ?></button>
<?php } ?>
</div>
</div>
@ -100,11 +100,20 @@ if ($VARS['gid'] && $database->has('groups', ['groupid' => $VARS['gid']])) {
?>
<div class="col-12 col-md-6">
<div class="form-group">
<label for="people-box"><i class="fas fa-users"></i> <?php lang("users"); ?></label>
<label for="people-box"><i class="fas fa-users"></i> <?php $Strings->get("users"); ?></label>
<div class="input-group">
<input type="text" class="form-control" id="people-box" placeholder="<?php lang("type to add a person") ?>" />
<select class="form-control" id="people-box">
<option></option>
<?php
$allusers = $database->select("accounts", "uid", ["deleted" => 0]);
foreach ($allusers as $user) {
$u = new User($user);
echo '<option value="' . htmlentities($u->getUsername()) . '">' . htmlentities($u->getName()) . ' (' . htmlentities($u->getUsername()) . ')</option>';
}
?>
</select>
<div class="input-group-append">
<button class="btn btn-default" type="button" id="addpersonbtn"><i class="fas fa-plus"></i> <?php lang("add") ?></button>
<button class="btn btn-default" type="button" id="addpersonbtn"><i class="fas fa-plus"></i> <?php $Strings->get("add") ?></button>
</div>
</div>
</div>
@ -133,7 +142,7 @@ if ($VARS['gid'] && $database->has('groups', ['groupid' => $VARS['gid']])) {
</div>
<?php if ($groupselected !== false) { ?>
<div class="card-footer d-flex">
<button type="submit" class="btn btn-success ml-auto" id="save-btn"><i class="fas fa-save"></i> <?php lang("save"); ?></button>
<button type="submit" class="btn btn-success ml-auto" id="save-btn"><i class="fas fa-save"></i> <?php $Strings->get("save"); ?></button>
</div>
</form>
<?php } ?>

@ -10,29 +10,29 @@ redirectifnotloggedin();
<div class="card-deck">
<div class="card text-dark bg-light-blue">
<div class="card-body">
<h4 class="card-title"><?php lang("total users") ?></h4>
<h4 class="card-title"><?php $Strings->get("total users") ?></h4>
<h1><i class="fas fa-fw fa-users"></i> <?php echo $database->count('accounts'); ?></h1>
</div>
<div class="card-footer">
<a href="app.php?page=users" class="text-dark"><i class="fa fa-arrow-right fa-fw"></i> <?php lang('view users'); ?></a>
<a href="app.php?page=users" class="text-dark"><i class="fa fa-arrow-right fa-fw"></i> <?php $Strings->get('view users'); ?></a>
</div>
</div>
<div class="card text-dark bg-amber">
<div class="card-body">
<h4 class="card-title"><?php lang("locked accounts") ?></h4>
<h4 class="card-title"><?php $Strings->get("locked accounts") ?></h4>
<h1><i class="fas fa-fw fa-user-times"></i> <?php echo $database->count('accounts', ['OR' => ['acctstatus #LOCKED_OR_DISABLED' => 2, 'acctstatus #CHANGE_PASSWORD' => 3]]); ?></h1>
</div>
<div class="card-footer">
<a href="app.php?page=users" class="text-dark"><i class="fa fa-arrow-right fa-fw"></i> <?php lang('view users'); ?></a>
<a href="app.php?page=users" class="text-dark"><i class="fa fa-arrow-right fa-fw"></i> <?php $Strings->get('view users'); ?></a>
</div>
</div>
<div class="card text-dark bg-light-green">
<div class="card-body">
<h4 class="card-title"><?php lang("security log entries") ?></h4>
<h4 class="card-title"><?php $Strings->get("security log entries") ?></h4>
<h1><i class="fas fa-fw fa-list"></i> <?php echo $database->count('authlog'); ?></h1>
</div>
<div class="card-footer">
<a href="app.php?page=authlog" class="text-dark"><i class="fa fa-arrow-right fa-fw"></i> <?php lang('view security log'); ?></a>
<a href="app.php?page=authlog" class="text-dark"><i class="fa fa-arrow-right fa-fw"></i> <?php $Strings->get('view security log'); ?></a>
</div>
</div>
</div>

@ -11,10 +11,10 @@ redirectifnotloggedin();
$assigned = [];
$employees = false;
$user = "";
if ($VARS['man'] && $database->has('accounts', ['username' => $VARS['man']])) {
if (!empty($VARS['man']) && $database->has('accounts', ['username' => $VARS['man']])) {
$user = $VARS['man'];
require_once __DIR__ . "/../lib/userinfo.php";
$uid = getUserByUsername($user)['uid'];
$uid = User::byUsername($user)->getUID();
$assigned = $database->select('managers', ["[>]accounts" => ["employeeid" => "uid"]], 'username', ['managerid' => $uid]);
$employees = true;
}
@ -24,21 +24,37 @@ if ($VARS['man'] && $database->has('accounts', ['username' => $VARS['man']])) {
<?php if ($employees !== false) { ?>
<form role="form" action="action.php" method="POST">
<?php } ?>
<h4 class="card-header text-brown"><?php lang("select a manager to view or edit employees"); ?></h4>
<h4 class="card-header text-brown"><?php $Strings->get("select a manager to view or edit employees"); ?></h4>
<div class="card-body">
<div class="row">
<div class="col-xs-12 col-md-6">
<div class="form-group">
<label for="manager-box"><i class="fas fa-id-card"></i> <?php lang("manager"); ?></label><br />
<label for="manager-box"><i class="fas fa-id-card"></i> <?php $Strings->get("manager"); ?></label><br />
<div class="input-group">
<input type="text"<?php if ($employees === false) { ?> id="manager-box"<?php } ?> class="form-control" value="<?php echo $user ?>" name="manager" placeholder="<?php lang("type to select a manager"); ?>" <?php
if ($employees !== false) {
echo "readonly";
<?php
if ($employees === false) {
?>
<select class="form-control" id="manager-box" name="manager">
<option></option>
<?php
$allusers = $database->select("accounts", "uid", ["deleted" => 0]);
foreach ($allusers as $user) {
$u = new User($user);
echo '<option value="' . htmlentities($u->getUsername()) . '">' . htmlentities($u->getName()) . ' (' . htmlentities($u->getUsername()) . ')</option>';
}
?>
</select>
<?php
} else {
?>
<input type="text" class="form-control" name="manager" readonly="readonly" value="<?php echo htmlentities($user); ?>" />
<?php
}
?>/>
?>
<div class="input-group-append">
<?php if ($employees === false) { ?>
<button class="btn btn-default" type="button" id="selectmanagerbtn"><i class="fa fa-chevron-right"></i> <?php lang("next") ?></button>
<button class="btn btn-default" type="button" id="selectmanagerbtn"><i class="fa fa-chevron-right"></i> <?php $Strings->get("next") ?></button>
<?php } ?>
</div>
</div>
@ -49,11 +65,20 @@ if ($VARS['man'] && $database->has('accounts', ['username' => $VARS['man']])) {
?>
<div class="col-xs-12 col-md-6">
<div class="form-group">
<label for="people-box"><i class="fa fa-user"></i> <?php lang("employees"); ?></label><br />
<label for="people-box"><i class="fa fa-user"></i> <?php $Strings->get("employees"); ?></label><br />
<div class="input-group">
<input type="text" id="people-box" class="form-control" placeholder="<?php lang("type to add a person") ?>" />
<select class="form-control" id="people-box">
<option></option>
<?php
$allusers = $database->select("accounts", "uid", ["deleted" => 0]);
foreach ($allusers as $user) {
$u = new User($user);
echo '<option value="' . htmlentities($u->getUsername()) . '">' . htmlentities($u->getName()) . ' (' . htmlentities($u->getUsername()) . ')</option>';
}
?>
</select>
<div class="input-group-append">
<button class="btn btn-default" type="button" id="addpersonbtn"><i class="fa fa-plus"></i> <?php lang("add") ?></button>
<button class="btn btn-default" type="button" id="addpersonbtn"><i class="fa fa-plus"></i> <?php $Strings->get("add") ?></button>
</div>
</div>
</div>
@ -83,7 +108,7 @@ if ($VARS['man'] && $database->has('accounts', ['username' => $VARS['man']])) {
<?php if ($employees !== false) { ?>
<div class="card-footer d-flex">
<button type="submit" class="btn btn-success ml-auto" id="save-btn"><i class="fas fa-save"></i> <?php lang("save"); ?></button>
<button type="submit" class="btn btn-success ml-auto" id="save-btn"><i class="fas fa-save"></i> <?php $Strings->get("save"); ?></button>
</div>
</form>
<?php } ?>

@ -10,61 +10,50 @@ redirectifnotloggedin();
$perms = [];
$permissions = false;
$user = "";
if ($VARS['user'] && $database->has('accounts', ['username' => $VARS['user']])) {
$user = $VARS['user'];
require_once __DIR__ . "/../lib/userinfo.php";
$uid = getUserByUsername($user)['uid'];
$perms = $database->select('assigned_permissions', ["[>]permissions" => ["permid" => "permid"]], ['permissions.permid', 'permcode', 'perminfo'], ['uid' => $uid]);
$permissions = true;
$user = null;
if (!empty($VARS['user'])) {
$user = User::byUsername($VARS['user']);
if ($user->exists()) {
$perms = $database->select('assigned_permissions', ["[>]permissions" => ["permid" => "permid"]], ['permissions.permid', 'permcode', 'perminfo'], ['uid' => $user->getUID()]);
$permissions = true;
}
}
?>
<div class="card border-brown">
<?php if ($permissions !== false) { ?>
<form role="form" action="action.php" method="POST">
<?php } ?>
<h4 class="card-header text-brown"><?php lang("select a user to view or edit permissions"); ?></h4>
<h4 class="card-header text-brown"><?php $Strings->get("select a user to view or edit permissions"); ?></h4>
<div class="card-body">
<div class="row">
<div class="col-12 col-md-6">
<div class="form-group">
<label for="user-box"><i class="fas fa-id-card"></i> <?php lang("user"); ?></label><br />
<label for="user-box"><i class="fas fa-id-card"></i> <?php $Strings->get("user"); ?></label><br />
<div class="input-group">
<?php
if ($database->count('accounts', ['deleted[!]' => 1]) > 30) {
if ($permissions === false) {
?>
<input type="text"<?php if ($permissions === false) { ?>id="user-box"<?php } ?> class="form-control" value="<?php echo $user ?>" name="user" placeholder="<?php lang("type to select a user"); ?>" <?php
if ($permissions !== false) {
echo "readonly";
}
?>/>
<?php
} else {
if ($permissions === false) {
?>
<select id="user-box" class="form-control" name="user">
<option value=""><?php lang("Choose a user") ?></option>
<?php
$allusers = $database->select('accounts', ['username', 'realname'], ['deleted[!]' => 1, 'ORDER' => ['realname' => 'ASC']]);
foreach ($allusers as $u) {
echo "<option value=\"$u[username]\">$u[realname] ($u[username])</option>\n";
}
?>
</select>
<select id="user-box" class="form-control" name="user">
<option value=""><?php $Strings->get("Choose a user") ?></option>
<?php
} else {
$realname = $database->get('accounts', 'realname', ['username' => $user]);
$allusers = $database->select('accounts', ['username', 'realname'], ['deleted[!]' => 1, 'ORDER' => ['realname' => 'ASC']]);
foreach ($allusers as $u) {
echo "<option value=\"$u[username]\">$u[realname] ($u[username])</option>\n";
}
?>
<input type="text" class="form-control" value="<?php echo "$realname ($user)" ?>" readonly disabled />
<input type="hidden" id="user-box" name="user" value="<?php echo $user ?>" />
<?php
}
</select>
<?php
} else {
?>
<input type="text" class="form-control" value="<?php echo $user->getName() . "(" . $user->getUsername() . ")" ?>" readonly disabled />
<input type="hidden" id="user-box" name="user" value="<?php echo $user->getUsername() ?>" />
<?php
}
?>
<div class = "input-group-append">
<?php if ($permissions === false) {
?>
<button class="btn btn-default" type="button" id="selectuserbtn"><i class="fa fa-chevron-right"></i> <?php lang("next") ?></button>
<button class="btn btn-default" type="button" id="selectuserbtn"><i class="fa fa-chevron-right"></i> <?php $Strings->get("next") ?></button>
<?php } ?>
</div>
</div>
@ -75,10 +64,10 @@ if ($VARS['user'] && $database->has('accounts', ['username' => $VARS['user']]))
?>
<div class="col-12 col-md-6">
<div class="form-group">
<label for="perms-box"><i class="fas fa-key"></i> <?php lang("permissions"); ?></label><br />
<label for="perms-box"><i class="fas fa-key"></i> <?php $Strings->get("permissions"); ?></label><br />
<div class="input-group">
<select id="perms-box" class="form-control" data-ac="false">
<option value="" selected><?php lang("Choose a permission") ?></option>
<option value="" selected><?php $Strings->get("Choose a permission") ?></option>
<?php
$allpermissions = $database->select('permissions', ['permid', 'permcode', 'perminfo']);
foreach ($allpermissions as $p) {
@ -89,7 +78,7 @@ if ($VARS['user'] && $database->has('accounts', ['username' => $VARS['user']]))
?>
</select>
<div class="input-group-append">
<button class="btn btn-default" type="button" id="addpermbtn"><i class="fa fa-plus"></i> <?php lang("add") ?></button>
<button class="btn btn-default" type="button" id="addpermbtn"><i class="fa fa-plus"></i> <?php $Strings->get("add") ?></button>
</div>
</div>
</div>
@ -120,7 +109,7 @@ if ($VARS['user'] && $database->has('accounts', ['username' => $VARS['user']]))
<?php if ($permissions !== false) { ?>
<div class="card-footer d-flex">
<button type="submit" class="btn btn-success ml-auto" id="save-btn"><i class="fas fa-save"></i> <?php lang("save"); ?></button>
<button type="submit" class="btn btn-success ml-auto" id="save-btn"><i class="fas fa-save"></i> <?php $Strings->get("save"); ?></button>
</div>
</form>
<?php } ?>

@ -1,5 +1,4 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@ -9,38 +8,75 @@ require_once __DIR__ . '/../required.php';
redirectifnotloggedin();
?>
<div class="btn-group">
<a href="app.php?page=edituser" class="btn btn-success"><i class="fa fa-user-plus"></i> <?php lang("new user"); ?></a>
<a href="app.php?page=edituser" class="btn btn-success"><i class="fa fa-user-plus"></i> <?php $Strings->get("new user"); ?></a>
</div>
<table id="usertable" class="table table-bordered table-hover table-sm">
<thead>
<tr>
<th data-priority="0"></th>
<th data-priority="1"><?php lang('actions'); ?></th>
<th data-priority="1"><i class="fas fa-fw fa-user d-none d-md-inline"></i> <?php lang('name'); ?></th>
<th data-priority="2"><i class="fas fa-fw fa-id-badge d-none d-md-inline"></i> <?php lang('username'); ?></th>
<th data-priority="3"><i class="fas fa-fw fa-envelope d-none d-md-inline"></i> <?php lang('email'); ?></th>
<th data-priority="4"><i class="fas fa-fw fa-lock d-none d-md-inline"></i> <?php lang('2fa'); ?></th>
<th data-priority="3"><i class="fas fa-fw fa-check-circle d-none d-md-inline"></i> <?php lang('status'); ?></th>
<th data-priority="4"><i class="fas fa-fw fa-server d-none d-md-inline"></i> <?php lang('type'); ?></th>
<th data-priority="1"><?php $Strings->get('actions'); ?></th>
<th data-priority="1"><i class="fas fa-fw fa-user d-none d-md-inline"></i> <?php $Strings->get('name'); ?></th>
<th data-priority="2"><i class="fas fa-fw fa-id-badge d-none d-md-inline"></i> <?php $Strings->get('username'); ?></th>
<th data-priority="3"><i class="fas fa-fw fa-envelope d-none d-md-inline"></i> <?php $Strings->get('email'); ?></th>
<th data-priority="4"><i class="fas fa-fw fa-lock d-none d-md-inline"></i> <?php $Strings->get('2fa'); ?></th>
<th data-priority="3"><i class="fas fa-fw fa-check-circle d-none d-md-inline"></i> <?php $Strings->get('status'); ?></th>
</tr>
</thead>
<tbody>
<?php
$where = [];
if (empty($_GET['show_deleted'])) {
$where = [
'deleted' => 0
];
}
$users = $database->select('accounts', [
"[>]acctstatus" => ['acctstatus' => 'statusid'],
"[>]accttypes" => ['accttype' => 'typeid']
], [
'uid',
'username',
'realname',
'email',
'authsecret (2fa)',
'acctstatus',
'statuscode',
'accttype',
'typecode',
'deleted'
], $where
);
foreach ($users as $user) {
?>
<tr>
<td></td>
<td>
<a class="btn btn-primary btn-sm" href="app.php?page=edituser&id=<?php echo $user['uid']; ?>"><i class="far fa-edit"></i> <?php $Strings->get("Edit"); ?></a>
</td>
<?php if ($user['deleted']) { ?>
<td class="text-danger font-italic"><?php echo $user['realname']; ?></td>
<td class="text-danger font-italic"><?php echo $user['username']; ?></td>
<?php } else { ?>
<td><?php echo $user['realname']; ?></td>
<td><?php echo $user['username']; ?></td>
<?php } ?>
<td><?php echo $user['email']; ?></td>
<td><?php echo $user['2fa'] == true ? "<i class='fas fa-check'></i>" : "<i class='fas fa-times'></i>"; ?></td>
<td><?php echo $user['statuscode']; ?></td>
</tr>
<?php
}
?>
</tbody>
<tfoot>
<tr>
<th data-priority="0"></th>
<th data-priority="1"><?php lang('actions'); ?></th>
<th data-priority="1"><i class="fas fa-fw fa-user d-none d-md-inline"></i> <?php lang('name'); ?></th>
<th data-priority="2"><i class="fas fa-fw fa-id-badge d-none d-md-inline"></i> <?php lang('username'); ?></th>
<th data-priority="3"><i class="fas fa-fw fa-envelope d-none d-md-inline"></i> <?php lang('email'); ?></th>
<th data-priority="4"><i class="fas fa-fw fa-lock d-none d-md-inline"></i> <?php lang('2fa'); ?></th>
<th data-priority="3"><i class="fas fa-fw fa-check-circle d-none d-md-inline"></i> <?php lang('status'); ?></th>
<th data-priority="4"><i class="fas fa-fw fa-server d-none d-md-inline"></i> <?php lang('type'); ?></th>
<th data-priority="1"><?php $Strings->get('actions'); ?></th>
<th data-priority="1"><i class="fas fa-fw fa-user d-none d-md-inline"></i> <?php $Strings->get('name'); ?></th>
<th data-priority="2"><i class="fas fa-fw fa-id-badge d-none d-md-inline"></i> <?php $Strings->get('username'); ?></th>
<th data-priority="3"><i class="fas fa-fw fa-envelope d-none d-md-inline"></i> <?php $Strings->get('email'); ?></th>
<th data-priority="4"><i class="fas fa-fw fa-lock d-none d-md-inline"></i> <?php $Strings->get('2fa'); ?></th>
<th data-priority="3"><i class="fas fa-fw fa-check-circle d-none d-md-inline"></i> <?php $Strings->get('status'); ?></th>
</tfoot>
</table>
<script nonce="<?php echo $SECURE_NONCE; ?>">
/* Give JavaScript access to the lang string
* it needs to inject the show deleted checkbox
*/
var lang_show_deleted = "<?php lang("show deleted") ?>";
</script>
</table>

@ -32,7 +32,6 @@ session_start(); // stick some cookies in it
// renew session cookie
setcookie(session_name(), session_id(), time() + $session_length, "/", false, false);
$captcha_server = (CAPTCHA_ENABLED === true ? preg_replace("/http(s)?:\/\//", "", CAPTCHA_SERVER) : "");
if ($_SESSION['mobile'] === TRUE) {
header("Content-Security-Policy: "
. "default-src 'self';"
@ -42,8 +41,8 @@ if ($_SESSION['mobile'] === TRUE) {
. "frame-src 'none'; "
. "font-src 'self'; "
. "connect-src *; "
. "style-src 'self' 'unsafe-inline' $captcha_server; "
. "script-src 'self' 'unsafe-inline' $captcha_server");
. "style-src 'self' 'unsafe-inline'; "
. "script-src 'self' 'unsafe-inline'");
} else {
header("Content-Security-Policy: "
. "default-src 'self';"
@ -53,8 +52,8 @@ if ($_SESSION['mobile'] === TRUE) {
. "frame-src 'none'; "
. "font-src 'self'; "
. "connect-src *; "
. "style-src 'self' 'nonce-$SECURE_NONCE' $captcha_server; "
. "script-src 'self' 'nonce-$SECURE_NONCE' $captcha_server");
. "style-src 'self' 'nonce-$SECURE_NONCE'; "
. "script-src 'self' 'nonce-$SECURE_NONCE'");
}
//
@ -62,9 +61,14 @@ if ($_SESSION['mobile'] === TRUE) {
require __DIR__ . '/vendor/autoload.php';
// List of alert messages
require __DIR__ . '/lang/messages.php';
// text strings (i18n)
require __DIR__ . '/lang/' . LANGUAGE . ".php";
require __DIR__ . '/langs/messages.php';
$libs = glob(__DIR__ . "/lib/*.lib.php");
foreach ($libs as $lib) {
require_once $lib;
}
$Strings = new Strings($SETTINGS['language']);
/**
* Kill off the running process and spit out an error message
@ -88,7 +92,7 @@ function sendError($error) {
. "<p>" . htmlspecialchars($error) . "</p>");
}
date_default_timezone_set(TIMEZONE);
date_default_timezone_set($SETTINGS['timezone']);
// Database settings
// Also inits database and stuff
@ -97,12 +101,12 @@ use Medoo\Medoo;
$database;
try {
$database = new Medoo([
'database_type' => DB_TYPE,
'database_name' => DB_NAME,
'server' => DB_SERVER,
'username' => DB_USER,
'password' => DB_PASS,
'charset' => DB_CHARSET
'database_type' => $SETTINGS['database']['type'],
'database_name' => $SETTINGS['database']['name'],
'server' => $SETTINGS['database']['server'],
'username' => $SETTINGS['database']['user'],
'password' => $SETTINGS['database']['password'],
'charset' => $SETTINGS['database']['charset']
]);
} catch (Exception $ex) {
//header('HTTP/1.1 500 Internal Server Error');
@ -112,19 +116,19 @@ try {
$database2;
try {
$database2 = new Medoo([
'database_type' => DB2_TYPE,
'database_name' => DB2_NAME,
'server' => DB2_SERVER,
'username' => DB2_USER,
'password' => DB2_PASS,
'charset' => DB2_CHARSET
'database_type' => $SETTINGS['database2']['type'],
'database_name' => $SETTINGS['database2']['name'],
'server' => $SETTINGS['database2']['server'],
'username' => $SETTINGS['database2']['user'],
'password' => $SETTINGS['database2']['password'],
'charset' => $SETTINGS['database2']['charset']
]);
} catch (Exception $ex) {
//header('HTTP/1.1 500 Internal Server Error');
sendError("Database error 2. Try again later. $ex");
}
if (!DEBUG) {
if (!$SETTINGS['debug']) {
error_reporting(0);
} else {
error_reporting(E_ALL);
@ -141,70 +145,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
define("GET", true);
}
/**
* Checks if a string or whatever is empty.
* @param $str The thingy to check
* @return boolean True if it's empty or whatever.
*/
function is_empty($str) {
return (is_null($str) || !isset($str) || $str == '');
}
/**
* I18N string getter. If the key doesn't exist, outputs the key itself.
* @param string $key I18N string key
* @param boolean $echo whether to echo the result or return it (default echo)
*/
function lang($key, $echo = true) {
if (array_key_exists($key, STRINGS)) {
$str = STRINGS[$key];
} else {
trigger_error("Language key \"$key\" does not exist in " . LANGUAGE, E_USER_WARNING);
$str = $key;
}
if ($echo) {
echo $str;
} else {
return $str;
}
}
/**
* I18N string getter (with builder). If the key doesn't exist, outputs the key itself.
* @param string $key I18N string key
* @param array $replace key-value array of replacements.
* If the string value is "hello {abc}" and you give ["abc" => "123"], the
* result will be "hello 123".
* @param boolean $echo whether to echo the result or return it (default echo)
*/
function lang2($key, $replace, $echo = true) {
if (array_key_exists($key, STRINGS)) {
$str = STRINGS[$key];
} else {
trigger_error("Language key \"$key\" does not exist in " . LANGUAGE, E_USER_WARNING);
$str = $key;
}
foreach ($replace as $find => $repl) {
$str = str_replace("{" . $find . "}", $repl, $str);
}
if ($echo) {
echo $str;
} else {
return $str;
}
}
function dieifnotloggedin() {
if ($_SESSION['loggedin'] != true) {
die("You don't have permission to be here.");
}
require_once __DIR__ . "/lib/login.php";
if (account_has_permission($_SESSION['username'], "ADMIN") == FALSE) {
die("You don't have permission to be here.");
}
}
/**
@ -224,46 +169,9 @@ function checkDBError($specials = []) {
}
}
/*
* http://stackoverflow.com/a/20075147
*/
if (!function_exists('base_url')) {
function base_url($atRoot = FALSE, $atCore = FALSE, $parse = FALSE) {
if (isset($_SERVER['HTTP_HOST'])) {
$http = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) !== 'off' ? 'https' : 'http';
$hostname = $_SERVER['HTTP_HOST'];
$dir = str_replace(basename($_SERVER['SCRIPT_NAME']), '', $_SERVER['SCRIPT_NAME']);
$core = preg_split('@/@', str_replace($_SERVER['DOCUMENT_ROOT'], '', realpath(dirname(__FILE__))), NULL, PREG_SPLIT_NO_EMPTY);
$core = $core[0];
$tmplt = $atRoot ? ($atCore ? "%s://%s/%s/" : "%s://%s/") : ($atCore ? "%s://%s/%s/" : "%s://%s%s");
$end = $atRoot ? ($atCore ? $core : $hostname) : ($atCore ? $core : $dir);
$base_url = sprintf($tmplt, $http, $hostname, $end);
} else
$base_url = 'http://localhost/';
if ($parse) {
$base_url = parse_url($base_url);
if (isset($base_url['path']))
if ($base_url['path'] == '/')
$base_url['path'] = '';
}
return $base_url;
}
}
function redirectIfNotLoggedIn() {
if ($_SESSION['loggedin'] !== TRUE) {
header('Location: ./index.php');
header('Location: ' . $SETTINGS['url'] . '/index.php');
die();
}
require_once __DIR__ . "/lib/login.php";
if (account_has_permission($_SESSION['username'], "ADMIN") == FALSE) {
header('Location: ./index.php');
die("You don't have permission to be here.");
}
}

@ -1,55 +1,65 @@
<?php
/* This Source Code Form is subject to the terms of the Mozilla Public
/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
// Whether to show debugging data in output.
// DO NOT SET TO TRUE IN PRODUCTION!!!
define("DEBUG", false);
// Settings for the app.
// Copy to settings.php and customize.
// AccountHub database connection settings
// See http://medoo.in/api/new for info
define("DB_TYPE", "mysql");
define("DB_NAME", "accounthub");
define("DB_SERVER", "localhost");
define("DB_USER", "accounthub");
define("DB_PASS", "");
define("DB_CHARSET", "utf8");
// ManagePanel DB connection
define("DB2_TYPE", "mysql");
define("DB2_NAME", "managepanel");
define("DB2_SERVER", "localhost");
define("DB2_USER", "managepanel");
define("DB2_PASS", "");
define("DB2_CHARSET", "utf8");
// Name of the app.
define("SITE_TITLE", "ManagePanel");
// URL of the AccountHub API endpoint
define("PORTAL_API", "http://localhost/accounthub/api.php");
// URL of the AccountHub home page
define("PORTAL_URL", "http://localhost/accounthub/home.php");
// AccountHub API Key
define("PORTAL_KEY", "123");
// For supported values, see http://php.net/manual/en/timezones.php
define("TIMEZONE", "America/Denver");
// Base URL for site links.
define('URL', '.');
// Use Captcheck on login screen
// https://captcheck.netsyms.com
define("CAPTCHA_ENABLED", FALSE);
define('CAPTCHA_SERVER', 'https://captcheck.netsyms.com');
// See lang folder for language options
define('LANGUAGE', "en_us");
define("FOOTER_TEXT", "");
define("COPYRIGHT_NAME", "Netsyms Technologies");
$SETTINGS = [
// Whether to output debugging info like PHP notices, warnings,
// and stacktraces.
// Turning this on in production is a security risk and can sometimes break
// things, such as JSON output where extra content is not expected.
"debug" => false,
// Database connection settings
// See http://medoo.in/api/new for info
// AccountHub database
"database" => [
"type" => "mysql",
"name" => "accounthub",
"server" => "localhost",
"user" => "app",
"password" => "",
"charset" => "utf8"
],
// ManagePanel database
"database2" => [
"type" => "mysql",
"name" => "managepanel",
"server" => "localhost",
"user" => "app",
"password" => "",
"charset" => "utf8"
],
// Name of the app.
"site_title" => "ManagePanel",
// Settings for connecting to the AccountHub server.
"accounthub" => [
// URL for the API endpoint
"api" => "http://localhost/accounthub/api/",
// URL of the home page
"home" => "http://localhost/accounthub/home.php",
// API key
"key" => "123"
],
// List of required user permissions to access this app.
"permissions" => [
"ADMIN"
],
// For supported values, see http://php.net/manual/en/timezones.php
"timezone" => "America/Denver",
// Language to use for localization. See langs folder to add a language.
"language" => "en",
// Shown in the footer of all the pages.
"footer_text" => "",
// Also shown in the footer, but with "Copyright <current_year>" in front.
"copyright" => "Netsyms Technologies",
// Base URL for building links relative to the location of the app.
// Only used when there's no good context for the path.
// The default is almost definitely fine.
"url" => "."
];

File diff suppressed because one or more lines are too long

@ -1,15 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
.banner-image {
max-height: 100px;
margin: 2em auto;
border: 1px solid grey;
border-radius: 15%;
}
.footer {
margin-top: 10em;
text-align: center;
}

@ -1,5 +1,5 @@
/*!
* Font Awesome Free 5.1.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
* Font Awesome Free 5.6.0 by @fontawesome - https://fontawesome.com
* License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License)
*/
.svg-inline--fa,svg:not(:root).svg-inline--fa{overflow:visible}.svg-inline--fa{display:inline-block;font-size:inherit;height:1em;vertical-align:-.125em}.svg-inline--fa.fa-lg{vertical-align:-.225em}.svg-inline--fa.fa-w-1{width:.0625em}.svg-inline--fa.fa-w-2{width:.125em}.svg-inline--fa.fa-w-3{width:.1875em}.svg-inline--fa.fa-w-4{width:.25em}.svg-inline--fa.fa-w-5{width:.3125em}.svg-inline--fa.fa-w-6{width:.375em}.svg-inline--fa.fa-w-7{width:.4375em}.svg-inline--fa.fa-w-8{width:.5em}.svg-inline--fa.fa-w-9{width:.5625em}.svg-inline--fa.fa-w-10{width:.625em}.svg-inline--fa.fa-w-11{width:.6875em}.svg-inline--fa.fa-w-12{width:.75em}.svg-inline--fa.fa-w-13{width:.8125em}.svg-inline--fa.fa-w-14{width:.875em}.svg-inline--fa.fa-w-15{width:.9375em}.svg-inline--fa.fa-w-16{width:1em}.svg-inline--fa.fa-w-17{width:1.0625em}.svg-inline--fa.fa-w-18{width:1.125em}.svg-inline--fa.fa-w-19{width:1.1875em}.svg-inline--fa.fa-w-20{width:1.25em}.svg-inline--fa.fa-pull-left{margin-right:.3em;width:auto}.svg-inline--fa.fa-pull-right{margin-left:.3em;width:auto}.svg-inline--fa.fa-border{height:1.5em}.svg-inline--fa.fa-li{width:2em}.svg-inline--fa.fa-fw{width:1.25em}.fa-layers svg.svg-inline--fa{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.fa-layers{display:inline-block;height:1em;position:relative;text-align:center;vertical-align:-.125em;width:1em}.fa-layers svg.svg-inline--fa{transform-origin:center center}.fa-layers-counter,.fa-layers-text{display:inline-block;position:absolute;text-align:center}.fa-layers-text{left:50%;top:50%;transform:translate(-50%,-50%);transform-origin:center center}.fa-layers-counter{background-color:#ff253a;border-radius:1em;box-sizing:border-box;color:#fff;height:1.5em;line-height:1;max-width:5em;min-width:1.5em;overflow:hidden;padding:.25em;right:0;text-overflow:ellipsis;top:0;transform:scale(.25);transform-origin:top right}.fa-layers-bottom-right{bottom:0;right:0;top:auto;transform:scale(.25);transform-origin:bottom right}.fa-layers-bottom-left{bottom:0;left:0;right:auto;top:auto;transform:scale(.25);transform-origin:bottom left}.fa-layers-top-right{right:0;top:0;transform:scale(.25);transform-origin:top right}.fa-layers-top-left{left:0;right:auto;top:0;transform:scale(.25);transform-origin:top left}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:a 2s infinite linear}.fa-pulse{animation:a 1s infinite steps(8)}@keyframes a{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{-webkit-filter:none;filter:none}.fa-stack{display:inline-block;height:2em;position:relative;width:2em}.fa-stack-1x,.fa-stack-2x{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.svg-inline--fa.fa-stack-1x{height:1em;width:1em}.svg-inline--fa.fa-stack-2x{height:2em;width:2em}.fa-inverse{color:#fff}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}
.svg-inline--fa,svg:not(:root).svg-inline--fa{overflow:visible}.svg-inline--fa{display:inline-block;font-size:inherit;height:1em;vertical-align:-.125em}.svg-inline--fa.fa-lg{vertical-align:-.225em}.svg-inline--fa.fa-w-1{width:.0625em}.svg-inline--fa.fa-w-2{width:.125em}.svg-inline--fa.fa-w-3{width:.1875em}.svg-inline--fa.fa-w-4{width:.25em}.svg-inline--fa.fa-w-5{width:.3125em}.svg-inline--fa.fa-w-6{width:.375em}.svg-inline--fa.fa-w-7{width:.4375em}.svg-inline--fa.fa-w-8{width:.5em}.svg-inline--fa.fa-w-9{width:.5625em}.svg-inline--fa.fa-w-10{width:.625em}.svg-inline--fa.fa-w-11{width:.6875em}.svg-inline--fa.fa-w-12{width:.75em}.svg-inline--fa.fa-w-13{width:.8125em}.svg-inline--fa.fa-w-14{width:.875em}.svg-inline--fa.fa-w-15{width:.9375em}.svg-inline--fa.fa-w-16{width:1em}.svg-inline--fa.fa-w-17{width:1.0625em}.svg-inline--fa.fa-w-18{width:1.125em}.svg-inline--fa.fa-w-19{width:1.1875em}.svg-inline--fa.fa-w-20{width:1.25em}.svg-inline--fa.fa-pull-left{margin-right:.3em;width:auto}.svg-inline--fa.fa-pull-right{margin-left:.3em;width:auto}.svg-inline--fa.fa-border{height:1.5em}.svg-inline--fa.fa-li{width:2em}.svg-inline--fa.fa-fw{width:1.25em}.fa-layers svg.svg-inline--fa{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.fa-layers{display:inline-block;height:1em;position:relative;text-align:center;vertical-align:-.125em;width:1em}.fa-layers svg.svg-inline--fa{transform-origin:center center}.fa-layers-counter,.fa-layers-text{display:inline-block;position:absolute;text-align:center}.fa-layers-text{left:50%;top:50%;transform:translate(-50%,-50%);transform-origin:center center}.fa-layers-counter{background-color:#ff253a;border-radius:1em;box-sizing:border-box;color:#fff;height:1.5em;line-height:1;max-width:5em;min-width:1.5em;overflow:hidden;padding:.25em;right:0;text-overflow:ellipsis;top:0;transform:scale(.25);transform-origin:top right}.fa-layers-bottom-right{bottom:0;right:0;top:auto;transform:scale(.25);transform-origin:bottom right}.fa-layers-bottom-left{bottom:0;left:0;right:auto;top:auto;transform:scale(.25);transform-origin:bottom left}.fa-layers-top-right{right:0;top:0;transform:scale(.25);transform-origin:top right}.fa-layers-top-left{left:0;right:auto;top:0;transform:scale(.25);transform-origin:top left}.fa-lg{font-size:1.33333em;line-height:.75em;vertical-align:-.0667em}.fa-xs{font-size:.75em}.fa-sm{font-size:.875em}.fa-1x{font-size:1em}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-6x{font-size:6em}.fa-7x{font-size:7em}.fa-8x{font-size:8em}.fa-9x{font-size:9em}.fa-10x{font-size:10em}.fa-fw{text-align:center;width:1.25em}.fa-ul{list-style-type:none;margin-left:2.5em;padding-left:0}.fa-ul>li{position:relative}.fa-li{left:-2em;position:absolute;text-align:center;width:2em;line-height:inherit}.fa-border{border:.08em solid #eee;border-radius:.1em;padding:.2em .25em .15em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left,.fab.fa-pull-left,.fal.fa-pull-left,.far.fa-pull-left,.fas.fa-pull-left{margin-right:.3em}.fa.fa-pull-right,.fab.fa-pull-right,.fal.fa-pull-right,.far.fa-pull-right,.fas.fa-pull-right{margin-left:.3em}.fa-spin{animation:fa-spin 2s infinite linear}.fa-pulse{animation:fa-spin 1s infinite steps(8)}@keyframes fa-spin{0%{transform:rotate(0deg)}to{transform:rotate(1turn)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";transform:scaleX(-1)}.fa-flip-vertical{transform:scaleY(-1)}.fa-flip-horizontal.fa-flip-vertical,.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)"}.fa-flip-horizontal.fa-flip-vertical{transform:scale(-1)}:root .fa-flip-horizontal,:root .fa-flip-vertical,:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270{filter:none}.fa-stack{display:inline-block;height:2em;position:relative;width:2.5em}.fa-stack-1x,.fa-stack-2x{bottom:0;left:0;margin:auto;position:absolute;right:0;top:0}.svg-inline--fa.fa-stack-1x{height:1em;width:1.25em}.svg-inline--fa.fa-stack-2x{height:2em;width:2.5em}.fa-inverse{color:#fff}.sr-only{border:0;clip:rect(0,0,0,0);height:1px;margin:-1px;overflow:hidden;padding:0;position:absolute;width:1px}.sr-only-focusable:active,.sr-only-focusable:focus{clip:auto;height:auto;margin:0;overflow:visible;position:static;width:auto}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -25,57 +25,12 @@ function removePerson(p) {
$("#peoplelist div[data-user=" + p + "]").remove();
}
var options = {
url: "action.php",
ajaxSettings: {
dataType: "json",
method: "GET",
data: {
action: "autocomplete_user"
}
},
preparePostData: function (data) {
data.q = $("#people-box").val();
return data;
},
getValue: function (element) {
return element.username;
},
template: {
type: "custom",
method: function (value, item) {
return item.name + " <i class=\"small\">" + item.username + "</i>";
}
},
list: {
onClickEvent: function () {
var value = $("#people-box").getSelectedItemData().username;
addPerson(value);
}
},
requestDelay: 500,
cssClasses: "form-control form-control-sm"
};
$("#people-box").easyAutocomplete(options);
$("#selectgroupbtn").click(function () {
document.location.href = "app.php?page=groups&gid=" + $("#group-box").val();
});
$("#people-box").keyup(function (event) {
if (event.keyCode == 13) {
$("#addpersonbtn").click();
event.preventDefault();
return false;
}
});
$("#people-box").keydown(function (event) {
if (event.keyCode == 13) {
event.preventDefault();
return false;
}
$("#people-box").on("change", function (event) {
addPerson($("#people-box").val());
});
$("#addpersonbtn").click(function () {

@ -25,96 +25,12 @@ function removePerson(p) {
$("#peoplelist div[data-user=" + p + "]").remove();
}
var empoptions = {
url: "action.php",
ajaxSettings: {
dataType: "json",
method: "GET",
data: {
action: "autocomplete_user"
}
},
preparePostData: function (data) {
data.q = $("#people-box").val();
return data;
},
getValue: function (element) {
return element.username;
},
template: {
type: "custom",
method: function (value, item) {
return item.name + " <i class=\"small\">" + item.username + "</i>";
}
},
list: {
onClickEvent: function () {
var value = $("#people-box").getSelectedItemData().username;
addPerson(value);
}
},
requestDelay: 500,
cssClasses: "form-control form-control-sm"
};
$("#people-box").easyAutocomplete(empoptions);
var manoptions = {
url: "action.php",
ajaxSettings: {
dataType: "json",
method: "GET",
data: {
action: "autocomplete_user"
}
},
preparePostData: function (data) {
data.q = $("#manager-box").val();
return data;
},
getValue: function (element) {
return element.username;
},
template: {
type: "custom",
method: function (value, item) {
return item.name + " <i class=\"small\">" + item.username + "</i>";
}
},
list: {
onClickEvent: function () {
var value = $("#manager-box").getSelectedItemData().username;
document.location.href = "app.php?page=managers&man=" + value;
}
},
requestDelay: 500,
cssClasses: "form-control form-control-sm"
};
$("#manager-box").easyAutocomplete(manoptions);
$("#manager-box").keyup(function (e) {
if (e.keyCode == 13) {
$("#selectmanagerbtn").click();
}
});
$("#selectmanagerbtn").click(function () {
document.location.href = "app.php?page=managers&man=" + $("#manager-box").val();
});
$("#people-box").keyup(function (event) {
if (event.keyCode == 13) {
$("#addpersonbtn").click();
event.preventDefault();
return false;
}
});
$("#people-box").keydown(function (event) {
if (event.keyCode == 13) {
event.preventDefault();
return false;
}
$("#people-box").on("change", function (event) {
addPerson($("#people-box").val());
});
$("#addpersonbtn").click(function () {

@ -51,64 +51,16 @@ function removePermission(permcode) {
$("#permslist div[data-permcode=" + permcode + "]").remove();
}
var options = {
url: "action.php",
ajaxSettings: {
dataType: "json",
method: "GET",
data: {
action: "autocomplete_user"
}
},
preparePostData: function (data) {
data.q = $("#user-box").val();
return data;
},
getValue: function (element) {
return element.username;
},
template: {
type: "custom",
method: function (value, item) {
return item.name + " <i class=\"small\">" + item.username + "</i>";
}
},
list: {
onClickEvent: function () {
var value = $("#user-box").getSelectedItemData().username;
document.location.href = "app.php?page=permissions&user=" + value;
}
},
requestDelay: 500,
cssClasses: "form-control form-control-sm"
};
if ($("#user-box").get(0).tagName != "SELECT") {
$("#user-box").easyAutocomplete(options);
}
$("#user-box").keyup(function (e) {
if (e.keyCode == 13) {
$("#selectuserbtn").click();
}
$("#user-box").on("change", function (e) {
$("#selectuserbtn").click();
});
$("#selectuserbtn").click(function () {
document.location.href = "app.php?page=permissions&user=" + $("#user-box").val();
});
$("#perms-box").keyup(function (event) {
if (event.keyCode == 13) {
$("#addpermbtn").click();
event.preventDefault();
return false;
}
});
$("#perms-box").keydown(function (event) {
if (event.keyCode == 13) {
event.preventDefault();
return false;
}
$("#perms-box").on("click", function (event) {
$("#addpermbtn").click();
});
$("#addpermbtn").click(function () {

@ -30,37 +30,5 @@ var usertable = $('#usertable').DataTable({
],
order: [
[2, 'asc']
],
serverSide: true,
ajax: {
url: "lib/getusertable.php",
data: function (d) {
if ($('#show_deleted_checkbox').is(':checked')) {
d.show_deleted = 1;
}
},
dataFilter: function (data) {
var json = jQuery.parseJSON(data);
json.data = [];
json.users.forEach(function (row) {
json.data.push([
"",
row.editbtn,
(row.deleted == 1 ? "<del style=\"color: red;\">" : "") + row.realname + (row.deleted == 1 ? "</del>" : ""),
(row.deleted == 1 ? "<span style=\"color: red;\">" : "") + row.username + (row.deleted == 1 ? "</span>" : ""),
row.email,
(row['2fa'] == true ? "<i class='fas fa-check'></i>" : "<i class='fa fa-times'></i>"),
row.statuscode,
row.typecode
]);
});
return JSON.stringify(json);
}
}
});
$('#usertable_filter').append("<div class=\"checkbox inblock\"><label><input type=\"checkbox\" id=\"show_deleted_checkbox\"> " + lang_show_deleted + "</label></div>");
$('#show_deleted_checkbox').click(function () {
usertable.ajax.reload();
]
});
Loading…
Cancel
Save