Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/auth/engine/PhabricatorAuthInviteEngine.php
12256 views
1
<?php
2
3
4
/**
5
* This class does an unusual amount of flow control via exceptions. The intent
6
* is to make the workflows highly testable, because this code is high-stakes
7
* and difficult to test.
8
*/
9
final class PhabricatorAuthInviteEngine extends Phobject {
10
11
private $viewer;
12
private $userHasConfirmedVerify;
13
14
public function setViewer(PhabricatorUser $viewer) {
15
$this->viewer = $viewer;
16
return $this;
17
}
18
19
public function getViewer() {
20
if (!$this->viewer) {
21
throw new PhutilInvalidStateException('setViewer');
22
}
23
return $this->viewer;
24
}
25
26
public function setUserHasConfirmedVerify($confirmed) {
27
$this->userHasConfirmedVerify = $confirmed;
28
return $this;
29
}
30
31
private function shouldVerify() {
32
return $this->userHasConfirmedVerify;
33
}
34
35
public function processInviteCode($code) {
36
$viewer = $this->getViewer();
37
38
$invite = id(new PhabricatorAuthInviteQuery())
39
->setViewer($viewer)
40
->withVerificationCodes(array($code))
41
->executeOne();
42
if (!$invite) {
43
throw id(new PhabricatorAuthInviteInvalidException(
44
pht('Bad Invite Code'),
45
pht(
46
'The invite code in the link you clicked is invalid. Check that '.
47
'you followed the link correctly.')))
48
->setCancelButtonURI('/')
49
->setCancelButtonText(pht('Curses!'));
50
}
51
52
$accepted_phid = $invite->getAcceptedByPHID();
53
if ($accepted_phid) {
54
if ($accepted_phid == $viewer->getPHID()) {
55
throw id(new PhabricatorAuthInviteInvalidException(
56
pht('Already Accepted'),
57
pht(
58
'You have already accepted this invitation.')))
59
->setCancelButtonURI('/')
60
->setCancelButtonText(pht('Awesome'));
61
} else {
62
throw id(new PhabricatorAuthInviteInvalidException(
63
pht('Already Accepted'),
64
pht(
65
'The invite code in the link you clicked has already '.
66
'been accepted.')))
67
->setCancelButtonURI('/')
68
->setCancelButtonText(pht('Continue'));
69
}
70
}
71
72
$email = id(new PhabricatorUserEmail())->loadOneWhere(
73
'address = %s',
74
$invite->getEmailAddress());
75
76
if ($viewer->isLoggedIn()) {
77
$this->handleLoggedInInvite($invite, $viewer, $email);
78
}
79
80
if ($email) {
81
$other_user = $this->loadUserForEmail($email);
82
83
if ($email->getIsVerified()) {
84
throw id(new PhabricatorAuthInviteLoginException(
85
pht('Already Registered'),
86
pht(
87
'The email address you just clicked a link from is already '.
88
'verified and associated with a registered account (%s). Log '.
89
'in to continue.',
90
phutil_tag('strong', array(), $other_user->getName()))))
91
->setCancelButtonText(pht('Log In'))
92
->setCancelButtonURI($this->getLoginURI());
93
} else if ($email->getIsPrimary()) {
94
throw id(new PhabricatorAuthInviteLoginException(
95
pht('Already Registered'),
96
pht(
97
'The email address you just clicked a link from is already '.
98
'the primary email address for a registered account (%s). Log '.
99
'in to continue.',
100
phutil_tag('strong', array(), $other_user->getName()))))
101
->setCancelButtonText(pht('Log In'))
102
->setCancelButtonURI($this->getLoginURI());
103
} else if (!$this->shouldVerify()) {
104
throw id(new PhabricatorAuthInviteVerifyException(
105
pht('Already Associated'),
106
pht(
107
'The email address you just clicked a link from is already '.
108
'associated with a registered account (%s), but is not '.
109
'verified. Log in to that account to continue. If you can not '.
110
'log in, you can register a new account.',
111
phutil_tag('strong', array(), $other_user->getName()))))
112
->setCancelButtonText(pht('Log In'))
113
->setCancelButtonURI($this->getLoginURI())
114
->setSubmitButtonText(pht('Register New Account'));
115
} else {
116
// NOTE: The address is not verified and not a primary address, so
117
// we will eventually steal it if the user completes registration.
118
}
119
}
120
121
// The invite and email address are OK, but the user needs to register.
122
return $invite;
123
}
124
125
private function handleLoggedInInvite(
126
PhabricatorAuthInvite $invite,
127
PhabricatorUser $viewer,
128
PhabricatorUserEmail $email = null) {
129
130
if ($email && ($email->getUserPHID() !== $viewer->getPHID())) {
131
$other_user = $this->loadUserForEmail($email);
132
if ($email->getIsVerified()) {
133
throw id(new PhabricatorAuthInviteAccountException(
134
pht('Wrong Account'),
135
pht(
136
'You are logged in as %s, but the email address you just '.
137
'clicked a link from is already verified and associated '.
138
'with another account (%s). Switch accounts, then try again.',
139
phutil_tag('strong', array(), $viewer->getUsername()),
140
phutil_tag('strong', array(), $other_user->getName()))))
141
->setSubmitButtonText(pht('Log Out'))
142
->setSubmitButtonURI($this->getLogoutURI())
143
->setCancelButtonURI('/');
144
} else if ($email->getIsPrimary()) {
145
// NOTE: We never steal primary addresses from other accounts, even
146
// if they are unverified. This would leave the other account with
147
// no address. Users can use password recovery to access the other
148
// account if they really control the address.
149
throw id(new PhabricatorAuthInviteAccountException(
150
pht('Wrong Account'),
151
pht(
152
'You are logged in as %s, but the email address you just '.
153
'clicked a link from is already the primary email address '.
154
'for another account (%s). Switch accounts, then try again.',
155
phutil_tag('strong', array(), $viewer->getUsername()),
156
phutil_tag('strong', array(), $other_user->getName()))))
157
->setSubmitButtonText(pht('Log Out'))
158
->setSubmitButtonURI($this->getLogoutURI())
159
->setCancelButtonURI('/');
160
} else if (!$this->shouldVerify()) {
161
throw id(new PhabricatorAuthInviteVerifyException(
162
pht('Verify Email'),
163
pht(
164
'You are logged in as %s, but the email address (%s) you just '.
165
'clicked a link from is already associated with another '.
166
'account (%s). You can log out to switch accounts, or verify '.
167
'the address and attach it to your current account. Attach '.
168
'email address %s to user account %s?',
169
phutil_tag('strong', array(), $viewer->getUsername()),
170
phutil_tag('strong', array(), $invite->getEmailAddress()),
171
phutil_tag('strong', array(), $other_user->getName()),
172
phutil_tag('strong', array(), $invite->getEmailAddress()),
173
phutil_tag('strong', array(), $viewer->getUsername()))))
174
->setSubmitButtonText(
175
pht(
176
'Verify %s',
177
$invite->getEmailAddress()))
178
->setCancelButtonText(pht('Log Out'))
179
->setCancelButtonURI($this->getLogoutURI());
180
}
181
}
182
183
if (!$email) {
184
$email = id(new PhabricatorUserEmail())
185
->setAddress($invite->getEmailAddress())
186
->setIsVerified(0)
187
->setIsPrimary(0);
188
}
189
190
if (!$email->getIsVerified()) {
191
// We're doing this check here so that we can verify the address if
192
// it's already attached to the viewer's account, just not verified.
193
if (!$this->shouldVerify()) {
194
throw id(new PhabricatorAuthInviteVerifyException(
195
pht('Verify Email'),
196
pht(
197
'Verify this email address (%s) and attach it to your '.
198
'account (%s)?',
199
phutil_tag('strong', array(), $invite->getEmailAddress()),
200
phutil_tag('strong', array(), $viewer->getUsername()))))
201
->setSubmitButtonText(
202
pht(
203
'Verify %s',
204
$invite->getEmailAddress()))
205
->setCancelButtonURI('/');
206
}
207
208
$editor = id(new PhabricatorUserEditor())
209
->setActor($viewer);
210
211
// If this is a new email, add it to the user's account.
212
if (!$email->getUserPHID()) {
213
$editor->addEmail($viewer, $email);
214
}
215
216
// If another user added this email (but has not verified it),
217
// take it from them.
218
$editor->reassignEmail($viewer, $email);
219
220
$editor->verifyEmail($viewer, $email);
221
}
222
223
$invite->setAcceptedByPHID($viewer->getPHID());
224
$invite->save();
225
226
// If we make it here, the user was already logged in with the email
227
// address attached to their account and verified, or we attached it to
228
// their account (if it was not already attached) and verified it.
229
throw new PhabricatorAuthInviteRegisteredException();
230
}
231
232
private function loadUserForEmail(PhabricatorUserEmail $email) {
233
$user = id(new PhabricatorHandleQuery())
234
->setViewer(PhabricatorUser::getOmnipotentUser())
235
->withPHIDs(array($email->getUserPHID()))
236
->executeOne();
237
if (!$user) {
238
throw new Exception(
239
pht(
240
'Email record ("%s") has bad associated user PHID ("%s").',
241
$email->getAddress(),
242
$email->getUserPHID()));
243
}
244
245
return $user;
246
}
247
248
private function getLoginURI() {
249
return '/auth/start/';
250
}
251
252
private function getLogoutURI() {
253
return '/logout/';
254
}
255
256
}
257
258