]> git.immae.eu Git - github/shaarli/Shaarli.git/blob - application/security/LoginManager.php
426e785e1a5431328d0818d49ec6d00d037eb0cd
[github/shaarli/Shaarli.git] / application / security / LoginManager.php
1 <?php
2 namespace Shaarli\Security;
3
4 use Exception;
5 use Psr\Log\LoggerInterface;
6 use Shaarli\Config\ConfigManager;
7
8 /**
9 * User login management
10 */
11 class LoginManager
12 {
13 /** @var array A reference to the $_GLOBALS array */
14 protected $globals = [];
15
16 /** @var ConfigManager Configuration Manager instance **/
17 protected $configManager = null;
18
19 /** @var SessionManager Session Manager instance **/
20 protected $sessionManager = null;
21
22 /** @var BanManager Ban Manager instance **/
23 protected $banManager;
24
25 /** @var bool Whether the user is logged in **/
26 protected $isLoggedIn = false;
27
28 /** @var bool Whether the Shaarli instance is open to public edition **/
29 protected $openShaarli = false;
30
31 /** @var string User sign-in token depending on remote IP and credentials */
32 protected $staySignedInToken = '';
33 /** @var CookieManager */
34 protected $cookieManager;
35 /** @var LoggerInterface */
36 protected $logger;
37
38 /**
39 * Constructor
40 *
41 * @param ConfigManager $configManager Configuration Manager 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
46 */
47 public function __construct(
48 ConfigManager $configManager,
49 SessionManager $sessionManager,
50 CookieManager $cookieManager,
51 BanManager $banManager,
52 LoggerInterface $logger
53 ) {
54 $this->configManager = $configManager;
55 $this->sessionManager = $sessionManager;
56 $this->cookieManager = $cookieManager;
57 $this->banManager = $banManager;
58 $this->logger = $logger;
59
60 if ($this->configManager->get('security.open_shaarli') === true) {
61 $this->openShaarli = true;
62 }
63 }
64
65 /**
66 * Generate a token depending on deployment salt, user password and client IP
67 *
68 * @param string $clientIpAddress The remote client IP address
69 */
70 public function generateStaySignedInToken($clientIpAddress)
71 {
72 if ($this->configManager->get('security.session_protection_disabled') === true) {
73 $clientIpAddress = '';
74 }
75 $this->staySignedInToken = sha1(
76 $this->configManager->get('credentials.hash')
77 . $clientIpAddress
78 . $this->configManager->get('credentials.salt')
79 );
80 }
81
82 /**
83 * Return the user's client stay-signed-in token
84 *
85 * @return string User's client stay-signed-in token
86 */
87 public function getStaySignedInToken()
88 {
89 return $this->staySignedInToken;
90 }
91
92 /**
93 * Check user session state and validity (expiration)
94 *
95 * @param string $clientIpId Client IP address identifier
96 */
97 public function checkLoginState($clientIpId)
98 {
99 if (! $this->configManager->exists('credentials.login')) {
100 // Shaarli is not configured yet
101 $this->isLoggedIn = false;
102 return;
103 }
104
105 if ($this->staySignedInToken === $this->cookieManager->getCookieParameter(CookieManager::STAY_SIGNED_IN)) {
106 // The user client has a valid stay-signed-in cookie
107 // Session information is updated with the current client information
108 $this->sessionManager->storeLoginInfo($clientIpId);
109 } elseif ($this->sessionManager->hasSessionExpired()
110 || $this->sessionManager->hasClientIpChanged($clientIpId)
111 ) {
112 $this->sessionManager->logout();
113 $this->isLoggedIn = false;
114 return;
115 }
116
117 $this->isLoggedIn = true;
118 $this->sessionManager->extendSession();
119 }
120
121 /**
122 * Return whether the user is currently logged in
123 *
124 * @return true when the user is logged in, false otherwise
125 */
126 public function isLoggedIn(): bool
127 {
128 if ($this->openShaarli) {
129 return true;
130 }
131 return $this->isLoggedIn;
132 }
133
134 /**
135 * Check user credentials are valid
136 *
137 * @param string $clientIpId Client IP address identifier
138 * @param string $login Username
139 * @param string $password Password
140 *
141 * @return bool true if the provided credentials are valid, false otherwise
142 */
143 public function checkCredentials($clientIpId, $login, $password)
144 {
145 // Check credentials
146 try {
147 $useLdapLogin = !empty($this->configManager->get('ldap.host'));
148 if ($login === $this->configManager->get('credentials.login')
149 && (
150 (false === $useLdapLogin && $this->checkCredentialsFromLocalConfig($login, $password))
151 || (true === $useLdapLogin && $this->checkCredentialsFromLdap($login, $password))
152 )
153 ) {
154 $this->sessionManager->storeLoginInfo($clientIpId);
155 $this->logger->info(format_log('Login successful', $clientIpId));
156
157 return true;
158 }
159 } catch(Exception $exception) {
160 $this->logger->info(format_log('Exception while checking credentials: ' . $exception, $clientIpId));
161 }
162
163 $this->logger->info(format_log('Login failed for user ' . $login, $clientIpId));
164
165 return false;
166 }
167
168
169 /**
170 * Check user credentials from local config
171 *
172 * @param string $login Username
173 * @param string $password Password
174 *
175 * @return bool true if the provided credentials are valid, false otherwise
176 */
177 public function checkCredentialsFromLocalConfig($login, $password) {
178 $hash = sha1($password . $login . $this->configManager->get('credentials.salt'));
179
180 return $login == $this->configManager->get('credentials.login')
181 && $hash == $this->configManager->get('credentials.hash');
182 }
183
184 /**
185 * Check user credentials are valid through LDAP bind
186 *
187 * @param string $remoteIp Remote client IP address
188 * @param string $clientIpId Client IP address identifier
189 * @param string $login Username
190 * @param string $password Password
191 *
192 * @return bool true if the provided credentials are valid, false otherwise
193 */
194 public function checkCredentialsFromLdap($login, $password, $connect = null, $bind = null)
195 {
196 $connect = $connect ?? function($host) {
197 $resource = ldap_connect($host);
198
199 ldap_set_option($resource, LDAP_OPT_PROTOCOL_VERSION, 3);
200
201 return $resource;
202 };
203 $bind = $bind ?? function($handle, $dn, $password) {
204 return ldap_bind($handle, $dn, $password);
205 };
206
207 return $bind(
208 $connect($this->configManager->get('ldap.host')),
209 sprintf($this->configManager->get('ldap.dn'), $login),
210 $password
211 );
212 }
213
214 /**
215 * Handle a failed login and ban the IP after too many failed attempts
216 *
217 * @param array $server The $_SERVER array
218 */
219 public function handleFailedLogin($server)
220 {
221 $this->banManager->handleFailedAttempt($server);
222 }
223
224 /**
225 * Handle a successful login
226 *
227 * @param array $server The $_SERVER array
228 */
229 public function handleSuccessfulLogin($server)
230 {
231 $this->banManager->clearFailures($server);
232 }
233
234 /**
235 * Check if the user can login from this IP
236 *
237 * @param array $server The $_SERVER array
238 *
239 * @return bool true if the user is allowed to login
240 */
241 public function canLogin($server)
242 {
243 return ! $this->banManager->isBanned($server);
244 }
245 }