ILIAS  trunk Revision v11.0_alpha-2638-g80c1d007f79
class.LocalUserPasswordManager.php
Go to the documentation of this file.
1 <?php
2 
19 declare(strict_types=1);
20 
22 
23 use ilAuthUtils;
24 use ilDBInterface;
25 use ilFileUtils;
26 use ilObjUser;
28 use ilPasswordUtils;
29 use ilSession;
30 use ilSetting;
31 use ilUserException;
32 
34 {
35  private const int MIN_SALT_SIZE = 16;
36 
37  private static ?self $instance = null;
39  private ?ilSetting $settings = null;
40  private ?ilDBInterface $db = null;
41  private ?string $encoderName = null;
42 
49  public function __construct(array $config = [])
50  {
51  if (!empty($config)) {
52  foreach ($config as $key => $value) {
53  switch (strtolower($key)) {
54  case 'settings':
55  $this->setSettings($value);
56 
57  break;
58  case 'db':
59  $this->setDb($value);
60 
61  break;
62  case 'password_encoder':
63  $this->setEncoderName($value);
64 
65  break;
66  case 'encoder_factory':
67  $this->setEncoderFactory($value);
68 
69  break;
70  }
71  }
72  }
73 
74  if (!$this->getEncoderName()) {
75  throw new ilUserException(
76  \sprintf(
77  '"password_encoder" must be set in %s.',
78  print_r($config, true)
79  )
80  );
81  }
82 
83  if (!$this->getEncoderFactory() instanceof LocalUserPasswordEncoderFactory) {
84  throw new ilUserException(
85  \sprintf(
86  '"encoder_factory" must be instance of LocalUserPasswordEncoderFactory and set in %s.',
87  print_r($config, true)
88  )
89  );
90  }
91  }
92 
98  public static function getInstance(): self
99  {
100  global $DIC;
101 
102  if (self::$instance instanceof self) {
103  return self::$instance;
104  }
105 
106  $password_manager = new LocalUserPasswordManager(
107  [
108  'encoder_factory' => new LocalUserPasswordEncoderFactory(
109  [
110  // bcrypt (native PHP impl.) is still the default for the factory
111  'default_password_encoder' => 'bcryptphp',
112  // Recommended: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id
113  'memory_cost' => 19_456,
114  'ignore_security_flaw' => true,
115  'data_directory' => ilFileUtils::getDataDir()
116  ]
117  ),
118  // https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#argon2id
119  'password_encoder' => 'argon2id',
120  'settings' => $DIC->isDependencyAvailable('settings') ? $DIC->settings() : null,
121  'db' => $DIC->database(),
122  ]
123  );
124 
125  self::$instance = $password_manager;
126 
127  return self::$instance;
128  }
129 
130  public function setSettings(?ilSetting $settings): void
131  {
132  $this->settings = $settings;
133  }
134 
135  public function setDb(ilDBInterface $db): void
136  {
137  $this->db = $db;
138  }
139 
140  public function getEncoderName(): ?string
141  {
142  return $this->encoderName;
143  }
144 
145  public function setEncoderName(string $encoderName): void
146  {
147  $this->encoderName = $encoderName;
148  }
149 
151  {
152  return $this->encoderFactory;
153  }
154 
155  public function setEncoderFactory(LocalUserPasswordEncoderFactory $encoderFactory): void
156  {
157  $this->encoderFactory = $encoderFactory;
158  }
159 
160  public function encodePassword(ilObjUser $user, string $raw): void
161  {
162  $encoder = $this->getEncoderFactory()->getEncoderByName($this->getEncoderName());
163  $user->setPasswordEncodingType($encoder->getName());
164  if ($encoder->requiresSalt()) {
165  $user->setPasswordSalt(
166  substr(
167  str_replace(
168  '+',
169  '.',
170  base64_encode(ilPasswordUtils::getBytes(self::MIN_SALT_SIZE))
171  ),
172  0,
173  22
174  )
175  );
176  } else {
177  $user->setPasswordSalt(null);
178  }
179  $user->setPasswd(
180  $encoder->encodePassword($raw, (string) $user->getPasswordSalt()),
182  );
183  }
184 
185  public function isEncodingTypeSupported(string $name): bool
186  {
187  return \in_array($name, $this->getEncoderFactory()->getSupportedEncoderNames());
188  }
189 
190  public function verifyPassword(ilObjUser $user, string $raw): bool
191  {
192  $encoder = $this->getEncoderFactory()->getEncoderByName($user->getPasswordEncodingType());
193  if ($this->getEncoderName() !== $encoder->getName()) {
194  if ($encoder->isPasswordValid($user->getPasswd(), $raw, (string) $user->getPasswordSalt())) {
195  $user->resetPassword($raw, $raw);
196 
197  return true;
198  }
199  } elseif ($encoder->isPasswordValid($user->getPasswd(), $raw, (string) $user->getPasswordSalt())) {
200  if ($encoder->requiresReencoding($user->getPasswd())) {
201  $user->resetPassword($raw, $raw);
202  }
203 
204  return true;
205  }
206 
207  return false;
208  }
209 
210  public function resetLastPasswordChangeForLocalUsers(): void
211  {
212  $defaultAuthMode = $this->settings->get('auth_mode');
213  $defaultAuthModeCondition = '';
214  if ((int) $defaultAuthMode === ilAuthUtils::AUTH_LOCAL) {
215  $defaultAuthModeCondition = ' OR auth_mode = ' . $this->db->quote('default', 'text');
216  }
217 
218  $this->db->manipulateF(
219  "UPDATE usr_data SET passwd_policy_reset = %s WHERE (auth_mode = %s $defaultAuthModeCondition)",
220  ['integer', 'text'],
221  [1, 'local']
222  );
223  }
224 
225  public function allowPasswordChange(ilObjUser $user): bool
226  {
227  if (ilSession::get('used_external_auth_mode')) {
228  return false;
229  }
230 
232  if ($status) {
233  return true;
234  }
235 
236  return ilAuthUtils::isPasswordModificationHidden()
237  && ($user->isPasswordChangeDemanded() || $user->isPasswordExpired());
238  }
239 }
static get(string $a_var)
resetPassword(string $raw, string $raw_retype)
Resets the user password.
setPasswordSalt(?string $password_salt)
getAuthMode(bool $a_auth_key=false)
setPasswd(string $a_str, string $a_type=ilObjUser::PASSWD_PLAIN)
while($session_entry=$r->fetchRow(ilDBConstants::FETCHMODE_ASSOC)) return null
const int AUTH_LOCAL
static getInstance()
Singleton method to reduce footprint (included files, created instances)
global $DIC
Definition: shib_login.php:26
static getBytes(int $length)
Generate random bytes using OpenSSL or Mcrypt and mt_rand() as fallback.
static getDataDir()
get data directory (outside webspace)
const PASSWD_CRYPTED
setPasswordEncodingType(?string $password_encryption_type)
__construct(array $config=[])
Please use the singleton method for instance creation The constructor is still public because of the ...
setEncoderFactory(LocalUserPasswordEncoderFactory $encoderFactory)
static isPasswordModificationEnabled($a_authmode)
Check if password modification is enabled.