Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/auth/controller/PhabricatorAuthController.php
12256 views
1
<?php
2
3
abstract class PhabricatorAuthController extends PhabricatorController {
4
5
protected function renderErrorPage($title, array $messages) {
6
$view = new PHUIInfoView();
7
$view->setTitle($title);
8
$view->setErrors($messages);
9
10
return $this->newPage()
11
->setTitle($title)
12
->appendChild($view);
13
14
}
15
16
/**
17
* Returns true if this install is newly setup (i.e., there are no user
18
* accounts yet). In this case, we enter a special mode to permit creation
19
* of the first account form the web UI.
20
*/
21
protected function isFirstTimeSetup() {
22
// If there are any auth providers, this isn't first time setup, even if
23
// we don't have accounts.
24
if (PhabricatorAuthProvider::getAllEnabledProviders()) {
25
return false;
26
}
27
28
// Otherwise, check if there are any user accounts. If not, we're in first
29
// time setup.
30
$any_users = id(new PhabricatorPeopleQuery())
31
->setViewer(PhabricatorUser::getOmnipotentUser())
32
->setLimit(1)
33
->execute();
34
35
return !$any_users;
36
}
37
38
39
/**
40
* Log a user into a web session and return an @{class:AphrontResponse} which
41
* corresponds to continuing the login process.
42
*
43
* Normally, this is a redirect to the validation controller which makes sure
44
* the user's cookies are set. However, event listeners can intercept this
45
* event and do something else if they prefer.
46
*
47
* @param PhabricatorUser User to log the viewer in as.
48
* @param bool True to issue a full session immediately, bypassing MFA.
49
* @return AphrontResponse Response which continues the login process.
50
*/
51
protected function loginUser(
52
PhabricatorUser $user,
53
$force_full_session = false) {
54
55
$response = $this->buildLoginValidateResponse($user);
56
$session_type = PhabricatorAuthSession::TYPE_WEB;
57
58
if ($force_full_session) {
59
$partial_session = false;
60
} else {
61
$partial_session = true;
62
}
63
64
$session_key = id(new PhabricatorAuthSessionEngine())
65
->establishSession($session_type, $user->getPHID(), $partial_session);
66
67
// NOTE: We allow disabled users to login and roadblock them later, so
68
// there's no check for users being disabled here.
69
70
$request = $this->getRequest();
71
$request->setCookie(
72
PhabricatorCookies::COOKIE_USERNAME,
73
$user->getUsername());
74
$request->setCookie(
75
PhabricatorCookies::COOKIE_SESSION,
76
$session_key);
77
78
$this->clearRegistrationCookies();
79
80
return $response;
81
}
82
83
protected function clearRegistrationCookies() {
84
$request = $this->getRequest();
85
86
// Clear the registration key.
87
$request->clearCookie(PhabricatorCookies::COOKIE_REGISTRATION);
88
89
// Clear the client ID / OAuth state key.
90
$request->clearCookie(PhabricatorCookies::COOKIE_CLIENTID);
91
92
// Clear the invite cookie.
93
$request->clearCookie(PhabricatorCookies::COOKIE_INVITE);
94
}
95
96
private function buildLoginValidateResponse(PhabricatorUser $user) {
97
$validate_uri = new PhutilURI($this->getApplicationURI('validate/'));
98
$validate_uri->replaceQueryParam('expect', $user->getUsername());
99
100
return id(new AphrontRedirectResponse())->setURI((string)$validate_uri);
101
}
102
103
protected function renderError($message) {
104
return $this->renderErrorPage(
105
pht('Authentication Error'),
106
array(
107
$message,
108
));
109
}
110
111
protected function loadAccountForRegistrationOrLinking($account_key) {
112
$request = $this->getRequest();
113
$viewer = $request->getUser();
114
115
$account = null;
116
$provider = null;
117
$response = null;
118
119
if (!$account_key) {
120
$response = $this->renderError(
121
pht('Request did not include account key.'));
122
return array($account, $provider, $response);
123
}
124
125
// NOTE: We're using the omnipotent user because the actual user may not
126
// be logged in yet, and because we want to tailor an error message to
127
// distinguish between "not usable" and "does not exist". We do explicit
128
// checks later on to make sure this account is valid for the intended
129
// operation. This requires edit permission for completeness and consistency
130
// but it won't actually be meaningfully checked because we're using the
131
// omnipotent user.
132
133
$account = id(new PhabricatorExternalAccountQuery())
134
->setViewer(PhabricatorUser::getOmnipotentUser())
135
->withAccountSecrets(array($account_key))
136
->needImages(true)
137
->requireCapabilities(
138
array(
139
PhabricatorPolicyCapability::CAN_VIEW,
140
PhabricatorPolicyCapability::CAN_EDIT,
141
))
142
->executeOne();
143
144
if (!$account) {
145
$response = $this->renderError(pht('No valid linkable account.'));
146
return array($account, $provider, $response);
147
}
148
149
if ($account->getUserPHID()) {
150
if ($account->getUserPHID() != $viewer->getPHID()) {
151
$response = $this->renderError(
152
pht(
153
'The account you are attempting to register or link is already '.
154
'linked to another user.'));
155
} else {
156
$response = $this->renderError(
157
pht(
158
'The account you are attempting to link is already linked '.
159
'to your account.'));
160
}
161
return array($account, $provider, $response);
162
}
163
164
$registration_key = $request->getCookie(
165
PhabricatorCookies::COOKIE_REGISTRATION);
166
167
// NOTE: This registration key check is not strictly necessary, because
168
// we're only creating new accounts, not linking existing accounts. It
169
// might be more hassle than it is worth, especially for email.
170
//
171
// The attack this prevents is getting to the registration screen, then
172
// copy/pasting the URL and getting someone else to click it and complete
173
// the process. They end up with an account bound to credentials you
174
// control. This doesn't really let you do anything meaningful, though,
175
// since you could have simply completed the process yourself.
176
177
if (!$registration_key) {
178
$response = $this->renderError(
179
pht(
180
'Your browser did not submit a registration key with the request. '.
181
'You must use the same browser to begin and complete registration. '.
182
'Check that cookies are enabled and try again.'));
183
return array($account, $provider, $response);
184
}
185
186
// We store the digest of the key rather than the key itself to prevent a
187
// theoretical attacker with read-only access to the database from
188
// hijacking registration sessions.
189
190
$actual = $account->getProperty('registrationKey');
191
$expect = PhabricatorHash::weakDigest($registration_key);
192
if (!phutil_hashes_are_identical($actual, $expect)) {
193
$response = $this->renderError(
194
pht(
195
'Your browser submitted a different registration key than the one '.
196
'associated with this account. You may need to clear your cookies.'));
197
return array($account, $provider, $response);
198
}
199
200
$config = $account->getProviderConfig();
201
if (!$config->getIsEnabled()) {
202
$response = $this->renderError(
203
pht(
204
'The account you are attempting to register with uses a disabled '.
205
'authentication provider ("%s"). An administrator may have '.
206
'recently disabled this provider.',
207
$config->getDisplayName()));
208
return array($account, $provider, $response);
209
}
210
211
$provider = $config->getProvider();
212
213
return array($account, $provider, null);
214
}
215
216
protected function loadInvite() {
217
$invite_cookie = PhabricatorCookies::COOKIE_INVITE;
218
$invite_code = $this->getRequest()->getCookie($invite_cookie);
219
if (!$invite_code) {
220
return null;
221
}
222
223
$engine = id(new PhabricatorAuthInviteEngine())
224
->setViewer($this->getViewer())
225
->setUserHasConfirmedVerify(true);
226
227
try {
228
return $engine->processInviteCode($invite_code);
229
} catch (Exception $ex) {
230
// If this fails for any reason, just drop the invite. In normal
231
// circumstances, we gave them a detailed explanation of any error
232
// before they jumped into this workflow.
233
return null;
234
}
235
}
236
237
protected function renderInviteHeader(PhabricatorAuthInvite $invite) {
238
$viewer = $this->getViewer();
239
240
// Since the user hasn't registered yet, they may not be able to see other
241
// user accounts. Load the inviting user with the omnipotent viewer.
242
$omnipotent_viewer = PhabricatorUser::getOmnipotentUser();
243
244
$invite_author = id(new PhabricatorPeopleQuery())
245
->setViewer($omnipotent_viewer)
246
->withPHIDs(array($invite->getAuthorPHID()))
247
->needProfileImage(true)
248
->executeOne();
249
250
// If we can't load the author for some reason, just drop this message.
251
// We lose the value of contextualizing things without author details.
252
if (!$invite_author) {
253
return null;
254
}
255
256
$invite_item = id(new PHUIObjectItemView())
257
->setHeader(
258
pht(
259
'Welcome to %s!',
260
PlatformSymbols::getPlatformServerName()))
261
->setImageURI($invite_author->getProfileImageURI())
262
->addAttribute(
263
pht(
264
'%s has invited you to join %s.',
265
$invite_author->getFullName(),
266
PlatformSymbols::getPlatformServerName()));
267
268
$invite_list = id(new PHUIObjectItemListView())
269
->addItem($invite_item)
270
->setFlush(true);
271
272
return id(new PHUIBoxView())
273
->addMargin(PHUI::MARGIN_LARGE)
274
->appendChild($invite_list);
275
}
276
277
278
final protected function newCustomStartMessage() {
279
$viewer = $this->getViewer();
280
281
$text = PhabricatorAuthMessage::loadMessageText(
282
$viewer,
283
PhabricatorAuthLoginMessageType::MESSAGEKEY);
284
285
if ($text === null || !strlen($text)) {
286
return null;
287
}
288
289
$remarkup_view = new PHUIRemarkupView($viewer, $text);
290
291
return phutil_tag(
292
'div',
293
array(
294
'class' => 'auth-custom-message',
295
),
296
$remarkup_view);
297
}
298
299
}
300
301