aboutsummaryrefslogtreecommitdiffhomepage
path: root/application/security
diff options
context:
space:
mode:
authorArthurHoaro <arthur@hoa.ro>2020-10-13 12:05:08 +0200
committerArthurHoaro <arthur@hoa.ro>2020-10-13 12:05:08 +0200
commitb6f678a5a1d15acf284ebcec16c905e976671ce1 (patch)
tree33c7da831482ed79c44896ef19c73c72ada84f2e /application/security
parentb14687036b9b800681197f51fdc47e62f0c88e2e (diff)
parent1c1520b6b98ab20201bfe15577782a52320339df (diff)
downloadShaarli-b6f678a5a1d15acf284ebcec16c905e976671ce1.tar.gz
Shaarli-b6f678a5a1d15acf284ebcec16c905e976671ce1.tar.zst
Shaarli-b6f678a5a1d15acf284ebcec16c905e976671ce1.zip
Merge branch 'v0.12' into latest
Diffstat (limited to 'application/security')
-rw-r--r--application/security/CookieManager.php33
-rw-r--r--application/security/LoginManager.php95
-rw-r--r--application/security/SessionManager.php114
3 files changed, 220 insertions, 22 deletions
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 0b0ce0b1..d74c3118 100644
--- a/application/security/LoginManager.php
+++ b/application/security/LoginManager.php
@@ -1,6 +1,7 @@
1<?php 1<?php
2namespace Shaarli\Security; 2namespace Shaarli\Security;
3 3
4use Exception;
4use Shaarli\Config\ConfigManager; 5use Shaarli\Config\ConfigManager;
5 6
6/** 7/**
@@ -8,9 +9,6 @@ use Shaarli\Config\ConfigManager;
8 */ 9 */
9class LoginManager 10class LoginManager
10{ 11{
11 /** @var string Name of the cookie set after logging in **/
12 public static $STAY_SIGNED_IN_COOKIE = 'shaarli_staySignedIn';
13
14 /** @var array A reference to the $_GLOBALS array */ 12 /** @var array A reference to the $_GLOBALS array */
15 protected $globals = []; 13 protected $globals = [];
16 14
@@ -31,17 +29,21 @@ class LoginManager
31 29
32 /** @var string User sign-in token depending on remote IP and credentials */ 30 /** @var string User sign-in token depending on remote IP and credentials */
33 protected $staySignedInToken = ''; 31 protected $staySignedInToken = '';
32 /** @var CookieManager */
33 protected $cookieManager;
34 34
35 /** 35 /**
36 * Constructor 36 * Constructor
37 * 37 *
38 * @param ConfigManager $configManager Configuration Manager instance 38 * @param ConfigManager $configManager Configuration Manager instance
39 * @param SessionManager $sessionManager SessionManager instance 39 * @param SessionManager $sessionManager SessionManager instance
40 * @param CookieManager $cookieManager CookieManager instance
40 */ 41 */
41 public function __construct($configManager, $sessionManager) 42 public function __construct($configManager, $sessionManager, $cookieManager)
42 { 43 {
43 $this->configManager = $configManager; 44 $this->configManager = $configManager;
44 $this->sessionManager = $sessionManager; 45 $this->sessionManager = $sessionManager;
46 $this->cookieManager = $cookieManager;
45 $this->banManager = new BanManager( 47 $this->banManager = new BanManager(
46 $this->configManager->get('security.trusted_proxies', []), 48 $this->configManager->get('security.trusted_proxies', []),
47 $this->configManager->get('security.ban_after'), 49 $this->configManager->get('security.ban_after'),
@@ -85,10 +87,9 @@ class LoginManager
85 /** 87 /**
86 * Check user session state and validity (expiration) 88 * Check user session state and validity (expiration)
87 * 89 *
88 * @param array $cookie The $_COOKIE array
89 * @param string $clientIpId Client IP address identifier 90 * @param string $clientIpId Client IP address identifier
90 */ 91 */
91 public function checkLoginState($cookie, $clientIpId) 92 public function checkLoginState($clientIpId)
92 { 93 {
93 if (! $this->configManager->exists('credentials.login')) { 94 if (! $this->configManager->exists('credentials.login')) {
94 // Shaarli is not configured yet 95 // Shaarli is not configured yet
@@ -96,9 +97,7 @@ class LoginManager
96 return; 97 return;
97 } 98 }
98 99
99 if (isset($cookie[self::$STAY_SIGNED_IN_COOKIE]) 100 if ($this->staySignedInToken === $this->cookieManager->getCookieParameter(CookieManager::STAY_SIGNED_IN)) {
100 && $cookie[self::$STAY_SIGNED_IN_COOKIE] === $this->staySignedInToken
101 ) {
102 // The user client has a valid stay-signed-in cookie 101 // The user client has a valid stay-signed-in cookie
103 // Session information is updated with the current client information 102 // Session information is updated with the current client information
104 $this->sessionManager->storeLoginInfo($clientIpId); 103 $this->sessionManager->storeLoginInfo($clientIpId);
@@ -139,26 +138,86 @@ class LoginManager
139 */ 138 */
140 public function checkCredentials($remoteIp, $clientIpId, $login, $password) 139 public function checkCredentials($remoteIp, $clientIpId, $login, $password)
141 { 140 {
142 $hash = sha1($password . $login . $this->configManager->get('credentials.salt')); 141 // Check login matches config
142 if ($login !== $this->configManager->get('credentials.login')) {
143 return false;
144 }
143 145
144 if ($login != $this->configManager->get('credentials.login') 146 // Check credentials
145 || $hash != $this->configManager->get('credentials.hash') 147 try {
146 ) { 148 $useLdapLogin = !empty($this->configManager->get('ldap.host'));
149 if ((false === $useLdapLogin && $this->checkCredentialsFromLocalConfig($login, $password))
150 || (true === $useLdapLogin && $this->checkCredentialsFromLdap($login, $password))
151 ) {
152 $this->sessionManager->storeLoginInfo($clientIpId);
153 logm(
154 $this->configManager->get('resource.log'),
155 $remoteIp,
156 'Login successful'
157 );
158 return true;
159 }
160 }
161 catch(Exception $exception) {
147 logm( 162 logm(
148 $this->configManager->get('resource.log'), 163 $this->configManager->get('resource.log'),
149 $remoteIp, 164 $remoteIp,
150 'Login failed for user ' . $login 165 'Exception while checking credentials: ' . $exception
151 ); 166 );
152 return false;
153 } 167 }
154 168
155 $this->sessionManager->storeLoginInfo($clientIpId);
156 logm( 169 logm(
157 $this->configManager->get('resource.log'), 170 $this->configManager->get('resource.log'),
158 $remoteIp, 171 $remoteIp,
159 'Login successful' 172 'Login failed for user ' . $login
173 );
174 return false;
175 }
176
177
178 /**
179 * Check user credentials from local config
180 *
181 * @param string $login Username
182 * @param string $password Password
183 *
184 * @return bool true if the provided credentials are valid, false otherwise
185 */
186 public function checkCredentialsFromLocalConfig($login, $password) {
187 $hash = sha1($password . $login . $this->configManager->get('credentials.salt'));
188
189 return $login == $this->configManager->get('credentials.login')
190 && $hash == $this->configManager->get('credentials.hash');
191 }
192
193 /**
194 * Check user credentials are valid through LDAP bind
195 *
196 * @param string $remoteIp Remote client IP address
197 * @param string $clientIpId Client IP address identifier
198 * @param string $login Username
199 * @param string $password Password
200 *
201 * @return bool true if the provided credentials are valid, false otherwise
202 */
203 public function checkCredentialsFromLdap($login, $password, $connect = null, $bind = null)
204 {
205 $connect = $connect ?? function($host) {
206 $resource = ldap_connect($host);
207
208 ldap_set_option($resource, LDAP_OPT_PROTOCOL_VERSION, 3);
209
210 return $resource;
211 };
212 $bind = $bind ?? function($handle, $dn, $password) {
213 return ldap_bind($handle, $dn, $password);
214 };
215
216 return $bind(
217 $connect($this->configManager->get('ldap.host')),
218 sprintf($this->configManager->get('ldap.dn'), $login),
219 $password
160 ); 220 );
161 return true;
162 } 221 }
163 222
164 /** 223 /**
diff --git a/application/security/SessionManager.php b/application/security/SessionManager.php
index b8b8ab8d..36df8c1c 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
@@ -196,4 +222,84 @@ class SessionManager
196 } 222 }
197 return true; 223 return true;
198 } 224 }
225
226 /** @return array Local reference to the global $_SESSION array */
227 public function getSession(): array
228 {
229 return $this->session;
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 public function cookieParameters(int $lifeTime, string $path, string $domain): bool
297 {
298 return session_set_cookie_params($lifeTime, $path, $domain);
299 }
300
301 public function regenerateId(bool $deleteOldSession = false): bool
302 {
303 return session_regenerate_id($deleteOldSession);
304 }
199} 305}