Skip to content

Commit 8893de0

Browse files
committed
session manager
1 parent 2eb1fbe commit 8893de0

24 files changed

Lines changed: 1656 additions & 291 deletions

CHANGELOG.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ The present file will list all changes made to the project; according to the
77
## [12.0.0] unreleased
88

99
### Added
10-
- Sessions tab for OAuth Clients to display non-expired sessions associated with the client and allow revoking them.
1110
- Improved client IP detection.
1211
If your GLPI instance is behind a reverse proxy, you should add its IP(s) the new `GLPI_TRUSTED_REVERSE_PROXIES` constant and modify the new `GLPI_REVERSE_PROXY_HEADERS` constant to include the headers your proxy uses to forward the client IP.
1312
Only the required HTTP headers should be listed for better security as any header not handled by the proxy could be spoofed by the client.

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
"league/oauth2-client": "^2.9",
5151
"league/oauth2-google": "^5.0",
5252
"league/oauth2-server": "^9.3",
53+
"matomo/device-detector": "^6.5",
5354
"mexitek/phpcolors": "^1.0",
5455
"monolog/monolog": "^3.10",
5556
"paragonie/sodium_compat": "^2.5",

composer.lock

Lines changed: 124 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
<?php
2+
3+
/**
4+
* ---------------------------------------------------------------------
5+
*
6+
* GLPI - Gestionnaire Libre de Parc Informatique
7+
*
8+
* http://glpi-project.org
9+
*
10+
* @copyright 2015-2026 Teclib' and contributors.
11+
* @licence https://www.gnu.org/licenses/gpl-3.0.html
12+
*
13+
* ---------------------------------------------------------------------
14+
*
15+
* LICENSE
16+
*
17+
* This file is part of GLPI.
18+
*
19+
* This program is free software: you can redistribute it and/or modify
20+
* it under the terms of the GNU General Public License as published by
21+
* the Free Software Foundation, either version 3 of the License, or
22+
* (at your option) any later version.
23+
*
24+
* This program is distributed in the hope that it will be useful,
25+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
26+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27+
* GNU General Public License for more details.
28+
*
29+
* You should have received a copy of the GNU General Public License
30+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
31+
*
32+
* ---------------------------------------------------------------------
33+
*/
34+
35+
/**
36+
* @var Migration $migration
37+
* @var DBmysql $DB
38+
*/
39+
40+
use Ramsey\Uuid\Uuid;
41+
42+
if (!$DB->tableExists('glpi_user_sessions')) {
43+
$DB->doQuery(<<<SQL
44+
CREATE TABLE `glpi_user_sessions` (
45+
`id` int unsigned NOT NULL AUTO_INCREMENT,
46+
`users_id` int unsigned NOT NULL,
47+
`session_token_hash` varchar(64) NOT NULL,
48+
`session_file` varchar(261) NOT NULL COMMENT 'Session filename. PHP allows up to 256 characters for session IDs + the "sess_" prefix used by default.',
49+
`ip_address` varchar(45) NOT NULL,
50+
`user_agent` varchar(512) NOT NULL,
51+
`auth_type` tinyint NOT NULL,
52+
`created_at` timestamp NOT NULL,
53+
`last_activity_at` timestamp NOT NULL,
54+
PRIMARY KEY (`id`),
55+
UNIQUE KEY `session_token_hash` (`session_token_hash`),
56+
KEY `users_id` (`users_id`),
57+
KEY `last_activity_at` (`last_activity_at`)
58+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
59+
SQL);
60+
}
61+
62+
if (!$DB->tableExists('glpi_user_session_history')) {
63+
$DB->doQuery(<<<SQL
64+
CREATE TABLE `glpi_user_session_history` (
65+
`id` int unsigned NOT NULL AUTO_INCREMENT,
66+
`users_id` int unsigned NOT NULL,
67+
`session_token_hash` varchar(64) NOT NULL,
68+
`ip_address` varchar(45) NOT NULL,
69+
`user_agent` varchar(512) NOT NULL,
70+
`auth_type` tinyint NOT NULL,
71+
`logged_in_at` timestamp NOT NULL,
72+
`logged_out_at` timestamp NULL DEFAULT NULL,
73+
`logout_reason` enum('user', 'admin', 'expired') DEFAULT NULL,
74+
`users_id_revoked_by` int unsigned DEFAULT NULL,
75+
PRIMARY KEY (`id`),
76+
UNIQUE KEY `session_token_hash` (`session_token_hash`),
77+
KEY `users_id` (`users_id`, `logged_in_at` DESC),
78+
KEY `users_id_revoked_by` (`users_id_revoked_by`),
79+
KEY `logged_out_at` (`logged_out_at`)
80+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
81+
SQL);
82+
}
83+
84+
// Add a uuid column to glpi_oauth_access_tokens without NOT NULL constraint, generate UUIDs for existing records, then add the NOT NULL constraint
85+
if (!$DB->fieldExists('glpi_oauth_access_tokens', 'uuid')) {
86+
$migration->addField('glpi_oauth_access_tokens', 'uuid', 'varchar(255)', ['null' => true, 'after' => 'identifier']);
87+
$migration->migrationOneTable('glpi_oauth_access_tokens');
88+
// Generating UUIDs in PHP because MySQL only has UUID() which is v1 and typically expect v4.
89+
$count = countElementsInTable('glpi_oauth_access_tokens', ['uuid' => null]);
90+
$it = $DB->request([
91+
'SELECT' => ['identifier'],
92+
'FROM' => 'glpi_oauth_access_tokens',
93+
'WHERE' => ['uuid' => null],
94+
]);
95+
foreach ($it as $row) {
96+
$DB->update('glpi_oauth_access_tokens', ['uuid' => Uuid::uuid4()->toString()], ['identifier' => $row['identifier']]);
97+
}
98+
$migration->changeField('glpi_oauth_access_tokens', 'uuid', 'uuid', 'varchar(255)', ['null' => false, 'after' => 'identifier']);
99+
}

install/mysql/glpi-empty.sql

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10024,12 +10024,14 @@ CREATE TABLE `glpi_changesatisfactions` (
1002410024
DROP TABLE IF EXISTS `glpi_oauth_access_tokens`;
1002510025
CREATE TABLE `glpi_oauth_access_tokens` (
1002610026
`identifier` varchar(255) NOT NULL,
10027+
`uuid` varchar(255) NOT NULL,
1002710028
`client` varchar(255) NOT NULL,
1002810029
`date_expiration` timestamp NOT NULL,
1002910030
`user_identifier` varchar(255) DEFAULT NULL,
1003010031
`scopes` text DEFAULT NULL,
1003110032
`ip_address` varchar(45) DEFAULT NULL,
1003210033
PRIMARY KEY (`identifier`),
10034+
KEY `uuid` (`uuid`),
1003310035
KEY `client` (`client`)
1003410036
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
1003510037

@@ -10502,8 +10504,6 @@ CREATE TABLE `glpi_itemtranslations_itemtranslations` (
1050210504
KEY `item` (`itemtype`, `items_id`)
1050310505
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
1050410506

10505-
### Dump table glpi_sharetokens
10506-
1050710507
DROP TABLE IF EXISTS `glpi_sharetokens`;
1050810508
CREATE TABLE `glpi_sharetokens` (
1050910509
`id` int unsigned NOT NULL AUTO_INCREMENT,
@@ -10529,4 +10529,41 @@ CREATE TABLE `glpi_sharetokens` (
1052910529
KEY `date_expiration` (`date_expiration`)
1053010530
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
1053110531

10532+
10533+
DROP TABLE IF EXISTS `glpi_user_sessions`;
10534+
CREATE TABLE `glpi_user_sessions` (
10535+
`id` int unsigned NOT NULL AUTO_INCREMENT,
10536+
`users_id` int unsigned NOT NULL,
10537+
`session_token_hash` varchar(64) NOT NULL,
10538+
`session_file` varchar(261) NOT NULL COMMENT 'Session filename. PHP allows up to 256 characters for session IDs + the "sess_" prefix used by default.',
10539+
`ip_address` varchar(45) NOT NULL,
10540+
`user_agent` varchar(512) NOT NULL,
10541+
`auth_type` tinyint NOT NULL,
10542+
`created_at` timestamp NOT NULL,
10543+
`last_activity_at` timestamp NOT NULL,
10544+
PRIMARY KEY (`id`),
10545+
UNIQUE KEY `session_token_hash` (`session_token_hash`),
10546+
KEY `users_id` (`users_id`),
10547+
KEY `last_activity_at` (`last_activity_at`)
10548+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
10549+
10550+
DROP TABLE IF EXISTS `glpi_user_session_history`;
10551+
CREATE TABLE `glpi_user_session_history` (
10552+
`id` int unsigned NOT NULL AUTO_INCREMENT,
10553+
`users_id` int unsigned NOT NULL,
10554+
`session_token_hash` varchar(64) NOT NULL,
10555+
`ip_address` varchar(45) NOT NULL,
10556+
`user_agent` varchar(512) NOT NULL,
10557+
`auth_type` tinyint NOT NULL,
10558+
`logged_in_at` timestamp NOT NULL,
10559+
`logged_out_at` timestamp NULL DEFAULT NULL,
10560+
`logout_reason` enum('user', 'admin', 'expired') DEFAULT NULL,
10561+
`users_id_revoked_by` int unsigned DEFAULT NULL,
10562+
PRIMARY KEY (`id`),
10563+
UNIQUE KEY `session_token_hash` (`session_token_hash`),
10564+
KEY `users_id` (`users_id`, `logged_in_at` DESC),
10565+
KEY `users_id_revoked_by` (`users_id_revoked_by`),
10566+
KEY `logged_out_at` (`logged_out_at`)
10567+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC;
10568+
1053210569
SET FOREIGN_KEY_CHECKS=1;

src/Auth.php

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1773,4 +1773,12 @@ public static function setRememberMeCookie(string $cookie_value): void
17731773
$_COOKIE[$cookie_name] = $cookie_value;
17741774
}
17751775
}
1776+
1777+
/**
1778+
* @return int
1779+
*/
1780+
public function getAuthType(): int
1781+
{
1782+
return $this->auth_type;
1783+
}
17761784
}

src/CronTask.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
use Glpi\DBAL\QueryFunction;
4242
use Glpi\Error\ErrorHandler;
4343
use Glpi\Event;
44+
use Glpi\Security\SessionTracker;
4445
use Safe\Exceptions\FilesystemException;
4546
use Safe\Exceptions\InfoException;
4647

@@ -1552,12 +1553,14 @@ public static function cronSession(self $task): int
15521553
// Delete session file if not delete before
15531554
try {
15541555
@unlink($filename);
1556+
$base_filename = basename($filename);
15551557
++$nb;
15561558
} catch (FilesystemException $e) {
1557-
//mepty catch
1559+
//empty catch
15581560
}
15591561
}
15601562
}
1563+
SessionTracker::revokeSessionsByAge($maxlifetime);
15611564

15621565
$task->setVolume($nb);
15631566
if ($nb) {

src/DBmysqlIterator.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class DBmysqlIterator implements SeekableIterator, Countable
9292
'|',
9393
'IN',
9494
'NOT IN',
95+
'BETWEEN',
9596
];
9697

9798
/**

0 commit comments

Comments
 (0)