Finish merge/upgrade
parent
47540e57d2
commit
28b8563ea0
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"Edit": "Edit",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"Choose a user": "Choose a user"
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
{
|
||||||
|
"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.",
|
||||||
|
"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}!",
|
||||||
|
"settings": "Settings",
|
||||||
|
"options": "Options",
|
||||||
|
"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.",
|
||||||
|
"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,72 @@
|
|||||||
|
<?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 Log {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @global $database
|
||||||
|
* @param int/LogType $type Either an integer (as defined by the constants in class LogType) or a LogType object.
|
||||||
|
* @param int/User $user Either a UID number or a User object.
|
||||||
|
* @param string $data Extra data to include in the log, in addition to the timestamp, log type, user, and IP address.
|
||||||
|
*/
|
||||||
|
public static function insert($type, $user, string $data = "") {
|
||||||
|
global $database;
|
||||||
|
// find IP address
|
||||||
|
$ip = IPUtils::getClientIP();
|
||||||
|
if (gettype($type) == "object" && is_a($type, "LogType")) {
|
||||||
|
$type = $type->getType();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_a($user, "User")) {
|
||||||
|
$uid = $user->getUID();
|
||||||
|
} else if (gettype($user) == "integer") {
|
||||||
|
$uid = $user;
|
||||||
|
} else {
|
||||||
|
$uid = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$database->insert("authlog", ['logtime' => date("Y-m-d H:i:s"), 'logtype' => $type, 'uid' => $uid, 'ip' => $ip, 'otherdata' => $data]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class LogType {
|
||||||
|
|
||||||
|
const LOGIN_OK = 1;
|
||||||
|
const LOGIN_FAILED = 2;
|
||||||
|
const PASSWORD_CHANGED = 3;
|
||||||
|
const API_LOGIN_OK = 4;
|
||||||
|
const API_LOGIN_FAILED = 5;
|
||||||
|
const BAD_2FA = 6;
|
||||||
|
const API_BAD_2FA = 7;
|
||||||
|
const BAD_CAPTCHA = 8;
|
||||||
|
const ADDED_2FA = 9;
|
||||||
|
const REMOVED_2FA = 10;
|
||||||
|
const LOGOUT = 11;
|
||||||
|
const API_AUTH_OK = 12;
|
||||||
|
const API_AUTH_FAILED = 13;
|
||||||
|
const API_BAD_KEY = 14;
|
||||||
|
const LOG_CLEARED = 15;
|
||||||
|
const USER_REMOVED = 16;
|
||||||
|
const USER_ADDED = 17;
|
||||||
|
const USER_EDITED = 18;
|
||||||
|
const MOBILE_LOGIN_OK = 19;
|
||||||
|
const MOBILE_LOGIN_FAILED = 20;
|
||||||
|
const MOBILE_BAD_KEY = 21;
|
||||||
|
|
||||||
|
private $type;
|
||||||
|
|
||||||
|
function __construct(int $type) {
|
||||||
|
$this->type = $type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getType(): int {
|
||||||
|
return $type;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,137 @@
|
|||||||
|
<?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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
class Report {
|
||||||
|
|
||||||
|
private $title = "";
|
||||||
|
private $header = [];
|
||||||
|
private $data = [];
|
||||||
|
|
||||||
|
public function __construct(string $title = "", array $header = [], array $data = []) {
|
||||||
|
$this->title = $title;
|
||||||
|
$this->header = $header;
|
||||||
|
$this->data = $data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setHeader(array $header) {
|
||||||
|
$this->header = $header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addDataRow(array $columns) {
|
||||||
|
$this->data[] = $columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getHeader(): array {
|
||||||
|
return $this->header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getData(): array {
|
||||||
|
return $this->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function output(string $format) {
|
||||||
|
switch ($format) {
|
||||||
|
case "ods":
|
||||||
|
$this->toODS();
|
||||||
|
break;
|
||||||
|
case "html":
|
||||||
|
$this->toHTML();
|
||||||
|
break;
|
||||||
|
case "csv":
|
||||||
|
default:
|
||||||
|
$this->toCSV();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function toODS() {
|
||||||
|
$ods = new ods();
|
||||||
|
$styleColumn = new odsStyleTableColumn();
|
||||||
|
$styleColumn->setUseOptimalColumnWidth(true);
|
||||||
|
$headerstyle = new odsStyleTableCell();
|
||||||
|
$headerstyle->setFontWeight("bold");
|
||||||
|
$table = new odsTable($this->title);
|
||||||
|
|
||||||
|
for ($i = 0; $i < count($this->header); $i++) {
|
||||||
|
$table->addTableColumn(new odsTableColumn($styleColumn));
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = new odsTableRow();
|
||||||
|
foreach ($this->header as $cell) {
|
||||||
|
$row->addCell(new odsTableCellString($cell, $headerstyle));
|
||||||
|
}
|
||||||
|
$table->addRow($row);
|
||||||
|
|
||||||
|
foreach ($this->data as $cols) {
|
||||||
|
$row = new odsTableRow();
|
||||||
|
foreach ($cols as $cell) {
|
||||||
|
$row->addCell(new odsTableCellString($cell));
|
||||||
|
}
|
||||||
|
$table->addRow($row);
|
||||||
|
}
|
||||||
|
$ods->addTable($table);
|
||||||
|
// The @ is a workaround to silence the tempnam notice,
|
||||||
|
// which breaks the file. This is apparently the intended behavior:
|
||||||
|
// https://bugs.php.net/bug.php?id=69489
|
||||||
|
@$ods->downloadOdsFile($this->title . "_" . date("Y-m-d_Hi") . ".ods");
|
||||||
|
}
|
||||||
|
|
||||||
|
private function toHTML() {
|
||||||
|
global $SECURE_NONCE;
|
||||||
|
$data = array_merge([$this->header], $this->data);
|
||||||
|
// 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>" . $this->title . "_" . date("Y-m-d_Hi") . "</title>\n"
|
||||||
|
. <<<STYLE
|
||||||
|
<style nonce="$SECURE_NONCE">
|
||||||
|
.table-csv-data {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function toCSV() {
|
||||||
|
$csv = Writer::createFromString('');
|
||||||
|
$data = array_merge([$this->header], $this->data);
|
||||||
|
$csv->insertAll($data);
|
||||||
|
header('Content-type: text/csv');
|
||||||
|
header('Content-Disposition: attachment; filename="' . $this->title . "_" . date("Y-m-d_Hi") . ".csv" . '"');
|
||||||
|
echo $csv;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,312 @@
|
|||||||
|
<?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/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Base32\Base32;
|
||||||
|
use OTPHP\TOTP;
|
||||||
|
|
||||||
|
class User {
|
||||||
|
|
||||||
|
private $uid = null;
|
||||||
|
private $username;
|
||||||
|
private $passhash;
|
||||||
|
private $email;
|
||||||
|
private $realname;
|
||||||
|
private $authsecret;
|
||||||
|
private $has2fa = false;
|
||||||
|
private $exists = false;
|
||||||
|
|
||||||
|
public function __construct(int $uid, string $username = "") {
|
||||||
|
global $database;
|
||||||
|
if ($database->has('accounts', ['AND' => ['uid' => $uid, 'deleted' => false]])) {
|
||||||
|
$this->uid = $uid;
|
||||||
|
$user = $database->get('accounts', ['username', 'password', 'email', 'realname', 'authsecret'], ['uid' => $uid]);
|
||||||
|
$this->username = $user['username'];
|
||||||
|
$this->passhash = $user['password'];
|
||||||
|
$this->email = $user['email'];
|
||||||
|
$this->realname = $user['realname'];
|
||||||
|
$this->authsecret = $user['authsecret'];
|
||||||
|
$this->has2fa = !empty($user['authsecret']);
|
||||||
|
$this->exists = true;
|
||||||
|
} else {
|
||||||
|
$this->uid = $uid;
|
||||||
|
$this->username = $username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function byUsername(string $username): User {
|
||||||
|
global $database;
|
||||||
|
$username = strtolower($username);
|
||||||
|
if ($database->has('accounts', ['AND' => ['username' => $username, 'deleted' => false]])) {
|
||||||
|
$uid = $database->get('accounts', 'uid', ['username' => $username]);
|
||||||
|
return new self($uid * 1);
|
||||||
|
}
|
||||||
|
return new self(-1, $username);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a user to the system. /!\ Assumes input is OK /!\
|
||||||
|
* @param string $username Username, saved in lowercase.
|
||||||
|
* @param string $password Password, will be hashed before saving.
|
||||||
|
* @param string $realname User's real legal name
|
||||||
|
* @param string $email User's email address.
|
||||||
|
* @param string $phone1 Phone number #1
|
||||||
|
* @param string $phone2 Phone number #2
|
||||||
|
* @param int $type Account type
|
||||||
|
* @return int The new user's ID number in the database.
|
||||||
|
*/
|
||||||
|
public static function add(string $username, string $password, string $realname, string $email = null, string $phone1 = "", string $phone2 = "", int $type = 1): int {
|
||||||
|
global $database;
|
||||||
|
$database->insert('accounts', [
|
||||||
|
'username' => strtolower($username),
|
||||||
|
'password' => (is_null($password) ? null : password_hash($password, PASSWORD_BCRYPT)),
|
||||||
|
'realname' => $realname,
|
||||||
|
'email' => $email,
|
||||||
|
'phone1' => $phone1,
|
||||||
|
'phone2' => $phone2,
|
||||||
|
'acctstatus' => 1,
|
||||||
|
'accttype' => $type
|
||||||
|
]);
|
||||||
|
return $database->id();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exists(): bool {
|
||||||
|
return $this->exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function has2fa(): bool {
|
||||||
|
return $this->has2fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUsername() {
|
||||||
|
return $this->username;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getUID() {
|
||||||
|
return $this->uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getEmail() {
|
||||||
|
return $this->email;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getName() {
|
||||||
|
return $this->realname;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the given plaintext password against the stored hash.
|
||||||
|
* @param string $password
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
function checkPassword(string $password): bool {
|
||||||
|
return password_verify($password, $this->passhash);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the user's password.
|
||||||
|
* @global $database $database
|
||||||
|
* @param string $old The current password
|
||||||
|
* @param string $new The new password
|
||||||
|
* @param string $new2 New password again
|
||||||
|
* @throws PasswordMatchException
|
||||||
|
* @throws PasswordMismatchException
|
||||||
|
* @throws IncorrectPasswordException
|
||||||
|
* @throws WeakPasswordException
|
||||||
|
*/
|
||||||
|
function changePassword(string $old, string $new, string $new2) {
|
||||||
|
global $database, $SETTINGS;
|
||||||
|
if ($old == $new) {
|
||||||
|
throw new PasswordMatchException();
|
||||||
|
}
|
||||||
|
if ($new != $new2) {
|
||||||
|
throw new PasswordMismatchException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->checkPassword($old)) {
|
||||||
|
throw new IncorrectPasswordException();
|
||||||
|
}
|
||||||
|
|
||||||
|
require_once __DIR__ . "/worst_passwords.php";
|
||||||
|
|
||||||
|
$passrank = checkWorst500List($new);
|
||||||
|
if ($passrank !== FALSE) {
|
||||||
|
throw new WeakPasswordException();
|
||||||
|
}
|
||||||
|
if (strlen($new) < $SETTINGS['min_password_length']) {
|
||||||
|
throw new WeakPasswordException();
|
||||||
|
}
|
||||||
|
|
||||||
|
$database->update('accounts', ['password' => password_hash($new, PASSWORD_DEFAULT), 'acctstatus' => 1], ['uid' => $this->uid]);
|
||||||
|
Log::insert(LogType::PASSWORD_CHANGED, $this);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function check2fa(string $code): bool {
|
||||||
|
if (!$this->has2fa) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$totp = new TOTP(null, $this->authsecret);
|
||||||
|
$time = time();
|
||||||
|
if ($totp->verify($code, $time)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ($totp->verify($code, $time - 30)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if ($totp->verify($code, $time + 30)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a TOTP secret for the given user.
|
||||||
|
* @return string OTP provisioning URI (for generating a QR code)
|
||||||
|
*/
|
||||||
|
function generate2fa(): string {
|
||||||
|
global $SETTINGS;
|
||||||
|
$secret = random_bytes(20);
|
||||||
|
$encoded_secret = Base32::encode($secret);
|
||||||
|
$totp = new TOTP((empty($this->email) ? $this->realname : $this->email), $encoded_secret);
|
||||||
|
$totp->setIssuer($SETTINGS['system_name']);
|
||||||
|
return $totp->getProvisioningUri();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a TOTP secret for the user.
|
||||||
|
* @global $database $database
|
||||||
|
* @param string $username
|
||||||
|
* @param string $secret
|
||||||
|
*/
|
||||||
|
function save2fa(string $secret) {
|
||||||
|
global $database;
|
||||||
|
$database->update('accounts', ['authsecret' => $secret], ['username' => $this->username]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the given username has the given permission (or admin access)
|
||||||
|
* @global $database $database
|
||||||
|
* @param string $code
|
||||||
|
* @return boolean TRUE if the user has the permission (or admin access), else FALSE
|
||||||
|
*/
|
||||||
|
function hasPermission(string $code): bool {
|
||||||
|
global $database;
|
||||||
|
return $database->has('assigned_permissions', [
|
||||||
|
'[>]permissions' => [
|
||||||
|
'permid' => 'permid'
|
||||||
|
]
|
||||||
|
], ['AND' => ['OR' => ['permcode #code' => $code, 'permcode #admin' => 'ADMIN'], 'uid' => $this->uid]]) === TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the account status.
|
||||||
|
* @return \AccountStatus
|
||||||
|
*/
|
||||||
|
function getStatus(): AccountStatus {
|
||||||
|
global $database;
|
||||||
|
$statuscode = $database->get('accounts', 'acctstatus', ['uid' => $this->uid]);
|
||||||
|
return new AccountStatus($statuscode);
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendAlertEmail(string $appname = null) {
|
||||||
|
global $SETTINGS;
|
||||||
|
if (is_null($appname)) {
|
||||||
|
$appname = $SETTINGS['site_title'];
|
||||||
|
}
|
||||||
|
if (empty(ADMIN_EMAIL) || filter_var(ADMIN_EMAIL, FILTER_VALIDATE_EMAIL) === FALSE) {
|
||||||
|
return "invalid_to_email";
|
||||||
|
}
|
||||||
|
if (empty(FROM_EMAIL) || filter_var(FROM_EMAIL, FILTER_VALIDATE_EMAIL) === FALSE) {
|
||||||
|
return "invalid_from_email";
|
||||||
|
}
|
||||||
|
|
||||||
|
$mail = new PHPMailer;
|
||||||
|
|
||||||
|
if ($SETTINGS['debug']) {
|
||||||
|
$mail->SMTPDebug = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($SETTINGS['email']['use_smtp']) {
|
||||||
|
$mail->isSMTP();
|
||||||
|
$mail->Host = $SETTINGS['email']['host'];
|
||||||
|
$mail->SMTPAuth = $SETTINGS['email']['auth'];
|
||||||
|
$mail->Username = $SETTINGS['email']['user'];
|
||||||
|
$mail->Password = $SETTINGS['email']['password'];
|
||||||
|
$mail->SMTPSecure = $SETTINGS['email']['secure'];
|
||||||
|
$mail->Port = $SETTINGS['email']['port'];
|
||||||
|
if ($SETTINGS['email']['allow_invalid_certificate']) {
|
||||||
|
$mail->SMTPOptions = array(
|
||||||
|
'ssl' => array(
|
||||||
|
'verify_peer' => false,
|
||||||
|
'verify_peer_name' => false,
|
||||||
|
'allow_self_signed' => true
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$mail->setFrom(FROM_EMAIL, 'Account Alerts');
|
||||||
|
$mail->addAddress(ADMIN_EMAIL, "System Admin");
|
||||||
|
$mail->isHTML(false);
|
||||||
|
$mail->Subject = $Strings->get("admin alert email subject", false);
|
||||||
|
$mail->Body = $Strings->build("admin alert email message", ["username" => $this->username, "datetime" => date("Y-m-d H:i:s"), "ipaddr" => IPUtils::getClientIP(), "appname" => $appname], false);
|
||||||
|
|
||||||
|
if (!$mail->send()) {
|
||||||
|
return $mail->ErrorInfo;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccountStatus {
|
||||||
|
|
||||||
|
const NORMAL = 1;
|
||||||
|
const LOCKED_OR_DISABLED = 2;
|
||||||
|
const CHANGE_PASSWORD = 3;
|
||||||
|
const TERMINATED = 4;
|
||||||
|
const ALERT_ON_ACCESS = 5;
|
||||||
|
|
||||||
|
private $status;
|
||||||
|
|
||||||
|
public function __construct(int $status) {
|
||||||
|
$this->status = $status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the account status/state as an integer.
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function get(): int {
|
||||||
|
return $this->status;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the account status/state as a string representation.
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function getString(): string {
|
||||||
|
switch ($this->status) {
|
||||||
|
case self::NORMAL:
|
||||||
|
return "NORMAL";
|
||||||
|
case self::LOCKED_OR_DISABLED:
|
||||||
|
return "LOCKED_OR_DISABLED";
|
||||||
|
case self::CHANGE_PASSWORD:
|
||||||
|
return "CHANGE_PASSWORD";
|
||||||
|
case self::TERMINATED:
|
||||||
|
return "TERMINATED";
|
||||||
|
case self::ALERT_ON_ACCESS:
|
||||||
|
return "ALERT_ON_ACCESS";
|
||||||
|
default:
|
||||||
|
return "OTHER_" . $this->status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue