Merge lp:~edwin-grubbs/launchpad/bug-554153-claim-profile-bug into lp:launchpad
- bug-554153-claim-profile-bug
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Edwin Grubbs |
Approved revision: | no longer in the source branch. |
Merged at revision: | not available |
Proposed branch: | lp:~edwin-grubbs/launchpad/bug-554153-claim-profile-bug |
Merge into: | lp:launchpad |
Diff against target: |
654 lines (+56/-377) 10 files modified
lib/canonical/launchpad/browser/logintoken.py (+4/-56) lib/canonical/launchpad/doc/logintoken-pages.txt (+9/-77) lib/canonical/launchpad/interfaces/authtoken.py (+0/-7) lib/canonical/launchpad/zcml/logintoken.zcml (+0/-7) lib/lp/registry/browser/configure.zcml (+0/-6) lib/lp/registry/browser/person.py (+22/-62) lib/lp/registry/stories/person/xx-person-claim-merge.txt (+17/-2) lib/lp/registry/stories/person/xx-person-claim.txt (+0/-148) lib/lp/registry/stories/team/xx-team-claim.txt (+4/-6) lib/lp/registry/templates/person-index.pt (+0/-6) |
To merge this branch: | bzr merge lp:~edwin-grubbs/launchpad/bug-554153-claim-profile-bug |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Abel Deuring (community) | code | Approve | |
Review via email: mp+23550@code.launchpad.net |
Commit message
Description of the change
Summary
-------
Fixes bug 554153.
Remove $person/+claim page and point links at /people/
Implementation details
-------
The +claim page emails the user a token that lets them access the
+claimprofile page. We can rid of them both.
lib/
lib/
lib/
The +claimteam page is still used. It was subclassed from
PersonClaimView, but now those two views can be combined.
lib/
There is no real reason to display a different set of links for an
anonymous user looking at an inactive person page. The anonymous
user will be forced to log in if the +requestmerge or +claimteam
links is clicked.
lib/
Tests:
lib/
lib/
lib/
Tests
-----
./bin/test -vv -t claim
Demo and Q/A
------------
* Open http://
* The "Are you Diogo Matsubara?" link should now point to +requestmerge.
* Open http://
* The "Is this a team you run?" link should still point to +claimteam.
Abel Deuring (adeuring) : | # |
Preview Diff
1 | === modified file 'lib/canonical/launchpad/browser/logintoken.py' | |||
2 | --- lib/canonical/launchpad/browser/logintoken.py 2010-04-01 19:22:36 +0000 | |||
3 | +++ lib/canonical/launchpad/browser/logintoken.py 2010-04-17 16:08:28 +0000 | |||
4 | @@ -6,7 +6,6 @@ | |||
5 | 6 | __all__ = [ | 6 | __all__ = [ |
6 | 7 | 'BaseTokenView', | 7 | 'BaseTokenView', |
7 | 8 | 'BugTrackerHandshakeView', | 8 | 'BugTrackerHandshakeView', |
8 | 9 | 'ClaimProfileView', | ||
9 | 10 | 'ClaimTeamView', | 9 | 'ClaimTeamView', |
10 | 11 | 'LoginTokenSetNavigation', | 10 | 'LoginTokenSetNavigation', |
11 | 12 | 'LoginTokenView', | 11 | 'LoginTokenView', |
12 | @@ -36,7 +35,6 @@ | |||
13 | 36 | IGPGHandler) | 35 | IGPGHandler) |
14 | 37 | from canonical.launchpad.interfaces.logintoken import ( | 36 | from canonical.launchpad.interfaces.logintoken import ( |
15 | 38 | IGPGKeyValidationForm, ILoginTokenSet) | 37 | IGPGKeyValidationForm, ILoginTokenSet) |
16 | 39 | from canonical.launchpad.interfaces.lpstorm import IMasterObject | ||
17 | 40 | from canonical.launchpad.webapp.interfaces import ( | 38 | from canonical.launchpad.webapp.interfaces import ( |
18 | 41 | IAlwaysSubmittedWidget, IPlacelessLoginSource) | 39 | IAlwaysSubmittedWidget, IPlacelessLoginSource) |
19 | 42 | from canonical.launchpad.webapp.login import logInPrincipal | 40 | from canonical.launchpad.webapp.login import logInPrincipal |
20 | @@ -45,10 +43,10 @@ | |||
21 | 45 | from canonical.launchpad.webapp import ( | 43 | from canonical.launchpad.webapp import ( |
22 | 46 | action, canonical_url, custom_widget, GetitemNavigation, | 44 | action, canonical_url, custom_widget, GetitemNavigation, |
23 | 47 | LaunchpadEditFormView, LaunchpadFormView, LaunchpadView) | 45 | LaunchpadEditFormView, LaunchpadFormView, LaunchpadView) |
25 | 48 | from canonical.widgets import LaunchpadRadioWidget, PasswordChangeWidget | 46 | from canonical.widgets import LaunchpadRadioWidget |
26 | 49 | 47 | ||
27 | 50 | from lp.registry.browser.team import HasRenewalPolicyMixin | 48 | from lp.registry.browser.team import HasRenewalPolicyMixin |
29 | 51 | from lp.registry.interfaces.person import IPerson, IPersonSet, ITeam | 49 | from lp.registry.interfaces.person import IPersonSet, ITeam |
30 | 52 | 50 | ||
31 | 53 | 51 | ||
32 | 54 | UTC = pytz.UTC | 52 | UTC = pytz.UTC |
33 | @@ -77,7 +75,6 @@ | |||
34 | 77 | LoginTokenType.VALIDATETEAMEMAIL: '+validateteamemail', | 75 | LoginTokenType.VALIDATETEAMEMAIL: '+validateteamemail', |
35 | 78 | LoginTokenType.VALIDATEGPG: '+validategpg', | 76 | LoginTokenType.VALIDATEGPG: '+validategpg', |
36 | 79 | LoginTokenType.VALIDATESIGNONLYGPG: '+validatesignonlygpg', | 77 | LoginTokenType.VALIDATESIGNONLYGPG: '+validatesignonlygpg', |
37 | 80 | LoginTokenType.PROFILECLAIM: '+claimprofile', | ||
38 | 81 | LoginTokenType.TEAMCLAIM: '+claimteam', | 78 | LoginTokenType.TEAMCLAIM: '+claimteam', |
39 | 82 | LoginTokenType.BUGTRACKER: '+bugtracker-handshake', | 79 | LoginTokenType.BUGTRACKER: '+bugtracker-handshake', |
40 | 83 | } | 80 | } |
41 | @@ -184,56 +181,6 @@ | |||
42 | 184 | return True | 181 | return True |
43 | 185 | 182 | ||
44 | 186 | 183 | ||
45 | 187 | class ClaimProfileView(BaseTokenView, LaunchpadFormView): | ||
46 | 188 | schema = IPerson | ||
47 | 189 | field_names = ['displayname', 'hide_email_addresses', 'password'] | ||
48 | 190 | custom_widget('password', PasswordChangeWidget) | ||
49 | 191 | label = 'Claim Launchpad profile' | ||
50 | 192 | |||
51 | 193 | expected_token_types = (LoginTokenType.PROFILECLAIM,) | ||
52 | 194 | |||
53 | 195 | def initialize(self): | ||
54 | 196 | if not self.redirectIfInvalidOrConsumedToken(): | ||
55 | 197 | self.claimed_profile = getUtility(IEmailAddressSet).getByEmail( | ||
56 | 198 | self.context.email).person | ||
57 | 199 | super(ClaimProfileView, self).initialize() | ||
58 | 200 | |||
59 | 201 | @property | ||
60 | 202 | def initial_values(self): | ||
61 | 203 | return {'displayname': self.claimed_profile.displayname} | ||
62 | 204 | |||
63 | 205 | @property | ||
64 | 206 | def default_next_url(self): | ||
65 | 207 | return canonical_url(self.claimed_profile) | ||
66 | 208 | |||
67 | 209 | @action(_('Continue'), name='confirm') | ||
68 | 210 | def confirm_action(self, action, data): | ||
69 | 211 | email = getUtility(IEmailAddressSet).getByEmail(self.context.email) | ||
70 | 212 | person = IMasterObject(email.person) | ||
71 | 213 | |||
72 | 214 | # The user is not yet logged in, but we need to set some | ||
73 | 215 | # things on his new account, so we need to remove the security | ||
74 | 216 | # proxy from it. | ||
75 | 217 | # XXX: Guilherme Salgado 2006-09-27 bug=62674: | ||
76 | 218 | # We should be able to login with this person and set the | ||
77 | 219 | # password, to avoid removing the security proxy, but it didn't | ||
78 | 220 | # work, so I'm leaving this hack for now. | ||
79 | 221 | naked_person = removeSecurityProxy(person) | ||
80 | 222 | naked_person.displayname = data['displayname'] | ||
81 | 223 | naked_person.hide_email_addresses = data['hide_email_addresses'] | ||
82 | 224 | |||
83 | 225 | naked_email = removeSecurityProxy(email) | ||
84 | 226 | |||
85 | 227 | removeSecurityProxy(IMasterObject(email.account)).activate( | ||
86 | 228 | comment="Activated by claim profile.", | ||
87 | 229 | password=data['password'], | ||
88 | 230 | preferred_email=naked_email) | ||
89 | 231 | self.context.consume() | ||
90 | 232 | self.logInPrincipalByEmail(naked_email.email) | ||
91 | 233 | self.request.response.addInfoNotification(_( | ||
92 | 234 | "Profile claimed successfully")) | ||
93 | 235 | |||
94 | 236 | |||
95 | 237 | class ClaimTeamView( | 184 | class ClaimTeamView( |
96 | 238 | BaseTokenView, HasRenewalPolicyMixin, LaunchpadEditFormView): | 185 | BaseTokenView, HasRenewalPolicyMixin, LaunchpadEditFormView): |
97 | 239 | 186 | ||
98 | @@ -356,7 +303,8 @@ | |||
99 | 356 | 303 | ||
100 | 357 | # We compare the word-splitted content to avoid failures due | 304 | # We compare the word-splitted content to avoid failures due |
101 | 358 | # to whitepace differences. | 305 | # to whitepace differences. |
103 | 359 | if signature.plain_data.split() != self.context.validation_phrase.split(): | 306 | if (signature.plain_data.split() |
104 | 307 | != self.context.validation_phrase.split()): | ||
105 | 360 | self.addError(_( | 308 | self.addError(_( |
106 | 361 | 'The signed content does not match the message found ' | 309 | 'The signed content does not match the message found ' |
107 | 362 | 'in the email.')) | 310 | 'in the email.')) |
108 | 363 | 311 | ||
109 | === modified file 'lib/canonical/launchpad/doc/logintoken-pages.txt' | |||
110 | --- lib/canonical/launchpad/doc/logintoken-pages.txt 2010-04-01 18:48:04 +0000 | |||
111 | +++ lib/canonical/launchpad/doc/logintoken-pages.txt 2010-04-17 16:08:28 +0000 | |||
112 | @@ -6,11 +6,16 @@ | |||
113 | 6 | token sent to him in an email. | 6 | token sent to him in an email. |
114 | 7 | 7 | ||
115 | 8 | 8 | ||
117 | 9 | == Claiming a profile == | 9 | == Validating GPG keys == |
118 | 10 | 10 | ||
122 | 11 | The ClaimProfileView allows users to claim accounts created by Launchpad | 11 | In order to add a GPG key to Launchpad we require that the person |
123 | 12 | processes. The user profile for Matsubara was created during an import. | 12 | registering it decrypt an email and follow the instructions in it. |
124 | 13 | The user profile does not have a preferred email address or a password. | 13 | The last step in that workflow is the +validategpg page, which can |
125 | 14 | be accessed by non-logged-in users. That page will attempt to access | ||
126 | 15 | all email addresses of the requester but that may fail when the user | ||
127 | 16 | is not logged in and the requester has chosen to hide his email | ||
128 | 17 | addresses, so here we'll make sure that even non-logged-in users can | ||
129 | 18 | perform the last step of the workflow for adding a GPG key. | ||
130 | 14 | 19 | ||
131 | 15 | >>> from zope.component import getMultiAdapter | 20 | >>> from zope.component import getMultiAdapter |
132 | 16 | >>> from canonical.launchpad.interfaces.authtoken import LoginTokenType | 21 | >>> from canonical.launchpad.interfaces.authtoken import LoginTokenType |
133 | @@ -25,79 +30,6 @@ | |||
134 | 25 | >>> person_set = PersonSet() | 30 | >>> person_set = PersonSet() |
135 | 26 | >>> login_token_set = getUtility(ILoginTokenSet) | 31 | >>> login_token_set = getUtility(ILoginTokenSet) |
136 | 27 | 32 | ||
137 | 28 | >>> matsubara = person_set.getByEmail('matsubara@async.com.br') | ||
138 | 29 | >>> matsubara.name | ||
139 | 30 | u'matsubara' | ||
140 | 31 | >>> matsubara.displayname | ||
141 | 32 | u'Diogo Matsubara' | ||
142 | 33 | >>> print matsubara.preferredemail | ||
143 | 34 | None | ||
144 | 35 | |||
145 | 36 | >>> matsubara.account.status | ||
146 | 37 | <DBItem AccountStatus.NOACCOUNT, ...> | ||
147 | 38 | >>> print matsubara.account.password | ||
148 | 39 | None | ||
149 | 40 | >>> matsubara.is_valid_person | ||
150 | 41 | False | ||
151 | 42 | |||
152 | 43 | The user can claim his profile by providing the email address that | ||
153 | 44 | is associated with the profile. If the correct address is provided, | ||
154 | 45 | the user is sent an email with a link to a LoginToken. | ||
155 | 46 | |||
156 | 47 | >>> login_token = login_token_set.new( | ||
157 | 48 | ... matsubara, 'matsubara@async.com.br', | ||
158 | 49 | ... 'matsubara@async.com.br', LoginTokenType.PROFILECLAIM) | ||
159 | 50 | |||
160 | 51 | The user is asked to provide a display name and password, and to | ||
161 | 52 | indicate if his email address can be shown to other Launchpad users. | ||
162 | 53 | |||
163 | 54 | >>> request = LaunchpadTestRequest( | ||
164 | 55 | ... SERVER_URL='http://launchpad.dev', | ||
165 | 56 | ... PATH_INFO='/token/%s/+claimprofile' % login_token.token, | ||
166 | 57 | ... method='POST', | ||
167 | 58 | ... form={ | ||
168 | 59 | ... 'field.displayname': 'D. Matsubara', | ||
169 | 60 | ... 'field.hide_email_addresses': 'on', | ||
170 | 61 | ... 'field.password': 'test4', | ||
171 | 62 | ... 'field.password_dupe': 'test4', | ||
172 | 63 | ... 'field.actions.confirm': 'Continue', | ||
173 | 64 | ... }) | ||
174 | 65 | >>> login(ANONYMOUS, request) | ||
175 | 66 | >>> claimprofile_view = getMultiAdapter( | ||
176 | 67 | ... (login_token, request), name="+claimprofile") | ||
177 | 68 | >>> claimprofile_view.initialize() | ||
178 | 69 | >>> claimprofile_view.errors | ||
179 | 70 | [] | ||
180 | 71 | >>> transaction.commit() | ||
181 | 72 | |||
182 | 73 | The user's Person, EmailAddress, and Account objects are updated. | ||
183 | 74 | |||
184 | 75 | >>> matsubara.displayname | ||
185 | 76 | u'D. Matsubara' | ||
186 | 77 | >>> matsubara.hide_email_addresses | ||
187 | 78 | True | ||
188 | 79 | >>> matsubara.account.preferredemail.email | ||
189 | 80 | u'matsubara@async.com.br' | ||
190 | 81 | |||
191 | 82 | >>> matsubara.account.status | ||
192 | 83 | <DBItem AccountStatus.ACTIVE, ...> | ||
193 | 84 | >>> matsubara.account.status_comment | ||
194 | 85 | u'Activated by claim profile.' | ||
195 | 86 | >>> matsubara.is_valid_person | ||
196 | 87 | True | ||
197 | 88 | |||
198 | 89 | |||
199 | 90 | == Validating GPG keys == | ||
200 | 91 | |||
201 | 92 | In order to add a GPG key to Launchpad we require that the person | ||
202 | 93 | registering it decrypt an email and follow the instructions in it. | ||
203 | 94 | The last step in that workflow is the +validategpg page, which can | ||
204 | 95 | be accessed by non-logged-in users. That page will attempt to access | ||
205 | 96 | all email addresses of the requester but that may fail when the user | ||
206 | 97 | is not logged in and the requester has chosen to hide his email | ||
207 | 98 | addresses, so here we'll make sure that even non-logged-in users can | ||
208 | 99 | perform the last step of the workflow for adding a GPG key. | ||
209 | 100 | |||
210 | 101 | >>> sample_person = person_set.getByEmail('test@canonical.com') | 33 | >>> sample_person = person_set.getByEmail('test@canonical.com') |
211 | 102 | >>> sample_person.hide_email_addresses | 34 | >>> sample_person.hide_email_addresses |
212 | 103 | True | 35 | True |
213 | 104 | 36 | ||
214 | === modified file 'lib/canonical/launchpad/interfaces/authtoken.py' | |||
215 | --- lib/canonical/launchpad/interfaces/authtoken.py 2010-03-30 20:02:53 +0000 | |||
216 | +++ lib/canonical/launchpad/interfaces/authtoken.py 2010-04-17 16:08:28 +0000 | |||
217 | @@ -78,13 +78,6 @@ | |||
218 | 78 | needs to be validated. | 78 | needs to be validated. |
219 | 79 | """) | 79 | """) |
220 | 80 | 80 | ||
221 | 81 | PROFILECLAIM = DBItem(8, """ | ||
222 | 82 | Claim an unvalidated Launchpad profile | ||
223 | 83 | |||
224 | 84 | A user has found an unvalidated profile in Launchpad and is trying | ||
225 | 85 | to claim it. | ||
226 | 86 | """) | ||
227 | 87 | |||
228 | 88 | NEWPROFILE = DBItem(9, """ | 81 | NEWPROFILE = DBItem(9, """ |
229 | 89 | A user created a new Launchpad profile for another person. | 82 | A user created a new Launchpad profile for another person. |
230 | 90 | 83 | ||
231 | 91 | 84 | ||
232 | === modified file 'lib/canonical/launchpad/zcml/logintoken.zcml' | |||
233 | --- lib/canonical/launchpad/zcml/logintoken.zcml 2010-03-30 20:02:53 +0000 | |||
234 | +++ lib/canonical/launchpad/zcml/logintoken.zcml 2010-04-17 16:08:28 +0000 | |||
235 | @@ -77,13 +77,6 @@ | |||
236 | 77 | 77 | ||
237 | 78 | <browser:page | 78 | <browser:page |
238 | 79 | for="canonical.launchpad.interfaces.ILoginToken" | 79 | for="canonical.launchpad.interfaces.ILoginToken" |
239 | 80 | name="+claimprofile" | ||
240 | 81 | permission="zope.Public" | ||
241 | 82 | class="canonical.launchpad.browser.ClaimProfileView" | ||
242 | 83 | template="../templates/logintoken-claimprofile.pt" /> | ||
243 | 84 | |||
244 | 85 | <browser:page | ||
245 | 86 | for="canonical.launchpad.interfaces.ILoginToken" | ||
246 | 87 | name="+claimteam" | 80 | name="+claimteam" |
247 | 88 | permission="launchpad.AnyPerson" | 81 | permission="launchpad.AnyPerson" |
248 | 89 | class="canonical.launchpad.browser.ClaimTeamView" | 82 | class="canonical.launchpad.browser.ClaimTeamView" |
249 | 90 | 83 | ||
250 | === modified file 'lib/lp/registry/browser/configure.zcml' | |||
251 | --- lib/lp/registry/browser/configure.zcml 2010-04-12 15:33:19 +0000 | |||
252 | +++ lib/lp/registry/browser/configure.zcml 2010-04-17 16:08:28 +0000 | |||
253 | @@ -790,12 +790,6 @@ | |||
254 | 790 | permission="launchpad.AnyPerson" | 790 | permission="launchpad.AnyPerson" |
255 | 791 | template="../templates/person-claim-team.pt"/> | 791 | template="../templates/person-claim-team.pt"/> |
256 | 792 | <browser:page | 792 | <browser:page |
257 | 793 | name="+claim" | ||
258 | 794 | for="lp.registry.interfaces.person.IPerson" | ||
259 | 795 | class="lp.registry.browser.person.PersonClaimView" | ||
260 | 796 | permission="zope.Public" | ||
261 | 797 | template="../templates/person-claim.pt"/> | ||
262 | 798 | <browser:page | ||
263 | 799 | name="+editlanguages" | 793 | name="+editlanguages" |
264 | 800 | for="lp.registry.interfaces.person.IPerson" | 794 | for="lp.registry.interfaces.person.IPerson" |
265 | 801 | class="lp.registry.browser.person.PersonLanguagesView" | 795 | class="lp.registry.browser.person.PersonLanguagesView" |
266 | 802 | 796 | ||
267 | === modified file 'lib/lp/registry/browser/person.py' | |||
268 | --- lib/lp/registry/browser/person.py 2010-04-14 18:31:51 +0000 | |||
269 | +++ lib/lp/registry/browser/person.py 2010-04-17 16:08:28 +0000 | |||
270 | @@ -18,7 +18,6 @@ | |||
271 | 18 | 'PersonAssignedBugTaskSearchListingView', | 18 | 'PersonAssignedBugTaskSearchListingView', |
272 | 19 | 'PersonBrandingView', | 19 | 'PersonBrandingView', |
273 | 20 | 'PersonBugsMenu', | 20 | 'PersonBugsMenu', |
274 | 21 | 'PersonClaimView', | ||
275 | 22 | 'PersonCodeOfConductEditView', | 21 | 'PersonCodeOfConductEditView', |
276 | 23 | 'PersonCommentedBugTaskSearchListingView', | 22 | 'PersonCommentedBugTaskSearchListingView', |
277 | 24 | 'PersonDeactivateAccountView', | 23 | 'PersonDeactivateAccountView', |
278 | @@ -218,8 +217,7 @@ | |||
279 | 218 | from canonical.launchpad.webapp.authorization import check_permission | 217 | from canonical.launchpad.webapp.authorization import check_permission |
280 | 219 | from canonical.launchpad.webapp.batching import BatchNavigator | 218 | from canonical.launchpad.webapp.batching import BatchNavigator |
281 | 220 | from canonical.launchpad.webapp.breadcrumb import Breadcrumb | 219 | from canonical.launchpad.webapp.breadcrumb import Breadcrumb |
284 | 221 | from canonical.launchpad.webapp.login import ( | 220 | from canonical.launchpad.webapp.login import logoutPerson |
283 | 222 | logoutPerson, allowUnauthenticatedSession) | ||
285 | 223 | from canonical.launchpad.webapp.menu import get_current_view | 221 | from canonical.launchpad.webapp.menu import get_current_view |
286 | 224 | from canonical.launchpad.webapp.publisher import LaunchpadView | 222 | from canonical.launchpad.webapp.publisher import LaunchpadView |
287 | 225 | from canonical.launchpad.webapp.tales import ( | 223 | from canonical.launchpad.webapp.tales import ( |
288 | @@ -1397,11 +1395,14 @@ | |||
289 | 1397 | self.next_url = self.request.getApplicationURL() | 1395 | self.next_url = self.request.getApplicationURL() |
290 | 1398 | 1396 | ||
291 | 1399 | 1397 | ||
294 | 1400 | class PersonClaimView(LaunchpadFormView): | 1398 | class BeginTeamClaimView(LaunchpadFormView): |
295 | 1401 | """The page where a user can claim an unvalidated profile.""" | 1399 | """Where you can claim an unvalidated profile turning it into a team. |
296 | 1402 | 1400 | ||
297 | 1401 | This is actually just the first step, where you enter the email address | ||
298 | 1402 | of the team and we email further instructions to that address. | ||
299 | 1403 | """ | ||
300 | 1404 | label = 'Claim team' | ||
301 | 1403 | schema = IPersonClaim | 1405 | schema = IPersonClaim |
302 | 1404 | label = 'Claim account' | ||
303 | 1405 | 1406 | ||
304 | 1406 | def initialize(self): | 1407 | def initialize(self): |
305 | 1407 | if self.context.is_valid_person_or_team: | 1408 | if self.context.is_valid_person_or_team: |
306 | @@ -1431,28 +1432,17 @@ | |||
307 | 1431 | "associated with." | 1432 | "associated with." |
308 | 1432 | % self.context.name) | 1433 | % self.context.name) |
309 | 1433 | elif email.personID != self.context.id: | 1434 | elif email.personID != self.context.id: |
332 | 1434 | if email.person.is_valid_person: | 1435 | error = structured( |
333 | 1435 | error = structured( | 1436 | "This email address is associated with yet another " |
334 | 1436 | "This email address is associated with yet another " | 1437 | "Launchpad profile, which you seem to have used at " |
335 | 1437 | "Launchpad profile, which you seem to have used at " | 1438 | "some point. If that's the case, you can " |
336 | 1438 | "some point. If that's the case, you can " | 1439 | '<a href="/people/+requestmerge' |
337 | 1439 | '<a href="/people/+requestmerge' | 1440 | '?field.dupe_person=%s">combine ' |
338 | 1440 | '?field.dupe_person=%s">combine ' | 1441 | "this profile with the other one</a> (you'll " |
339 | 1441 | "this profile with the other one</a> (you'll " | 1442 | "have to log in with the other profile first, " |
340 | 1442 | "have to log in with the other profile first, " | 1443 | "though). If that's not the case, please try with a " |
341 | 1443 | "though). If that's not the case, please try with a " | 1444 | "different email address.", |
342 | 1444 | "different email address.", | 1445 | self.context.name) |
321 | 1445 | self.context.name) | ||
322 | 1446 | else: | ||
323 | 1447 | # There seems to be another unvalidated profile for you! | ||
324 | 1448 | error = structured( | ||
325 | 1449 | "Although this email address is not associated with " | ||
326 | 1450 | "this profile, it's associated with yet another " | ||
327 | 1451 | 'one. You can <a href="%s/+claim">claim that other ' | ||
328 | 1452 | 'profile</a> and then later ' | ||
329 | 1453 | '<a href="/people/+requestmerge">combine</a> both ' | ||
330 | 1454 | 'of them into a single one.', | ||
331 | 1455 | canonical_url(email.person)) | ||
343 | 1456 | else: | 1446 | else: |
344 | 1457 | # Yay! You got the right email this time. | 1447 | # Yay! You got the right email this time. |
345 | 1458 | pass | 1448 | pass |
346 | @@ -1463,34 +1453,6 @@ | |||
347 | 1463 | def next_url(self): | 1453 | def next_url(self): |
348 | 1464 | return canonical_url(self.context) | 1454 | return canonical_url(self.context) |
349 | 1465 | 1455 | ||
350 | 1466 | @action(_("E-mail Me"), name="confirm") | ||
351 | 1467 | def confirm_action(self, action, data): | ||
352 | 1468 | email = data['emailaddress'] | ||
353 | 1469 | token = getUtility(ILoginTokenSet).new( | ||
354 | 1470 | requester=None, requesteremail=None, email=email, | ||
355 | 1471 | tokentype=LoginTokenType.PROFILECLAIM) | ||
356 | 1472 | token.sendClaimProfileEmail() | ||
357 | 1473 | # A dance to assert that we want to break the rules about no | ||
358 | 1474 | # unauthenticated sessions. Only after this next line is it safe | ||
359 | 1475 | # to use the ``addNoticeNotification`` method. | ||
360 | 1476 | allowUnauthenticatedSession(self.request) | ||
361 | 1477 | self.request.response.addInfoNotification(_( | ||
362 | 1478 | "A confirmation message has been sent to '${email}'. " | ||
363 | 1479 | "Follow the instructions in that message to finish claiming this " | ||
364 | 1480 | "profile. " | ||
365 | 1481 | "(If the message doesn't arrive in a few minutes, your mail " | ||
366 | 1482 | "provider might use 'greylisting', which could delay the message " | ||
367 | 1483 | "for up to an hour or two.)", mapping=dict(email=email))) | ||
368 | 1484 | |||
369 | 1485 | |||
370 | 1486 | class BeginTeamClaimView(PersonClaimView): | ||
371 | 1487 | """Where you can claim an unvalidated profile turning it into a team. | ||
372 | 1488 | |||
373 | 1489 | This is actually just the first step, where you enter the email address | ||
374 | 1490 | of the team and we email further instructions to that address. | ||
375 | 1491 | """ | ||
376 | 1492 | label = 'Claim team' | ||
377 | 1493 | |||
378 | 1494 | @action(_("Continue"), name="confirm") | 1456 | @action(_("Continue"), name="confirm") |
379 | 1495 | def confirm_action(self, action, data): | 1457 | def confirm_action(self, action, data): |
380 | 1496 | email = data['emailaddress'] | 1458 | email = data['emailaddress'] |
381 | @@ -2398,9 +2360,9 @@ | |||
382 | 2398 | # The call to redeemVoucher returns True if it succeeds or it | 2360 | # The call to redeemVoucher returns True if it succeeds or it |
383 | 2399 | # raises an exception. Therefore the return value does not need | 2361 | # raises an exception. Therefore the return value does not need |
384 | 2400 | # to be checked. | 2362 | # to be checked. |
388 | 2401 | result = salesforce_proxy.redeemVoucher(voucher.voucher_id, | 2363 | salesforce_proxy.redeemVoucher(voucher.voucher_id, |
389 | 2402 | self.context, | 2364 | self.context, |
390 | 2403 | project) | 2365 | project) |
391 | 2404 | project.redeemSubscriptionVoucher( | 2366 | project.redeemSubscriptionVoucher( |
392 | 2405 | voucher=voucher.voucher_id, | 2367 | voucher=voucher.voucher_id, |
393 | 2406 | registrant=self.context, | 2368 | registrant=self.context, |
394 | @@ -3391,7 +3353,7 @@ | |||
395 | 3391 | 'The URL scheme "%(scheme)s" is not allowed. ' | 3353 | 'The URL scheme "%(scheme)s" is not allowed. ' |
396 | 3392 | 'Only http or https URLs may be used.', scheme=uri.scheme) | 3354 | 'Only http or https URLs may be used.', scheme=uri.scheme) |
397 | 3393 | return False | 3355 | return False |
399 | 3394 | except InvalidURIError, e: | 3356 | except InvalidURIError: |
400 | 3395 | self.error_message = structured( | 3357 | self.error_message = structured( |
401 | 3396 | '"%(url)s" is not a valid URL.', url=url) | 3358 | '"%(url)s" is not a valid URL.', url=url) |
402 | 3397 | return False | 3359 | return False |
403 | @@ -4226,8 +4188,6 @@ | |||
404 | 4226 | 4188 | ||
405 | 4227 | @action(_("Leave"), name="leave") | 4189 | @action(_("Leave"), name="leave") |
406 | 4228 | def action_save(self, action, data): | 4190 | def action_save(self, action, data): |
407 | 4229 | response = self.request.response | ||
408 | 4230 | |||
409 | 4231 | if self.user_can_request_to_leave: | 4191 | if self.user_can_request_to_leave: |
410 | 4232 | self.user.leave(self.context) | 4192 | self.user.leave(self.context) |
411 | 4233 | 4193 | ||
412 | 4234 | 4194 | ||
413 | === modified file 'lib/lp/registry/stories/person/xx-person-claim-merge.txt' | |||
414 | --- lib/lp/registry/stories/person/xx-person-claim-merge.txt 2009-07-20 15:27:26 +0000 | |||
415 | +++ lib/lp/registry/stories/person/xx-person-claim-merge.txt 2010-04-17 16:08:28 +0000 | |||
416 | @@ -1,3 +1,20 @@ | |||
417 | 1 | Claiming a person while not logged in | ||
418 | 2 | ===================================== | ||
419 | 3 | |||
420 | 4 | An anonymous user visiting an unclaimed account will be given the option | ||
421 | 5 | to request a merge, but the +requestmerge page will give an Unauthorized | ||
422 | 6 | exception that will redirect the user to the login page. | ||
423 | 7 | |||
424 | 8 | >>> anon_browser.open('http://launchpad.dev/~matsubara') | ||
425 | 9 | >>> link = anon_browser.getLink("Are you Diogo Matsubara?") | ||
426 | 10 | >>> print link.url | ||
427 | 11 | http://launchpad.dev/people/+requestmerge?field.dupe_person=matsubara | ||
428 | 12 | >>> link.click() | ||
429 | 13 | Traceback (most recent call last): | ||
430 | 14 | ... | ||
431 | 15 | Unauthorized: ... | ||
432 | 16 | |||
433 | 17 | |||
434 | 1 | Claiming a person while logged in | 18 | Claiming a person while logged in |
435 | 2 | ================================= | 19 | ================================= |
436 | 3 | 20 | ||
437 | @@ -5,8 +22,6 @@ | |||
438 | 5 | option to request a merge. | 22 | option to request a merge. |
439 | 6 | 23 | ||
440 | 7 | >>> user_browser.open('http://launchpad.dev/~matsubara') | 24 | >>> user_browser.open('http://launchpad.dev/~matsubara') |
441 | 8 | >>> user_browser.url | ||
442 | 9 | 'http://launchpad.dev/~matsubara' | ||
443 | 10 | >>> user_browser.getLink("Are you Diogo Matsubara?").click() | 25 | >>> user_browser.getLink("Are you Diogo Matsubara?").click() |
444 | 11 | 26 | ||
445 | 12 | Clicking on that link brought him to a page where he can request to | 27 | Clicking on that link brought him to a page where he can request to |
446 | 13 | 28 | ||
447 | === removed file 'lib/lp/registry/stories/person/xx-person-claim.txt' | |||
448 | --- lib/lp/registry/stories/person/xx-person-claim.txt 2009-09-16 17:56:17 +0000 | |||
449 | +++ lib/lp/registry/stories/person/xx-person-claim.txt 1970-01-01 00:00:00 +0000 | |||
450 | @@ -1,148 +0,0 @@ | |||
451 | 1 | = Claiming a person = | ||
452 | 2 | |||
453 | 3 | Matsubara was browsing through Launchpad anonymously and found a page | ||
454 | 4 | for himself, even though he has never used Launchpad (what a lie!). | ||
455 | 5 | |||
456 | 6 | >>> browser.open('http://launchpad.dev/~matsubara') | ||
457 | 7 | >>> browser.url | ||
458 | 8 | 'http://launchpad.dev/~matsubara' | ||
459 | 9 | |||
460 | 10 | He was wondering who created that when he saw a very prominent link, | ||
461 | 11 | saying 'Are you Diogo Matsubara?'. He promptly clicked on it. | ||
462 | 12 | |||
463 | 13 | >>> print extract_text( | ||
464 | 14 | ... find_tag_by_id(browser.contents, 'not-lp-user-or-team')) | ||
465 | 15 | Diogo Matsubara does not use Launchpad. This page was created... | ||
466 | 16 | ...2006-12-13...when importing the Portuguese (Brazil) (pt_BR)... | ||
467 | 17 | |||
468 | 18 | >>> browser.getLink("Are you Diogo Matsubara?").click() | ||
469 | 19 | |||
470 | 20 | Clicking on that link brought him to a page where he can claim that | ||
471 | 21 | Launchpad profile. | ||
472 | 22 | |||
473 | 23 | >>> browser.url | ||
474 | 24 | 'http://launchpad.dev/%7Ematsubara/+claim' | ||
475 | 25 | |||
476 | 26 | He first tries it with an old email address he had, but we tell him that | ||
477 | 27 | the given email address is not associated with that profile. | ||
478 | 28 | |||
479 | 29 | >>> browser.getControl('Email address').value = 'non@existent.org' | ||
480 | 30 | >>> browser.getControl('E-mail Me').click() | ||
481 | 31 | >>> soup = find_main_content(browser.contents) | ||
482 | 32 | >>> for tag in soup('div', 'message'): | ||
483 | 33 | ... print tag.renderContents() | ||
484 | 34 | We couldn't find this email address. Please try another one that could | ||
485 | 35 | possibly be associated with this profile. Note that this profile's name | ||
486 | 36 | (matsubara) was generated based on the email address it's associated with. | ||
487 | 37 | |||
488 | 38 | Then he tries with Sample Person's email address. | ||
489 | 39 | |||
490 | 40 | >>> browser.getControl('Email address').value = 'test@canonical.com' | ||
491 | 41 | >>> browser.getControl('E-mail Me').click() | ||
492 | 42 | >>> soup = find_main_content(browser.contents) | ||
493 | 43 | >>> for tag in soup('div', 'message'): | ||
494 | 44 | ... print tag.renderContents() | ||
495 | 45 | This email address is associated with yet another Launchpad profile, which | ||
496 | 46 | you seem to have used at some point. If that's the case, you can <a | ||
497 | 47 | href="/people/+requestmerge?field.dupe_person=matsubara">combine this | ||
498 | 48 | profile with the other one</a> (you'll have to log in with the other | ||
499 | 49 | profile first, though). If that's not the case, please try with a | ||
500 | 50 | different email address. | ||
501 | 51 | |||
502 | 52 | And once again he tries with an email address he found in Launchpad, but | ||
503 | 53 | this one is associated with an unvalidated profile. | ||
504 | 54 | |||
505 | 55 | >>> browser.getControl('Email address').value = 'morten@wtf.dk' | ||
506 | 56 | >>> browser.getControl('E-mail Me').click() | ||
507 | 57 | >>> soup = find_main_content(browser.contents) | ||
508 | 58 | >>> for tag in soup('div', 'message'): | ||
509 | 59 | ... print tag.renderContents() | ||
510 | 60 | Although this email address is not associated with this profile, it's | ||
511 | 61 | associated with yet another one. You can <a | ||
512 | 62 | href="http://launchpad.dev/~morten/+claim">claim that other | ||
513 | 63 | profile</a> and then later <a href="/people/+requestmerge">combine</a> | ||
514 | 64 | both of them into a single one. | ||
515 | 65 | |||
516 | 66 | Now he finally remembered his email address. | ||
517 | 67 | |||
518 | 68 | >>> browser.getControl('Email address').value = 'matsubara@async.com.br' | ||
519 | 69 | >>> browser.getControl('E-mail Me').click() | ||
520 | 70 | |||
521 | 71 | >>> from lp.services.mail import stub | ||
522 | 72 | >>> from canonical.launchpad.ftests.logintoken import ( | ||
523 | 73 | ... get_token_url_from_email) | ||
524 | 74 | >>> from_addr, to_addr, msg = stub.test_emails.pop() | ||
525 | 75 | >>> to_addr | ||
526 | 76 | ['matsubara@async.com.br'] | ||
527 | 77 | >>> token_url = get_token_url_from_email(msg) | ||
528 | 78 | >>> token_url | ||
529 | 79 | 'http://launchpad.dev/token/...' | ||
530 | 80 | |||
531 | 81 | >>> browser.open(token_url) | ||
532 | 82 | >>> browser.url == "%s/+claimprofile" % token_url | ||
533 | 83 | True | ||
534 | 84 | |||
535 | 85 | >>> browser.getControl(name='field.password').value = 'foo' | ||
536 | 86 | >>> browser.getControl(name='field.password_dupe').value = 'foo' | ||
537 | 87 | >>> browser.getControl('Continue').click() | ||
538 | 88 | |||
539 | 89 | >>> browser.url | ||
540 | 90 | 'http://launchpad.dev/~matsubara' | ||
541 | 91 | >>> soup = find_main_content(browser.contents) | ||
542 | 92 | >>> for tag in soup('div', 'informational message'): | ||
543 | 93 | ... print tag.renderContents() | ||
544 | 94 | Profile claimed successfully | ||
545 | 95 | |||
546 | 96 | It's also possible that a person tries to claim an account that has a | ||
547 | 97 | preferred email but no password, like some accounts imported from | ||
548 | 98 | Ubuntu's bugzilla. Although these accounts are not considered valid | ||
549 | 99 | (that's why they can be claimed) they /do/ have a preferred email and we | ||
550 | 100 | need to handle this. | ||
551 | 101 | |||
552 | 102 | # To see kiko's email we must be logged in, so we'll use user_browser. | ||
553 | 103 | >>> user_browser.open('http://launchpad.dev/~kiko') | ||
554 | 104 | >>> unregistered_user_info = find_tag_by_id( | ||
555 | 105 | ... user_browser.contents, 'not-lp-user-or-team') | ||
556 | 106 | >>> unregistered_user_info.li.renderContents() | ||
557 | 107 | '...christian.reis@ubuntulinux.com...' | ||
558 | 108 | >>> unregistered_user_info.a.renderContents() | ||
559 | 109 | 'Are you Christian Reis?' | ||
560 | 110 | |||
561 | 111 | >>> anon_browser.open('http://launchpad.dev/~kiko') | ||
562 | 112 | >>> anon_browser.getLink("Are you Christian Reis?").click() | ||
563 | 113 | >>> anon_browser.url | ||
564 | 114 | 'http://launchpad.dev/%7Ekiko/+claim' | ||
565 | 115 | |||
566 | 116 | >>> anon_browser.getControl('Email address').value = ( | ||
567 | 117 | ... 'christian.reis@ubuntulinux.com') | ||
568 | 118 | >>> anon_browser.getControl('E-mail Me').click() | ||
569 | 119 | |||
570 | 120 | >>> from_addr, to_addr, msg = stub.test_emails.pop() | ||
571 | 121 | >>> to_addr | ||
572 | 122 | ['christian.reis@ubuntulinux.com'] | ||
573 | 123 | >>> token_url = get_token_url_from_email(msg) | ||
574 | 124 | >>> token_url | ||
575 | 125 | 'http://launchpad.dev/token/...' | ||
576 | 126 | |||
577 | 127 | >>> anon_browser.open(token_url) | ||
578 | 128 | >>> anon_browser.url == "%s/+claimprofile" % token_url | ||
579 | 129 | True | ||
580 | 130 | |||
581 | 131 | >>> anon_browser.getControl(name='field.password').value = 'foo' | ||
582 | 132 | >>> anon_browser.getControl(name='field.password_dupe').value = 'foo' | ||
583 | 133 | >>> anon_browser.getControl('Continue').click() | ||
584 | 134 | |||
585 | 135 | >>> anon_browser.url | ||
586 | 136 | 'http://launchpad.dev/~kiko' | ||
587 | 137 | >>> soup = find_main_content(anon_browser.contents) | ||
588 | 138 | >>> for tag in soup('div', 'informational message'): | ||
589 | 139 | ... print tag.renderContents() | ||
590 | 140 | Profile claimed successfully | ||
591 | 141 | |||
592 | 142 | Trying to access +claim for an already active team 404s as you would | ||
593 | 143 | expect it to (we shouldn't be linking to from anywhere): | ||
594 | 144 | |||
595 | 145 | >>> user_browser.open("http://launchpad.dev/~ubuntu-team/+claim") | ||
596 | 146 | Traceback (most recent call last): | ||
597 | 147 | ... | ||
598 | 148 | NotFound:... | ||
599 | 149 | 0 | ||
600 | === modified file 'lib/lp/registry/stories/team/xx-team-claim.txt' | |||
601 | --- lib/lp/registry/stories/team/xx-team-claim.txt 2009-11-22 15:43:16 +0000 | |||
602 | +++ lib/lp/registry/stories/team/xx-team-claim.txt 2010-04-17 16:08:28 +0000 | |||
603 | @@ -14,7 +14,7 @@ | |||
604 | 14 | definitely doesn't seem to be a team. | 14 | definitely doesn't seem to be a team. |
605 | 15 | 15 | ||
606 | 16 | >>> browser.open('http://launchpad.dev/~matsubara') | 16 | >>> browser.open('http://launchpad.dev/~matsubara') |
608 | 17 | >>> browser.getLink(url='+claim') | 17 | >>> browser.getLink(url='+requestmerge') |
609 | 18 | <Link text='Are you Diogo Matsubara?'... | 18 | <Link text='Are you Diogo Matsubara?'... |
610 | 19 | 19 | ||
611 | 20 | If a profile does look like a team and there is a logged in user, the link | 20 | If a profile does look like a team and there is a logged in user, the link |
612 | @@ -25,15 +25,13 @@ | |||
613 | 25 | >>> [email.email for email in doc_team.guessedemails] | 25 | >>> [email.email for email in doc_team.guessedemails] |
614 | 26 | [u'doc@lists.ubuntu.com'] | 26 | [u'doc@lists.ubuntu.com'] |
615 | 27 | 27 | ||
617 | 28 | Non-logged-in users see only a link to claim the profile.. | 28 | Non-logged-in users must log in to claim a team. |
618 | 29 | 29 | ||
619 | 30 | >>> browser.open('http://launchpad.dev/~doc') | 30 | >>> browser.open('http://launchpad.dev/~doc') |
621 | 31 | >>> browser.getLink(url='+claimteam') | 31 | >>> browser.getLink(url='+claimteam').click() |
622 | 32 | Traceback (most recent call last): | 32 | Traceback (most recent call last): |
623 | 33 | ... | 33 | ... |
627 | 34 | LinkNotFoundError | 34 | Unauthorized:... |
625 | 35 | >>> browser.getLink(url='+claim') | ||
626 | 36 | <Link text='Are you Ubuntu Doc Team?'... | ||
628 | 37 | 35 | ||
629 | 38 | While logged-in users get a link to claim that profile and turn it into a | 36 | While logged-in users get a link to claim that profile and turn it into a |
630 | 39 | team. This works even if the profile's email addresses would normally be | 37 | team. This works even if the profile's email addresses would normally be |
631 | 40 | 38 | ||
632 | === modified file 'lib/lp/registry/templates/person-index.pt' | |||
633 | --- lib/lp/registry/templates/person-index.pt 2009-12-12 15:21:34 +0000 | |||
634 | +++ lib/lp/registry/templates/person-index.pt 2010-04-17 16:08:28 +0000 | |||
635 | @@ -157,7 +157,6 @@ | |||
636 | 157 | </tal:block> | 157 | </tal:block> |
637 | 158 | </p> | 158 | </p> |
638 | 159 | 159 | ||
639 | 160 | <tal:logged-in condition="request/lp:person"> | ||
640 | 161 | <tal:person condition="not: view/context_is_probably_a_team"> | 160 | <tal:person condition="not: view/context_is_probably_a_team"> |
641 | 162 | <ul tal:condition="context/preferredemail"> | 161 | <ul tal:condition="context/preferredemail"> |
642 | 163 | <li> | 162 | <li> |
643 | @@ -183,11 +182,6 @@ | |||
644 | 183 | <tal:team condition="view/context_is_probably_a_team"> | 182 | <tal:team condition="view/context_is_probably_a_team"> |
645 | 184 | <a href="+claimteam">Is this a team you run?</a> | 183 | <a href="+claimteam">Is this a team you run?</a> |
646 | 185 | </tal:team> | 184 | </tal:team> |
647 | 186 | </tal:logged-in> | ||
648 | 187 | |||
649 | 188 | <tal:not-logged-in condition="not: request/lp:person"> | ||
650 | 189 | <a href="+claim">Are you <span tal:replace="context/displayname" />?</a> | ||
651 | 190 | </tal:not-logged-in> | ||
652 | 191 | </tal:noaccount> | 185 | </tal:noaccount> |
653 | 192 | 186 | ||
654 | 193 | <tal:deactivated-account | 187 | <tal:deactivated-account |