diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d6d5d3c..b10ce88 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -34,5 +34,7 @@ If applicable, add screenshots to help explain your problem. - Browser [e.g. stock browser, safari] - Version [e.g. 22] +**TimeTrack Version**: e.g. v7.9 + **Additional context** Add any other context about the problem here. diff --git a/CHANGELOG.md b/CHANGELOG.md index bca98a0..0ac2d86 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,45 @@ # CHANGELOG +## v7.12 + +* Added a simple Favicon +* TimeTrack and API version are now displayed in the settings menu +* Added Events which can be listened to by plugins (see `api/v1/class/events/README.md`) / Developers can now create their own events +* Mails can now be disabled by setting the `smtp` setting to `false` within the `smtp` section of the `app.json` +* Fixed `composer.json` contents for LDAPTools plugin again +* Removed unused `Hooks` plugin class file + +## v7.11 + +* Added plugin to allow NFC PC/SC login (see `api/v1/class/plugins/plugins/nfclogin/README.md`) +* Added db migrations with phinx to update the database schema +* improved overall security with function node system +* API can now handle public endpoints + +## v7.10.2 + +* Added native function to `Benutzer` class to update user proprties which not lets the `userdetail` plugin actually update user properties +* Fixed bug + +## v7.10.1.1 + +* Hotfix preventing to add a worktime in normal mode + +## v7.10.1 + +* Added some CSS to certain elements which were missing it + +## v7.10 + +* Reflected changes from v7.9 release into Mails.md +* You now get redirected when calendar ID is not found +* Fixed being unable to access the "Forgot password" page +* Fixed typo in userdetail plugin preventing save +* Remove usercount plugin entirely +* Removed webedit for app.json +* A warning is now displayed when an admin changes user information within the userdetail plugin +* Fixed an bug causing userdetail plugin to crash when the selected user could not be found + ## v7.9 * Fixed being unable to access "userdetail" plugin diff --git a/README.md b/README.md index 5cb11cd..5fa5c7b 100644 --- a/README.md +++ b/README.md @@ -22,8 +22,8 @@ Additional functionality can be unlocked with TimeTrack Oval ### Requirements -- at least PHP 8.2 (`curl|gd|gmp|intl|mbstring|mysqli|openssl|pgsql|xsl|gettext|dom|ldap`) -- composer (to install dependencies; phpmailer: for sending emails via smtp, parsedown: markdown parser for the `CHANGELOG.md`, simple-router: does the API routing, yaml: for reading plugin yaml files, ldaptools: for LDAP authentication, dompdf: for PDF generation) +- at least PHP 8.2 (`curl|gd|gmp|intl|mbstring|mysqli|openssl|xsl|gettext|dom|ldap`) +- composer (to install dependencies; phpmailer: for sending emails via smtp, parsedown: markdown parser for the `CHANGELOG.md`, simple-router: does the API routing, yaml: for reading plugin yaml files, ldaptools: for LDAP authentication, dompdf: for PDF generation, phinx: for database migrations) - Apache2.4 with enabled rewrite mod (optional) This software has been tested on Debian 11/12, XAMPP, PHP internal server (e.g. `php -S 0.0.0.0:80`). @@ -36,11 +36,10 @@ Simply install the software by following these steps: - Git clone timetrack to e.g. `/var/www`: `cd /var/www && git clone https://github.com/Ente/timetrack.git && cd timetrack` - Install requirements for composer `composer install` - Create a new database, e.g. with the name `ab` and create a dedicated user, login (`mysql -u root -p`) then e.g. `timetool`: `CREATE DATABASE ab;` and `CREATE USER 'timetool'@'localhost' IDENTIFIED BY 'yourpassword';` and `GRANT ALL PRIVILEGES ON ab.* TO 'timetool'@'localhost';` don't forget to `FLUSH PRIVILEGES;`! -- Import the `setup/sql.sql` into your database, e.g. `mysql -u timetool -p ab < /full/path/to/sql.sql` -- To create your first user, run the `setup/usercreate.php` file, e.g. `php ./usercreate.php admin yourpassword email@admin.com` - `usercreate.php [USERNAME] [PASSWORD] [EMAIL]` -- Run the statement printed by the `usercreate.php` inside your database (`mysql -u root -p` and `use ab;` then the statement). - Configure `app.json` (see below - required changes: `base_url`, `db_user`, `db_password`, `smtp` section and any other if your installation is different) then `mv api/v1/inc/app.json.sample app.json && cd /var/www/timetrack` +- Run DB migrations: `vendor/bin/phinx migrate` - Start webserver e.g. `service apache2 stop && php -S 0.0.0.0:80` or using apache2 (then you have to configure the `sites-available` conf yourself) +- You can then access TimeTrack in your browser at `http://localhost`, default login is `admin` with password `admin`. Create yourself a new admin account, login and delete the default account afterwards. ### Configure app.json @@ -53,9 +52,11 @@ In step 2, you need to configure the `app.json.sample` within the `api/v1/inc` f - `auto_update`: (not yet implemented) - `db_*`: Set the connection details for your mysql instance - `app`: If set to true, users will be able to use the TimeTrack mobile application +- `timezone`: Set the timezone of your application, e.g. `Europe/Berlin` or `America/New_York` (default: `UTC`) #### **SMTP section** +- `smtp`: Set to `true` to enable SMTP functionality (default: false) - `host`: FQDN of your mail server - `username`: Username for the mailbox you want to send emails from - `password`: Self explaining @@ -83,10 +84,16 @@ LDAP authentication works with OpenLDAP and Active Directory. - `ldap_ip`: IP address of your LDAP server (e.g. `1.1.1.1`) - `ldap_domain`: The domain your LDAP server controls (e.g. `example.local`) - `ldap_basedn`: Base DN for your domain (e.g. `dc=example,dc=local`) -- `ldap_group`: Group membership required by LDAP users to be able to authenticate +- `ldap_group`: Group membership required by LDAP users to be able to authenticate (e.g. `Domain Users`, (new group) `TimeTrack Users`) - `saf`: Specify if you only have one LDAP server (true) or another one as fallback (false) - `saf_*`: If `saf` is set to `false`, please specify the corresponding values to the `saf_*` configuration -- `create_user`: If set to `true` it creates an user account automatically if the desired account is authenticated and within specified group. If set to `false` login simply fails, even if authenticated +- `create_user`: If set to `true` it creates an user account automatically if the desired account is authenticated and within specified group. If set to `false` login simply fails, even if authenticated. + +#### **Export** + +##### **PDF** + +- `css`: Full path to the CSS file used for the PDF export (default: `api/v1/class/exports/modules/PDFExportModule/css/index.css`) - **optional value** If done correctly, you should now be able to access the application via http://BASE_URL/ - redirects to http://BASE_URL/suite/ @@ -125,7 +132,6 @@ Already existing local accounts will get their authentication overwritten if an In order to create accounts automatically if `create_user` is `true` make sure to set the user's email address! Otherwise login fails. If above mentioned setting is set to `false` you have to create a user on your own locally and then let the user login with their LDAP credentials. The credentials you have entered will become usable if you disable LDAP or rename the account on your LDAP server. -Please run `run-patch.sh` within the `setup` folder to get LDAP working with php >8.0 ## Export @@ -182,9 +188,4 @@ If downloaded any other way, just make sure to copy and paste the new files into ### Database -You can update the database by downloading the `setup/upgrade.php` file into your local `setup` directory. -From here on just edit the `$missingUpdate` variable to the desired version as specified. - -Please be aware that you are not able to skip an database update. You have to update one by one, e.g. from 1 -> 2, 2 -> 3, ... - - +You can update the database by using `vendor/bin/phinx migrate` to migrate to latest release or `vendor/bin/phinx rollback` to rollback. \ No newline at end of file diff --git a/VERSION b/VERSION index c8357ab..4f294e2 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.9 \ No newline at end of file +7.12 \ No newline at end of file diff --git a/api/v1/class/arbeitszeit.inc.php b/api/v1/class/arbeitszeit.inc.php index e70b7f9..0f86ec5 100644 --- a/api/v1/class/arbeitszeit.inc.php +++ b/api/v1/class/arbeitszeit.inc.php @@ -12,6 +12,17 @@ use Arbeitszeit\Sickness; use Arbeitszeit\ExportModule; use Arbeitszeit\Mails; + use Arbeitszeit\Nodes; + use Arbeitszeit\Events\EventDispatcherService; + use Arbeitszeit\Events\EasymodeWorktimeAddedEvent; // "EasymodeWorktimeSTARTED" Event, actually. + use Arbeitszeit\Events\EasymodeWorktimeEndedEvent; + use Arbeitszeit\Events\EasymodeWorktimePauseEndEvent; + use Arbeitszeit\Events\EasymodeWorktimePauseStartEvent; + use Arbeitszeit\Events\WorktimeAddedEvent; + use Arbeitszeit\Events\WorktimeDeletedEvent; + use Arbeitszeit\Events\WorktimeMarkedForReviewEvent; + use Arbeitszeit\Events\WorktimeUnlockedFromReviewEvent; + use Arbeitszeit\Events\FixEasymodeWorktimeEvent; /** * Beinhaltet wesentliche Inhalte, wie Einstellungen, Arbeitszeiten erstellen, etc. * @@ -32,11 +43,20 @@ class Arbeitszeit private $vacation; private $exportModule; private $mails; + private $nodes; public function __construct() { $this->db = new DB(); $this->init_lang() ?? null; + + if(isset($this->get_app_ini()["general"]["timezone"])){ + try { + date_default_timezone_set($this->get_app_ini()["general"]["timezone"]); + } catch (\Exception $e) { + Exceptions::error_rep("Error setting timezone: " . $e->getMessage()); + } + } } public function __destruct() @@ -65,6 +85,9 @@ public function init_lang() */ public function delete_worktime($id) { + if(!$this->nodes()->checkNode("arbeitszeit.inc", "delete_worktime")){ + return false; + } $data = $this->db->sendQuery("DELETE FROM arbeitszeiten WHERE id = ?")->execute([$id]); if ($data == false) { Exceptions::error_rep("An error occured while deleting an worktime entry. See previous message for more information."); @@ -75,6 +98,7 @@ public function delete_worktime($id) ] ]; } else { + EventDispatcherService::get()->dispatch(new WorktimeDeletedEvent($_SESSION["username"], (int)$id), WorktimeDeletedEvent::NAME); Exceptions::error_rep("Worktime entry with ID '{$id}' deleted successfully."); return 1; } @@ -83,6 +107,10 @@ public function delete_worktime($id) public static function add_easymode_worktime($username) { + $nodes = new Nodes; + if(!$nodes->checkNode("arbeitszeit.inc", "add_easymode_worktime")) { + return false; + } Exceptions::error_rep("Creating easymode worktime entry for user '{$username}'..."); $date = date("Y-m-d"); $time = date("H:i"); @@ -101,6 +129,7 @@ public static function add_easymode_worktime($username) Exceptions::error_rep("An error occured while creating easymode worktime entry. See previous message for more information"); return false; } else { + EventDispatcherService::get()->dispatch(new EasymodeWorktimeAddedEvent($username), EasymodeWorktimeAddedEvent::NAME); Exceptions::error_rep("Easymode worktime entry created for user '{$username}'"); return true; } @@ -109,6 +138,10 @@ public static function add_easymode_worktime($username) public static function end_easymode_worktime($username, $id) { + $nodes = new Nodes; + if(!$nodes->checkNode("arbeitszeit.inc", "end_easymode_worktime")) { + return false; + } Exceptions::error_rep("Ending easymode worktime for user '{$username}'..."); $time = date("H:i"); $conn = new DB; @@ -125,6 +158,7 @@ public static function end_easymode_worktime($username, $id) Exceptions::error_rep("An error occured while ending easymode worktime. See previous message for more information."); return false; } else { + EventDispatcherService::get()->dispatch(new EasymodeWorktimeEndedEvent($username, (int)$id), EasymodeWorktimeEndedEvent::NAME); Exceptions::error_rep("Easymode worktime ended for user '{$username}'"); return true; } @@ -133,6 +167,9 @@ public static function end_easymode_worktime($username, $id) public function start_easymode_pause_worktime($username, $id) { + if(!$this->nodes()->checkNode("arbeitszeit.inc", "start_easymode_pause_worktime")) { + return false; + } Exceptions::error_rep("Starting easymode pause for user '{$username}'..."); $time = date("H:i"); $user = new Benutzer; @@ -148,6 +185,7 @@ public function start_easymode_pause_worktime($username, $id) Exceptions::error_rep("An error occured while starting user pause for worktime with ID '{$id}' for user '{$username}'. See previous message for more information."); return false; } else { + EventDispatcherService::get()->dispatch(new EasymodeWorktimePauseStartEvent($username, (int)$id), EasymodeWorktimePauseStartEvent::NAME); Exceptions::error_rep("Easymode pause started for user '{$username}'"); return true; } @@ -155,6 +193,9 @@ public function start_easymode_pause_worktime($username, $id) } public function end_easymode_pause_worktime($username, $id) { + if(!$this->nodes()->checkNode("arbeitszeit.inc", "end_easymode_pause_worktime")) { + return false; + } Exceptions::error_rep("Ending easymode pause for user '{$username}'..."); $time = date("H:i"); $user = new Benutzer; @@ -170,6 +211,7 @@ public function end_easymode_pause_worktime($username, $id) Exceptions::error_rep("An error occured while ending user pause for worktime with ID '{$id}' for user '{$username}'. See previous message for more information."); return false; } else { + EventDispatcherService::get()->dispatch(new EasymodeWorktimePauseEndEvent($username, (int)$id), EasymodeWorktimePauseEndEvent::NAME); Exceptions::error_rep("Easymode pause ended for user '{$username}'"); return true; } @@ -178,6 +220,9 @@ public function end_easymode_pause_worktime($username, $id) public function toggle_easymode($username) { + if(!$this->nodes()->checkNode("arbeitszeit.inc", "toggle_easymode")) { + return false; + } Exceptions::error_rep("Toggling easymode for user '{$username}'..."); $sql = "SELECT * FROM `users` WHERE username = ?;"; $res = $this->db->sendQuery($sql); @@ -210,6 +255,9 @@ public function toggle_easymode($username) public function get_easymode_status($username, $mode = 0) { + if(!$this->nodes()->checkNode("arbeitszeit.inc", "get_easymode_status")) { + return false; + } Exceptions::error_rep("Getting easymode status for user '{$username}'..."); $sql = "SELECT * FROM `users` WHERE username = ?;"; $res = $this->db->sendQuery($sql); @@ -246,6 +294,10 @@ public function get_easymode_status($username, $mode = 0) */ public static function check_easymode_worktime_finished($username) { + $nodes = new Nodes; + if(!$nodes->checkNode("arbeitszeit.inc", "check_easymode_worktime_finished")) { + return false; + } Exceptions::error_rep("Checking easymode worktime for user '{$username}'..."); $db = new DB; $sql = "SELECT * FROM `arbeitszeiten` WHERE active = '1' AND username = ?;"; @@ -280,6 +332,9 @@ public static function check_easymode_worktime_finished($username) */ public function add_worktime($start, $end, $location, $date, $username, $type, $pause = null, $meta = null) { + if(!$this->nodes()->checkNode("arbeitszeit.inc", "add_worktime")) { + return false; + } $user = new Benutzer; $usr = $user->get_user($username); if ($date > date("y-m-d") /*|| $date < date("y-m-d")*/) { @@ -298,6 +353,7 @@ public function add_worktime($start, $end, $location, $date, $username, $type, $ Exceptions::error_rep("An error occured while creating an worktime entry. See previous message for more information."); return false; } else { + EventDispatcherService::get()->dispatch(new WorktimeAddedEvent($username, ["start" => $start, "end" => $end]), WorktimeAddedEvent::NAME); Exceptions::error_rep("Worktime entry for user '{$username}' created successfully."); return true; } @@ -311,8 +367,8 @@ public function add_worktime($start, $end, $location, $date, $username, $type, $ */ public static function get_app_ini() { - $ini_path = $_SERVER["DOCUMENT_ROOT"] . "/api/v1/inc/app.ini"; - $json_path = $_SERVER["DOCUMENT_ROOT"] . "/api/v1/inc/app.json"; + $ini_path = dirname(__DIR__, 3) . "/api/v1/inc/app.ini"; + $json_path = dirname(__DIR__, 3) . "/api/v1/inc/app.json"; Exceptions::error_rep("Loading application configuration..."); @@ -356,6 +412,9 @@ private static function sanitizeOutput($data) public function get_all_worktime() { + if(!$this->nodes()->checkNode("arbeitszeit.inc", "get_all_worktime")) { + return false; + } Exceptions::error_rep("Getting all worktimes..."); $sql = "SELECT * FROM `arbeitszeiten`;"; $res = $this->db->sendQuery($sql); @@ -373,6 +432,9 @@ public function get_all_worktime() public function get_all_user_worktime($username) { + if(!$this->nodes()->checkNode("arbeitszeit.inc", "get_all_user_worktime")) { + return false; + } Exceptions::error_rep("Getting all worktimes for user '{$username}'..."); $sql = "SELECT * FROM `arbeitszeiten` WHERE username = ?;"; $res = $this->db->sendQuery($sql); @@ -390,6 +452,9 @@ public function get_all_user_worktime($username) public function get_specific_worktime_html(int $month, int $year) { + if(!$this->nodes()->checkNode("arbeitszeit.inc", "get_specific_worktime_html")) { + return false; + } Exceptions::error_rep("Getting worktimes rendered for month '{$month}' and year '{$year}'..."); $base_url = $ini = Arbeitszeit::get_app_ini()["general"]["base_url"]; $sql = "SELECT * FROM `arbeitszeiten` WHERE YEAR(schicht_tag) = ? AND MONTH(schicht_tag) = ? ORDER BY schicht_tag DESC;"; @@ -460,6 +525,9 @@ public function get_specific_worktime_html(int $month, int $year) } public function get_employee_worktime_html($username) { + if(!$this->nodes()->checkNode("arbeitszeit.inc", "get_employee_worktime_html")) { + return false; + } Exceptions::error_rep("Getting worktimes rendered for user '{$username}'..."); $sql = "SELECT * FROM `arbeitszeiten` WHERE username = ? ORDER BY id DESC;"; $res = $this->db->sendQuery($sql); @@ -519,6 +587,9 @@ public function get_employee_worktime_html($username) public function mark_for_review($id) { + if(!$this->nodes()->checkNode("arbeitszeit.inc", "mark_for_review")) { + return false; + } Exceptions::error_rep("Marking worktime with ID '{$id}' for review..."); $sql = "UPDATE `arbeitszeiten` SET `review` = '1' WHERE `id` = ?;"; $res = $this->db->sendQuery($sql)->execute([$id]); @@ -526,12 +597,16 @@ public function mark_for_review($id) Exceptions::error_rep("An error occured while marking an worktime as under review, id '{$id}'. See previous message for more information."); return false; } else { + EventDispatcherService::get()->dispatch(new WorktimeMarkedForReviewEvent($_SESSION["username"], (int)$id), WorktimeMarkedForReviewEvent::NAME); return true; } } public function unlock_for_review($id) { + if(!$this->nodes()->checkNode("arbeitszeit.inc", "unlock_for_review")) { + return false; + } Exceptions::error_rep("Unlocking worktime from review with ID '{$id}'..."); $sql = "UPDATE `arbeitszeiten` SET `review` = '0' WHERE `id` = ?;"; $res = $this->db->sendQuery($sql)->execute([$id]); @@ -539,6 +614,7 @@ public function unlock_for_review($id) Exceptions::error_rep("An error occured while unlocking an worktime from review, id '{$id}'. See previous message for more information."); return false; } else { + EventDispatcherService::get()->dispatch(new WorktimeUnlockedFromReviewEvent($_SESSION["username"], (int)$id), WorktimeUnlockedFromReviewEvent::NAME); return true; } } @@ -628,6 +704,9 @@ public static function check_status_code($url) if (strpos($url, "info=error")) { return "

{$loc["error"]}

"; } + if (strpos($url, "info=notification_not_found")) { + return "

{$loc["notification_not_found"]}

"; + } } @@ -652,54 +731,6 @@ public function calculate_hours_specific_time($username, $month, $year) } } - /** - * change_settings() - Allows you to change specific settings in the app.ini - * - * @param array $array Contains the array with changing values - * @return bool Returns true o success and false otherwise - */ - public static function change_settings($array) - { - $ini = self::get_app_ini(); - foreach ($array as $key => $value) { - unset($ini["general"][(string) $key]); - $ini["general"][(string) $key] = $value; - - } - Exceptions::error_rep("Changing settings..."); - $file = fopen($_SERVER["DOCUMENT_ROOT"] . "/api/v1/inc/app.ini", "w"); - $cini = self::arr2ini($ini); - if (fwrite($file, $cini)) { - fclose($file); - return true; - } else { - Exceptions::error_rep("An error occured while chaning settings"); - fclose($file); - return false; - } - } - - private static function arr2ini(array $a, array $parent = array()) - { - Exceptions::error_rep("Writing to app.ini..."); - $out = ''; - foreach ($a as $k => $v) { - if (is_array($v)) { - //subsection case - //merge all the sections into one array... - $sec = array_merge((array) $parent, (array) $k); - //add section information to the output - $out .= '[' . join('.', $sec) . ']' . PHP_EOL; - //recursively traverse deeper - $out .= self::arr2ini($v, $sec); - } else { - //plain key->value case - $out .= "$k=\"$v\"" . PHP_EOL; - } - } - return $out; - } - public static function get_worktime_by_id($id) { $conn = new DB; @@ -744,11 +775,30 @@ public static function fix_easymode_worktime($username) $activateStatement = $db->sendQuery($activateQuery); $activateStatement->bindValue(':latestId', $latestId); $activateStatement->execute(); + EventDispatcherService::get()->dispatch(new FixEasymodeWorktimeEvent($username), FixEasymodeWorktimeEvent::NAME); Exceptions::error_rep("[ARBEITSZEIT] Finished fixing attempt for user '{$username}'"); } } + public function blockIfNotAdmin(){ + if(!Benutzer::is_admin(Benutzer::get_user($_SESSION["username"]))){ + header("Location: /"); + } + return false; + } + + public function global_dispatcher(): \Symfony\Component\EventDispatcher\EventDispatcher { + return \Arbeitszeit\Events\EventDispatcherService::get(); + } + + public function getTimeTrackVersion(){ + return file_get_contents(dirname(__DIR__, 3) . "/VERSION"); + } + + public function getToilVersion(){ + return file_get_contents(dirname(__DIR__, 1) . "/toil/VERSION"); + } public function notifications(): Notifications { @@ -826,6 +876,13 @@ public function mails(): Mails $this->mails = new Mails; return $this->mails; } + + public function nodes(): Nodes + { + if (!$this->nodes) + $this->nodes = new Nodes; + return $this->nodes; + } } } diff --git a/api/v1/class/auth/auth.arbeit.inc.php b/api/v1/class/auth/auth.arbeit.inc.php index 7686eb9..e7163c4 100644 --- a/api/v1/class/auth/auth.arbeit.inc.php +++ b/api/v1/class/auth/auth.arbeit.inc.php @@ -4,6 +4,13 @@ use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\SMTP; use PHPMailer\PHPMailer\Exception; + use Arbeitszeit\Events\EventDispatcherService; + use Symfony\Component\EventDispatcher\EventDispatcher; + use Arbeitszeit\Events\LoggedInUserEvent; + use Arbeitszeit\Events\LoggedOutUserEvent; + use Arbeitszeit\Events\ValidatedLoginEvent; + use LdapTools\Event\Event; + class Auth extends Arbeitszeit{ public $db; @@ -13,14 +20,17 @@ public function __construct(){ } public static function login($username, $password, $option){ # "option"-> array [ "remember" => true/false, ... ] + Exceptions::error_rep("Logging in user '$username'..."); $db = new DB; session_start(); $ini = Arbeitszeit::get_app_ini(); $base_url = $ini["general"]["base_url"]; + $username = preg_replace("/\s+/", "", $username); - if($ini["ldap"]["ldap"] == "true" && !isset($option["LOCAL"])){ + if($ini["ldap"]["ldap"] == "true" && !isset($option["LOCAL"]) && !isset($option["nfclogin"])){ if(LDAP::authenticate($username, $password) != true){ + EventDispatcherService::get()->dispatch(new LoggedInUserEvent($username, "failed")); Exceptions::error_rep("Login failed for username '$username' - Could not authenticate user via LDAP. See errors from before to find issue."); die(header("Location: http://{$ini["general"]["base_url"]}/suite/login.php?error=ldapauth")); } else { @@ -28,8 +38,8 @@ public static function login($username, $password, $option){ # "option"-> array $ldap = true; } } - $username = preg_replace("/\s+/", "", $username); if(!isset($username, $password)){ + EventDispatcherService::get()->dispatch(new LoggedInUserEvent($username ?? "N/A", "failed")); Exceptions::error_rep("Login failed for username '$username' - no data supplied. Redirecting..."); die(header("Location: http://{$ini["general"]["base_url"]}/suite/login.php?error=nodata")); } else { @@ -38,12 +48,14 @@ public static function login($username, $password, $option){ # "option"-> array $res->execute([$username]); if($res == false){ + EventDispatcherService::get()->dispatch(new LoggedInUserEvent($username, "failed")); Exceptions::error_rep("Login failed for username '$username' - Database connection error. Redirecting..."); die(header("Location: http://{$ini["general"]["base_url"]}/suite/login.php?error=nodata")); } $count = $res->rowCount(); if($count != 1){ + EventDispatcherService::get()->dispatch(new LoggedInUserEvent($username, "failed")); Exceptions::error_rep("Login failed for username '$username' - User not found. Redirecting..."); die(header("Location: http://{$ini["general"]["base_url"]}/suite/login.php?error=nodata")); } @@ -61,6 +73,7 @@ public static function login($username, $password, $option){ # "option"-> array if(@isset($option["remember"])){ if($ini["general"]["app"] == "true"){ + EventDispatcherService::get()->dispatch(new LoggedInUserEvent($username, "success")); Exceptions::error_rep("Successfully authenticated user '" . $username . "' - LDAP Auth"); @ini_set("session.cookie_samesite", "None"); @session_set_cookie_params(["path" => "/", "domain" => $ini["general"]["base_url"], "secure" => true, "samesite" => "None"]); @@ -68,20 +81,29 @@ public static function login($username, $password, $option){ # "option"-> array setcookie("username", $username, ["samesite" => "None", "secure" => true, "domain" => $ini["general"]["base_url"], "expires" => $ts + (60*60*24*30), "path" => "/"]); header("Refresh: 1; url=http://{$ini["general"]["base_url"]}/suite"); } else { + EventDispatcherService::get()->dispatch(new LoggedInUserEvent($username, "success")); Exceptions::error_rep("Successfully authenticated user '" . $username . "' - LDAP Auth"); setcookie("erinnern", "true", $ts+(60*60*24*30), "/"); setcookie("username", $username, $ts + (60*60*24*30), "/"); header("Refresh: 1; url=http://{$ini["general"]["base_url"]}/suite"); } } else { + EventDispatcherService::get()->dispatch(new LoggedInUserEvent($username, "success")); Exceptions::error_rep("Successfully authenticated user '" . $username . "' - LDAP Auth"); header("Refresh: 1; url=http://{$ini["general"]["base_url"]}/suite"); } die(); } - + if(isset($option["nfclogin"])){ + goto nfclogin; + } if(password_verify($password, $data["password"])){ + if($option["nfclogin"]){ + nfclogin: + Exceptions::error_rep("Authenticated user via NFC login '" . $username . "'"); + } Exceptions::error_rep("Successfully authenticated user '" . $username . "'"); + $ini = Arbeitszeit::get_app_ini(); $ts = time(); $_SESSION["logged_in"] = true; $_SESSION["username"] = $username; @@ -91,6 +113,7 @@ public static function login($username, $password, $option){ # "option"-> array if(@isset($option["remember"])){ if($ini["general"]["app"] == "true"){ + EventDispatcherService::get()->dispatch(new LoggedInUserEvent($username, "success")); Exceptions::error_rep("Successfully authenticated user '" . $username . "'"); @ini_set("session.cookie_samesite", "None"); @session_set_cookie_params(["path" => "/", "domain" => $ini["general"]["base_url"], "secure" => true, "samesite" => "None"]); @@ -98,16 +121,19 @@ public static function login($username, $password, $option){ # "option"-> array setcookie("username", $username, ["samesite" => "None", "secure" => true, "domain" => $ini["general"]["base_url"], "expires" => $ts + (60*60*24*30), "path" => "/"]); header("Refresh: 1; url=http://{$ini["general"]["base_url"]}/suite"); } else { + EventDispatcherService::get()->dispatch(new LoggedInUserEvent($username, "success")); Exceptions::error_rep("Successfully authenticated user '" . $username . "'"); setcookie("erinnern", "true", $ts+(60*60*24*30), "/"); setcookie("username", $username, $ts + (60*60*24*30), "/"); header("Refresh: 1; url=http://{$ini["general"]["base_url"]}/suite"); } } else { + EventDispatcherService::get()->dispatch(new LoggedInUserEvent($username, "success")); Exceptions::error_rep("Successfully authenticated user '" . $username . "'"); header("Refresh: 1; url=http://{$ini["general"]["base_url"]}/suite"); } } else { + EventDispatcherService::get()->dispatch(new LoggedInUserEvent($username, "failed")); Exceptions::error_rep("Login failed for username '$username' - Incorrect credentials. Redirecting..."); die(header("Location: http://{$base_url}/suite/login.php?error=wrongdata")); } @@ -125,26 +151,28 @@ public function login_validation(){ } @session_start(); if(isset($_SESSION["logged_in"]) == false){ + EventDispatcherService::get()->dispatch(new ValidatedLoginEvent($_SESSION["username"] ?? "N/A", "failed")); Exceptions::error_rep("User not logged in. Redirecting..."); header("Location: http://{$baseurl}/suite/login.php?error=notloggedin"); } if($this->get_state($_SESSION["username"]) != $_COOKIE["state"]){ + EventDispatcherService::get()->dispatch(new ValidatedLoginEvent($_SESSION["username"] ?? "N/A", "failed")); $this->remove_state($_SESSION["username"]); Exceptions::error_rep("State mismatch on user {$_SESSION["username"]}. Removing state and redirecting..."); header("Location: http://{$baseurl}/suite/login.php?error=statemismatch"); - }# temp removed this as it causes errors + } } /** - * logout() - Loggt den Nutzer aus seiner aktuellen Sitzung aus + * logout() - Logs out user * - * @return bool Gibt true bei Erfolg zurück + * @return bool Returns true on success */ public function logout(){ + EventDispatcherService::get()->dispatch(new LoggedOutUserEvent($_SESSION["username"])); session_start(); session_unset(); session_destroy(); - return true; } @@ -248,7 +276,6 @@ public function mail_init($user, $html = false){ return false; } - return $mail; } diff --git a/api/v1/class/auth/plugins/ldap/ldap.auth.arbeit.inc.php b/api/v1/class/auth/plugins/ldap/ldap.auth.arbeit.inc.php index 31601de..f32e323 100644 --- a/api/v1/class/auth/plugins/ldap/ldap.auth.arbeit.inc.php +++ b/api/v1/class/auth/plugins/ldap/ldap.auth.arbeit.inc.php @@ -4,6 +4,8 @@ use LdapTools\LdapManager; use LdapTools\DomainConfiguration; use LdapTools\Operation\AuthenticationOperation; + use Arbeitszeit\Events\LDAPAuthenticationEvent; + use Arbeitszeit\Events\EventDispatcherService; class LDAP extends Auth { public static function get_bind(){ @@ -45,9 +47,10 @@ public static function authenticate($username, $password){ Exceptions::error_rep("Authenticating user '{$username}' through LDAP..."); $operation = (new AuthenticationOperation())->setUsername($username)->setPassword($password); $response = self::get_bind()->getConnection()->execute($operation); - + $code1 = null; if(!$response->isAuthenticated()){ $code = $response->getErrorCode(); + $code = $code1; switch($code){ case "1317": Exceptions::error_rep("Could not authenticate user '{$username}': Account does not exist."); @@ -126,6 +129,7 @@ public static function authenticate($username, $password){ $_SESSION["provider"] = "LDAP"; $_SESSION["provider"] .= "." . $user->get("upn") . "+" . $user->get("sid"); Exceptions::error_rep("Successfully authenticated user '{$username}' through LDAP."); + EventDispatcherService::get()->dispatch(new LDAPAuthenticationEvent($username, $user->get("mail"), "LDAP" . $code1), LDAPAuthenticationEvent::NAME); return true; } } diff --git a/api/v1/class/benutzer/benutzer.arbeit.inc.php b/api/v1/class/benutzer/benutzer.arbeit.inc.php index 9e02b93..aba0f34 100644 --- a/api/v1/class/benutzer/benutzer.arbeit.inc.php +++ b/api/v1/class/benutzer/benutzer.arbeit.inc.php @@ -1,11 +1,17 @@ db = $this->db(); $this->i18n = $this->i18n()->loadLanguage(null, "class/benutzer"); } @@ -17,26 +23,28 @@ public function __construct(){ * @param string $name First name of the employee * @return array|bool Returns true on success and an array otherwise */ - public function create_user($username, $name, $email, $password, $isAdmin = 0){ - #$originalFunction = function($username, $name, $email, $password, $isAdmin){ - Exceptions::error_rep("Creating user '$username'..."); - $password = password_hash($password, PASSWORD_DEFAULT); - $sql = "INSERT INTO `users` (`name`, `username`, `email`, `password`, `email_confirmed`, `isAdmin`) VALUES (?, ?, ?, ?, '1', ?);"; - $data = $this->db->sendQuery($sql)->execute([$name, $username, $email, $password, $isAdmin]); - if($data == false){ - Exceptions::error_rep("An error occured while creating a user. See previous message for more information"); - return [ - "error" => [ - "error_code" => 3, - "error_message" => "Error while creating a user!" - ] - ]; - } else { - Exceptions::error_rep("User '$username' created successfully."); - return true; - } - #}; - #return Hooks::executeWithHooks('create_user', $originalFunction, $username, $name, $email, $password, $isAdmin); + public function create_user($username, $name, $email, $password, $isAdmin = 0) + { + if($this->nodes()->checkNode("benutzer.inc", "create_user") == false){ + return false; + } + Exceptions::error_rep("Creating user '$username'..."); + $password = password_hash($password, PASSWORD_DEFAULT); + $sql = "INSERT INTO `users` (`name`, `username`, `email`, `password`, `email_confirmed`, `isAdmin`) VALUES (?, ?, ?, ?, '1', ?);"; + $data = $this->db->sendQuery($sql)->execute([$name, $username, $email, $password, $isAdmin]); + if ($data == false) { + Exceptions::error_rep("An error occured while creating a user. See previous message for more information"); + return [ + "error" => [ + "error_code" => 3, + "error_message" => "Error while creating a user!" + ] + ]; + } else { + Exceptions::error_rep("User '$username' created successfully."); + EventDispatcherService::get()->dispatch(new UserCreatedEvent($username, $email, $isAdmin), UserCreatedEvent::NAME); + return true; + } } /** @@ -47,11 +55,18 @@ public function create_user($username, $name, $email, $password, $isAdmin = 0){ * * @note This function only deletes the user but not their other data. */ - public function delete_user($id){ + public function delete_user($id) + { + if($this->nodes()->checkNode("benutzer.inc", "delete_user") == false){ + return false; + } + $user = $this->get_user_from_id($id); + $username = $user["username"]; + $email = $user["email"]; Exceptions::error_rep("Deleting user with id '$id'..."); $sql = "DELETE FROM `users` WHERE id = ?;"; $data = $this->db->sendQuery($sql)->execute([$id]); - if($data == false){ + if ($data == false) { Exceptions::error_rep("An error occured while deleting an user. See previous message for more information"); return [ "error" => [ @@ -61,6 +76,7 @@ public function delete_user($id){ ]; } else { Exceptions::error_rep("User with id '$id' deleted successfully."); + EventDispatcherService::get()->dispatch(new UserDeletedEvent($username, $email), UserDeletedEvent::NAME); return true; } } @@ -70,14 +86,15 @@ public function delete_user($id){ * @param string $username * @return array|bool Returns false on failure and an array otherwise */ - public static function get_user($username){ + public static function get_user($username) + { Exceptions::error_rep("Getting user '$username'..."); $sql = "SELECT * FROM `users` WHERE username = ?;"; $db = new DB; $res = $db->sendQuery($sql); $res->execute([$username]); $count = $res->rowCount(); - if($count == 1){ + if ($count == 1) { $data = $res->fetch(\PDO::FETCH_ASSOC); Exceptions::error_rep("User '$username' found."); return $data; @@ -92,13 +109,14 @@ public static function get_user($username){ * @param int $id * @return array|bool Returns false on failure and an array otherwise */ - public static function get_user_from_id($id){ + public static function get_user_from_id($id) + { Exceptions::error_rep("Getting user with id '$id'..."); $conn = new DB(); $sql = "SELECT * FROM `users` WHERE id = ?;"; $res = $conn->sendQuery($sql); $res->execute([$id]); - if($res->rowCount() == 1){ + if ($res->rowCount() == 1) { $data = $res->fetch(\PDO::FETCH_ASSOC); Exceptions::error_rep("User with id '$id' found."); return $data; @@ -113,13 +131,14 @@ public static function get_user_from_id($id){ * @param string $email * @return array|bool Returns false on failure and an array otherwise */ - public static function get_user_from_email($email){ + public static function get_user_from_email($email) + { Exceptions::error_rep("Getting user with email '$email'..."); $conn = new DB(); $sql = "SELECT * FROM `users` WHERE email = ?;"; $res = $conn->sendQuery($sql); $res->execute([$email]); - if($res->rowCount() == 1){ + if ($res->rowCount() == 1) { $data = $res->fetch(\PDO::FETCH_ASSOC); Exceptions::error_rep("User with email '$email' found."); return $data; @@ -134,13 +153,14 @@ public static function get_user_from_email($email){ * @param string $email * @return string|bool Returns false on failure and a string otherwise */ - public function get_username_from_email($email){ + public function get_username_from_email($email) + { Exceptions::error_rep("Getting username with email '$email'..."); $conn = new DB(); $sql = "SELECT username FROM `users` WHERE email = ?;"; $res = $conn->sendQuery($sql); $res->execute([$email]); - if($res->rowCount() == 1){ + if ($res->rowCount() == 1) { $data = $res->fetch(\PDO::FETCH_ASSOC); Exceptions::error_rep("Username with email '$email' found."); return $data["username"]; @@ -154,15 +174,16 @@ public function get_username_from_email($email){ * get_all_users() - Gets all users from the database * @return array|bool Returns false on failure and an array otherwise */ - public function get_all_users(){ + public function get_all_users() + { Exceptions::error_rep("Getting all users..."); $sql = "SELECT * FROM `users`;"; $res = $this->db->sendQuery($sql); $res->execute(); $count = $res->rowCount(); $dat = []; - if($count >= 1){ - while($data = $res->fetch(\PDO::FETCH_ASSOC)){ + if ($count >= 1) { + while ($data = $res->fetch(\PDO::FETCH_ASSOC)) { $dat[$data["id"]] = $data; } Exceptions::error_rep("Users found and returing data."); @@ -177,7 +198,11 @@ public function get_all_users(){ * get_all_users_html() - Gets all users from the database * @return string Returns a string (rendered HTML) on success */ - public function get_all_users_html(){ + public function get_all_users_html() + { + if($this->nodes()->checkNode("benutzer.inc", "get_all_users_html") == false){ + return false; + } Exceptions::error_rep("Getting all users..."); $base_url = $ini = $this->get_app_ini()["general"]["base_url"]; $sql = "SELECT * FROM `users`;"; @@ -185,12 +210,12 @@ public function get_all_users_html(){ $res->execute(); $count = $res->rowCount(); - if($count == 0){ + if ($count == 0) { Exceptions::failure("ERR-NO-USERS", "No users found?", "N/A"); return "

{$this->i18n["no_users"]}

"; } - while($data = $res->fetch(\PDO::FETCH_ASSOC)){ - if($data["username"] == "api"){ + while ($data = $res->fetch(\PDO::FETCH_ASSOC)) { + if ($data["username"] == "api") { $data["email"] = "System user"; } @@ -199,7 +224,7 @@ public function get_all_users_html(){ $username = $data["username"]; $id = $data["id"]; - $html = <<< DOC + $html = << {$this->i18n["delete_user"]} | {$this->i18n["edit_user"]} $name @@ -220,21 +245,25 @@ public function get_all_users_html(){ * @param string $username * @return string Returns a string (rendered HTML) on success */ - public function get_user_html($username){ + public function get_user_html($username) + { + if($this->nodes()->checkNode("benutzer.inc", "get_user_html") == false){ + return false; + } Exceptions::error_rep("Getting user '$username'..."); $base_url = $ini = $this->get_app_ini()["general"]["base_url"]; $data = $this->get_user($username); - if($data == false){ + if ($data == false) { Exceptions::error_rep("An error occured while generating user html. User '$username' is either not logged in or the session expired."); return "

{$this->i18n["unknown_error"]}

"; } - while($data){ + while ($data) { $reww = $data["name"]; $rol = $data["id"]; $ral = $data["email"]; $rww = $data["username"]; - $html = <<< DATA + $html = <<

{$this->i18n["name"]}: {$reww}

@@ -259,10 +288,59 @@ public function get_user_html($username){ * @param array $user * @return bool Returns true if the user is an admin and false otherwise */ - public function is_admin($user){ - if($user["isAdmin"] == true){ + public static function is_admin($user) + { + if ($user["isAdmin"] == true) { + return true; + } else { + return false; + } + } + + public function editUserProperties(mixed $username_or_id, string $name, mixed $value): bool + { + if($this->nodes()->checkNode("benutzer.inc", "editUserProperties") == false){ + return false; + } + if ( + !isset($_SESSION["username"]) || + !$this->is_admin($this->get_user($_SESSION["username"])) + ) { + Exceptions::error_rep("Unauthorized attempt by user '{$_SESSION["username"]}' to update user properties!"); + return false; + } + + $allowed_types = ["username", "email", "isAdmin", "name"]; + if (!in_array($name, $allowed_types)) { + Exceptions::error_rep("Could not update user entry – invalid property '{$name}'"); + return false; + } + + if (is_int($username_or_id)) { + $search_type = "id"; + } elseif (is_string($username_or_id)) { + $search_type = "username"; + } else { + Exceptions::error_rep("Invalid identifier type for user update."); + return false; + } + + Exceptions::error_rep("Updating user entry '{$username_or_id}' (Type: {$search_type}); Property: {$name}; Value: {$value}"); + + $sql = "UPDATE `users` SET `$name` = ? WHERE `$search_type` = ?"; + try { + $stmt = $this->db->sendQuery($sql); + $success = $stmt->execute([$value, $username_or_id]); + } catch (\Exception $e) { + Exceptions::error_rep("Database exception while updating user: " . $e->getMessage()); + return false; + } + + if ($success) { + Exceptions::error_rep("Successfully updated user entry '{$username_or_id}' (Type: {$search_type})"); return true; } else { + Exceptions::error_rep("Update failed for user '{$username_or_id}' (Type: {$search_type})"); return false; } } diff --git a/api/v1/class/events/EventDispatcherService.events.arbeit.inc.php b/api/v1/class/events/EventDispatcherService.events.arbeit.inc.php new file mode 100644 index 0000000..0a3c3f4 --- /dev/null +++ b/api/v1/class/events/EventDispatcherService.events.arbeit.inc.php @@ -0,0 +1,15 @@ +addListener(UserCreatedEvent::class, function (UserCreatedEvent $event){ + // do something + $user = $event->getUsername(); +}); +``` + +## Register own event + +Create event class: + +```php + +namespace Yourplugin\plugin; +use Symfony\Contracts\EventDispatcher\Event; + +class YourEvent extends Event +{ + public const NAME = 'yourplugin.your_event'; + + private $data; + + public function __construct($data) + { + $this->data = $data; + } + + public function getData() + { + return $this->data; + } +} + +``` + +Inside your Main.php / plugin main file: + +```php +// require_once "path/to/event.php"; +// use Yourplugin\plugin\YourEvent; + + +... onLoad(): void { // which is called when the plugin is loaded, place below line somewhere else if you want to trigger it later + EventDispatcherService::get()->dispatch(new YourEvent($data), YourEvent::NAME); +} + +``` diff --git a/api/v1/class/events/auth/LDAPAuthenticationEvent.php b/api/v1/class/events/auth/LDAPAuthenticationEvent.php new file mode 100644 index 0000000..ffaed6d --- /dev/null +++ b/api/v1/class/events/auth/LDAPAuthenticationEvent.php @@ -0,0 +1,21 @@ +username = $username; + } + + public function getUsername(): string + { + return $this->username; + } +} diff --git a/api/v1/class/events/auth/LoggedInUserEvent.php b/api/v1/class/events/auth/LoggedInUserEvent.php new file mode 100644 index 0000000..09a175a --- /dev/null +++ b/api/v1/class/events/auth/LoggedInUserEvent.php @@ -0,0 +1,33 @@ +username = $username; + $this->type = $type; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getType(): string + { + return $this->type; + } +} diff --git a/api/v1/class/events/auth/LoggedOutUserEvent.php b/api/v1/class/events/auth/LoggedOutUserEvent.php new file mode 100644 index 0000000..37abff1 --- /dev/null +++ b/api/v1/class/events/auth/LoggedOutUserEvent.php @@ -0,0 +1,21 @@ +username = $username; + } + + public function getUsername(): string + { + return $this->username; + } +} diff --git a/api/v1/class/events/auth/ValidatedLoginEvent.php b/api/v1/class/events/auth/ValidatedLoginEvent.php new file mode 100644 index 0000000..0ea01b1 --- /dev/null +++ b/api/v1/class/events/auth/ValidatedLoginEvent.php @@ -0,0 +1,28 @@ +username = $username; + $this->type = $type; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getType(): string + { + return $this->type; + } +} diff --git a/api/v1/class/events/exports/generatedExportEvent.php b/api/v1/class/events/exports/generatedExportEvent.php new file mode 100644 index 0000000..1f7e4c2 --- /dev/null +++ b/api/v1/class/events/exports/generatedExportEvent.php @@ -0,0 +1,29 @@ +username = $username; + $this->module = $module; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getModule(): string + { + return $this->module; + } +} diff --git a/api/v1/class/events/loader.events.arbeit.inc.php b/api/v1/class/events/loader.events.arbeit.inc.php new file mode 100644 index 0000000..aa3aa3d --- /dev/null +++ b/api/v1/class/events/loader.events.arbeit.inc.php @@ -0,0 +1,39 @@ + \ No newline at end of file diff --git a/api/v1/class/events/mails/SentMailEvent.php b/api/v1/class/events/mails/SentMailEvent.php new file mode 100644 index 0000000..5a05299 --- /dev/null +++ b/api/v1/class/events/mails/SentMailEvent.php @@ -0,0 +1,36 @@ +username = $username; + $this->email = $email; + $this->type = $type; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getEmail(): string + { + return $this->email; + } + + public function getType(): string + { + return $this->type; + } +} diff --git a/api/v1/class/events/notifications/CreatedNotificationEvent.php b/api/v1/class/events/notifications/CreatedNotificationEvent.php new file mode 100644 index 0000000..5f71aec --- /dev/null +++ b/api/v1/class/events/notifications/CreatedNotificationEvent.php @@ -0,0 +1,42 @@ +username = $username; + $this->date = $date; + $this->id = $id; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getTitle(): int + { + return $this->title; + } + + public function getDate(): string + { + return $this->date; + } + + public function getId(): int + { + return $this->id; + } +} diff --git a/api/v1/class/events/notifications/DeletedNotificationEvent.php b/api/v1/class/events/notifications/DeletedNotificationEvent.php new file mode 100644 index 0000000..a8d142a --- /dev/null +++ b/api/v1/class/events/notifications/DeletedNotificationEvent.php @@ -0,0 +1,28 @@ +username = $username; + $this->id = $id; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getId(): int + { + return $this->id; + } +} diff --git a/api/v1/class/events/notifications/DeletedObsoleteNotificationsEvent.php b/api/v1/class/events/notifications/DeletedObsoleteNotificationsEvent.php new file mode 100644 index 0000000..04484e7 --- /dev/null +++ b/api/v1/class/events/notifications/DeletedObsoleteNotificationsEvent.php @@ -0,0 +1,28 @@ +username = 'system'; + $this->type = 0; // Assuming 0 is the type for system events + } + + public function getUsername(): string + { + return $this->username; + } + + public function getType(): int + { + return $this->type; + } +} diff --git a/api/v1/class/events/notifications/EditedNotificationEvent.php b/api/v1/class/events/notifications/EditedNotificationEvent.php new file mode 100644 index 0000000..103e1f9 --- /dev/null +++ b/api/v1/class/events/notifications/EditedNotificationEvent.php @@ -0,0 +1,28 @@ +username = $username; + $this->id = $id; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getId(): int + { + return $this->id; + } +} diff --git a/api/v1/class/events/sickness/SicknessCreatedEvent.php b/api/v1/class/events/sickness/SicknessCreatedEvent.php new file mode 100644 index 0000000..efc8a69 --- /dev/null +++ b/api/v1/class/events/sickness/SicknessCreatedEvent.php @@ -0,0 +1,35 @@ +username = $username; + $this->start = $start; + $this->end = $end; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getStart(): string + { + return $this->start; + } + public function getEnd(): string + { + return $this->end; + } + +} diff --git a/api/v1/class/events/sickness/SicknessDeletedEvent.php b/api/v1/class/events/sickness/SicknessDeletedEvent.php new file mode 100644 index 0000000..18e5f48 --- /dev/null +++ b/api/v1/class/events/sickness/SicknessDeletedEvent.php @@ -0,0 +1,28 @@ +username = $username; + $this->id = $id; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getId(): int + { + return $this->id; + } +} diff --git a/api/v1/class/events/sickness/SicknessUpdatedEvent.php b/api/v1/class/events/sickness/SicknessUpdatedEvent.php new file mode 100644 index 0000000..081ab1b --- /dev/null +++ b/api/v1/class/events/sickness/SicknessUpdatedEvent.php @@ -0,0 +1,34 @@ +username = $username; + $this->id = $id; + $this->status = $status; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getId(): int + { + return $this->id; + } + + public function getStatus(): string + { + return $this->status; + } +} diff --git a/api/v1/class/events/users/UserCreatedEvent.php b/api/v1/class/events/users/UserCreatedEvent.php new file mode 100644 index 0000000..ff57d52 --- /dev/null +++ b/api/v1/class/events/users/UserCreatedEvent.php @@ -0,0 +1,36 @@ +username = $username; + $this->email = $email; + $this->isAdmin = $isAdmin; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getEmail(): string + { + return $this->email; + } + + public function getIsAdmin(): int + { + return $this->isAdmin; + } +} diff --git a/api/v1/class/events/users/UserDeletedEvent.php b/api/v1/class/events/users/UserDeletedEvent.php new file mode 100644 index 0000000..92cdc7c --- /dev/null +++ b/api/v1/class/events/users/UserDeletedEvent.php @@ -0,0 +1,30 @@ +username = $username; + $this->email = $email; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getEmail(): string + { + return $this->email; + } +} diff --git a/api/v1/class/events/vacation/VacationCreatedEvent.php b/api/v1/class/events/vacation/VacationCreatedEvent.php new file mode 100644 index 0000000..c2728b5 --- /dev/null +++ b/api/v1/class/events/vacation/VacationCreatedEvent.php @@ -0,0 +1,34 @@ +username = $username; + $this->start = $start; + $this->end = $end; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getStart(): string + { + return $this->start; + } + public function getEnd(): string + { + return $this->end; + } +} diff --git a/api/v1/class/events/vacation/VacationDeletedEvent.php b/api/v1/class/events/vacation/VacationDeletedEvent.php new file mode 100644 index 0000000..ff4c8fd --- /dev/null +++ b/api/v1/class/events/vacation/VacationDeletedEvent.php @@ -0,0 +1,28 @@ +username = $username; + $this->id = $id; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getId(): int + { + return $this->id; + } +} diff --git a/api/v1/class/events/vacation/VacationUpdatedEvent.php b/api/v1/class/events/vacation/VacationUpdatedEvent.php new file mode 100644 index 0000000..a43ad3d --- /dev/null +++ b/api/v1/class/events/vacation/VacationUpdatedEvent.php @@ -0,0 +1,34 @@ +username = $username; + $this->id = $id; + $this->status = $status; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getId(): int + { + return $this->id; + } + + public function getStatus(): string + { + return $this->status; + } +} diff --git a/api/v1/class/events/worktimes/EasymodeWorktimeAddedEvent.php b/api/v1/class/events/worktimes/EasymodeWorktimeAddedEvent.php new file mode 100644 index 0000000..36ecc37 --- /dev/null +++ b/api/v1/class/events/worktimes/EasymodeWorktimeAddedEvent.php @@ -0,0 +1,20 @@ +username = $username; + } + + public function getUsername(): string + { + return $this->username; + } + +} diff --git a/api/v1/class/events/worktimes/EasymodeWorktimeEndedEvent.php b/api/v1/class/events/worktimes/EasymodeWorktimeEndedEvent.php new file mode 100644 index 0000000..7e9fbbc --- /dev/null +++ b/api/v1/class/events/worktimes/EasymodeWorktimeEndedEvent.php @@ -0,0 +1,27 @@ +username = $username; + $this->id = $id; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getId(): int + { + return $this->id; + } +} diff --git a/api/v1/class/events/worktimes/EasymodeWorktimePauseEndEvent.php b/api/v1/class/events/worktimes/EasymodeWorktimePauseEndEvent.php new file mode 100644 index 0000000..63b4139 --- /dev/null +++ b/api/v1/class/events/worktimes/EasymodeWorktimePauseEndEvent.php @@ -0,0 +1,27 @@ +username = $username; + $this->id = $id; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getId(): int + { + return $this->id; + } +} diff --git a/api/v1/class/events/worktimes/EasymodeWorktimePauseStartEvent.php b/api/v1/class/events/worktimes/EasymodeWorktimePauseStartEvent.php new file mode 100644 index 0000000..989b07d --- /dev/null +++ b/api/v1/class/events/worktimes/EasymodeWorktimePauseStartEvent.php @@ -0,0 +1,27 @@ +username = $username; + $this->id = $id; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getId(): int + { + return $this->id; + } +} diff --git a/api/v1/class/events/worktimes/FixEasymodeWorktimeEvent.php b/api/v1/class/events/worktimes/FixEasymodeWorktimeEvent.php new file mode 100644 index 0000000..10484c7 --- /dev/null +++ b/api/v1/class/events/worktimes/FixEasymodeWorktimeEvent.php @@ -0,0 +1,20 @@ +username = $username; + } + + public function getUsername(): string + { + return $this->username; + } +} diff --git a/api/v1/class/events/worktimes/WorktimeAddedEvent.php b/api/v1/class/events/worktimes/WorktimeAddedEvent.php new file mode 100644 index 0000000..b686454 --- /dev/null +++ b/api/v1/class/events/worktimes/WorktimeAddedEvent.php @@ -0,0 +1,27 @@ +username = $username; + $this->dates = $dates; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getDates(): array + { + return $this->dates; + } +} diff --git a/api/v1/class/events/worktimes/WorktimeDeletedEvent.php b/api/v1/class/events/worktimes/WorktimeDeletedEvent.php new file mode 100644 index 0000000..e164419 --- /dev/null +++ b/api/v1/class/events/worktimes/WorktimeDeletedEvent.php @@ -0,0 +1,27 @@ +username = $username; + $this->id = $id; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getId(): int + { + return $this->id; + } +} diff --git a/api/v1/class/events/worktimes/WorktimeMarkedForReviewEvent.php b/api/v1/class/events/worktimes/WorktimeMarkedForReviewEvent.php new file mode 100644 index 0000000..101c543 --- /dev/null +++ b/api/v1/class/events/worktimes/WorktimeMarkedForReviewEvent.php @@ -0,0 +1,27 @@ +username = $username; + $this->id = $id; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getId(): int + { + return $this->id; + } +} diff --git a/api/v1/class/events/worktimes/WorktimeUnlockedFromReviewEvent.php b/api/v1/class/events/worktimes/WorktimeUnlockedFromReviewEvent.php new file mode 100644 index 0000000..f93fec5 --- /dev/null +++ b/api/v1/class/events/worktimes/WorktimeUnlockedFromReviewEvent.php @@ -0,0 +1,27 @@ +username = $username; + $this->id = $id; + } + + public function getUsername(): string + { + return $this->username; + } + + public function getId(): int + { + return $this->id; + } +} diff --git a/api/v1/class/exports/ExportModule.arbeit.inc.php b/api/v1/class/exports/ExportModule.arbeit.inc.php index 99bf527..e48a5b1 100644 --- a/api/v1/class/exports/ExportModule.arbeit.inc.php +++ b/api/v1/class/exports/ExportModule.arbeit.inc.php @@ -1,5 +1,7 @@ getExportModule($args['module']); if ($module) { + EventDispatcherService::get()->dispatch(new generatedExportEvent($_SESSION["username"], $args['module']), generatedExportEvent::NAME); return $module->export($args); } return false; diff --git a/api/v1/class/i18n/suite/status/snippets_DE.json b/api/v1/class/i18n/suite/status/snippets_DE.json index 3cc1e08..1fd374c 100644 --- a/api/v1/class/i18n/suite/status/snippets_DE.json +++ b/api/v1/class/i18n/suite/status/snippets_DE.json @@ -25,5 +25,6 @@ "error": "Hinweis: Ein Fehler ist aufgetreten. Bitte überprüfe den Log, falls möglich.", "statemismatch": "Fehler: Sicherheitsfehler", "ldapauth": "Fehler: Die Authentifizierung über LDAP ist fehlgeschlagen.", - "ldapcreated": "Hinweis: Bitte melde dich erneut an. Dein Benutzerkonto wurde nun erstellt. (LDAP allowed self-login)" + "ldapcreated": "Hinweis: Bitte melde dich erneut an. Dein Benutzerkonto wurde nun erstellt. (LDAP allowed self-login)", + "notification_not_found": "Fehler: Benachrichtigung nicht gefunden." } \ No newline at end of file diff --git a/api/v1/class/i18n/suite/status/snippets_EN.json b/api/v1/class/i18n/suite/status/snippets_EN.json index 7d01ccb..319c372 100644 --- a/api/v1/class/i18n/suite/status/snippets_EN.json +++ b/api/v1/class/i18n/suite/status/snippets_EN.json @@ -25,6 +25,7 @@ "error": "Note: An error has occurred. Please check the log if possible.", "statemismatch": "Error: Security error", "ldapauth": "Error: The authentication via LDAP failed!", - "ldapcreated": "Note: Please login again. Your account has just been created. (LDAP allowed self-login)" + "ldapcreated": "Note: Please login again. Your account has just been created. (LDAP allowed self-login)", + "notification_not_found": "Error: Notification not found." } \ No newline at end of file diff --git a/api/v1/class/i18n/suite/status/snippets_NL.json b/api/v1/class/i18n/suite/status/snippets_NL.json index 576e8bb..4a9d771 100644 --- a/api/v1/class/i18n/suite/status/snippets_NL.json +++ b/api/v1/class/i18n/suite/status/snippets_NL.json @@ -25,5 +25,6 @@ "error": "Opmerking: er is een fout opgetreden. Controleer indien mogelijk het logboek.", "statemismatch": "Fout: beveiligingsfout", "ldapauth": "Fout: Authenticatie via LDAP mislukt.", - "ldapcreated": "Opmerking: log opnieuw in. Uw gebruikersaccount is nu aangemaakt. (LDAP staat zelfinloggen toe)" + "ldapcreated": "Opmerking: log opnieuw in. Uw gebruikersaccount is nu aangemaakt. (LDAP staat zelfinloggen toe)", + "notification_not_found": "Fout: melding niet gevonden." } \ No newline at end of file diff --git a/api/v1/class/mails/Mails.arbeit.inc.php b/api/v1/class/mails/Mails.arbeit.inc.php index 1f7203c..77f7e01 100644 --- a/api/v1/class/mails/Mails.arbeit.inc.php +++ b/api/v1/class/mails/Mails.arbeit.inc.php @@ -2,7 +2,12 @@ namespace Arbeitszeit; use Arbeitszeit\Mails\MailsProviderInterface; +use Arbeitszeit\Arbeitszeit; use Arbeitszeit\Mails\MailTemplateData; +use Symfony\Component\EventDispatcher\EventDispatcher; +use Arbeitszeit\Events\EventDispatcherService; +use Arbeitszeit\Events\SentMailEvent; + class Mails { @@ -52,7 +57,14 @@ public static function sendMail(string $templateName, array $data) $mailContent = $template->render($data); - return self::$provider->send($mailContent->toArray()); + $ini = Arbeitszeit::get_app_ini()["smtp"]; + if(!$ini["smtp"]){ + Exceptions::error_rep("SMTP disabled, not sending mail."); + return true; + } else { + EventDispatcherService::get()->dispatch(new SentMailEvent($mailContent->toArray()["to"], $mailContent->toArray()["subject"], $mailContent->toArray()["body"]), SentMailEvent::NAME); + return self::$provider->send($mailContent->toArray()); + } } diff --git a/api/v1/class/mails/Mails.md b/api/v1/class/mails/Mails.md index f869567..b2d1f45 100644 --- a/api/v1/class/mails/Mails.md +++ b/api/v1/class/mails/Mails.md @@ -35,20 +35,17 @@ You can implement your own MailTemplate like this: "Subject of the mail", - "body" => "Body of the mail", - "username" => "Username" - ] + return new MailTemplateData($data["subject"], $data["body"], $data["username"]); } } diff --git a/api/v1/class/mails/templates/WorktimeUncompliantTemplate.mails.arbeit.inc.php b/api/v1/class/mails/templates/WorktimeUncompliantTemplate.mails.arbeit.inc.php index d155f97..92afc06 100644 --- a/api/v1/class/mails/templates/WorktimeUncompliantTemplate.mails.arbeit.inc.php +++ b/api/v1/class/mails/templates/WorktimeUncompliantTemplate.mails.arbeit.inc.php @@ -18,7 +18,7 @@ public function render(array $data): MailTemplateData $conn = $arbeit->db(); $sql = "SELECT * FROM `users` WHERE username = ?;"; $res = $conn->sendQuery($sql); - $res->execute($data["username"]); + $res->execute([$data["username"]]); $count = $res->rowCount(); $ii = Arbeitszeit::get_app_ini()["general"]["app_name"]; diff --git a/api/v1/class/mode/mode.arbeit.inc.php b/api/v1/class/mode/mode.arbeit.inc.php index 92da39a..44d9bed 100644 --- a/api/v1/class/mode/mode.arbeit.inc.php +++ b/api/v1/class/mode/mode.arbeit.inc.php @@ -18,12 +18,16 @@ public static function check($username){ private static function get_normal_mode_html(){ + $nodes = new Nodes; + if($nodes->checkNode("mode.inc", "get_normal_mode_html") == false){ + return; + } $i18n = new i18n; $loc = $i18n->loadLanguage(null, "mode/easymode"); $data = <<< DATA
- +
@@ -60,6 +64,10 @@ private static function get_normal_mode_html(){ } private static function get_easymode_html(){ + $nodes = new Nodes; + if($nodes->checkNode("mode.inc", "get_easymode_html") == false){ + return; + } $i18n = new i18n; $loc = $i18n->loadLanguage(null, "mode/easymode"); $active = Arbeitszeit::check_easymode_worktime_finished($_SESSION["username"]); diff --git a/api/v1/class/nodes/json/nodes.json b/api/v1/class/nodes/json/nodes.json new file mode 100644 index 0000000..d0a5b09 --- /dev/null +++ b/api/v1/class/nodes/json/nodes.json @@ -0,0 +1,126 @@ +{ + "nodes": { + "functions": { + "notifications.inc": { + "create_notifications_entry": true, + "create_notifications_entry_P": 1, + + "notifications_delete": true, + "notifications_delete_P": 1, + + "get_notifications_edit_html": true, + "get_notifications_edit_html_P": 1, + + "get_all_notifications": true, + "get_all_notifications_P": 0, + + "get_notifications_html": true, + "get_notifications_html_P": 0, + + "get_notifications_entry": true, + "get_notifications_entry_P": 0, + + "edit_notifications_entry": true, + "edit_notifications_entry_P": 1, + + "delete_notifications_entry": true, + "delete_notifications_entry_P": 1 + }, + "benutzer.inc": { + "editUserProperties": true, + "editUserProperties_P": 1, + + "get_user_html": true, + "get_user_html_P": 0, + + "get_all_users_html": true, + "get_all_users_html_P": 1, + + "delete_user": true, + "delete_user_P": 1, + + "create_user": true, + "create_user_P": 1 + }, + "mode.inc": { + "get_normal_mode_html": true, + "get_normal_mode_html_P": 0, + + "get_easymode_html": true, + "get_easymode_html_P": 0 + }, + "sickness.inc": { + "add_sickness": true, + "add_sickness_P": 0, + + "remove_sickness": true, + "remove_sickness_P": 1, + + "change_status": true, + "change_status_P": 1, + + "display_sickness_all": true, + "display_sickness_all_P": 1 + }, + "vacation.inc": { + "add_vacation": true, + "add_vacation_P": 0, + + "remove_vacation": true, + "remove_vacation_P": 1, + + "change_status": true, + "change_status_P": 1, + + "display_vacation_all": true, + "display_vacation_all_P": 1 + }, + "arbeitszeit.inc": { + "delete_worktime": true, + "delete_worktime_P": 1, + + "add_easymode_worktime": true, + "add_easymode_worktime_P": 0, + + "end_easymode_worktime": true, + "end_easymode_worktime_P": 0, + + "start_easymode_pause_worktime": true, + "start_easymode_pause_worktime_P": 0, + + "end_easymode_pause_worktime": true, + "end_easymode_pause_worktime_P": 0, + + "toggle_easymode": true, + "toggle_easymode_P": 0, + + "get_easymode_status": true, + "get_easymode_status_P": 0, + + "check_easymode_worktime_finished": true, + "check_easymode_worktime_finished_P": 0, + + "add_worktime": true, + "add_worktime_P": 0, + + "get_all_worktime": true, + "get_all_worktime_P": 1, + + "get_all_user_worktime": true, + "get_all_user_worktime_P": 0, + + "get_specific_worktime_html": true, + "get_specific_worktime_html_P": 1, + + "get_employee_worktime_html": true, + "get_employee_worktime_html_P": 0, + + "mark_for_review": true, + "mark_for_review_P": 1, + + "unlock_for_review": true, + "unlock_for_review_P": 1 + } + } + } +} diff --git a/api/v1/class/nodes/nodes.arbeit.inc.php b/api/v1/class/nodes/nodes.arbeit.inc.php new file mode 100644 index 0000000..b16db4c --- /dev/null +++ b/api/v1/class/nodes/nodes.arbeit.inc.php @@ -0,0 +1,66 @@ +benutzer()->is_admin($this->benutzer()->get_user($_SESSION["username"])) ? 1 : 0; + $nodeSet = $panel + ? $data["nodes"]["panels"][$file] ?? null + : $data["nodes"]["functions"][$file] ?? null; + + if (!$nodeSet || !is_array($nodeSet)) { + Exceptions::error_rep("checkNode: Invalid node set for $file, expected array, got " . gettype($nodeSet)); + return false; + } + + $baseKey = $function; + $permKey = $function . '_P'; + + $allowed = $nodeSet[$baseKey] ?? false; + + if ($allowed === false) { + Exceptions::error_rep("checkNode: Access denied for function $function in file $file"); + return false; + } + + if (!array_key_exists($permKey, $nodeSet)) { + Exceptions::error_rep("checkNode: Permission key $permKey not found in node set for $file"); + return $allowed === true; + } + + $level = $nodeSet[$permKey]; + if (!in_array($level, [0, 1, 2], true)) { + Exceptions::error_rep("checkNode: Invalid permission level for $function"); + return false; + } + if ($level === 2) { + //access granted for public + Exceptions::error_rep("checkNode: Access granted for function $function in file $file, user level: $userIsAdmin, required level: $level"); + return true; + } + if ($level === 1 && $userIsAdmin === 1) { + //access granted for admin user + Exceptions::error_rep("checkNode: Access granted for function $function in file $file, user level: $userIsAdmin, required level: $level"); + return true; + } + if ($level === 0 && ($userIsAdmin === 0 || $userIsAdmin === 1)) { + //acces granted for normal user + Exceptions::error_rep("checkNode: Access granted for function $function in file $file, user level: $userIsAdmin, required level: $level"); + return true; + } + Exceptions::error_rep("checkNode: Access denied for function $function in file $file, user level: $userIsAdmin, required level: $level"); + return false; + } +} \ No newline at end of file diff --git a/api/v1/class/notifications/notifications.arbeit.inc.php b/api/v1/class/notifications/notifications.arbeit.inc.php index 96bfc51..504df72 100644 --- a/api/v1/class/notifications/notifications.arbeit.inc.php +++ b/api/v1/class/notifications/notifications.arbeit.inc.php @@ -1,6 +1,13 @@ dispatch(new DeletedObsoleteNotificationsEvent(), DeletedObsoleteNotificationsEvent::NAME); Exceptions::error_rep("[NOTIFICATIONS] Deleted expired notifications entries."); return 1; } } public function get_notifications_edit_html(){ + if(!$this->nodes()->checkNode("notifications.inc", "get_notifications_edit_html")) return "

{$this->i18n["no_entries"]}

"; Exceptions::error_rep("[NOTIFICATIONS] Getting notifications edit html..."); $sql = "SELECT * FROM `kalender` ORDER BY id DESC;"; $res = $this->db->sendQuery($sql); @@ -70,6 +79,7 @@ public function get_notifications_edit_html(){ } public function get_all_notifications(){ + if(!$this->nodes()->checkNode("notifications.inc", "get_all_notifications")) return null; Exceptions::error_rep("[NOTIFICATIONS] Getting all notifications..."); $sql = "SELECT * FROM `kalender` ORDER BY id DESC;"; $res = $this->db->sendQuery($sql); @@ -99,6 +109,7 @@ public function get_all_notifications(){ } public function get_notifications_html(){ + if(!$this->nodes()->checkNode("notifications.inc", "get_notifications_html")) return null; Exceptions::error_rep("[NOTIFICATIONS] Getting notifications html..."); $base_url = Arbeitszeit::get_app_ini()["general"]["base_url"]; $sql = "SELECT * FROM `kalender` ORDER BY id DESC;"; @@ -137,6 +148,7 @@ public function get_notifications_html(){ * */ public function get_notifications_entry($id){ + if(!$this->nodes()->checkNode("notifications.inc", "get_notifications_entry")) return null; Exceptions::error_rep("[NOTIFICATIONS] Getting notifications entry with ID '$id'..."); $sql = "SELECT * FROM `kalender` WHERE id = ?"; $res = $this->db->sendQuery($sql); @@ -171,6 +183,7 @@ public function get_notifications_entry($id){ * @param bool|array Returns "true" on success and an error array on failure */ public function create_notifications_entry($time, $date, $location, $comment){ + if(!$this->nodes()->checkNode("notifications.inc", "create_notifications_entry")) return false; Exceptions::error_rep("[NOTIFICATIONS] Creating new notifications entry..."); $sql = "INSERT INTO `kalender` (`datum`, `uhrzeit`, `ort`, `notiz`) VALUES (?, ?, ?, ?)"; $res = $this->db->sendQuery($sql)->execute([$date, $time, $location, $comment]); @@ -183,6 +196,7 @@ public function create_notifications_entry($time, $date, $location, $comment){ ] ]; } else { + EventDispatcherService::get()->dispatch(new CreatedNotificationEvent($_SESSION["username"], "N/A", $location, 0), CreatedNotificationEvent::NAME); Exceptions::error_rep("[NOTIFICATIONS] Created new notifications entry."); return true; } @@ -199,6 +213,7 @@ public function create_notifications_entry($time, $date, $location, $comment){ * @Note All parameters are required, if not set, the function will return an error array */ public function edit_notifications_entry($id, $time, $date, $location, $comment){ + if(!$this->nodes()->checkNode("notifications.inc", "edit_notifications_entry")) return false; Exceptions::error_rep("[NOTIFICATIONS] Editing notifications entry with ID '$id'..."); $sql = "UPDATE `kalender` SET `datum` = ?, `uhrzeit` = ?, `ort` = ?, `notiz` = ? WHERE id = ?;"; $res = $this->db->sendQuery($sql)->execute([$date, $time, $location, $comment, $id]); @@ -211,6 +226,7 @@ public function edit_notifications_entry($id, $time, $date, $location, $comment) ] ]; } else { + EventDispatcherService::get()->dispatch(new EditedNotificationEvent($_SESSION["username"], (int)$id == 0), EditedNotificationEvent::NAME); Exceptions::error_rep("[NOTIFICATIONS] Edited notifications entry with ID '$id'."); return true; } @@ -223,6 +239,7 @@ public function edit_notifications_entry($id, $time, $date, $location, $comment) * @return bool|array Returns "true" on success and an error array on failure */ public function delete_notifications_entry($id){ + if(!$this->nodes()->checkNode("notifications.inc", "delete_notifications_entry")) return false; Exceptions::error_rep("[NOTIFICATIONS] Deleting notifications entry with ID '$id'..."); $sql = "DELETE FROM `kalender` WHERE id = ?;"; $res = $this->db->sendQuery($sql)->execute([$id]); @@ -235,6 +252,7 @@ public function delete_notifications_entry($id){ ] ]; } else { + EventDispatcherService::get()->dispatch(new DeletedNotificationEvent($_SESSION["username"], (int)$id ?? 0), DeletedNotificationEvent::NAME); Exceptions::error_rep("[NOTIFICATIONS] Deleted notifications entry with ID '$id'."); return true; } diff --git a/api/v1/class/plugins/Hooks.plugins.arbeit.inc.php b/api/v1/class/plugins/Hooks.plugins.arbeit.inc.php deleted file mode 100644 index eb28ada..0000000 --- a/api/v1/class/plugins/Hooks.plugins.arbeit.inc.php +++ /dev/null @@ -1,202 +0,0 @@ - $timings) { - foreach ($timings as $timing => $callbacks) { - foreach ($callbacks as $callbackData) { - if (isset($callbackData['closure']) && $callbackData['closure']) { - $data[$hookName][$timing][] = ['closure' => true]; - } else { - $data[$hookName][$timing][] = [ - 'closure' => false, - 'class' => $callbackData['class'] ?? null, - 'method' => $callbackData['method'] ?? null - ]; - } - } - } - } - file_put_contents(self::HOOKS_FILE, json_encode($data, JSON_PRETTY_PRINT)); - } - - public static function addHook($event, $type, callable $callback, $pluginName = null) { - if ($pluginName) { - // Plugin-Klasse dynamisch laden - $pluginBuilder = new PluginBuilder(); - $pluginBuilder->loadPluginClass($pluginName); - } - // Hook registrieren - self::$hooks[$event][$type][] = $callback; - self::saveHooks(); - } - - public static function executeHook(string $hookName, ...$args) { - if (isset(self::$hooks[$hookName])) { - foreach (self::$hooks[$hookName]['callback'] as $callbackData) { - if (isset($callbackData['closure']) && $callbackData['closure']) { - $callback = $callbackData['callback']; - if (is_callable($callback)) { - call_user_func_array($callback, $args); - } else { - throw new InvalidArgumentException("Callback for hook '$hookName' is not callable."); - } - } else { - $className = $callbackData['class']; - $methodName = $callbackData['method']; - if (class_exists($className) && method_exists($className, $methodName)) { - $instance = new $className(); - if (is_callable([$instance, $methodName])) { - call_user_func_array([$instance, $methodName], $args); - } else { - throw new InvalidArgumentException("Method '$methodName' does not exist in class '$className'."); - } - } else { - throw new InvalidArgumentException("Class '$className' does not exist."); - } - } - } - } - } - - public static function executeWithHooks(string $hookName, callable $originalFunction, ...$args) { - $result = null; - - if (isset(self::$hooks[$hookName])) { - foreach (self::$hooks[$hookName]['around'] as $callbackData) { - if (isset($callbackData['closure']) && $callbackData['closure']) { - $callback = $callbackData['callback']; - if (is_callable($callback)) { - $result = call_user_func_array($callback, [$originalFunction, ...$args]); - return $result; - } else { - throw new InvalidArgumentException("Callback for hook '$hookName' is not callable."); - } - } - } - - foreach (self::$hooks[$hookName]['before'] as $callbackData) { - if (isset($callbackData['closure']) && $callbackData['closure']) { - $callback = $callbackData['callback']; - if (is_callable($callback)) { - call_user_func_array($callback, $args); - } else { - throw new InvalidArgumentException("Callback for hook '$hookName' is not callable."); - } - } - } - - $result = call_user_func_array($originalFunction, $args); - - foreach (self::$hooks[$hookName]['after'] as $callbackData) { - if (isset($callbackData['closure']) && $callbackData['closure']) { - $callback = $callbackData['callback']; - if (is_callable($callback)) { - call_user_func_array($callback, $args); - } else { - throw new InvalidArgumentException("Callback for hook '$hookName' is not callable."); - } - } - } - } else { - $result = call_user_func_array($originalFunction, $args); - } - - self::executeHook($hookName, ...$args); - - return $result; - } - - public static function removeHook(string $hookName, string $timing, callable $callback = null): void { - if (isset(self::$hooks[$hookName][$timing])) { - if ($callback === null) { - self::$hooks[$hookName][$timing] = []; - } else { - foreach (self::$hooks[$hookName][$timing] as $index => $callbackData) { - if (isset($callbackData['closure']) && $callbackData['closure']) { - if ($callbackData['callback'] === $callback) { - unset(self::$hooks[$hookName][$timing][$index]); - } - } else { - if ($callbackData['class'] === (is_object($callback[0]) ? get_class($callback[0]) : $callback[0]) && - $callbackData['method'] === $callback[1]) { - unset(self::$hooks[$hookName][$timing][$index]); - } - } - } - - self::$hooks[$hookName][$timing] = array_values(self::$hooks[$hookName][$timing]); - - if (empty(self::$hooks[$hookName]['before']) && empty(self::$hooks[$hookName]['after']) && empty(self::$hooks[$hookName]['around']) && empty(self::$hooks[$hookName]['callback'])) { - unset(self::$hooks[$hookName]); - } - } - - self::saveHooks(); - } - } - - private static function convertCallbacksFromJson(array $data): array { - $converted = []; - - foreach ($data as $hookName => $timings) { - foreach ($timings as $timing => $callbacks) { - foreach ($callbacks as $callbackData) { - if (isset($callbackData['closure']) && $callbackData['closure']) { - $converted[$hookName][$timing][] = ['closure' => true]; - } else { - $className = $callbackData['class'] ?? null; - $methodName = $callbackData['method'] ?? null; - - if ($className && $methodName) { - $converted[$hookName][$timing][] = [ - 'closure' => false, - 'class' => $className, - 'method' => $methodName - ]; - } else { - $converted[$hookName][$timing][] = [ - 'closure' => false, - 'class' => null, - 'method' => null - ]; - } - } - } - } - } - - return $converted; - } - - private static function createCallableFromClass(string $className, string $methodName): callable { - if (!class_exists($className)) { - throw new InvalidArgumentException("Class '$className' does not exist."); - } - - $instance = new $className(); - if (!method_exists($instance, $methodName)) { - throw new InvalidArgumentException("Method '$methodName' does not exist in class '$className'."); - } - - return [$instance, $methodName]; - } -} -?> diff --git a/api/v1/class/plugins/plugins/exportmanager/plugin.yml b/api/v1/class/plugins/plugins/exportmanager/plugin.yml index 3fd76cc..c6f63bd 100644 --- a/api/v1/class/plugins/plugins/exportmanager/plugin.yml +++ b/api/v1/class/plugins/plugins/exportmanager/plugin.yml @@ -4,7 +4,7 @@ main: Main namespace: exportmanager author: Ente description: Easily manage all your export modules -version: "1.2" +version: "1.2.1" api: 0.1 permissions: none enabled: false diff --git a/api/v1/class/plugins/plugins/nfclogin/README.md b/api/v1/class/plugins/plugins/nfclogin/README.md new file mode 100644 index 0000000..c74fd4b --- /dev/null +++ b/api/v1/class/plugins/plugins/nfclogin/README.md @@ -0,0 +1,26 @@ +# NFClogin plugin + +This plugin allows you to interact with a PC/SC device (e.g. NFC-card reader/writer) to login to the system. + +## Requirements + +Please make sure your PHP instance does not block `exec()` function. Tested with ACR122U NFC reader. +This plugin requires the `pcscd` service to be running on the system. You can install it using the following command: + +```bash +sudo apt update +sudo apt install pcscd pcsc-tools libpcsclite-dev python3-pip +pip3 install pyscard +sudo systemctl enable --now pcscd + +``` + +For Windows: + +- Download Python3 +- Download Visual C++ Redistributable +- Install official drivers for your NFC reader +- Install `pyscard` using pip: +```bash +pip install pyscard +``` diff --git a/api/v1/class/plugins/plugins/nfclogin/plugin.yml b/api/v1/class/plugins/plugins/nfclogin/plugin.yml new file mode 100644 index 0000000..c2e821f --- /dev/null +++ b/api/v1/class/plugins/plugins/nfclogin/plugin.yml @@ -0,0 +1,14 @@ +name: nfclogin +src: /src +main: Main +namespace: NFClogin +author: Ente +description: 'Allow the use of NFC cards to login.' +version: '1.1' +api: 0.1 +permissions: none +enabled: true +custom.values: + license: LIC +nav_links: + 'Manage Cards': views/cards.php \ No newline at end of file diff --git a/api/v1/class/plugins/plugins/nfclogin/src/Main.php b/api/v1/class/plugins/plugins/nfclogin/src/Main.php new file mode 100644 index 0000000..5dd7109 --- /dev/null +++ b/api/v1/class/plugins/plugins/nfclogin/src/Main.php @@ -0,0 +1,192 @@ + null, + "username" => null, + "pin" => null, + ]; + private string $mastertoken; + public $setup; + public $code; + + public function __construct() { + $this->set_plugin_configuration(); + $this->set_log_append(); + + $this->setup(); + } + + public function setup(): void { + $this->register_routes(); + } + + public function register_routes(): void { + Exceptions::error_rep("{$this->log_append} Registering custom routes..."); + CustomRoutes::registerCustomRoute("readNfc", "/api/v1/class/plugins/plugins/nfclogin/src/routes/readNfc.ep.toil.arbeit.inc.php", 1); + CustomRoutes::registerCustomRoute("writeNfc", "/api/v1/class/plugins/plugins/nfclogin/src/routes/writeNfc.ep.toil.arbeit.inc.php", 1); + CustomRoutes::registerCustomRoute("readBlock4", "/api/v1/class/plugins/plugins/nfclogin/src/routes/readBlock4.ep.toil.arbeit.inc.php", 1); + CustomRoutes::registerCustomRoute("nfcclogin", "/api/v1/class/plugins/plugins/nfclogin/src/routes/nfcclogin.ep.toil.arbeit.inc.php", 2); + } + + public function set_log_append(): void { + $v = $this->read_plugin_configuration("nfclogin")["version"] ?? "unknown"; + $this->log_append = "[nfclogin v{$v}]"; + } + + public function get_log_append(): string { + return $this->log_append; + } + + public function set_plugin_configuration(): void { + $this->plugin_configuration = $this->read_plugin_configuration("nfclogin"); + } + + public function get_plugin_configuration(): array { + return $this->plugin_configuration; + } + + public function onDisable(): void { + $this->log_append = $this->get_log_append(); + } + + public function onEnable(): void {} + + public function onLoad(): void {} + + public function readCard(): ?array { + $scriptPath = __DIR__ . '/read_nfc_uid.py'; + exec("python3 " . escapeshellarg($scriptPath) . " 2>&1", $output, $status); + + if ($status !== 0) { + return [ + "error" => "Helper script execution failed", + "output" => $output + ]; + } + + $json = implode("", $output); + $data = json_decode($json, true); + + if (json_last_error() !== JSON_ERROR_NONE) { + return [ + "error" => "Invalid JSON response from helper script", + "raw_output" => $output + ]; + } + + return $data; + } + + public function removeCard($id){ + $data = json_decode(file_get_contents(__DIR__ . "/data/map.json"), true); + if ($data === null) { + return null; + } + if (isset($data[$id])) { + unset($data[$id]); + file_put_contents(__DIR__ . "/data/map.json", json_encode($data, JSON_PRETTY_PRINT)); + return $data; + } else { + return null; + } + } + + public function memorize($id, $user){ + $data = json_decode(file_get_contents(__DIR__ . "/data/map.json"), true); + if ($data === null) { + $data = []; + } + $data[$id] = $user["username"]; + file_put_contents(__DIR__ . "/data/map.json", json_encode($data, JSON_PRETTY_PRINT)); + return $data; + } + + public function getUser($id){ + $data = json_decode(file_get_contents(__DIR__ . "/data/map.json"), true); + if ($data === null) { + return null; + } + // return array("id" => $user); + if (isset($data[$id])) { + return $data[$id]; + } else { + return null; + } + } + + public function assignCard(int $id){ + $scriptPath = __DIR__ . "/write_nfc.py"; + exec("python3 " . escapeshellarg($scriptPath) . " " . escapeshellarg((string)$id) . " 2>&1", $output, $status); + if ($status !== 0) { + return [ + "error" => "Helper script execution failed", + "output" => $output + ]; + } + $this->memorize($this->readCard()["uid"], Benutzer::get_user_from_id($id)); + $json = implode("", $output); + $data = json_decode($json, true); + if (json_last_error() !== JSON_ERROR_NONE) { + return [ + "error" => "Invalid JSON response from helper script", + "raw_output" => $output + ]; + } + return $data; + } + + public function readBlock4() { + $scriptPath = __DIR__ . "/read_nfc_block.py"; + exec("python3 " . escapeshellarg($scriptPath) . " 2>&1", $output, $status); + if ($status !== 0) { + return [ + "error" => "Helper script execution failed", + "output" => $output + ]; + } + $json = implode("", $output); + $data = json_decode($json, true); + if (json_last_error() !== JSON_ERROR_NONE) { + return [ + "error" => "Invalid JSON response from helper script", + "raw_output" => $output + ]; + } + return $data; + } + + public function nfcloginHtml(){ + $html = "Login with NFC"; + return $html; + } + + public function allCardAssignmentsHtml(){ + $data = json_decode(file_get_contents(__DIR__ . "/data/map.json"), true); + if ($data === null) { + return "No cards."; + } + $html = ""; + $html .= ""; + foreach ($data as $id => $user) { + $html .= ""; + } + $html .= "
Card IDUsername
{$id}{$user}
"; + return $html; + } +} diff --git a/api/v1/class/plugins/plugins/nfclogin/src/data/map.json b/api/v1/class/plugins/plugins/nfclogin/src/data/map.json new file mode 100644 index 0000000..e69de29 diff --git a/api/v1/class/plugins/plugins/nfclogin/src/read_nfc_block.py b/api/v1/class/plugins/plugins/nfclogin/src/read_nfc_block.py new file mode 100644 index 0000000..1475c13 --- /dev/null +++ b/api/v1/class/plugins/plugins/nfclogin/src/read_nfc_block.py @@ -0,0 +1,21 @@ +from smartcard.System import readers +from smartcard.util import toHexString + +BLOCK = 4 + +r = readers() +if not r: + print("No NFC reader found.") + exit(1) + +reader = r[0] +connection = reader.createConnection() +connection.connect() + +data, sw1, sw2 = connection.transmit([0xFF, 0xB0, 0x00, BLOCK, 0x10]) + +if sw1 == 0x90 and sw2 == 0x00: + value = bytearray(data).decode('utf-8', errors='ignore').rstrip('\x00') + print('{"value": "' + value + '"}') +else: + print('{"error": "Read failed", "sw1": %d, "sw2": %d}' % (sw1, sw2)) diff --git a/api/v1/class/plugins/plugins/nfclogin/src/read_nfc_uid.py b/api/v1/class/plugins/plugins/nfclogin/src/read_nfc_uid.py new file mode 100644 index 0000000..96bde5f --- /dev/null +++ b/api/v1/class/plugins/plugins/nfclogin/src/read_nfc_uid.py @@ -0,0 +1,19 @@ +from smartcard.System import readers +from smartcard.util import toHexString + +r = readers() +if not r: + print("No reader found") + exit(1) + +reader = r[0] +connection = reader.createConnection() +connection.connect() + +GET_UID = [0xFF, 0xCA, 0x00, 0x00, 0x00] # Command to get UID +data, sw1, sw2 = connection.transmit(GET_UID) + +if sw1 == 0x90 and sw2 == 0x00: + print('{"uid": "' + ''.join(format(x, '02X') for x in data) + '"}') +else: + print('{"error": "Failed to get UID"}') diff --git a/api/v1/class/plugins/plugins/nfclogin/src/routes/nfcclogin.ep.toil.arbeit.inc.php b/api/v1/class/plugins/plugins/nfclogin/src/routes/nfcclogin.ep.toil.arbeit.inc.php new file mode 100644 index 0000000..15fa528 --- /dev/null +++ b/api/v1/class/plugins/plugins/nfclogin/src/routes/nfcclogin.ep.toil.arbeit.inc.php @@ -0,0 +1,69 @@ +$name = $value; + } + + public function __get($name) + { + return $this->$name; + } + + public function get() + { + header('Content-Type: application/json'); + + try { + $nfc = new NFClogin; + $block = $nfc->readBlock4(); + $uid = $nfc->readCard()["uid"] ?? null; + $val = $block["value"]; + $getMapUser = $nfc->getUser($uid); + $dbUser = Benutzer::get_user_from_id($val)["username"] ?? null; + if($getMapUser == $dbUser){ + Auth::login($dbUser, "", ["nfclogin" => true]); + } else { + header("Location: /suite/login.php?error=wrongdata&uid={$uid}&block={$val}"); + exit; + } + + } catch (\Exception $e) { + echo json_encode(["error" => true, "message" => $e->getMessage()]); + } + } + + public function post($post = null) + { + // Optional + } + + public function delete() + { + // Optional + } + + public function put() + { + // Optional + } + } +} diff --git a/api/v1/class/plugins/plugins/nfclogin/src/routes/readBlock4.ep.toil.arbeit.inc.php b/api/v1/class/plugins/plugins/nfclogin/src/routes/readBlock4.ep.toil.arbeit.inc.php new file mode 100644 index 0000000..58b7d6d --- /dev/null +++ b/api/v1/class/plugins/plugins/nfclogin/src/routes/readBlock4.ep.toil.arbeit.inc.php @@ -0,0 +1,61 @@ +$name = $value; + } + + public function __get($name) + { + return $this->$name; + } + + public function get() + { + header('Content-Type: application/json'); + + try { + $nfc = new NFClogin; + $data = $nfc->readBlock4(); + if(isset($data["value"])) { + $data["username"] = Benutzer::get_user_from_id($data["value"])["username"] ?? null; + } + echo json_encode($data); + } catch (\Exception $e) { + echo json_encode(["error" => true, "message" => $e->getMessage()]); + } + } + + public function post($post = null) + { + // Optional + } + + public function delete() + { + // Optional + } + + public function put() + { + // Optional + } + } +} diff --git a/api/v1/class/plugins/plugins/nfclogin/src/routes/readNfc.ep.toil.arbeit.inc.php b/api/v1/class/plugins/plugins/nfclogin/src/routes/readNfc.ep.toil.arbeit.inc.php new file mode 100644 index 0000000..fbdd722 --- /dev/null +++ b/api/v1/class/plugins/plugins/nfclogin/src/routes/readNfc.ep.toil.arbeit.inc.php @@ -0,0 +1,57 @@ +$name = $value; + } + + public function __get($name) + { + return $this->$name; + } + + public function get() + { + header('Content-Type: application/json'); + + try { + $nfc = new NFClogin; + $data = $nfc->readCard(); + echo json_encode($data); + } catch (\Exception $e) { + echo json_encode(["error" => true, "message" => $e->getMessage()]); + } + } + + public function post($post = null) + { + // Optional + } + + public function delete() + { + // Optional + } + + public function put() + { + // Optional + } + } +} diff --git a/api/v1/class/plugins/plugins/nfclogin/src/routes/writeNfc.ep.toil.arbeit.inc.php b/api/v1/class/plugins/plugins/nfclogin/src/routes/writeNfc.ep.toil.arbeit.inc.php new file mode 100644 index 0000000..685dfe1 --- /dev/null +++ b/api/v1/class/plugins/plugins/nfclogin/src/routes/writeNfc.ep.toil.arbeit.inc.php @@ -0,0 +1,63 @@ +$name = $value; + } + + public function __get($name) + { + return $this->$name; + } + + public function get() + { + header('Content-Type: application/json'); + + try { + $nfc = new NFClogin; + $user = Benutzer::get_user($_GET["username"]); + if(!$user){ + echo json_encode(["error" => true, "message" => "User not found"]); + return; + } + $data = $nfc->assignCard((int)$user["id"]); + echo json_encode($data); + } catch (\Exception $e) { + echo json_encode(["error" => true, "message" => $e->getMessage()]); + } + } + + public function post($post = null) + { + // Optional + } + + public function delete() + { + // Optional + } + + public function put() + { + // Optional + } + } +} diff --git a/api/v1/class/plugins/plugins/nfclogin/src/write_nfc.py b/api/v1/class/plugins/plugins/nfclogin/src/write_nfc.py new file mode 100644 index 0000000..1a50e1e --- /dev/null +++ b/api/v1/class/plugins/plugins/nfclogin/src/write_nfc.py @@ -0,0 +1,29 @@ +import sys +from smartcard.System import readers +from smartcard.util import toBytes + +BLOCK = 4 + +if len(sys.argv) != 2: + print("Usage: python3 write_ultralight_block.py ") + sys.exit(1) + +data = sys.argv[1][:4].ljust(4, '\x00') +data_bytes = toBytes(' '.join([f"{ord(c):02X}" for c in data])) + +r = readers() +if not r: + print('{"error": "No reader found"}') + sys.exit(1) + +reader = r[0] +connection = reader.createConnection() +connection.connect() + +command = [0xFF, 0xD6, 0x00, BLOCK, 0x04] + data_bytes +response, sw1, sw2 = connection.transmit(command) + +if sw1 == 0x90 and sw2 == 0x00: + print(f'{{"success": true, "block": {BLOCK}, "written": "{data}"}}') +else: + print(f'{{"error": "Write failed", "sw1": "{sw1:02X}", "sw2": "{sw2:02X}"}}') diff --git a/api/v1/class/plugins/plugins/nfclogin/views/cards.php b/api/v1/class/plugins/plugins/nfclogin/views/cards.php new file mode 100644 index 0000000..4fed322 --- /dev/null +++ b/api/v1/class/plugins/plugins/nfclogin/views/cards.php @@ -0,0 +1,141 @@ +auth()->login_validation(); +$arbeit->blockIfNotAdmin(); +?> + +
+

NFCLogin Plugin

+ +

Functions:

+ +

+ + / Open user list

+ + + +

+ +
+ + +
+

+ readCard(); + if ($cardData) { + echo "Initial check (technical data may be provided):

";
+            echo htmlspecialchars(json_encode($cardData, JSON_PRETTY_PRINT));
+            echo "
"; + } else { + echo "No card detected."; + } + } catch (Exception $e) { + echo "Error: " . $e->getMessage(); + } + ?> +


+
+

All Cards

+ allCardAssignmentsHtml(); ?> +
+
+ + diff --git a/api/v1/class/plugins/plugins/usercount/plugin.yml b/api/v1/class/plugins/plugins/usercount/plugin.yml deleted file mode 100644 index dade5d2..0000000 --- a/api/v1/class/plugins/plugins/usercount/plugin.yml +++ /dev/null @@ -1,13 +0,0 @@ -name: usercount -src: /src -main: Main -namespace: userdetail -author: none -description: Add User details -version: "1.0" -api: 0.1 -permissions: none -enabled: false -custom.values: -nav_links: - Get User Count: views/test.php \ No newline at end of file diff --git a/api/v1/class/plugins/plugins/usercount/src/Main.php b/api/v1/class/plugins/plugins/usercount/src/Main.php deleted file mode 100644 index 7bc0e7b..0000000 --- a/api/v1/class/plugins/plugins/usercount/src/Main.php +++ /dev/null @@ -1,100 +0,0 @@ -plugin_configuration["version"]; - $this->log_append = "[ExamplePlugin v{$v}]"; - } - - public function get_log_append(): string{ - return $this->log_append; - } - - public function set_plugin_configuration(): void{ - $this->plugin_configuration = $this->read_plugin_configuration("usercount"); - } - - public function get_plugin_configuration(): array{ - return $this->plugin_configuration; - } - - public function __construct(){ - $this->set_plugin_configuration(); - $this->set_log_append(); - $this->db = new DB; - } - - public function onLoad(): void{ - $lga = $this->get_log_append(); - $this->logger("{$lga} Loading userdetail plugin..."); - } - - public function onEnable(): void{ - - } - - public function onDisable(): void{ - - } - - public function load_data(){ - $this->data = $this->unmemorize_plugin("userdetail"); - } - - public function save_data(){ - $this->memorize_plugin("userdetail"); - } - - public function saveDataDisk(array $payload = null){ - $this->logger("[usercount] Saving data to disk..."); - if($payload == null){ - $payload = $this->additional_payload; - } - $handle = fopen($_SERVER["DOCUMENT_ROOT"] . parent::get_basepath() . "/data/userdetail.json", "w+"); - if(fwrite($handle, json_encode($payload))){ - fclose($handle); - return true; - } - } - - public function getDataDisk(){ - $this->logger("[usercount] Getting data from disk..."); - return json_decode(file_get_contents($_SERVER["DOCUMENT_ROOT"] . parent::get_basepath() . "/data/userdetail.json"), true); - } - - public function get_users(){ - $this->logger("[usercount] Getting user count..."); - - try { - $sql = "SELECT COUNT(*) FROM `users`;"; - $result = $this->db->sendQuery($sql)->execute(); - - $r = $result->fetch(\PDO::FETCH_ASSOC); - - return $r[0]; - } catch (\Exception $e){ - throw new \Exception((string)$e); - } - } -} - - -?> \ No newline at end of file diff --git a/api/v1/class/plugins/plugins/usercount/views/test.php b/api/v1/class/plugins/plugins/usercount/views/test.php deleted file mode 100644 index e273338..0000000 --- a/api/v1/class/plugins/plugins/usercount/views/test.php +++ /dev/null @@ -1,12 +0,0 @@ -get_users(); -?> -

Amount of users:

\ No newline at end of file diff --git a/api/v1/class/plugins/plugins/userdetail/plugin.yml b/api/v1/class/plugins/plugins/userdetail/plugin.yml index 090477c..b860420 100644 --- a/api/v1/class/plugins/plugins/userdetail/plugin.yml +++ b/api/v1/class/plugins/plugins/userdetail/plugin.yml @@ -4,7 +4,7 @@ main: Main namespace: userdetail author: Ente description: Save more information about your employees -version: "1.1.1" +version: "1.2" api: 0.1 permissions: none enabled: true diff --git a/api/v1/class/plugins/plugins/userdetail/src/Main.php b/api/v1/class/plugins/plugins/userdetail/src/Main.php index ecb2f64..4cd7940 100644 --- a/api/v1/class/plugins/plugins/userdetail/src/Main.php +++ b/api/v1/class/plugins/plugins/userdetail/src/Main.php @@ -117,7 +117,11 @@ public function save_employee_data($payload){ public function get_employee_data($username){ $this->logger("[userdetail] Getting employee data for employee: {$username}..."); - if(json_decode(@file_get_contents(dirname(__DIR__, 1) . "/data/" . $username . ".json"), true) != false){ + $path = @file_get_contents(dirname(__DIR__, 1) . "/data/" . $username . ".json"); + if($path == null || $path == false){ + header("Location: /suite/plugins/index.php?pn=userdetail&p_view=views/user.php&user={$username}&nuser=true"); + } + if(json_decode($path, true) != false){ return json_decode(file_get_contents(dirname(__DIR__, 1) . "/data/" . $username . ".json"), true); } else { $this->logger("[userdetail] No data found for employee: {$username}..."); diff --git a/api/v1/class/plugins/plugins/userdetail/views/user.php b/api/v1/class/plugins/plugins/userdetail/views/user.php index 599a872..3da1e98 100644 --- a/api/v1/class/plugins/plugins/userdetail/views/user.php +++ b/api/v1/class/plugins/plugins/userdetail/views/user.php @@ -14,43 +14,69 @@ $nav = $main->compute_user_nav(); $user = $benutzer->get_user($_GET["user"]); -$id = filter_var($_POS["id"], FILTER_SANITIZE_NUMBER_INT); +if (isset($_GET["nuser"])) { + echo "

User not found.

"; + die(); +} -if($id){ - $payload = [ - "id" => $id, - "username" => $_POST["username"], - "notes" => $_POST["notes"], - "position" => $_POST["position"], - "employee-id" => $_POST["employee-id"], - "department" => $_POST["department"], - "email" => $_POST["email"], - ]; - if($main->save_employee_data($payload)){ - echo "Successfully saved data for {$payload["username"]}
"; +if ($_SERVER["REQUEST_METHOD"] === "POST") { + $id = $user["id"]; + $username = $_POST["username"]; + $email = $_POST["email"]; + $name = $_POST["name"]; + + if (!empty($username)) { + if ($benutzer->editUserProperties($id, "username", $username)) { + echo "Updated username for ID {$id} (If changed - Please reload the page again to reload the new properties.).
"; + } } - if($_POST["reset-password"] == true || $_POST["reset-password"] == "on"){ - if($auth->reset_password($_POST["username"])){ - echo "Successfully reset password for {$payload["username"]}"; + if (!empty($_POST["name"])) { + if ($benutzer->editUserProperties($id, "name", $_POST["name"])) { + echo "Updated name for ID {$id} (If changed).
"; } } -} + if (!empty($email)) { + if ($benutzer->editUserProperties($id, "email", $email)) { + echo "Updated email for ID {$id} (If changed).
"; + } + } + + if (!empty($_POST["reset-password"])) { + if ($auth->reset_password($username)) { + echo "Successfully reset password for {$username}
"; + } + } -if($r = $main->get_employee_data($_GET["user"])){ - $notes = $r["notes"] ?? ""; - $pos = $r["position"] ?? ""; - $eid = $r["employee-id"] ?? ""; - $department = $r["department"] ?? ""; + $payload = [ + "id" => $id, + "username" => $username, + "notes" => $_POST["notes"] ?? null, + "position" => $_POST["position"] ?? null, + "employee-id" => $_POST["employee-id"] ?? null, + "department" => $_POST["department"] ?? null, + "email" => $email, + "name" => $_POST["name"] ?? null, + ]; + if ($main->save_employee_data($payload)) { + echo "Successfully saved employee data for {$username}
"; + } } + +$r = $main->get_employee_data($_GET["user"]); +$notes = $r["notes"] ?? ""; +$pos = $r["position"] ?? ""; +$eid = $r["employee-id"] ?? ""; +$department = $r["department"] ?? ""; ?>

| Userdetail

-
&id=" method="POST"> + &id=" method="POST"> ">
+ " placeholder="John Doe">



@@ -63,3 +89,14 @@
+ + diff --git a/api/v1/class/sickness/sickness.arbeit.inc.php b/api/v1/class/sickness/sickness.arbeit.inc.php index 4425fa9..d5dc521 100644 --- a/api/v1/class/sickness/sickness.arbeit.inc.php +++ b/api/v1/class/sickness/sickness.arbeit.inc.php @@ -6,11 +6,18 @@ * v1 * - Added function to add sickness */ + use Arbeitszeit\Events\EventDispatcherService; + use Arbeitszeit\Events\SicknessCreatedEvent; + use Arbeitszeit\Events\SicknessDeletedEvent; + use Arbeitszeit\Events\SicknessUpdatedEvent; class Sickness extends Arbeitszeit { public function add_sickness($start, $stop, $user = null) { + if($this->nodes()->checkNode("sickness.inc", "add_sickness") == false){ + return false; + } Exceptions::error_rep("[SICK] Adding sickness for user '{$user}'..."); $user = $_SESSION["username"]; $dateString = $start; @@ -28,23 +35,31 @@ public function add_sickness($start, $stop, $user = null) Exceptions::error_rep("[SICK] An error occured while adding an sickness for user '$user'. See previous message for more information."); return false; } else { + EventDispatcherService::get()->dispatch(new SicknessCreatedEvent($user, $start, $stop), SicknessCreatedEvent::NAME); Exceptions::error_rep("[SICK] Successfully added sickness for user '$user'."); return true; } } public function remove_sickness($id){ # admin function only + if($this->nodes()->checkNode("sickness.inc", "remove_sickness") == false){ + return false; + } Exceptions::error_rep("[SICK] Removing sickness with id '{$id}'..."); $data = $this->db()->sendQuery("DELETE * FROM sick WHERE id = ?")->execute([$id]); if($data == false){ Exceptions::error_rep("[SICK] An error occured while deleting a sickness with id '{$id}'. See previous message for more information."); return false; } + EventDispatcherService::get()->dispatch(new SicknessDeletedEvent($_SESSION["username"], $id), SicknessDeletedEvent::NAME); return true; } public function change_status($id, $new_state = 3) # admin function only { + if($this->nodes()->checkNode("sickness.inc", "change_status") == false){ + return false; + } Exceptions::error_rep("[SICK] Changing status for sickness id '{$id}' to '{$new_state}'..."); if($new_state == 1 /* approve */){ $sql = "UPDATE `sick` SET `status` = 'approved' WHERE `id` = ?;"; @@ -58,6 +73,7 @@ public function change_status($id, $new_state = 3) # admin function only Exceptions::error_rep("[SICK] An error occured while setting status for sickness. id '{$id}', new state: '{$new_state}'. See previous message for more information."); return false; } else { + EventDispatcherService::get()->dispatch(new SicknessUpdatedEvent($_SESSION["username"], $id, $new_state), SicknessUpdatedEvent::NAME); Exceptions::error_rep("[SICK] Successfully changed status for sickness id '{$id}', new state: '{$new_state}'."); return true; } @@ -65,6 +81,9 @@ public function change_status($id, $new_state = 3) # admin function only } public function display_sickness_all(){ # admin function only + if($this->nodes()->checkNode("sickness.inc", "display_sickness_all") == false){ + return false; + } Exceptions::error_rep("[SICK] Displaying all sicknesses..."); $i18n = $this->i18n()->loadLanguage(null, "worktime/sick/all", "admin"); @@ -75,7 +94,7 @@ public function display_sickness_all(){ # admin function only if($count > 0){ # compute and return data - while($row = $data->fetch(\PDO::FETCH_ASSOC)){ + foreach($data->fetchAll(\PDO::FETCH_ASSOC) as $row){ $rnw = $row["username"]; $start = @strftime("%d.%m.%Y", strtotime($row["start"])); $stop = @strftime("%d.%m.%Y", strtotime($row["stop"])); @@ -89,12 +108,12 @@ public function display_sickness_all(){ # admin function only case "approved": $status = "{$i18n["status"]["approved"]}"; break; - case "action": - $status = "Action needed"; + case "rejected": + $status = "{$i18n["status"]["rejected"]}"; break; } - if($stop = "01.01.1970"){ + if($stop == "01.01.1970"){ $stop = "-"; } diff --git a/api/v1/class/updates/data/migrations/2.sql b/api/v1/class/updates/data/migrations/2.sql deleted file mode 100644 index df573db..0000000 --- a/api/v1/class/updates/data/migrations/2.sql +++ /dev/null @@ -1,18 +0,0 @@ -ALTER TABLE arbeitszeiten -ADD COLUMN project VARCHAR(255); - -CREATE TABLE `projects` ( - `id` int(11) NOT NULL, - `name` text NOT NULL, - `users` text NOT NULL, - `description` text NOT NULL, - `note` text NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_german1_ci; - -ALTER TABLE projects - ADD PRIMARY KEY (id) -ALTER TABLE projects - MODIFY id int(11) NOT NULL AUTO_INCREMENT; - -UPDATE scheme -SET v = '2'; diff --git a/api/v1/class/updates/data/scheme.db b/api/v1/class/updates/data/scheme.db deleted file mode 100644 index 56a6051..0000000 --- a/api/v1/class/updates/data/scheme.db +++ /dev/null @@ -1 +0,0 @@ -1 \ No newline at end of file diff --git a/api/v1/class/updates/updates.arbeit.inc.php b/api/v1/class/updates/updates.arbeit.inc.php deleted file mode 100644 index 8d3eb76..0000000 --- a/api/v1/class/updates/updates.arbeit.inc.php +++ /dev/null @@ -1,91 +0,0 @@ -$name; - } - - public function __set($name, $value){ - $this->$name = $value; - } - - private function request_schemev(){ - $conn = new DB; - Exceptions::error_rep("Checking database scheme version..."); - try { - $sql = "SELECT * FROM scheme;"; - $res = $conn->sendQuery($sql); - $res->execute(); - Exceptions::error_rep("Successfully checked database scheme version."); - $data = $res->fetch(\PDO::FETCH_ASSOC)["v"]; - } catch(\Exception $e){ - Exceptions::error_rep("Database scheme not found. Creating new scheme..."); - $sql = "CREATE TABLE scheme (v INT DEFAULT 1);"; - $sql1 = "INSERT INTO scheme (v) VALUES (1);"; - $conn->simpleQuery($sql); - $conn->simpleQuery($sql1); - Exceptions::error_rep("Successfully created new database scheme. Retrying to get scheme version..."); - $this->request_schemev(); - } - - return $data; - } - - private function get_latest_schemev(){ - Exceptions::error_rep("Getting latest database scheme version..."); - $dir = dirname(__DIR__) . "/updates/data/scheme.db"; - $d = file_get_contents($dir); - return $d; - } - - public function compare_scheme(){ - Exceptions::error_rep("Comparing database scheme versions..."); - $current = $this->request_schemev(); - $latest = $this->get_latest_schemev(); - $current1 = $current + 1; - if((int)$current < (int)$latest){ - Exceptions::error_rep("Database scheme is not up to date. Please migrate your database immediately! | Missing upgrades: {$current1}-{$latest}"); - } elseif((int)$current > (int)$latest){ - Exceptions::error_rep("You have somehow managed to be more than up to date for your database. Your database is probably defect and needs manual repair."); - } - } - - private function get_upgrade_file($scheme){ - Exceptions::error_rep("Getting upgrade file for scheme {$scheme}..."); - return __DIR__ . "/data/migrations/{$scheme}.sql"; - } - - public function perform_migration($updateToSchemeV){ - Exceptions::error_rep("Performing database migration to scheme from {$this->request_schemev()} to {$updateToSchemeV}..."); - $current = $this->request_schemev(); - $path = $this->get_upgrade_file($updateToSchemeV); - if(($current + 1) == $updateToSchemeV){ - Exceptions::error_rep("Upgrading database scheme to {$updateToSchemeV}..."); - $db = Arbeitszeit::get_app_ini()["mysql"]; - $command = "mysql --user={$db["db_user"]} --password={$db["db_password"]} -h {$db["db_host"]} -D {$db["db"]} < {$path}"; - $output = shell_exec($command); - if($output == ""){ - Exceptions::error_rep("Successfully migrated database to scheme {$updateToSchemeV}"); - return true; - } else { - Exceptions::error_rep("An error occured while migrating database: {$output}"); - return false; - } - } else { - Exceptions::error_rep("Cannot upgrade database scheme: You are missing other updates! | Tried scheme: {$updateToSchemeV} - Current: {$current}"); - return false; - } - } - } -} - - -?> diff --git a/api/v1/class/vacation/vacation.arbeit.inc.php b/api/v1/class/vacation/vacation.arbeit.inc.php index 42fb69f..79cafb8 100644 --- a/api/v1/class/vacation/vacation.arbeit.inc.php +++ b/api/v1/class/vacation/vacation.arbeit.inc.php @@ -6,6 +6,10 @@ * v1 * - Added function to add vacation */ + use Arbeitszeit\Events\EventDispatcherService; + use Arbeitszeit\Events\VacationCreatedEvent; + use Arbeitszeit\Events\VacationUpdatedEvent; + use Arbeitszeit\Events\VacationDeletedEvent; class Vacation extends Arbeitszeit { @@ -17,6 +21,9 @@ public function __construct(){ public function add_vacation($start, $stop, $username = null) { + if($this->nodes()->checkNode("vacation.inc", "add_vacation") == false){ + return false; + } Exceptions::error_rep("[VACATION] Adding vacation for user '{$username}'..."); if($username != null){ $user = $_SESSION["username"]; @@ -36,12 +43,16 @@ public function add_vacation($start, $stop, $username = null) Exceptions::error_rep("[VACATION] An error occured while adding an vacation for user '$user'. See previous message for more information."); return false; } else { + EventDispatcherService::get()->dispatch(new VacationCreatedEvent($user, $start, $stop), VacationCreatedEvent::NAME); Exceptions::error_rep("[VACATION] Successfully added vacation for user '$user'."); return true; } } public function remove_vacation($id){ # admin function only + if($this->nodes()->checkNode("vacation.inc", "remove_vacation") == false){ + return false; + } Exceptions::error_rep("[VACATION] Removing vacation with id '{$id}'..."); $sql = "DELETE * FROM `vacation` WHERE id = ?"; $data = $this->db->sendQuery($sql)->execute(array([$id])); @@ -49,12 +60,16 @@ public function remove_vacation($id){ # admin function only Exceptions::error_rep("[VACATION] An error occured while deleting a vacation with id '{$id}'. See previous message for more information"); return false; } + EventDispatcherService::get()->dispatch(new VacationDeletedEvent($_SESSION["username"], (int)$id), VacationDeletedEvent::NAME); Exceptions::error_rep("[VACATION] Successfully removed vacation with id '{$id}'."); return true; } public function change_status($id, $new_state = 3) # admin function only { + if($this->nodes()->checkNode("vacation.inc", "change_status") == false){ + return false; + } Exceptions::error_rep("[VACATION] Changing status for vacation id '{$id}' to '{$new_state}'..."); if($new_state == 1 /* approve */){ $sql = "UPDATE `vacation` SET `status` = 'approved' WHERE `id` = ?;"; @@ -68,6 +83,7 @@ public function change_status($id, $new_state = 3) # admin function only Exceptions::error_rep("[VACATION] An error occured while setting status for vacaction. id '{$id}', new state: '{$new_state}'. See previous message for more information."); return false; } else { + EventDispatcherService::get()->dispatch(new VacationUpdatedEvent($_SESSION["username"], (int)$id, $new_state), VacationUpdatedEvent::NAME); Exceptions::error_rep("[VACATION] Successfully changed status for vacation id '{$id}', new state: '{$new_state}'."); return true; } @@ -93,6 +109,9 @@ public function get_vacation($id, $mode = 1){ } public function display_vacation_all(){ # admin function only + if($this->nodes()->checkNode("vacation.inc", "display_vacation_all") == false){ + return false; + } Exceptions::error_rep("[VACATION] Displaying all vacations..."); $i18n = $this->i18n()->loadLanguage(null, "worktime/vacation/all", "admin"); @@ -117,7 +136,7 @@ public function display_vacation_all(){ # admin function only $status = "{$i18n["status"]["approved"]}"; break; case "rejected": - $status = "{$i18n["status"]["rejected"]}"; + $status = "{$i18n["status"]["rejected"]}"; break; } diff --git a/api/v1/inc/app.json.sample b/api/v1/inc/app.json.sample index 8fb211e..2c80167 100644 --- a/api/v1/inc/app.json.sample +++ b/api/v1/inc/app.json.sample @@ -5,7 +5,8 @@ "support_email": "support@your-website.com", "debug": "false", "auto_update": "false", - "app": "false" + "app": "false", + "timezone": "UTC" }, "mysql": { "db_host": "localhost", @@ -14,6 +15,7 @@ "db": "ab" }, "smtp": { + "smtp": false, "host": "smtp.host.com", "username": "user", "password": "pass", diff --git a/api/v1/inc/arbeit.inc.php b/api/v1/inc/arbeit.inc.php index 8723275..010b49c 100644 --- a/api/v1/inc/arbeit.inc.php +++ b/api/v1/inc/arbeit.inc.php @@ -7,8 +7,11 @@ header("Location: /errors/503.html"); die(); } +require_once dirname(__DIR__, 3) . "/vendor/autoload.php"; +require_once dirname(__DIR__, 1) . "/class/events/loader.events.arbeit.inc.php"; require_once dirname(__DIR__, 1) . "/class/arbeitszeit.inc.php"; +require_once dirname(__DIR__, 1) . "/class/nodes/nodes.arbeit.inc.php"; require_once dirname(__DIR__, 1) . "/class/db/db.arbeit.inc.php"; require_once dirname(__DIR__, 1) . "/class/notifications/notifications.arbeit.inc.php"; require_once dirname(__DIR__, 1) . "/class/i18n/i18n.arbeit.inc.php"; @@ -19,7 +22,6 @@ require_once dirname(__DIR__, 1) . "/class/exceptions/exceptions.arbeit.inc.php"; require_once dirname(__DIR__, 1) . "/class/vacation/vacation.arbeit.inc.php"; require_once dirname(__DIR__, 1) . "/class/sickness/sickness.arbeit.inc.php"; -require_once $_SERVER["DOCUMENT_ROOT"] . "/vendor/autoload.php"; require_once dirname(__DIR__, 1) . "/class/exports/ExportModule.arbeit.inc.php"; require_once dirname(__DIR__, 1) . "/class/exports/modules/ExportModuleInterface.em.arbeit.inc.php"; @@ -31,9 +33,7 @@ require_once dirname(__DIR__, 1) . "/toil/Permissions.routes.toil.arbeit.inc.php"; require_once dirname(__DIR__, 1) . "/toil/CustomRoutes.routes.toil.arbeit.inc.php"; -require_once dirname(__DIR__, 1) . "/class/plugins/Hooks.plugins.arbeit.inc.php"; require_once dirname(__DIR__, 1) . "/class/auth/plugins/ldap/ldap.auth.arbeit.inc.php"; -require_once dirname(__DIR__, 1) . "/class/updates/updates.arbeit.inc.php"; require_once dirname(__DIR__ . 1) . "/class/mails/Mails.arbeit.inc.php"; require_once dirname(__DIR__ . 1) . "/class/mails/MailTemplateData.mails.arbeit.inc.php"; @@ -42,10 +42,5 @@ require_once dirname(__DIR__ . 1) . "/class/mails/provider/PHPMailerMailsProvider.mails.arbeit.inc.php"; require_once dirname(__DIR__ . 1) . "/class/mails/provider/DefaultMailsProvider.mails.arbeit.inc.php"; -use Arbeitszeit\Hooks; -Hooks::initialize(); -use Arbeitszeit\Updates; -$updates = new Updates; -$updates->compare_scheme(); ?> \ No newline at end of file diff --git a/api/v1/toil/Permissions.routes.toil.arbeit.inc.php b/api/v1/toil/Permissions.routes.toil.arbeit.inc.php index f58e9c3..829efd8 100644 --- a/api/v1/toil/Permissions.routes.toil.arbeit.inc.php +++ b/api/v1/toil/Permissions.routes.toil.arbeit.inc.php @@ -43,23 +43,34 @@ private function checkUserPermission($username){ } - public function checkPermissions($user, $endpoint){ - $endpointP = @$this->loadPermissionSet()[$endpoint]; - if($endpointP === null) {$endpointP = "?"; Exceptions::error_rep("[API] Failed to check for permissions on endpoint: " . $endpoint . " (req: ". $endpointP . ", for user " . $user . ")"); return false;} - $perm = $this->checkUserPermission($user); - switch ($perm) { - case 1: - case $endpointP === 0: - return true; - case $perm == $endpointP: - return true; - default: - $endpointP = "?"; - Exceptions::error_rep("[API] Failed to check for permissions on endpoint: " . $endpoint . " (req: ". $endpointP . ", for user " . $user . ")"); - return false; + public function checkPermissions($user, $endpoint) { + $permissionsSet = $this->__get("permissions"); + $endpointPermission = $permissionsSet[$endpoint] ?? null; + + if ($endpointPermission === null) { + Exceptions::error_rep("[API] No permissions defined for endpoint: '$endpoint'"); + return false; + } + + if ($endpointPermission === 2) { + Exceptions::error_rep("[API] Public access granted for endpoint '$endpoint'"); + return true; + } + $userIsAdmin = $this->checkUserPermission($user); + + if ($endpointPermission === 1 && $userIsAdmin) { + return true; } + + if ($endpointPermission === 0 && !$userIsAdmin) { + return true; + } + + Exceptions::error_rep("[API] Permission denied for user '$user' on endpoint '$endpoint' (required: $endpointPermission, isAdmin: $userIsAdmin)"); + return false; } + } } diff --git a/api/v1/toil/Routes.toil.arbeit.inc.php b/api/v1/toil/Routes.toil.arbeit.inc.php index 1be5148..82b406e 100644 --- a/api/v1/toil/Routes.toil.arbeit.inc.php +++ b/api/v1/toil/Routes.toil.arbeit.inc.php @@ -15,7 +15,8 @@ use Toil\Controller; use Toil\Permissions; -class Routes extends Toil { +class Routes extends Toil +{ private Arbeitszeit $arbeitszeit; private Benutzer $benutzer; @@ -29,9 +30,22 @@ class Routes extends Toil { private $bString = "Toil API"; - public function __construct(){ - $user = $_SERVER["PHP_AUTH_USER"]; - $pw = $_SERVER["PHP_AUTH_PW"]; + public function __construct() + { + $this->__set("eventHandler", new EventHandler()); + + $endpoint = preg_replace('/\?.*/', '', $_SERVER['REQUEST_URI']); + $endpointName = $this->getResourceNameFromPath($endpoint); + $permissions = new Permissions(); + + if ($permissions->checkPermissions(null, $endpointName)) { + Exceptions::error_rep("[API] Public access to '$endpointName' allowed – skipping authentication."); + return; + } + + $user = $_SERVER["PHP_AUTH_USER"] ?? null; + $pw = $_SERVER["PHP_AUTH_PW"] ?? null; + $this->__set("arbeitszeit", new Arbeitszeit()); $this->__set("benutzer", new Benutzer()); if (!$user) { @@ -40,34 +54,37 @@ public function __construct(){ } else { $this->__set("api_username", $user); } - + $this->__set("api_password", $pw or $this->authError($user)); - if(!$this->login(username: $user, password: $pw)){ + if (!$this->login(username: $user, password: $pw)) { $this->authError($user); } - $this->__set("eventHandler", new EventHandler()); } - public function __set($name, $value){ + public function __set($name, $value) + { $this->$name = $value; } - public function __get($name){ + public function __get($name) + { return $this->$name; } - public function authError($name = null){ + public function authError($name = null) + { Exceptions::error_rep("[API] Failed authentication for Toil API for user '{$name}'"); header("WWW-Authenticate: Basic realm='" . $this->bString . "'"); header("HTTP/1.0 401 Unauthorized"); die("Not authenticated"); } - public function login($username, $password){ - if($this->__get("benutzer")->get_user($username)){ + public function login($username, $password) + { + if ($this->__get("benutzer")->get_user($username)) { $data = $this->__get("benutzer")->get_user($username); - if(password_verify($password, $data["password"])){ + if (password_verify($password, $data["password"])) { Exceptions::error_rep("[API] Successfully authenticated user '$username' via API."); return true; } @@ -76,16 +93,17 @@ public function login($username, $password){ } } - public function routing($eventHandler){ + public function routing($eventHandler) + { Exceptions::error_rep("[API] Start API routing request and registering routes..."); $base = $this->__get("basepath"); $user = $_SERVER["PHP_AUTH_USER"]; - $eventHandler->register(EventHandler::EVENT_ADD_ROUTE, function(EventArgument $event) use ($base){ + $eventHandler->register(EventHandler::EVENT_ADD_ROUTE, function (EventArgument $event) use ($base) { $route = $event->route; - if(!$event->isSubRoute){ + if (!$event->isSubRoute) { return; } - switch(true){ + switch (true) { case $route instanceof ILoadableRoute: $route->prependUrl($base); break; @@ -100,7 +118,7 @@ public function routing($eventHandler){ $permissions = new Permissions; preg_match("/\/([^\/?]+)(\?.*)?$/m", $_SERVER["REQUEST_URI"], $matches); - if(!$permissions->checkPermissions($user, $matches[1])){ + if (!$permissions->checkPermissions($user, $matches[1])) { Exceptions::error_rep("[API] Failed checking permissions for expected endpoint: " . $matches[1]); header("Content-type: application/json"); header("HTTP/1.1 403 Forbidden"); @@ -109,86 +127,86 @@ public function routing($eventHandler){ } Router::addEventHandler($eventHandler); - Router::get("/api/v1/toil/getVersion", function(){ + Router::get("/api/v1/toil/getVersion", function () { Exceptions::error_rep("Accessing 'getVersion' endpoint. User: " . $this->__get("api_username")); Controller::createview("getVersion"); }); - Router::get("/api/v1/toil/healthcheck", function(){ + Router::get("/api/v1/toil/healthcheck", function () { Exceptions::error_rep("[API] User authenticated and accessing 'healthcheck' endpoint"); Controller::createview("healthcheck"); }); - Router::get("/api/v1/toil/getUserCount", function(){ + Router::get("/api/v1/toil/getUserCount", function () { Exceptions::error_rep("[API] User authenticated and acceessing 'getUserCount' endpoint"); Controller::createview("getUserCount"); }); - Router::get("/api/v1/toil/getApiVersion", function(){ + Router::get("/api/v1/toil/getApiVersion", function () { Exceptions::error_rep("[API] User authenticated and accessing 'getApiVersion' endpoint"); Controller::createview("getApiVersion"); }); - Router::get("/api/v1/toil/getLog", function(){ + Router::get("/api/v1/toil/getLog", function () { Exceptions::error_rep("[API] User authenticated and accessing 'getLog' endpoint"); Controller::createview("getLog"); }); - Router::get("/api/v1/toil/getWorktimes", function(){ + Router::get("/api/v1/toil/getWorktimes", function () { Exceptions::error_rep("[API] User authenticated and accessing 'getWorktimes' endpoint"); Controller::createview("getWorktimes"); }); - Router::get("/api/v1/toil/getVacations", function(){ + Router::get("/api/v1/toil/getVacations", function () { Exceptions::error_rep("[API] User authenticated and accessing 'getVacations' endpoint"); Controller::createview("getVacations"); }); - Router::get("/api/v1/toil/getUsers", function(){ + Router::get("/api/v1/toil/getUsers", function () { Exceptions::error_rep("[API] User authenticated and accessing 'getUsers' endpoint"); Controller::createview("getUsers"); }); - Router::get("/api/v1/toil/getUserDetails", function(){ + Router::get("/api/v1/toil/getUserDetails", function () { Exceptions::error_rep("[API] User authenticated and accessing 'getUserDetails' endpoint"); Controller::createview("getUserDetails"); }); - Router::get("/api/v1/toil/approveVacation", function(){ + Router::get("/api/v1/toil/approveVacation", function () { Exceptions::error_rep("[API] User authenticated and accessing 'approveVacation' endpoint"); Controller::createview("approveVacation"); }); - Router::get("/api/v1/toil/addWorktime", function(){ + Router::get("/api/v1/toil/addWorktime", function () { Exceptions::error_rep("[API] User authenticated and accessing 'addWorktime' endpoint"); Controller::createview("addWorktime"); }); - Router::get("/api/v1/toil/addVacation", function(){ + Router::get("/api/v1/toil/addVacation", function () { Exceptions::error_rep("[API] User authenticated and accessing 'addVacation' endpoint"); Controller::createview("addVacation"); }); - Router::get("/api/v1/toil/addProject", function(){ + Router::get("/api/v1/toil/addProject", function () { Exceptions::error_rep("[API] User authenticated and accessing 'addProject' endpoint"); Controller::createview("addProject"); }); - - Router::get("/api/v1/toil/getUserWorktimes", function(){ + + Router::get("/api/v1/toil/getUserWorktimes", function () { Exceptions::error_rep("[API] User authenticated and accessing 'getUserWorktimes' endpoint"); Controller::createview("getUserWorktimes"); }); - Router::get("/api/v1/toil/deleteWorktime", function(){ + Router::get("/api/v1/toil/deleteWorktime", function () { Exceptions::error_rep("[API] User authenticated and accessing 'deleteWorktime' endpoint"); Controller::createview("deleteWorktime"); }); - Router::get("/api/v1/toil/deleteUser", function(){ + Router::get("/api/v1/toil/deleteUser", function () { Exceptions::error_rep("[API] User authenticated and accessing 'deleteUser' endpoint"); Controller::createview("deleteUser"); }); - Router::get("/api/v1/toil/addUser", function(){ + Router::get("/api/v1/toil/addUser", function () { Exceptions::error_rep("[API] User authenticated and accessing 'addUser' endpoint"); Controller::createview("addUser"); }); - Router::get("/api/v1/toil/getOwnWorktime", function(){ + Router::get("/api/v1/toil/getOwnWorktime", function () { Exceptions::error_rep("[API] User authenticated and accessing 'getOwnWorktime' endpoint"); Controller::createview("getOwnWorktime"); }); @@ -212,8 +230,8 @@ public function routing($eventHandler){ // Loading all custom routes CustomRoutes::loadCustomRoutes(); - Router::error(function(Request $request, \Exception $e){ - switch($e->getCode()){ + Router::error(function (Request $request, \Exception $e) { + switch ($e->getCode()) { case 404: Exceptions::error_rep("[API] 404 Not found. Endpoint: " . $request->getUrl()); header("Content-type: application/json"); @@ -230,7 +248,8 @@ public function routing($eventHandler){ }); } - public function getResourceNameFromPath($path){ + public function getResourceNameFromPath($path) + { preg_match("/[^\/]+$/", $path, $match); return $match[0]; } diff --git a/api/v1/toil/VERSION b/api/v1/toil/VERSION index d4a29ee..fa0755c 100644 --- a/api/v1/toil/VERSION +++ b/api/v1/toil/VERSION @@ -1 +1 @@ -v1.10 \ No newline at end of file +v1.11 diff --git a/api/v1/toil/permissions.json b/api/v1/toil/permissions.json index 3335250..f86e493 100644 --- a/api/v1/toil/permissions.json +++ b/api/v1/toil/permissions.json @@ -6,7 +6,7 @@ "approveVacation": 1, "deleteUser": 1, "deleteWorktime": 1, - "getApiVersion": 0, + "getApiVersion": 2, "getLog": 1, "getUserCount": 1, "getUserDetails": 1, @@ -16,9 +16,9 @@ "addOwnWorktime": 0, "addOwnVacation": 0, "getVacations": 1, - "getVersion": 0, + "getVersion": 2, "getWorktimes": 1, - "healthcheck": 0, + "healthcheck": 2, "autoremoveNotifications": 0, "removeNotification": 1, "addNotification": 1, diff --git a/composer.json b/composer.json index 207b473..fa8421a 100644 --- a/composer.json +++ b/composer.json @@ -1,39 +1,49 @@ { - "name": "ente/timetrack", - "description": "TimeTrack is a PHP-written time recording tool for small businesses", - "type": "software", - "license": "GNU GPL", - "version": "7.9", - "authors": [ - { - "name": "Ente", - "email": "github@openducks.org", - "homepage": "https://openducks.org" - } - ], - "require": { - "phpmailer/phpmailer": "^6.1", - "erusev/parsedown": "^1.7", - "pecee/simple-router": "4.3.7.2", - "symfony/yaml": "^7.1", - "cweagans/composer-patches": "^1.7", - "ldaptools/ldaptools": "dev-master#69d086b137ece33a7b4e182bc90bb65dc0454630", - - "php": ">=8.0", - "dompdf/dompdf": "^3.0" - }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/ldaptools/ldaptools" - } - ], - "config": { - "allow-plugins": { - "cweagans/composer-patches": true + "name": "ente/timetrack", + "description": "TimeTrack is a PHP-written time recording tool for small businesses", + "type": "software", + "license": "GNU GPL", + "version": "7.12", + "authors": [ + { + "name": "Ente", + "email": "github@openducks.org", + "homepage": "https://openducks.org" + } + ], + "require": { + "phpmailer/phpmailer": "^6.1", + "erusev/parsedown": "^1.7", + "pecee/simple-router": "4.3.7.2", + "symfony/yaml": "^7.1", + "cweagans/composer-patches": "^1.7", + "ldaptools/ldaptools": "dev-master", + "php": ">=8.0", + "dompdf/dompdf": "^3.0", + "robmorgan/phinx": "^0.16.6", + "symfony/event-dispatcher": "^7.2", + "symfony/contracts": "^3.5" + }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/ldaptools/ldaptools" + } + ], + "config": { + "allow-plugins": { + "cweagans/composer-patches": true + } + }, + "require-dev": { + "phpunit/phpunit": "^9.5" + }, + "extra": { + "patches": { + "ldaptools/ldaptools": { + "Fix PHP 8 paged result handling": "patches/ldaptools-php8.patch" } - }, - "require-dev": { - "phpunit/phpunit": "^9.5" } } + +} diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..a60de8e Binary files /dev/null and b/favicon.ico differ diff --git a/migrations/migrations/20250331155531_init_az_scheme.php b/migrations/migrations/20250331155531_init_az_scheme.php new file mode 100644 index 0000000..5ec619d --- /dev/null +++ b/migrations/migrations/20250331155531_init_az_scheme.php @@ -0,0 +1,36 @@ +hasTable('arbeitszeiten'); + if ($exists) { + echo "\nSkipping. Table already exists.\n"; + return; + } + + $this->execute("SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';"); + $this->execute("SET time_zone = '+00:00';"); + + $this->table('arbeitszeiten') + ->addColumn('name', 'string', ['limit' => 256]) + ->addColumn('email', 'string', ['limit' => 256]) + ->addColumn('schicht_tag', 'string', ['limit' => 256]) + ->addColumn('schicht_anfang', 'string', ['limit' => 256]) + ->addColumn('schicht_ende', 'string', ['limit' => 256]) + ->addColumn('username', 'string', ['limit' => 255]) + ->addColumn('ort', 'text', ['null' => true]) + ->addColumn('active', 'boolean', ['null' => true]) + ->addColumn('review', 'boolean', ['null' => true]) + ->addColumn('type', 'string', ['limit' => 11, 'null' => true]) + ->addColumn('pause_start', 'string', ['limit' => 255, 'null' => true]) + ->addColumn('pause_end', 'string', ['limit' => 255, 'null' => true]) + ->addColumn('attachements', 'text', ['null' => true]) + ->addColumn('project', 'string', ['limit' => 255, 'null' => true]) + ->create(); + + } +} \ No newline at end of file diff --git a/migrations/migrations/20250331155756_init_notif_scheme.php b/migrations/migrations/20250331155756_init_notif_scheme.php new file mode 100644 index 0000000..9d66793 --- /dev/null +++ b/migrations/migrations/20250331155756_init_notif_scheme.php @@ -0,0 +1,27 @@ +hasTable("kalender"); + if($exists){ + echo "\nSkipping. Table already exists.\n"; + return; + } + + $this->execute("SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';"); + $this->table("kalender") + ->addColumn("datum", "string", ["limit" => 255]) + ->addColumn("uhrzeit", "string", ["limit" => 255]) + ->addColumn("ort", "string", ["limit" => 255]) + ->addColumn("notiz", "string", ["limit" => 255]) + ->changePrimaryKey(["id"]) + ->create(); + } +} diff --git a/migrations/migrations/20250331160130_init_sick_scheme.php b/migrations/migrations/20250331160130_init_sick_scheme.php new file mode 100644 index 0000000..5c48e1a --- /dev/null +++ b/migrations/migrations/20250331160130_init_sick_scheme.php @@ -0,0 +1,28 @@ +hasTable('sick'); + if ($exists) { + echo "\nSkipping. Table already exists.\n"; + return; + } + + $this->execute("SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';"); + $this->execute("SET time_zone = '+00:00';"); + $this->table("sick") + ->addColumn("username", "string", ["limit" => 255]) + ->addColumn("start", "text", ["null" => false]) + ->addColumn("stop", "text", ["null" => false]) + ->addColumn("status", "text", ["null" => false]) + ->changePrimaryKey(["id"]) + ->create(); + } +} diff --git a/migrations/migrations/20250331160400_init_vacation_scheme.php b/migrations/migrations/20250331160400_init_vacation_scheme.php new file mode 100644 index 0000000..786c076 --- /dev/null +++ b/migrations/migrations/20250331160400_init_vacation_scheme.php @@ -0,0 +1,28 @@ +hasTable('vacation'); + if ($exists) { + echo "\nSkipping. Table already exists.\n"; + return; + } + + $this->execute("SET SQL_MODE = 'NO_AUTO_VALUE_ON_ZERO';"); + $this->execute("SET time_zone = '+00:00';"); + $this->table("vacation") + ->addColumn("username", "string", ["limit" => 255]) + ->addColumn("start", "text", ["null" => false]) + ->addColumn("stop", "text", ["null" => false]) + ->addColumn("status", "text", ["null" => false]) + ->changePrimaryKey(["id"]) + ->create(); + } +} diff --git a/migrations/migrations/20250331160543_init_user_scheme.php b/migrations/migrations/20250331160543_init_user_scheme.php new file mode 100644 index 0000000..cc8abe8 --- /dev/null +++ b/migrations/migrations/20250331160543_init_user_scheme.php @@ -0,0 +1,29 @@ +hasTable('users'); + if ($exists) { + echo "\nSkipping. Table already exists.\n"; + return; + } + + $this->table("users") + ->addColumn("name", "string", ["limit" => 256]) + ->addColumn("username", "string", ["limit" => 255]) + ->addColumn("email", "string", ["limit" => 256]) + ->addColumn("password", "string", ["limit" => 256]) + ->addColumn("email_confirmed", "boolean") + ->addColumn("isAdmin", "string", ["limit" => 256]) + ->addColumn("state", "text", ["null" => true]) + ->addColumn("easymode", "boolean", ["null" => true]) + ->changePrimaryKey(["id"]) + ->create(); + } +} diff --git a/migrations/migrations/20250331162111_init_schicht_scheme.php b/migrations/migrations/20250331162111_init_schicht_scheme.php new file mode 100644 index 0000000..03f093d --- /dev/null +++ b/migrations/migrations/20250331162111_init_schicht_scheme.php @@ -0,0 +1,27 @@ +hasTable('schicht'); + if ($exists) { + echo "\nSkipping. Table already exists.\n"; + return; + } + + $this->table('schicht') + ->addColumn('name', 'string', ['limit' => 256]) + ->addColumn('email', 'string', ['limit' => 256]) + ->addColumn('schicht_gestartet_zeit', 'string', ['limit' => 256, 'null' => true]) + ->addColumn('schicht_ende_zeit', 'string', ['limit' => 256, 'null' => true]) + ->addColumn('schicht_datum', 'string', ['limit' => 256, 'null' => true]) + ->changePrimaryKey(['id']) + ->create(); + } +} diff --git a/migrations/migrations/20250331164242_init_first_user.php b/migrations/migrations/20250331164242_init_first_user.php new file mode 100644 index 0000000..5bcb32e --- /dev/null +++ b/migrations/migrations/20250331164242_init_first_user.php @@ -0,0 +1,35 @@ +hasTable("users")) { + + $data = [ + 'name' => 'admin', + 'username' => 'admin', + 'email' => 'admin@admin.com', + 'password' => password_hash('admin', PASSWORD_DEFAULT), + 'email_confirmed' => true, + 'isAdmin' => true, + 'state' => null, + 'easymode' => false, + ]; + + $users = $this->fetchRow("SELECT COUNT(*) as count FROM users WHERE username = 'admin'"); + if ($users['count'] == 0) { + $this->table('users')->insert($data)->saveData(); + } else { + echo "User count > 0, not inserting default user\n"; + } + + } else { + echo "Table users does not exist\n"; + } + } +} diff --git a/patch/ldaptools-php8.patch b/patches/ldaptools-php8.patch similarity index 100% rename from patch/ldaptools-php8.patch rename to patches/ldaptools-php8.patch diff --git a/phinx.php b/phinx.php new file mode 100644 index 0000000..7fa4117 --- /dev/null +++ b/phinx.php @@ -0,0 +1,27 @@ + [ + 'migrations' => '%%PHINX_CONFIG_DIR%%/migrations/migrations', + 'seeds' => '%%PHINX_CONFIG_DIR%%/migrations/seeds' + ], + 'environments' => [ + 'default_migration_table' => 'phinxlog', + 'default_environment' => 'production', + 'production' => [ + 'adapter' => 'mysql', + 'host' => $data['mysql']['db_host'], + 'name' => $data['mysql']['db'], + 'user' => $data['mysql']['db_user'], + 'pass' => $data['mysql']['db_password'], + 'port' => '3306', + 'charset' => 'utf8', + ], + // you can add development and testing environments here + ], + 'version_order' => 'creation' +]; diff --git a/setup/sql.sql b/setup/sql.sql deleted file mode 100644 index 53e9549..0000000 --- a/setup/sql.sql +++ /dev/null @@ -1,106 +0,0 @@ -USE ab; --- Remove the above line if your database name is not 'ab' -SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; -START TRANSACTION; -SET time_zone = "+00:00"; - -CREATE TABLE `arbeitszeiten` ( - `id` int(11) NOT NULL, - `name` varchar(256) NOT NULL, - `email` varchar(256) NOT NULL, - `schicht_tag` varchar(256) NOT NULL, - `schicht_anfang` varchar(256) NOT NULL, - `schicht_ende` varchar(256) NOT NULL, - `username` varchar(255) NOT NULL, - `ort` text DEFAULT NULL, - `active` tinyint(1) DEFAULT NULL, - `review` tinyint(1) DEFAULT NULL, - `type` varchar(11) DEFAULT NULL, - `pause_start` varchar(255) DEFAULT NULL, - `pause_end` varchar(255) DEFAULT NULL, - `attachements` text DEFAULT NULL, - `project` varchar(255) DEFAULT null -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE `kalender` ( - `id` int(11) NOT NULL, - `datum` text NOT NULL, - `uhrzeit` text NOT NULL, - `ort` text NOT NULL, - `notiz` text NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=latin1 COLLATE=latin1_german1_ci; - -CREATE TABLE `schicht` ( - `id` int(11) NOT NULL, - `name` varchar(256) NOT NULL, - `email` varchar(256) NOT NULL, - `schicht_gestartet_zeit` varchar(256) DEFAULT NULL, - `schicht_ende_zeit` varchar(256) DEFAULT NULL, - `schicht_datum` varchar(256) DEFAULT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE `sick` ( - `id` int(11) NOT NULL, - `username` varchar(256) NOT NULL, - `start` text NOT NULL, - `stop` text DEFAULT NULL, - `status` text NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE `users` ( - `id` int(11) NOT NULL, - `name` varchar(256) NOT NULL, - `username` varchar(255) NOT NULL, - `email` varchar(256) NOT NULL, - `password` varchar(256) NOT NULL, - `email_confirmed` tinyint(1) NOT NULL, - `isAdmin` varchar(256) NOT NULL, - `state` text DEFAULT NULL, - `easymode` tinyint(1) DEFAULT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE `vacation` ( - `id` int(11) NOT NULL, - `username` varchar(256) NOT NULL, - `start` text NOT NULL, - `stop` text DEFAULT NULL, - `status` text NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -ALTER TABLE `arbeitszeiten` - ADD PRIMARY KEY (`id`); - -ALTER TABLE `kalender` - ADD PRIMARY KEY (`id`); - -ALTER TABLE `schicht` - ADD PRIMARY KEY (`id`); - -ALTER TABLE `sick` - ADD PRIMARY KEY (`id`); - -ALTER TABLE `users` - ADD PRIMARY KEY (`id`), - ADD UNIQUE KEY `email` (`email`); - -ALTER TABLE `vacation` - ADD PRIMARY KEY (`id`); - -ALTER TABLE `arbeitszeiten` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; - -ALTER TABLE `kalender` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; - -ALTER TABLE `schicht` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; - -ALTER TABLE `sick` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; - -ALTER TABLE `users` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; - -ALTER TABLE `vacation` - MODIFY `id` int(11) NOT NULL AUTO_INCREMENT; -COMMIT; \ No newline at end of file diff --git a/setup/upgrade.php b/setup/upgrade.php deleted file mode 100644 index 9a345e0..0000000 --- a/setup/upgrade.php +++ /dev/null @@ -1,16 +0,0 @@ - 2, 2 -> 3, ... -$o = $updates->perform_migration($missingUpdate); - -?> \ No newline at end of file diff --git a/setup/usercreate.php b/setup/usercreate.php index bc1bf15..baf0464 100644 --- a/setup/usercreate.php +++ b/setup/usercreate.php @@ -4,6 +4,8 @@ * This script allows 3 input variables: * username, desired password and email * and returns an SQL statement to be run in the database + * + * @deprecated This script should not be used, the default user is created via phinx now */ if(isset($argv[1], $argv[2], $argv[3])){ diff --git a/suite/actions/auth/reset.php b/suite/actions/auth/reset.php index 1cfd4ed..ba4a836 100644 --- a/suite/actions/auth/reset.php +++ b/suite/actions/auth/reset.php @@ -56,10 +56,10 @@

Password forgot?

Please enter your new password in the below's form.

You will recieve a email after submiting your new password.

- + " hidden>
- + \ No newline at end of file diff --git a/suite/actions/worktime/add.php b/suite/actions/worktime/add.php index 7e526e4..8268857 100644 --- a/suite/actions/worktime/add.php +++ b/suite/actions/worktime/add.php @@ -2,8 +2,8 @@ require $_SERVER["DOCUMENT_ROOT"] . "/api/v1/inc/arbeit.inc.php"; use Arbeitszeit\Arbeitszeit; $worktime = new Arbeitszeit; -$base_url = $arbeit->get_app_ini()["general"]["base_url"]; -$arbeit->auth()->login_validation(); +$base_url = $worktime->get_app_ini()["general"]["base_url"]; +$worktime->auth()->login_validation(); $work = $worktime->add_worktime($_POST["time_start"], $_POST["time_end"], $_POST["ort"], $_POST["date"], $_POST["username"],"worktime", array("start" => $_POST["pause_start"], "end" => $_POST["pause_end"])); if(!$work){ header("Location: http://{$base_url}/suite/?info=error_worktime"); diff --git a/suite/admin/actions/users/edit_settings.php b/suite/admin/actions/users/edit_settings.php deleted file mode 100644 index 2069de2..0000000 --- a/suite/admin/actions/users/edit_settings.php +++ /dev/null @@ -1,26 +0,0 @@ -get_app_ini(); -$base_url = $ini["general"]["base_url"]; -$arbeit->auth()->login_validation(); -if($arbeit->benutzer()->is_admin($arbeit->benutzer()->get_user($_SESSION["username"]))){ - # build array - if(isset($_POST["app_name"])){ - $settings["app_name"] = $_POST["app_name"]; - } - if(isset($_POST["base_url"])){ - $settings["base_url"] = $_POST["base_url"]; - } - if(!isset($settings)){ - header("Location: http://{$base_url}/suite/?info=error"); - } - if($arbeit->change_settings($settings) == true){ - header("Location: http://{$base_url}/suite/?info=settings_changed"); - } -} else { - header("Location: http://{$base_url}/suite/?info=noperms"); -} -?> \ No newline at end of file diff --git a/suite/admin/users/edit.php b/suite/admin/users/edit.php index 861824d..5bfafc6 100644 --- a/suite/admin/users/edit.php +++ b/suite/admin/users/edit.php @@ -42,7 +42,7 @@

- +

diff --git a/suite/admin/users/settings.php b/suite/admin/users/settings.php index 4328552..60e5f62 100644 --- a/suite/admin/users/settings.php +++ b/suite/admin/users/settings.php @@ -12,17 +12,6 @@ ?>
-

- -

- - - " value=""> -
- " value=""> -
- -

" method="POST">

- - + +
diff --git a/suite/forgot_password.php b/suite/forgot_password.php index 7c2a86d..db5688d 100644 --- a/suite/forgot_password.php +++ b/suite/forgot_password.php @@ -1,6 +1,5 @@ get_app_ini(); @@ -19,7 +18,7 @@

- +
diff --git a/suite/login.php b/suite/login.php index 4b771b1..fea7d45 100644 --- a/suite/login.php +++ b/suite/login.php @@ -1,8 +1,11 @@ get_app_ini(); $base_url = $ini["general"]["base_url"]; echo $arbeit->check_status_code($_SERVER["REQUEST_URI"]); @@ -27,6 +30,16 @@
+ read_plugin_configuration("nfclogin")["enabled"] == "true"){ + require_once dirname(__DIR__, 1) . "/api/v1/class/plugins/plugins/nfclogin/src/Main.php"; + $nfc = new NFClogin; + echo "
" . $nfc->nfcloginHtml() . "
"; + } + + + ?> \ No newline at end of file diff --git a/suite/notifications/view.php b/suite/notifications/view.php index 836d63b..4b19e51 100644 --- a/suite/notifications/view.php +++ b/suite/notifications/view.php @@ -6,6 +6,12 @@ $arbeit->auth()->login_validation(); $id = $_GET["id"]; $data = $arbeit->notifications()->get_notifications_entry($id); + +if(isset($data["error"])){ + header("Location: /suite/?info=notification_not_found"); + exit; +} + $loc = $arbeit->i18n()->loadLanguage(null, "notifications/view"); $iid = htmlspecialchars($id); ?> diff --git a/suite/users/settings.php b/suite/users/settings.php index 4962f61..52a9abe 100644 --- a/suite/users/settings.php +++ b/suite/users/settings.php @@ -45,6 +45,7 @@

: ">

|

+

TimeTrack Version: getTimeTrackVersion(); ?> | API Version: getToilVersion(); ?>

benutzer()->is_admin($arbeit->benutzer()->get_user($_SESSION["username"]))){ require_once "../admin/users/settings.php"; } ?>
diff --git a/suite/worktime/sick/index.php b/suite/worktime/sick/index.php index 68d4ed6..47998b6 100644 --- a/suite/worktime/sick/index.php +++ b/suite/worktime/sick/index.php @@ -21,10 +21,10 @@

- +

:

- +
diff --git a/suite/worktime/vacation/index.php b/suite/worktime/vacation/index.php index 8d33a85..36550b5 100644 --- a/suite/worktime/vacation/index.php +++ b/suite/worktime/vacation/index.php @@ -23,10 +23,10 @@

- +

:

- +