Skip to content

Commit eb18618

Browse files
author
Pascal MARTINEZ
committed
First published version.
0 parents  commit eb18618

14 files changed

Lines changed: 2036 additions & 0 deletions

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# CHANGE LOG: User sessions (z4m_usersessions)
2+
3+
## Version 1.0, 2025-04-21
4+
First version.

LICENSE.TXT

Lines changed: 674 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# ZnetDK 4 Mobile module: User sessions (z4m_usersessions)
2+
![Screenshot of the User sessions view provided by the ZnetDK 4 Mobile 'z4m_usersessions' module](https://mobile.znetdk.fr/applications/default/public/images/modules/z4m_usersessions/screenshot1.jpg?v1.0)
3+
The **z4m_usersessions** module adds the ability to display PHP user sessions to the [ZnetDK 4 Mobile](/../../../znetdk4mobile) Starter application.
4+
5+
## LICENCE
6+
This module is published under the [version 3 of GPL General Public Licence](LICENSE.TXT).
7+
8+
## FEATURES
9+
![Screenshot of the User sessions configuration modal dialog of the 'z4m_usersessions' module](https://mobile.znetdk.fr/applications/default/public/images/modules/z4m_usersessions/screenshot2.jpg?v1.0)
10+
- Shows the existing user sessions of the application,
11+
- Displays the PHP and ZnetDK session configuration,
12+
- Kills sessions of a specific user,
13+
- Cleans expired sessions (via UI and a web service call),
14+
- Kills all existing sessions (via UI and a web service call).
15+
16+
## REQUIREMENTS
17+
- [ZnetDK 4 Mobile](/../../../znetdk4mobile) version 2.9 or higher,
18+
- A **MySQL** database [is configured](https://mobile.znetdk.fr/getting-started#z4m-gs-connect-config) to store the application data,
19+
- **PHP version 7.4** or higher,
20+
- Authentication is enabled
21+
([`CFG_AUTHENT_REQUIRED`](https://mobile.znetdk.fr/settings#z4m-settings-auth-required)
22+
is `TRUE` in the App's
23+
[`config.php`](/../../../znetdk4mobile/blob/master/applications/default/app/config.php)).
24+
25+
## INSTALLATION
26+
1. Add a new subdirectory named `z4m_usersessions` within the
27+
[`./engine/modules/`](/../../../znetdk4mobile/tree/master/engine/modules/) subdirectory of your
28+
ZnetDK 4 Mobile starter App,
29+
2. Copy module's code in the new `./engine/modules/z4m_usersessions/` subdirectory,
30+
or from your IDE, pull the code from this module's GitHub repository,
31+
3. Edit the App's [`menu.php`](/../../../znetdk4mobile/blob/master/applications/default/app/menu.php)
32+
located in the [`./applications/default/app/`](/../../../znetdk4mobile/tree/master/applications/default/app/)
33+
subfolder and add a new menu item definition for the view `z4m_usersessions`.
34+
For example:
35+
```php
36+
\MenuManager::addMenuItem(NULL, 'z4m_usersessions', MOD_Z4M_USERSESSIONS_MENU_LABEL, 'fa-ticket');
37+
```
38+
4. Go to the **User sessions** menu to display the users sessions.
39+
40+
## USERS GRANTED TO MODULE FEATURES
41+
Once the **User sessions** menu item is added to the application, you can restrict
42+
its access via a [user profile](https://mobile.znetdk.fr/settings#z4m-settings-user-rights).
43+
For example:
44+
1. Create a user profile named `Admin` from the **Authorizations | Profiles** menu,
45+
2. Select for this new profile, the **User sessions** menu item,
46+
3. Finally for each allowed user, add them the `Admin` profile from the
47+
**Authorizations | Users** menu.
48+
49+
## RECOMMANDED USER SESSION CONFIGURATION
50+
### PHP configuration
51+
- [session.name](https://www.php.net/manual/en/session.configuration.php#ini.session.name): change `PHPSESSID` to a more common name. For example `id`.
52+
- [session.save_path](https://www.php.net/manual/en/session.configuration.php#ini.session.save-path): a dedicated directory must be created to store the PHP session files of the application.
53+
- [session.gc_maxlifetime](https://www.php.net/manual/en/session.configuration.php#ini.session.gc-maxlifetime): don't exceed if possible a value of `14400` seconds (4 hours).
54+
- [session.use_strict_mode](https://www.php.net/manual/en/session.configuration.php#ini.session.use-strict-mode): recommended value is `1` for security purpose.
55+
### ZnetDK configuration
56+
- [CFG_SESSION_ONLY_ONE_PER_USER](https://mobile.znetdk.fr/settings#z4m-settings-auth-session-only-one-per-user): value `true` to avoid the same user to log in on multiple devices with the same login name.
57+
58+
## CLEANING OLD SESSIONS AUTOMATICALLY
59+
It is recommended to clean expired PHP sessions every hour and to remove all PHP session files every day.
60+
To do this, you can call the appropriate module web services from your crontab as shown below.
61+
62+
```
63+
# Clean expired PHP sessions every hour
64+
47 * * * * nice curl "https://mydomain/myapp/?control=Z4MUserSessionsCtrl&action=clean" > /home/log/session_clean.log ?>&1
65+
# Remove all PHP session files every day
66+
09 23 * * * nice curl "https://webserviceusr:password@mydomain/myapp/?control=Z4MUserSessionsCtrl&action=killall" > /home/log/session_kill.log ?>&1
67+
```
68+
No authentication is necessary to call the `Z4MUserSessionCtrl:clean` controller action.
69+
70+
On the other hand, authentication is required to call the `Z4MUserSessionCtrl:killall` controller action as it is more sensitive.
71+
For example, to authorize the user `webserviceusr` (you can name your web service user as you like) to run this web service, apply the procedure below:
72+
1. Declare a new user named `webserviceusr` in the App. This user does not need any rights so be sure the option "Full menu access" is unchecked and no User profile is selected.
73+
2. Define [`CFG_HTTP_BASIC_AUTHENTICATION_ENABLED`](https://mobile.znetdk.fr/settings#z4m-settings-webservices-basic-auth) constant to `TRUE` in the [`config.php`](/../../../znetdk4mobile/blob/master/applications/default/app/config.php) of your App.
74+
```php
75+
define('CFG_HTTP_BASIC_AUTHENTICATION_ENABLED', TRUE);
76+
```
77+
3. Configure access to the `Z4MUserSessionCtrl:killall` controller action through the [`CFG_ACTIONS_ALLOWED_FOR_WEBSERVICE_USERS`](https://mobile.znetdk.fr/settings#z4m-settings-webservices-actions-allowed) constant also defined in the [`config.php`](/../../../znetdk4mobile/blob/master/applications/default/app/config.php) of your App.
78+
```php
79+
define('CFG_ACTIONS_ALLOWED_FOR_WEBSERVICE_USERS', serialize([
80+
'webserviceusr|Z4MUserSessionCtrl:killall'
81+
]));
82+
```
83+
84+
## TRANSLATIONS
85+
This module is translated in **French**, **English** and **Spanish** languages.
86+
To translate this module in another language or change the standard
87+
translations:
88+
1. Copy in the clipboard the PHP constants declared within the
89+
[`locale_en.php`](mod/lang/locale_en.php) script of the module,
90+
2. Paste them from the clipboard within the
91+
[`locale.php`](/../../../znetdk4mobile/blob/master/applications/default/app/lang/locale.php) script of your application,
92+
3. Finally, translate each text associated with these PHP constants into your own language.
93+
94+
## CHANGE LOG
95+
See [CHANGELOG.md](CHANGELOG.md) file.
96+
97+
## CONTRIBUTING
98+
Your contribution to the **ZnetDK 4 Mobile** project is welcome. Please refer to the [CONTRIBUTING.md](https://github.com/pascal-martinez/znetdk4mobile/blob/master/CONTRIBUTING.md) file.

mod/UserSessionFile.php

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
<?php
2+
3+
/*
4+
* ZnetDK, Starter Web Application for rapid & easy development
5+
* See official website https://www.znetdk.fr
6+
* Copyright (C) 2025 Pascal MARTINEZ (contact@znetdk.fr)
7+
* License GNU GPL http://www.gnu.org/licenses/gpl-3.0.html GNU GPL
8+
* --------------------------------------------------------------------
9+
* This program is free software: you can redistribute it and/or modify
10+
* it under the terms of the GNU General Public License as published by
11+
* the Free Software Foundation, either version 3 of the License, or
12+
* (at your option) any later version.
13+
* This program is distributed in the hope that it will be useful,
14+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16+
* GNU General Public License for more details.
17+
* You should have received a copy of the GNU General Public License
18+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
* --------------------------------------------------------------------
20+
* ZnetDK 4 Mobile User sessions module PHP class
21+
*
22+
* File version: 1.0
23+
* Last update: 04/24/2025
24+
*/
25+
26+
namespace z4m_usersessions\mod;
27+
28+
/**
29+
* Retrieves session data from a PHP session file
30+
*/
31+
class UserSessionFile {
32+
protected $filePath;
33+
protected $sessionId;
34+
protected $fileModificationTimestamp;
35+
protected $sessionData;
36+
/**
37+
* Constructor of the UserSessionFile class
38+
* @param string|NULL $sessionFilePath Absolute file path of the user session
39+
* file. If is NULL, the session file is the one matching the current user's
40+
* session.
41+
* @throws \Exception File is missing or its name does not match a user
42+
* session file
43+
*/
44+
public function __construct($sessionFilePath = NULL) {
45+
if (is_null($sessionFilePath)) {
46+
$sessionFilePath = session_save_path() . DIRECTORY_SEPARATOR . 'sess_' . session_id();
47+
}
48+
if (!file_exists($sessionFilePath)) {
49+
throw new \Exception("File '{$sessionFilePath}' does not exist.");
50+
}
51+
$basename = basename($sessionFilePath);
52+
$basenameParts = explode('_', $basename);
53+
if (count($basenameParts) !== 2 || $basenameParts[0] !== 'sess') {
54+
throw new \Exception("File '{$sessionFilePath}' does not match a session file name.");
55+
}
56+
$this->sessionId = $basenameParts[1];
57+
$this->fileModificationTimestamp = filemtime($sessionFilePath);
58+
$this->filePath = $sessionFilePath;
59+
$this->decodeSessionData();
60+
}
61+
62+
/**
63+
* Decodes session data
64+
* @throws \Exception Unable to read file content or file content is empty.
65+
*/
66+
protected function decodeSessionData() {
67+
$sessionData = file_get_contents($this->filePath);
68+
if (!is_string($sessionData)) {
69+
throw new \Exception("Unable to read content of the session file '{$this->filePath}'.");
70+
}
71+
if(strlen($sessionData) === 0) {
72+
throw new \Exception("Session file '{$this->filePath}' is empty.");
73+
}
74+
$decodedData = [];
75+
while ($i = strpos($sessionData, '|'))
76+
{
77+
$key = substr($sessionData, 0, $i);
78+
$value = unserialize(substr($sessionData, 1 + $i));
79+
$sessionData = substr($sessionData, 1 + $i + strlen(serialize($value)));
80+
$decodedData[$key] = $value;
81+
}
82+
$this->sessionData = $decodedData;
83+
}
84+
85+
/**
86+
* Returns the PHP session ID
87+
* @return string The user PHP session ID.
88+
*/
89+
public function getSessionId() {
90+
return $this->sessionId;
91+
}
92+
93+
/**
94+
* Returns the modification time of the user session file
95+
* @return int Unix timestamp
96+
*/
97+
public function getFileModificationTimestamp() {
98+
return $this->fileModificationTimestamp;
99+
}
100+
101+
/**
102+
* Returns the unserialized user session data of the user session file
103+
* @return array The user session data of the user session file
104+
*/
105+
public function getSessionData() {
106+
return $this->sessionData;
107+
}
108+
109+
/**
110+
* Returns the session file path
111+
* @return string File path
112+
*/
113+
public function getFilePath() {
114+
return $this->filePath;
115+
}
116+
117+
/**
118+
* Checks if the session file matches the current user's session.
119+
* @return boolean Value TRUE if the session file matches the current user's
120+
* session, FALSE otherwise.
121+
*/
122+
public function doesMatchCurrentUserSession() {
123+
return $this->sessionId === session_id();
124+
}
125+
126+
/**
127+
* Removes the session file
128+
* @throws \Exception The session file can't be removed.
129+
*/
130+
public function remove() {
131+
if (!unlink($this->filePath)) {
132+
throw new \Exception("Unable to remove '{$this->filePath}' session file.");
133+
}
134+
}
135+
136+
}

0 commit comments

Comments
 (0)