Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/auth/controller/PhabricatorAuthStartController.php
12256 views
1
<?php
2
3
final class PhabricatorAuthStartController
4
extends PhabricatorAuthController {
5
6
public function shouldRequireLogin() {
7
return false;
8
}
9
10
public function handleRequest(AphrontRequest $request) {
11
$viewer = $request->getUser();
12
13
if ($viewer->isLoggedIn()) {
14
// Kick the user home if they are already logged in.
15
return id(new AphrontRedirectResponse())->setURI('/');
16
}
17
18
if ($request->isAjax()) {
19
return $this->processAjaxRequest();
20
}
21
22
if ($request->isConduit()) {
23
return $this->processConduitRequest();
24
}
25
26
// If the user gets this far, they aren't logged in, so if they have a
27
// user session token we can conclude that it's invalid: if it was valid,
28
// they'd have been logged in above and never made it here. Try to clear
29
// it and warn the user they may need to nuke their cookies.
30
31
$session_token = $request->getCookie(PhabricatorCookies::COOKIE_SESSION);
32
$did_clear = $request->getStr('cleared');
33
34
if ($session_token !== null && strlen($session_token)) {
35
$kind = PhabricatorAuthSessionEngine::getSessionKindFromToken(
36
$session_token);
37
switch ($kind) {
38
case PhabricatorAuthSessionEngine::KIND_ANONYMOUS:
39
// If this is an anonymous session. It's expected that they won't
40
// be logged in, so we can just continue.
41
break;
42
default:
43
// The session cookie is invalid, so try to clear it.
44
$request->clearCookie(PhabricatorCookies::COOKIE_USERNAME);
45
$request->clearCookie(PhabricatorCookies::COOKIE_SESSION);
46
47
// We've previously tried to clear the cookie but we ended up back
48
// here, so it didn't work. Hard fatal instead of trying again.
49
if ($did_clear) {
50
return $this->renderError(
51
pht(
52
'Your login session is invalid, and clearing the session '.
53
'cookie was unsuccessful. Try clearing your browser cookies.'));
54
}
55
56
$redirect_uri = $request->getRequestURI();
57
$redirect_uri->replaceQueryParam('cleared', 1);
58
return id(new AphrontRedirectResponse())->setURI($redirect_uri);
59
}
60
}
61
62
// If we just cleared the session cookie and it worked, clean up after
63
// ourselves by redirecting to get rid of the "cleared" parameter. The
64
// the workflow will continue normally.
65
if ($did_clear) {
66
$redirect_uri = $request->getRequestURI();
67
$redirect_uri->removeQueryParam('cleared');
68
return id(new AphrontRedirectResponse())->setURI($redirect_uri);
69
}
70
71
$providers = PhabricatorAuthProvider::getAllEnabledProviders();
72
foreach ($providers as $key => $provider) {
73
if (!$provider->shouldAllowLogin()) {
74
unset($providers[$key]);
75
}
76
}
77
78
$configs = array();
79
foreach ($providers as $provider) {
80
$configs[] = $provider->getProviderConfig();
81
}
82
83
if (!$providers) {
84
if ($this->isFirstTimeSetup()) {
85
// If this is a fresh install, let the user register their admin
86
// account.
87
return id(new AphrontRedirectResponse())
88
->setURI($this->getApplicationURI('/register/'));
89
}
90
91
return $this->renderError(
92
pht(
93
'This server is not configured with any enabled authentication '.
94
'providers which can be used to log in. If you have accidentally '.
95
'locked yourself out by disabling all providers, you can use `%s` '.
96
'to recover access to an account.',
97
'./bin/auth recover <username>'));
98
}
99
100
$next_uri = $request->getStr('next');
101
if (phutil_nonempty_string($next_uri)) {
102
if ($this->getDelegatingController()) {
103
// Only set a next URI from the request path if this controller was
104
// delegated to, which happens when a user tries to view a page which
105
// requires them to login.
106
107
// If this controller handled the request directly, we're on the main
108
// login page, and never want to redirect the user back here after they
109
// login.
110
$next_uri = (string)$this->getRequest()->getRequestURI();
111
}
112
}
113
114
if (!$request->isFormPost()) {
115
if (phutil_nonempty_string($next_uri)) {
116
PhabricatorCookies::setNextURICookie($request, $next_uri);
117
}
118
PhabricatorCookies::setClientIDCookie($request);
119
}
120
121
$auto_response = $this->tryAutoLogin($providers);
122
if ($auto_response) {
123
return $auto_response;
124
}
125
126
$invite = $this->loadInvite();
127
128
$not_buttons = array();
129
$are_buttons = array();
130
$providers = msort($providers, 'getLoginOrder');
131
foreach ($providers as $provider) {
132
if ($invite) {
133
$form = $provider->buildInviteForm($this);
134
} else {
135
$form = $provider->buildLoginForm($this);
136
}
137
if ($provider->isLoginFormAButton()) {
138
$are_buttons[] = $form;
139
} else {
140
$not_buttons[] = $form;
141
}
142
}
143
144
$out = array();
145
$out[] = $not_buttons;
146
if ($are_buttons) {
147
require_celerity_resource('auth-css');
148
149
foreach ($are_buttons as $key => $button) {
150
$are_buttons[$key] = phutil_tag(
151
'div',
152
array(
153
'class' => 'phabricator-login-button mmb',
154
),
155
$button);
156
}
157
158
// If we only have one button, add a second pretend button so that we
159
// always have two columns. This makes it easier to get the alignments
160
// looking reasonable.
161
if (count($are_buttons) == 1) {
162
$are_buttons[] = null;
163
}
164
165
$button_columns = id(new AphrontMultiColumnView())
166
->setFluidLayout(true);
167
$are_buttons = array_chunk($are_buttons, ceil(count($are_buttons) / 2));
168
foreach ($are_buttons as $column) {
169
$button_columns->addColumn($column);
170
}
171
172
$out[] = phutil_tag(
173
'div',
174
array(
175
'class' => 'phabricator-login-buttons',
176
),
177
$button_columns);
178
}
179
180
$invite_message = null;
181
if ($invite) {
182
$invite_message = $this->renderInviteHeader($invite);
183
}
184
185
$custom_message = $this->newCustomStartMessage();
186
187
$email_login = $this->newEmailLoginView($configs);
188
189
$crumbs = $this->buildApplicationCrumbs();
190
$crumbs->addTextCrumb(pht('Login'));
191
$crumbs->setBorder(true);
192
193
$title = pht('Login');
194
$view = array(
195
$invite_message,
196
$custom_message,
197
$out,
198
$email_login,
199
);
200
201
return $this->newPage()
202
->setTitle($title)
203
->setCrumbs($crumbs)
204
->appendChild($view);
205
}
206
207
208
private function processAjaxRequest() {
209
$request = $this->getRequest();
210
$viewer = $request->getUser();
211
212
// We end up here if the user clicks a workflow link that they need to
213
// login to use. We give them a dialog saying "You need to login...".
214
215
if ($request->isDialogFormPost()) {
216
return id(new AphrontRedirectResponse())->setURI(
217
$request->getRequestURI());
218
}
219
220
// Often, users end up here by clicking a disabled action link in the UI
221
// (for example, they might click "Edit Subtasks" on a Maniphest task
222
// page). After they log in we want to send them back to that main object
223
// page if we can, since it's confusing to end up on a standalone page with
224
// only a dialog (particularly if that dialog is another error,
225
// like a policy exception).
226
227
$via_header = AphrontRequest::getViaHeaderName();
228
$via_uri = AphrontRequest::getHTTPHeader($via_header);
229
if ($via_uri !== null && strlen($via_uri)) {
230
PhabricatorCookies::setNextURICookie($request, $via_uri, $force = true);
231
}
232
233
return $this->newDialog()
234
->setTitle(pht('Login Required'))
235
->appendParagraph(pht('You must log in to take this action.'))
236
->addSubmitButton(pht('Log In'))
237
->addCancelButton('/');
238
}
239
240
241
private function processConduitRequest() {
242
$request = $this->getRequest();
243
$viewer = $request->getUser();
244
245
// A common source of errors in Conduit client configuration is getting
246
// the request path wrong. The client will end up here, so make some
247
// effort to give them a comprehensible error message.
248
249
$request_path = $this->getRequest()->getPath();
250
$conduit_path = '/api/<method>';
251
$example_path = '/api/conduit.ping';
252
253
$message = pht(
254
'ERROR: You are making a Conduit API request to "%s", but the correct '.
255
'HTTP request path to use in order to access a Conduit method is "%s" '.
256
'(for example, "%s"). Check your configuration.',
257
$request_path,
258
$conduit_path,
259
$example_path);
260
261
return id(new AphrontPlainTextResponse())->setContent($message);
262
}
263
264
protected function renderError($message) {
265
return $this->renderErrorPage(
266
pht('Authentication Failure'),
267
array($message));
268
}
269
270
private function tryAutoLogin(array $providers) {
271
$request = $this->getRequest();
272
273
// If the user just logged out, don't immediately log them in again.
274
if ($request->getURIData('loggedout')) {
275
return null;
276
}
277
278
// If we have more than one provider, we can't autologin because we
279
// don't know which one the user wants.
280
if (count($providers) != 1) {
281
return null;
282
}
283
284
$provider = head($providers);
285
if (!$provider->supportsAutoLogin()) {
286
return null;
287
}
288
289
$config = $provider->getProviderConfig();
290
if (!$config->getShouldAutoLogin()) {
291
return null;
292
}
293
294
$auto_uri = $provider->getAutoLoginURI($request);
295
296
return id(new AphrontRedirectResponse())
297
->setIsExternal(true)
298
->setURI($auto_uri);
299
}
300
301
private function newEmailLoginView(array $configs) {
302
assert_instances_of($configs, 'PhabricatorAuthProviderConfig');
303
304
// Check if password auth is enabled. If it is, the password login form
305
// renders a "Forgot password?" link, so we don't need to provide a
306
// supplemental link.
307
308
$has_password = false;
309
foreach ($configs as $config) {
310
$provider = $config->getProvider();
311
if ($provider instanceof PhabricatorPasswordAuthProvider) {
312
$has_password = true;
313
}
314
}
315
316
if ($has_password) {
317
return null;
318
}
319
320
$view = array(
321
pht('Trouble logging in?'),
322
' ',
323
phutil_tag(
324
'a',
325
array(
326
'href' => '/login/email/',
327
),
328
pht('Send a login link to your email address.')),
329
);
330
331
return phutil_tag(
332
'div',
333
array(
334
'class' => 'auth-custom-message',
335
),
336
$view);
337
}
338
339
340
}
341
342