Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/auth/provider/PhabricatorPasswordAuthProvider.php
12256 views
1
<?php
2
3
final class PhabricatorPasswordAuthProvider extends PhabricatorAuthProvider {
4
5
private $adapter;
6
7
public function getProviderName() {
8
return pht('Username/Password');
9
}
10
11
public function getConfigurationHelp() {
12
return pht(
13
"(WARNING) Examine the table below for information on how password ".
14
"hashes will be stored in the database.\n\n".
15
"(NOTE) You can select a minimum password length by setting ".
16
"`%s` in configuration.",
17
'account.minimum-password-length');
18
}
19
20
public function renderConfigurationFooter() {
21
$hashers = PhabricatorPasswordHasher::getAllHashers();
22
$hashers = msort($hashers, 'getStrength');
23
$hashers = array_reverse($hashers);
24
25
$yes = phutil_tag(
26
'strong',
27
array(
28
'style' => 'color: #009900',
29
),
30
pht('Yes'));
31
32
$no = phutil_tag(
33
'strong',
34
array(
35
'style' => 'color: #990000',
36
),
37
pht('Not Installed'));
38
39
$best_hasher_name = null;
40
try {
41
$best_hasher = PhabricatorPasswordHasher::getBestHasher();
42
$best_hasher_name = $best_hasher->getHashName();
43
} catch (PhabricatorPasswordHasherUnavailableException $ex) {
44
// There are no suitable hashers. The user might be able to enable some,
45
// so we don't want to fatal here. We'll fatal when users try to actually
46
// use this stuff if it isn't fixed before then. Until then, we just
47
// don't highlight a row. In practice, at least one hasher should always
48
// be available.
49
}
50
51
$rows = array();
52
$rowc = array();
53
foreach ($hashers as $hasher) {
54
$is_installed = $hasher->canHashPasswords();
55
56
$rows[] = array(
57
$hasher->getHumanReadableName(),
58
$hasher->getHashName(),
59
$hasher->getHumanReadableStrength(),
60
($is_installed ? $yes : $no),
61
($is_installed ? null : $hasher->getInstallInstructions()),
62
);
63
$rowc[] = ($best_hasher_name == $hasher->getHashName())
64
? 'highlighted'
65
: null;
66
}
67
68
$table = new AphrontTableView($rows);
69
$table->setRowClasses($rowc);
70
$table->setHeaders(
71
array(
72
pht('Algorithm'),
73
pht('Name'),
74
pht('Strength'),
75
pht('Installed'),
76
pht('Install Instructions'),
77
));
78
79
$table->setColumnClasses(
80
array(
81
'',
82
'',
83
'',
84
'',
85
'wide',
86
));
87
88
$header = id(new PHUIHeaderView())
89
->setHeader(pht('Password Hash Algorithms'))
90
->setSubheader(
91
pht(
92
'Stronger algorithms are listed first. The highlighted algorithm '.
93
'will be used when storing new hashes. Older hashes will be '.
94
'upgraded to the best algorithm over time.'));
95
96
return id(new PHUIObjectBoxView())
97
->setHeader($header)
98
->setTable($table);
99
}
100
101
public function getDescriptionForCreate() {
102
return pht(
103
'Allow users to log in or register using a username and password.');
104
}
105
106
public function getAdapter() {
107
if (!$this->adapter) {
108
$adapter = new PhutilEmptyAuthAdapter();
109
$adapter->setAdapterType('password');
110
$adapter->setAdapterDomain('self');
111
$this->adapter = $adapter;
112
}
113
return $this->adapter;
114
}
115
116
public function getLoginOrder() {
117
// Make sure username/password appears first if it is enabled.
118
return '100-'.$this->getProviderName();
119
}
120
121
public function shouldAllowAccountLink() {
122
return false;
123
}
124
125
public function shouldAllowAccountUnlink() {
126
return false;
127
}
128
129
public function isDefaultRegistrationProvider() {
130
return true;
131
}
132
133
public function buildLoginForm(
134
PhabricatorAuthStartController $controller) {
135
$request = $controller->getRequest();
136
return $this->renderPasswordLoginForm($request);
137
}
138
139
public function buildInviteForm(
140
PhabricatorAuthStartController $controller) {
141
$request = $controller->getRequest();
142
$viewer = $request->getViewer();
143
144
$form = id(new AphrontFormView())
145
->setUser($viewer)
146
->addHiddenInput('invite', true)
147
->appendChild(
148
id(new AphrontFormTextControl())
149
->setLabel(pht('Username'))
150
->setName('username'));
151
152
$dialog = id(new AphrontDialogView())
153
->setUser($viewer)
154
->setTitle(pht('Register an Account'))
155
->appendForm($form)
156
->setSubmitURI('/auth/register/')
157
->addSubmitButton(pht('Continue'));
158
159
return $dialog;
160
}
161
162
public function buildLinkForm($controller) {
163
throw new Exception(pht("Password providers can't be linked."));
164
}
165
166
private function renderPasswordLoginForm(
167
AphrontRequest $request,
168
$require_captcha = false,
169
$captcha_valid = false) {
170
171
$viewer = $request->getUser();
172
173
$dialog = id(new AphrontDialogView())
174
->setSubmitURI($this->getLoginURI())
175
->setUser($viewer)
176
->setTitle(pht('Log In'))
177
->addSubmitButton(pht('Log In'));
178
179
if ($this->shouldAllowRegistration()) {
180
$dialog->addCancelButton(
181
'/auth/register/',
182
pht('Register New Account'));
183
}
184
185
$dialog->addFooter(
186
phutil_tag(
187
'a',
188
array(
189
'href' => '/login/email/',
190
),
191
pht('Forgot your password?')));
192
193
$v_user = nonempty(
194
$request->getStr('username'),
195
$request->getCookie(PhabricatorCookies::COOKIE_USERNAME));
196
197
$e_user = null;
198
$e_pass = null;
199
$e_captcha = null;
200
201
$errors = array();
202
if ($require_captcha && !$captcha_valid) {
203
if (AphrontFormRecaptchaControl::hasCaptchaResponse($request)) {
204
$e_captcha = pht('Invalid');
205
$errors[] = pht('CAPTCHA was not entered correctly.');
206
} else {
207
$e_captcha = pht('Required');
208
$errors[] = pht(
209
'Too many login failures recently. You must '.
210
'submit a CAPTCHA with your login request.');
211
}
212
} else if ($request->isHTTPPost()) {
213
// NOTE: This is intentionally vague so as not to disclose whether a
214
// given username or email is registered.
215
$e_user = pht('Invalid');
216
$e_pass = pht('Invalid');
217
$errors[] = pht('Username or password are incorrect.');
218
}
219
220
if ($errors) {
221
$errors = id(new PHUIInfoView())->setErrors($errors);
222
}
223
224
$form = id(new PHUIFormLayoutView())
225
->setFullWidth(true)
226
->appendChild($errors)
227
->appendChild(
228
id(new AphrontFormTextControl())
229
->setLabel(pht('Username or Email'))
230
->setName('username')
231
->setAutofocus(true)
232
->setValue($v_user)
233
->setError($e_user))
234
->appendChild(
235
id(new AphrontFormPasswordControl())
236
->setLabel(pht('Password'))
237
->setName('password')
238
->setError($e_pass));
239
240
if ($require_captcha) {
241
$form->appendChild(
242
id(new AphrontFormRecaptchaControl())
243
->setError($e_captcha));
244
}
245
246
$dialog->appendChild($form);
247
248
return $dialog;
249
}
250
251
public function processLoginRequest(
252
PhabricatorAuthLoginController $controller) {
253
254
$request = $controller->getRequest();
255
$viewer = $request->getUser();
256
$content_source = PhabricatorContentSource::newFromRequest($request);
257
258
$rate_actor = PhabricatorSystemActionEngine::newActorFromRequest($request);
259
260
PhabricatorSystemActionEngine::willTakeAction(
261
array($rate_actor),
262
new PhabricatorAuthTryPasswordAction(),
263
1);
264
265
// If the same remote address has submitted several failed login attempts
266
// recently, require they provide a CAPTCHA response for new attempts.
267
$require_captcha = false;
268
$captcha_valid = false;
269
if (AphrontFormRecaptchaControl::isRecaptchaEnabled()) {
270
try {
271
PhabricatorSystemActionEngine::willTakeAction(
272
array($rate_actor),
273
new PhabricatorAuthTryPasswordWithoutCAPTCHAAction(),
274
1);
275
} catch (PhabricatorSystemActionRateLimitException $ex) {
276
$require_captcha = true;
277
$captcha_valid = AphrontFormRecaptchaControl::processCaptcha($request);
278
}
279
}
280
281
$response = null;
282
$account = null;
283
$log_user = null;
284
285
if ($request->isFormPost()) {
286
if (!$require_captcha || $captcha_valid) {
287
$username_or_email = $request->getStr('username');
288
if (strlen($username_or_email)) {
289
$user = id(new PhabricatorUser())->loadOneWhere(
290
'username = %s',
291
$username_or_email);
292
293
if (!$user) {
294
$user = PhabricatorUser::loadOneWithEmailAddress(
295
$username_or_email);
296
}
297
298
if ($user) {
299
$envelope = new PhutilOpaqueEnvelope($request->getStr('password'));
300
301
$engine = id(new PhabricatorAuthPasswordEngine())
302
->setViewer($user)
303
->setContentSource($content_source)
304
->setPasswordType(PhabricatorAuthPassword::PASSWORD_TYPE_ACCOUNT)
305
->setObject($user);
306
307
if ($engine->isValidPassword($envelope)) {
308
$account = $this->newExternalAccountForUser($user);
309
$log_user = $user;
310
}
311
}
312
}
313
}
314
}
315
316
if (!$account) {
317
if ($request->isFormPost()) {
318
$log = PhabricatorUserLog::initializeNewLog(
319
null,
320
$log_user ? $log_user->getPHID() : null,
321
PhabricatorLoginFailureUserLogType::LOGTYPE);
322
$log->save();
323
}
324
325
$request->clearCookie(PhabricatorCookies::COOKIE_USERNAME);
326
327
$response = $controller->buildProviderPageResponse(
328
$this,
329
$this->renderPasswordLoginForm(
330
$request,
331
$require_captcha,
332
$captcha_valid));
333
}
334
335
return array($account, $response);
336
}
337
338
public function shouldRequireRegistrationPassword() {
339
return true;
340
}
341
342
public static function getPasswordProvider() {
343
$providers = self::getAllEnabledProviders();
344
345
foreach ($providers as $provider) {
346
if ($provider instanceof PhabricatorPasswordAuthProvider) {
347
return $provider;
348
}
349
}
350
351
return null;
352
}
353
354
public function willRenderLinkedAccount(
355
PhabricatorUser $viewer,
356
PHUIObjectItemView $item,
357
PhabricatorExternalAccount $account) {
358
return;
359
}
360
361
public function shouldAllowAccountRefresh() {
362
return false;
363
}
364
365
public function shouldAllowEmailTrustConfiguration() {
366
return false;
367
}
368
369
}
370
371