aboutsummaryrefslogtreecommitdiffhomepage
path: root/application/security
diff options
context:
space:
mode:
Diffstat (limited to 'application/security')
-rw-r--r--application/security/BanManager.php30
-rw-r--r--application/security/CookieManager.php33
-rw-r--r--application/security/LoginManager.php83
-rw-r--r--application/security/SessionManager.php111
4 files changed, 190 insertions, 67 deletions
diff --git a/application/security/BanManager.php b/application/security/BanManager.php
index 68190c54..288cbde0 100644
--- a/application/security/BanManager.php
+++ b/application/security/BanManager.php
@@ -3,7 +3,8 @@
3 3
4namespace Shaarli\Security; 4namespace Shaarli\Security;
5 5
6use Shaarli\FileUtils; 6use Psr\Log\LoggerInterface;
7use Shaarli\Helper\FileUtils;
7 8
8/** 9/**
9 * Class BanManager 10 * Class BanManager
@@ -28,8 +29,8 @@ class BanManager
28 /** @var string Path to the file containing IP bans and failures */ 29 /** @var string Path to the file containing IP bans and failures */
29 protected $banFile; 30 protected $banFile;
30 31
31 /** @var string Path to the log file, used to log bans */ 32 /** @var LoggerInterface Path to the log file, used to log bans */
32 protected $logFile; 33 protected $logger;
33 34
34 /** @var array List of IP with their associated number of failed attempts */ 35 /** @var array List of IP with their associated number of failed attempts */
35 protected $failures = []; 36 protected $failures = [];
@@ -40,18 +41,19 @@ class BanManager
40 /** 41 /**
41 * BanManager constructor. 42 * BanManager constructor.
42 * 43 *
43 * @param array $trustedProxies List of allowed proxies IP 44 * @param array $trustedProxies List of allowed proxies IP
44 * @param int $nbAttempts Number of allowed failed attempt before the ban 45 * @param int $nbAttempts Number of allowed failed attempt before the ban
45 * @param int $banDuration Ban duration in seconds 46 * @param int $banDuration Ban duration in seconds
46 * @param string $banFile Path to the file containing IP bans and failures 47 * @param string $banFile Path to the file containing IP bans and failures
47 * @param string $logFile Path to the log file, used to log bans 48 * @param LoggerInterface $logger PSR-3 logger to save login attempts in log directory
48 */ 49 */
49 public function __construct($trustedProxies, $nbAttempts, $banDuration, $banFile, $logFile) { 50 public function __construct($trustedProxies, $nbAttempts, $banDuration, $banFile, LoggerInterface $logger) {
50 $this->trustedProxies = $trustedProxies; 51 $this->trustedProxies = $trustedProxies;
51 $this->nbAttempts = $nbAttempts; 52 $this->nbAttempts = $nbAttempts;
52 $this->banDuration = $banDuration; 53 $this->banDuration = $banDuration;
53 $this->banFile = $banFile; 54 $this->banFile = $banFile;
54 $this->logFile = $logFile; 55 $this->logger = $logger;
56
55 $this->readBanFile(); 57 $this->readBanFile();
56 } 58 }
57 59
@@ -78,11 +80,7 @@ class BanManager
78 80
79 if ($this->failures[$ip] >= $this->nbAttempts) { 81 if ($this->failures[$ip] >= $this->nbAttempts) {
80 $this->bans[$ip] = time() + $this->banDuration; 82 $this->bans[$ip] = time() + $this->banDuration;
81 logm( 83 $this->logger->info(format_log('IP address banned from login: '. $ip, $ip));
82 $this->logFile,
83 $server['REMOTE_ADDR'],
84 'IP address banned from login: '. $ip
85 );
86 } 84 }
87 $this->writeBanFile(); 85 $this->writeBanFile();
88 } 86 }
@@ -138,7 +136,7 @@ class BanManager
138 unset($this->failures[$ip]); 136 unset($this->failures[$ip]);
139 } 137 }
140 unset($this->bans[$ip]); 138 unset($this->bans[$ip]);
141 logm($this->logFile, $server['REMOTE_ADDR'], 'Ban lifted for: '. $ip); 139 $this->logger->info(format_log('Ban lifted for: '. $ip, $ip));
142 140
143 $this->writeBanFile(); 141 $this->writeBanFile();
144 return false; 142 return false;
diff --git a/application/security/CookieManager.php b/application/security/CookieManager.php
new file mode 100644
index 00000000..cde4746e
--- /dev/null
+++ b/application/security/CookieManager.php
@@ -0,0 +1,33 @@
1<?php
2
3declare(strict_types=1);
4
5namespace Shaarli\Security;
6
7class CookieManager
8{
9 /** @var string Name of the cookie set after logging in **/
10 public const STAY_SIGNED_IN = 'shaarli_staySignedIn';
11
12 /** @var mixed $_COOKIE set by reference */
13 protected $cookies;
14
15 public function __construct(array &$cookies)
16 {
17 $this->cookies = $cookies;
18 }
19
20 public function setCookieParameter(string $key, string $value, int $expires, string $path): self
21 {
22 $this->cookies[$key] = $value;
23
24 setcookie($key, $value, $expires, $path);
25
26 return $this;
27 }
28
29 public function getCookieParameter(string $key, string $default = null): ?string
30 {
31 return $this->cookies[$key] ?? $default;
32 }
33}
diff --git a/application/security/LoginManager.php b/application/security/LoginManager.php
index 39ec9b2e..426e785e 100644
--- a/application/security/LoginManager.php
+++ b/application/security/LoginManager.php
@@ -2,6 +2,7 @@
2namespace Shaarli\Security; 2namespace Shaarli\Security;
3 3
4use Exception; 4use Exception;
5use Psr\Log\LoggerInterface;
5use Shaarli\Config\ConfigManager; 6use Shaarli\Config\ConfigManager;
6 7
7/** 8/**
@@ -9,9 +10,6 @@ use Shaarli\Config\ConfigManager;
9 */ 10 */
10class LoginManager 11class LoginManager
11{ 12{
12 /** @var string Name of the cookie set after logging in **/
13 public static $STAY_SIGNED_IN_COOKIE = 'shaarli_staySignedIn';
14
15 /** @var array A reference to the $_GLOBALS array */ 13 /** @var array A reference to the $_GLOBALS array */
16 protected $globals = []; 14 protected $globals = [];
17 15
@@ -32,24 +30,32 @@ class LoginManager
32 30
33 /** @var string User sign-in token depending on remote IP and credentials */ 31 /** @var string User sign-in token depending on remote IP and credentials */
34 protected $staySignedInToken = ''; 32 protected $staySignedInToken = '';
33 /** @var CookieManager */
34 protected $cookieManager;
35 /** @var LoggerInterface */
36 protected $logger;
35 37
36 /** 38 /**
37 * Constructor 39 * Constructor
38 * 40 *
39 * @param ConfigManager $configManager Configuration Manager instance 41 * @param ConfigManager $configManager Configuration Manager instance
40 * @param SessionManager $sessionManager SessionManager instance 42 * @param SessionManager $sessionManager SessionManager instance
43 * @param CookieManager $cookieManager CookieManager instance
44 * @param BanManager $banManager
45 * @param LoggerInterface $logger Used to log login attempts
41 */ 46 */
42 public function __construct($configManager, $sessionManager) 47 public function __construct(
43 { 48 ConfigManager $configManager,
49 SessionManager $sessionManager,
50 CookieManager $cookieManager,
51 BanManager $banManager,
52 LoggerInterface $logger
53 ) {
44 $this->configManager = $configManager; 54 $this->configManager = $configManager;
45 $this->sessionManager = $sessionManager; 55 $this->sessionManager = $sessionManager;
46 $this->banManager = new BanManager( 56 $this->cookieManager = $cookieManager;
47 $this->configManager->get('security.trusted_proxies', []), 57 $this->banManager = $banManager;
48 $this->configManager->get('security.ban_after'), 58 $this->logger = $logger;
49 $this->configManager->get('security.ban_duration'),
50 $this->configManager->get('resource.ban_file', 'data/ipbans.php'),
51 $this->configManager->get('resource.log')
52 );
53 59
54 if ($this->configManager->get('security.open_shaarli') === true) { 60 if ($this->configManager->get('security.open_shaarli') === true) {
55 $this->openShaarli = true; 61 $this->openShaarli = true;
@@ -86,10 +92,9 @@ class LoginManager
86 /** 92 /**
87 * Check user session state and validity (expiration) 93 * Check user session state and validity (expiration)
88 * 94 *
89 * @param array $cookie The $_COOKIE array
90 * @param string $clientIpId Client IP address identifier 95 * @param string $clientIpId Client IP address identifier
91 */ 96 */
92 public function checkLoginState($cookie, $clientIpId) 97 public function checkLoginState($clientIpId)
93 { 98 {
94 if (! $this->configManager->exists('credentials.login')) { 99 if (! $this->configManager->exists('credentials.login')) {
95 // Shaarli is not configured yet 100 // Shaarli is not configured yet
@@ -97,9 +102,7 @@ class LoginManager
97 return; 102 return;
98 } 103 }
99 104
100 if (isset($cookie[self::$STAY_SIGNED_IN_COOKIE]) 105 if ($this->staySignedInToken === $this->cookieManager->getCookieParameter(CookieManager::STAY_SIGNED_IN)) {
101 && $cookie[self::$STAY_SIGNED_IN_COOKIE] === $this->staySignedInToken
102 ) {
103 // The user client has a valid stay-signed-in cookie 106 // The user client has a valid stay-signed-in cookie
104 // Session information is updated with the current client information 107 // Session information is updated with the current client information
105 $this->sessionManager->storeLoginInfo($clientIpId); 108 $this->sessionManager->storeLoginInfo($clientIpId);
@@ -120,7 +123,7 @@ class LoginManager
120 * 123 *
121 * @return true when the user is logged in, false otherwise 124 * @return true when the user is logged in, false otherwise
122 */ 125 */
123 public function isLoggedIn() 126 public function isLoggedIn(): bool
124 { 127 {
125 if ($this->openShaarli) { 128 if ($this->openShaarli) {
126 return true; 129 return true;
@@ -131,48 +134,34 @@ class LoginManager
131 /** 134 /**
132 * Check user credentials are valid 135 * Check user credentials are valid
133 * 136 *
134 * @param string $remoteIp Remote client IP address
135 * @param string $clientIpId Client IP address identifier 137 * @param string $clientIpId Client IP address identifier
136 * @param string $login Username 138 * @param string $login Username
137 * @param string $password Password 139 * @param string $password Password
138 * 140 *
139 * @return bool true if the provided credentials are valid, false otherwise 141 * @return bool true if the provided credentials are valid, false otherwise
140 */ 142 */
141 public function checkCredentials($remoteIp, $clientIpId, $login, $password) 143 public function checkCredentials($clientIpId, $login, $password)
142 { 144 {
143 // Check login matches config
144 if ($login !== $this->configManager->get('credentials.login')) {
145 return false;
146 }
147
148 // Check credentials 145 // Check credentials
149 try { 146 try {
150 $useLdapLogin = !empty($this->configManager->get('ldap.host')); 147 $useLdapLogin = !empty($this->configManager->get('ldap.host'));
151 if ((false === $useLdapLogin && $this->checkCredentialsFromLocalConfig($login, $password)) 148 if ($login === $this->configManager->get('credentials.login')
152 || (true === $useLdapLogin && $this->checkCredentialsFromLdap($login, $password)) 149 && (
150 (false === $useLdapLogin && $this->checkCredentialsFromLocalConfig($login, $password))
151 || (true === $useLdapLogin && $this->checkCredentialsFromLdap($login, $password))
152 )
153 ) { 153 ) {
154 $this->sessionManager->storeLoginInfo($clientIpId); 154 $this->sessionManager->storeLoginInfo($clientIpId);
155 logm( 155 $this->logger->info(format_log('Login successful', $clientIpId));
156 $this->configManager->get('resource.log'), 156
157 $remoteIp, 157 return true;
158 'Login successful'
159 );
160 return true;
161 } 158 }
162 } 159 } catch(Exception $exception) {
163 catch(Exception $exception) { 160 $this->logger->info(format_log('Exception while checking credentials: ' . $exception, $clientIpId));
164 logm(
165 $this->configManager->get('resource.log'),
166 $remoteIp,
167 'Exception while checking credentials: ' . $exception
168 );
169 } 161 }
170 162
171 logm( 163 $this->logger->info(format_log('Login failed for user ' . $login, $clientIpId));
172 $this->configManager->get('resource.log'), 164
173 $remoteIp,
174 'Login failed for user ' . $login
175 );
176 return false; 165 return false;
177 } 166 }
178 167
diff --git a/application/security/SessionManager.php b/application/security/SessionManager.php
index 994fcbe5..96bf193c 100644
--- a/application/security/SessionManager.php
+++ b/application/security/SessionManager.php
@@ -8,6 +8,14 @@ use Shaarli\Config\ConfigManager;
8 */ 8 */
9class SessionManager 9class SessionManager
10{ 10{
11 public const KEY_LINKS_PER_PAGE = 'LINKS_PER_PAGE';
12 public const KEY_VISIBILITY = 'visibility';
13 public const KEY_UNTAGGED_ONLY = 'untaggedonly';
14
15 public const KEY_SUCCESS_MESSAGES = 'successes';
16 public const KEY_WARNING_MESSAGES = 'warnings';
17 public const KEY_ERROR_MESSAGES = 'errors';
18
11 /** @var int Session expiration timeout, in seconds */ 19 /** @var int Session expiration timeout, in seconds */
12 public static $SHORT_TIMEOUT = 3600; // 1 hour 20 public static $SHORT_TIMEOUT = 3600; // 1 hour
13 21
@@ -23,16 +31,35 @@ class SessionManager
23 /** @var bool Whether the user should stay signed in (LONG_TIMEOUT) */ 31 /** @var bool Whether the user should stay signed in (LONG_TIMEOUT) */
24 protected $staySignedIn = false; 32 protected $staySignedIn = false;
25 33
34 /** @var string */
35 protected $savePath;
36
26 /** 37 /**
27 * Constructor 38 * Constructor
28 * 39 *
29 * @param array $session The $_SESSION array (reference) 40 * @param array $session The $_SESSION array (reference)
30 * @param ConfigManager $conf ConfigManager instance 41 * @param ConfigManager $conf ConfigManager instance
42 * @param string $savePath Session save path returned by builtin function session_save_path()
31 */ 43 */
32 public function __construct(& $session, $conf) 44 public function __construct(&$session, $conf, string $savePath)
33 { 45 {
34 $this->session = &$session; 46 $this->session = &$session;
35 $this->conf = $conf; 47 $this->conf = $conf;
48 $this->savePath = $savePath;
49 }
50
51 /**
52 * Initialize XSRF token and links per page session variables.
53 */
54 public function initialize(): void
55 {
56 if (!isset($this->session['tokens'])) {
57 $this->session['tokens'] = [];
58 }
59
60 if (!isset($this->session['LINKS_PER_PAGE'])) {
61 $this->session['LINKS_PER_PAGE'] = $this->conf->get('general.links_per_page', 20);
62 }
36 } 63 }
37 64
38 /** 65 /**
@@ -156,7 +183,6 @@ class SessionManager
156 unset($this->session['expires_on']); 183 unset($this->session['expires_on']);
157 unset($this->session['username']); 184 unset($this->session['username']);
158 unset($this->session['visibility']); 185 unset($this->session['visibility']);
159 unset($this->session['untaggedonly']);
160 } 186 }
161 } 187 }
162 188
@@ -202,4 +228,81 @@ class SessionManager
202 { 228 {
203 return $this->session; 229 return $this->session;
204 } 230 }
231
232 /**
233 * @param mixed $default value which will be returned if the $key is undefined
234 *
235 * @return mixed Content stored in session
236 */
237 public function getSessionParameter(string $key, $default = null)
238 {
239 return $this->session[$key] ?? $default;
240 }
241
242 /**
243 * Store a variable in user session.
244 *
245 * @param string $key Session key
246 * @param mixed $value Session value to store
247 *
248 * @return $this
249 */
250 public function setSessionParameter(string $key, $value): self
251 {
252 $this->session[$key] = $value;
253
254 return $this;
255 }
256
257 /**
258 * Store a variable in user session.
259 *
260 * @param string $key Session key
261 *
262 * @return $this
263 */
264 public function deleteSessionParameter(string $key): self
265 {
266 unset($this->session[$key]);
267
268 return $this;
269 }
270
271 public function getSavePath(): string
272 {
273 return $this->savePath;
274 }
275
276 /*
277 * Next public functions wrapping native PHP session API.
278 */
279
280 public function destroy(): bool
281 {
282 $this->session = [];
283
284 return session_destroy();
285 }
286
287 public function start(): bool
288 {
289 if (session_status() === PHP_SESSION_ACTIVE) {
290 $this->destroy();
291 }
292
293 return session_start();
294 }
295
296 /**
297 * Be careful, return type of session_set_cookie_params() changed between PHP 7.1 and 7.2.
298 */
299 public function cookieParameters(int $lifeTime, string $path, string $domain): void
300 {
301 session_set_cookie_params($lifeTime, $path, $domain);
302 }
303
304 public function regenerateId(bool $deleteOldSession = false): bool
305 {
306 return session_regenerate_id($deleteOldSession);
307 }
205} 308}