ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.LocalUserPasswordManager.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
23use ilAuthUtils;
25use ilFileUtils;
26use ilObjUser;
29use ilSession;
30use ilSetting;
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 {
153 }
154
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);
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);
202 }
203
204 return true;
205 }
206
207 return false;
208 }
209
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}
__construct(array $config=[])
Please use the singleton method for instance creation The constructor is still public because of the ...
setEncoderFactory(LocalUserPasswordEncoderFactory $encoderFactory)
static getInstance()
Singleton method to reduce footprint (included files, created instances)
const int AUTH_LOCAL
static isPasswordModificationEnabled($a_authmode)
Check if password modification is enabled.
Class ilFileUtils.
static getDataDir()
get data directory (outside webspace)
User class.
getAuthMode(bool $a_auth_key=false)
setPasswordSalt(?string $password_salt)
setPasswordEncodingType(?string $password_encryption_type)
const PASSWD_CRYPTED
setPasswd(string $a_str, string $a_type=ilObjUser::PASSWD_PLAIN)
resetPassword(string $new_raw_password)
Class for user password exception handling in ILIAS.
static getBytes(int $length)
Generate random bytes using OpenSSL or Mcrypt and mt_rand() as fallback.
static get(string $a_var)
ILIAS Setting Class.
Interface ilDBInterface.
global $DIC
Definition: shib_login.php:26