Merge lp:~salgado/launchpad/lp-as-openid-rp into lp:launchpad
- lp-as-openid-rp
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Gary Poster |
Approved revision: | not available |
Merged at revision: | not available |
Proposed branch: | lp:~salgado/launchpad/lp-as-openid-rp |
Merge into: | lp:launchpad |
Diff against target: |
1163 lines (+939/-0) 23 files modified
BRANCH.TODO (+53/-0) configs/development/launchpad-lazr.conf (+4/-0) lib/canonical/config/schema-lazr.conf (+8/-0) lib/canonical/launchpad/browser/launchpad.py (+2/-0) lib/canonical/launchpad/configure.zcml (+1/-0) lib/canonical/launchpad/layers.py (+4/-0) lib/canonical/launchpad/systemhomes.py (+6/-0) lib/canonical/launchpad/webapp/servers.py (+16/-0) lib/lp/testopenid/adapters/openid.py (+32/-0) lib/lp/testopenid/browser/configure.zcml (+82/-0) lib/lp/testopenid/browser/server.py (+285/-0) lib/lp/testopenid/configure.zcml (+28/-0) lib/lp/testopenid/interfaces/server.py (+29/-0) lib/lp/testopenid/stories/basics.txt (+163/-0) lib/lp/testopenid/stories/logging-in.txt (+64/-0) lib/lp/testopenid/stories/tests.py (+22/-0) lib/lp/testopenid/templates/application-index.pt (+5/-0) lib/lp/testopenid/templates/application-xrds.pt (+14/-0) lib/lp/testopenid/templates/auth.pt (+18/-0) lib/lp/testopenid/templates/persistentidentity-index.pt (+14/-0) lib/lp/testopenid/templates/persistentidentity-xrds.pt (+14/-0) lib/lp/testopenid/testing/helpers.py (+74/-0) utilities/rocketfuel-setup (+1/-0) |
To merge this branch: | bzr merge lp:~salgado/launchpad/lp-as-openid-rp |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gary Poster (community) | code | Approve | |
Canonical Launchpad Engineering | Pending | ||
Review via email: mp+17842@code.launchpad.net |
Commit message
Description of the change
Guilherme Salgado (salgado) wrote : | # |
Gary Poster (gary) wrote : | # |
Thank you, Guilherme, this is a great step forward.
I didn't get too far on this today. Hopefully I'll have some time tomorrow: I'll resume on line 336, and actually try the server out on my system, per your instructions.
I only have one trivial comment so far. In lib/lp/
279 + <browser:page
280 + for="..
281 + class="
282 + permission=
283 + name="+openid"
284 + />
285 + <browser:page
286 + for="..
287 + class="
288 + permission=
289 + name="+index"
290 + />
Unless I misread it, the "server" module in lines 281 and 287 are the same, which is not as obvious as it could be. I don't care whether you choose relative or absolute, but sticking with one, particularly for the same module within the same zcml file, would help readability. It seems you mostly use relative, so I'd be inclined to standardize on that.
(I also hate browser:page directives because of the class mixin magic they do, but I won't choose you as the target for that rant. What you have done has precedence in Launchpad and is fine.)
Gary Poster (gary) wrote : | # |
merge-conditional
As I said before, this is a great branch. I just a have a few comments. I've given them all to you on IRC, but I'll summarize here.
- I had trouble getting the loom branch to work. We eventually had success with lp:~salgado/launchpad/lp-as-openid-rp-for-ec2 after I manually updated my /etc/hosts (I had not run your revised rocketfuel-setup) and you handled the fact that I had pycurl installed in my system Python.
- lib/lp/
- OpenIDMixin class in lib/lp/
- The docstrings are so nice in lib/lp/
- I'd prefer to be super-explicit in the first para of lib/lp/
- For new doctest files we are supposed to be using ReST.
Thank you again!
Preview Diff
1 | === modified file 'BRANCH.TODO' | |||
2 | --- BRANCH.TODO 2010-02-09 03:16:27 +0000 | |||
3 | +++ BRANCH.TODO 2010-02-17 15:49:22 +0000 | |||
4 | @@ -2,3 +2,56 @@ | |||
5 | 2 | # landing. There is a test to ensure it is empty in trunk. If there is | 2 | # landing. There is a test to ensure it is empty in trunk. If there is |
6 | 3 | # stuff still here when you are ready to land, the items should probably | 3 | # stuff still here when you are ready to land, the items should probably |
7 | 4 | # be converted to bugs so they can be scheduled. | 4 | # be converted to bugs so they can be scheduled. |
8 | 5 | |||
9 | 6 | * Do we want to change c-i-p to use a separate cookie so that logging into | ||
10 | 7 | login.lp.net doesn't cause you to end up logged into lp.net or should we just | ||
11 | 8 | wait for ISD to roll a rebranded version of login.u.c on login.lp.net, which | ||
12 | 9 | will probably use separate cookies. | ||
13 | 10 | - Apart from not being trivial to change the cookie name for a given vhost | ||
14 | 11 | we also need to worry about making sure the new cookie (which is supposed | ||
15 | 12 | to be valid only for login.lp.net) is not valid for all other vhosts, which | ||
16 | 13 | would normally happen thanks to LaunchpadCookieClientIdManager. | ||
17 | 14 | |||
18 | 15 | * We still rely heavily on the Account table and expect all users to have an | ||
19 | 16 | Account. We need to fix that if we're going to copy the Account table into the | ||
20 | 17 | main replication set and stop using the one from the auth set. If we fix | ||
21 | 18 | that, though, the Account table won't be necessary so there'll be no point in | ||
22 | 19 | copying it. *I'm not sure this is feasible.* | ||
23 | 20 | |||
24 | 21 | * As discussed with Francis, it's not worth porting the team-restricted login | ||
25 | 22 | stuff to the new system, so we'll drop it. | ||
26 | 23 | |||
27 | 24 | * Functionality needed in the callback view: | ||
28 | 25 | - reactivate accounts when credentials belong to a deactivated profile | ||
29 | 26 | - create Person entries when the credentials don't exist in our db | ||
30 | 27 | - forbid to log suspended accounts in. btw, I guess we're going to copy the | ||
31 | 28 | AccountStatus table to the main db. | ||
32 | 29 | |||
33 | 30 | # To test all this I'll just have to monkey patch | ||
34 | 31 | # OpenIDCallbackView.openid_response to return my hand-crafted | ||
35 | 32 | # openid response. | ||
36 | 33 | |||
37 | 34 | * Some tests use anon_browser.open() on protected pages so that they get | ||
38 | 35 | reidrected to +login and can show how the protected page works when the user | ||
39 | 36 | is not logged in, including the preserved query string. If we want to keep | ||
40 | 37 | doing that in these tests, we'll need an OpenID provider accessible to the | ||
41 | 38 | test suite. One option would be to include a crippled version of the one in | ||
42 | 39 | c-i-p in our tree, to be used only in tests. | ||
43 | 40 | (lib/canonical/launchpad/pagetests/oauth/authorize-token.txt is one of the | ||
44 | 41 | tests that rely on +login) | ||
45 | 42 | |||
46 | 43 | * Does not work for /people/+me, because when the OpenID provider sends the | ||
47 | 44 | user back to /people/+me/+openid-callback, there's a redirect to | ||
48 | 45 | /~person/+openid-callback, which causes the openid dance to fail: * | ||
49 | 46 | <openid.consumer.consumer.FailureResponse id=None message="return_to does not | ||
50 | 47 | match return URL. Expected 'https://launchpad.dev/%7Ename16/+openid-callback', | ||
51 | 48 | got | ||
52 | 49 | u'https://launchpad.dev/people/+me/+openid-callback?janrain_nonce=2009-12-23T14%3A47%3A47ZsgdbOJ'"> | ||
53 | 50 | I think this is a general problem with OpenID and login.lp.net because when | ||
54 | 51 | you log into login.lp.net and is sent to the callback page (in lp.net), that | ||
55 | 52 | page will see you as logged in, regardless of the openid response, because the | ||
56 | 53 | cookie is shared, and that causes /people/+me/+openid-callback to redirect to | ||
57 | 54 | /~name16/+openid-callback, which causes the error above. | ||
58 | 55 | One way around this is to always use /+openid-callback as the return_to URL, | ||
59 | 56 | and include a lp_redirect_to URL in the query string, where LP will send the | ||
60 | 57 | user to, once the openid dance is completed. | ||
61 | 5 | 58 | ||
62 | === modified file 'configs/development/launchpad-lazr.conf' | |||
63 | --- configs/development/launchpad-lazr.conf 2010-01-22 04:01:17 +0000 | |||
64 | +++ configs/development/launchpad-lazr.conf 2010-02-17 15:49:22 +0000 | |||
65 | @@ -124,6 +124,7 @@ | |||
66 | 124 | public_host: keyserver.launchpad.dev | 124 | public_host: keyserver.launchpad.dev |
67 | 125 | 125 | ||
68 | 126 | [launchpad] | 126 | [launchpad] |
69 | 127 | enable_test_openid_provider: True | ||
70 | 127 | code_domain: code.launchpad.dev | 128 | code_domain: code.launchpad.dev |
71 | 128 | default_batch_size: 5 | 129 | default_batch_size: 5 |
72 | 129 | max_attachment_size: 2097152 | 130 | max_attachment_size: 2097152 |
73 | @@ -277,6 +278,9 @@ | |||
74 | 277 | [vhost.openid] | 278 | [vhost.openid] |
75 | 278 | hostname: openid.launchpad.dev | 279 | hostname: openid.launchpad.dev |
76 | 279 | 280 | ||
77 | 281 | [vhost.testopenid] | ||
78 | 282 | hostname: testopenid.dev | ||
79 | 283 | |||
80 | 280 | [vhost.ubuntu_openid] | 284 | [vhost.ubuntu_openid] |
81 | 281 | hostname: ubuntu-openid.launchpad.dev | 285 | hostname: ubuntu-openid.launchpad.dev |
82 | 282 | 286 | ||
83 | 283 | 287 | ||
84 | === modified file 'lib/canonical/config/schema-lazr.conf' | |||
85 | --- lib/canonical/config/schema-lazr.conf 2010-01-22 04:01:17 +0000 | |||
86 | +++ lib/canonical/config/schema-lazr.conf 2010-02-17 15:49:22 +0000 | |||
87 | @@ -884,6 +884,11 @@ | |||
88 | 884 | storm_cache: generational | 884 | storm_cache: generational |
89 | 885 | storm_cache_size: 10000 | 885 | storm_cache_size: 10000 |
90 | 886 | 886 | ||
91 | 887 | # Whether or not to enable a test OpenID provider on the testopenid vhost. | ||
92 | 888 | # It's a test provider, not meant to be enabled on production. | ||
93 | 889 | # datatype: boolean | ||
94 | 890 | enable_test_openid_provider: False | ||
95 | 891 | |||
96 | 887 | # Assume the slave database is lagged if it takes more than this many | 892 | # Assume the slave database is lagged if it takes more than this many |
97 | 888 | # milliseconds to calculate this information from the Slony-I tables. | 893 | # milliseconds to calculate this information from the Slony-I tables. |
98 | 889 | # datatype: integer | 894 | # datatype: integer |
99 | @@ -1839,6 +1844,9 @@ | |||
100 | 1839 | [vhost.openid] | 1844 | [vhost.openid] |
101 | 1840 | 1845 | ||
102 | 1841 | 1846 | ||
103 | 1847 | [vhost.testopenid] | ||
104 | 1848 | |||
105 | 1849 | |||
106 | 1842 | [vhost.ubuntu_openid] | 1850 | [vhost.ubuntu_openid] |
107 | 1843 | 1851 | ||
108 | 1844 | 1852 | ||
109 | 1845 | 1853 | ||
110 | === modified file 'lib/canonical/launchpad/browser/launchpad.py' | |||
111 | --- lib/canonical/launchpad/browser/launchpad.py 2010-02-02 17:12:29 +0000 | |||
112 | +++ lib/canonical/launchpad/browser/launchpad.py 2010-02-17 15:49:22 +0000 | |||
113 | @@ -96,6 +96,7 @@ | |||
114 | 96 | ITranslationGroupSet) | 96 | ITranslationGroupSet) |
115 | 97 | from lp.translations.interfaces.translationimportqueue import ( | 97 | from lp.translations.interfaces.translationimportqueue import ( |
116 | 98 | ITranslationImportQueue) | 98 | ITranslationImportQueue) |
117 | 99 | from lp.testopenid.interfaces.server import ITestOpenIDApplication | ||
118 | 99 | 100 | ||
119 | 100 | from canonical.launchpad.webapp import ( | 101 | from canonical.launchpad.webapp import ( |
120 | 101 | LaunchpadFormView, LaunchpadView, Link, Navigation, | 102 | LaunchpadFormView, LaunchpadView, Link, Navigation, |
121 | @@ -565,6 +566,7 @@ | |||
122 | 565 | 'token': ILoginTokenSet, | 566 | 'token': ILoginTokenSet, |
123 | 566 | '+groups': ITranslationGroupSet, | 567 | '+groups': ITranslationGroupSet, |
124 | 567 | 'translations': IRosettaApplication, | 568 | 'translations': IRosettaApplication, |
125 | 569 | 'testopenid': ITestOpenIDApplication, | ||
126 | 568 | 'questions': IQuestionSet, | 570 | 'questions': IQuestionSet, |
127 | 569 | '+rpconfig': IOpenIDRPConfigSet, | 571 | '+rpconfig': IOpenIDRPConfigSet, |
128 | 570 | # These three have been renamed, and no redirects done, as the old | 572 | # These three have been renamed, and no redirects done, as the old |
129 | 571 | 573 | ||
130 | === modified file 'lib/canonical/launchpad/configure.zcml' | |||
131 | --- lib/canonical/launchpad/configure.zcml 2010-02-02 17:12:29 +0000 | |||
132 | +++ lib/canonical/launchpad/configure.zcml 2010-02-17 15:49:22 +0000 | |||
133 | @@ -26,6 +26,7 @@ | |||
134 | 26 | <include package="lp.code" /> | 26 | <include package="lp.code" /> |
135 | 27 | <include package="lp.soyuz" /> | 27 | <include package="lp.soyuz" /> |
136 | 28 | <include package="lp.translations" /> | 28 | <include package="lp.translations" /> |
137 | 29 | <include package="lp.testopenid" /> | ||
138 | 29 | <include package="lp.blueprints" /> | 30 | <include package="lp.blueprints" /> |
139 | 30 | <include package="lp.services.comments" /> | 31 | <include package="lp.services.comments" /> |
140 | 31 | 32 | ||
141 | 32 | 33 | ||
142 | === modified file 'lib/canonical/launchpad/layers.py' | |||
143 | --- lib/canonical/launchpad/layers.py 2009-10-21 18:33:11 +0000 | |||
144 | +++ lib/canonical/launchpad/layers.py 2010-02-17 15:49:22 +0000 | |||
145 | @@ -57,6 +57,10 @@ | |||
146 | 57 | """ | 57 | """ |
147 | 58 | 58 | ||
148 | 59 | 59 | ||
149 | 60 | class TestOpenIDLayer(LaunchpadLayer): | ||
150 | 61 | """The `TestOpenIDLayer` layer.""" | ||
151 | 62 | |||
152 | 63 | |||
153 | 60 | class PageTestLayer(LaunchpadLayer): | 64 | class PageTestLayer(LaunchpadLayer): |
154 | 61 | """The `PageTestLayer` layer. (need to register a 404 view for this and | 65 | """The `PageTestLayer` layer. (need to register a 404 view for this and |
155 | 62 | for the debug page too. and make the debugview a base class in the | 66 | for the debug page too. and make the debugview a base class in the |
156 | 63 | 67 | ||
157 | === modified file 'lib/canonical/launchpad/systemhomes.py' | |||
158 | --- lib/canonical/launchpad/systemhomes.py 2010-02-10 23:14:56 +0000 | |||
159 | +++ lib/canonical/launchpad/systemhomes.py 2010-02-17 15:49:22 +0000 | |||
160 | @@ -12,6 +12,7 @@ | |||
161 | 12 | 'MaloneApplication', | 12 | 'MaloneApplication', |
162 | 13 | 'PrivateMaloneApplication', | 13 | 'PrivateMaloneApplication', |
163 | 14 | 'RosettaApplication', | 14 | 'RosettaApplication', |
164 | 15 | 'TestOpenIDApplication', | ||
165 | 15 | ] | 16 | ] |
166 | 16 | 17 | ||
167 | 17 | __metaclass__ = type | 18 | __metaclass__ = type |
168 | @@ -31,6 +32,7 @@ | |||
169 | 31 | IMailingListApplication, IMaloneApplication, | 32 | IMailingListApplication, IMaloneApplication, |
170 | 32 | IPrivateMaloneApplication, IProductSet, IRosettaApplication, | 33 | IPrivateMaloneApplication, IProductSet, IRosettaApplication, |
171 | 33 | IWebServiceApplication) | 34 | IWebServiceApplication) |
172 | 35 | from lp.testopenid.interfaces.server import ITestOpenIDApplication | ||
173 | 34 | from lp.translations.interfaces.translationgroup import ITranslationGroupSet | 36 | from lp.translations.interfaces.translationgroup import ITranslationGroupSet |
174 | 35 | from lp.translations.interfaces.translationsoverview import ( | 37 | from lp.translations.interfaces.translationsoverview import ( |
175 | 36 | ITranslationsOverview) | 38 | ITranslationsOverview) |
176 | @@ -382,3 +384,7 @@ | |||
177 | 382 | wadl = super(WebServiceApplication, self).toWADL() | 384 | wadl = super(WebServiceApplication, self).toWADL() |
178 | 383 | self.__class__.cached_wadl = wadl | 385 | self.__class__.cached_wadl = wadl |
179 | 384 | return wadl | 386 | return wadl |
180 | 387 | |||
181 | 388 | |||
182 | 389 | class TestOpenIDApplication: | ||
183 | 390 | implements(ITestOpenIDApplication) | ||
184 | 385 | 391 | ||
185 | === modified file 'lib/canonical/launchpad/webapp/servers.py' | |||
186 | --- lib/canonical/launchpad/webapp/servers.py 2009-12-16 19:59:45 +0000 | |||
187 | +++ lib/canonical/launchpad/webapp/servers.py 2010-02-17 15:49:22 +0000 | |||
188 | @@ -45,6 +45,7 @@ | |||
189 | 45 | from lazr.restful.publisher import ( | 45 | from lazr.restful.publisher import ( |
190 | 46 | WebServicePublicationMixin, WebServiceRequestTraversal) | 46 | WebServicePublicationMixin, WebServiceRequestTraversal) |
191 | 47 | 47 | ||
192 | 48 | from lp.testopenid.interfaces.server import ITestOpenIDApplication | ||
193 | 48 | from canonical.launchpad.interfaces.launchpad import ( | 49 | from canonical.launchpad.interfaces.launchpad import ( |
194 | 49 | IFeedsApplication, IPrivateApplication, IWebServiceApplication) | 50 | IFeedsApplication, IPrivateApplication, IWebServiceApplication) |
195 | 50 | from canonical.launchpad.interfaces.oauth import ( | 51 | from canonical.launchpad.interfaces.oauth import ( |
196 | @@ -1121,6 +1122,17 @@ | |||
197 | 1121 | """Request type for a launchpad feed.""" | 1122 | """Request type for a launchpad feed.""" |
198 | 1122 | implements(canonical.launchpad.layers.FeedsLayer) | 1123 | implements(canonical.launchpad.layers.FeedsLayer) |
199 | 1123 | 1124 | ||
200 | 1125 | |||
201 | 1126 | # ---- testopenid | ||
202 | 1127 | |||
203 | 1128 | class TestOpenIDBrowserRequest(LaunchpadBrowserRequest): | ||
204 | 1129 | implements(canonical.launchpad.layers.TestOpenIDLayer) | ||
205 | 1130 | |||
206 | 1131 | |||
207 | 1132 | class TestOpenIDBrowserPublication(LaunchpadBrowserPublication): | ||
208 | 1133 | root_object_interface = ITestOpenIDApplication | ||
209 | 1134 | |||
210 | 1135 | |||
211 | 1124 | # ---- web service | 1136 | # ---- web service |
212 | 1125 | 1137 | ||
213 | 1126 | class WebServicePublication(WebServicePublicationMixin, | 1138 | class WebServicePublication(WebServicePublicationMixin, |
214 | @@ -1442,6 +1454,10 @@ | |||
215 | 1442 | 'xmlrpc', PublicXMLRPCRequest, PublicXMLRPCPublication) | 1454 | 'xmlrpc', PublicXMLRPCRequest, PublicXMLRPCPublication) |
216 | 1443 | ] | 1455 | ] |
217 | 1444 | 1456 | ||
218 | 1457 | if config.launchpad.enable_test_openid_provider: | ||
219 | 1458 | factories.append(VHRP('testopenid', TestOpenIDBrowserRequest, | ||
220 | 1459 | TestOpenIDBrowserPublication)) | ||
221 | 1460 | |||
222 | 1445 | # We may also have a private XML-RPC server. | 1461 | # We may also have a private XML-RPC server. |
223 | 1446 | private_port = None | 1462 | private_port = None |
224 | 1447 | for server in config.servers: | 1463 | for server in config.servers: |
225 | 1448 | 1464 | ||
226 | === added directory 'lib/lp/testopenid' | |||
227 | === added file 'lib/lp/testopenid/__init__.py' | |||
228 | === added directory 'lib/lp/testopenid/adapters' | |||
229 | === added file 'lib/lp/testopenid/adapters/__init__.py' | |||
230 | === added file 'lib/lp/testopenid/adapters/openid.py' | |||
231 | --- lib/lp/testopenid/adapters/openid.py 1970-01-01 00:00:00 +0000 | |||
232 | +++ lib/lp/testopenid/adapters/openid.py 2010-02-17 15:49:22 +0000 | |||
233 | @@ -0,0 +1,32 @@ | |||
234 | 1 | # Copyright 2010 Canonical Ltd. This software is licensed under the | ||
235 | 2 | # GNU Affero General Public License version 3 (see the file LICENSE). | ||
236 | 3 | |||
237 | 4 | """TestOpenID adapters and helpers.""" | ||
238 | 5 | |||
239 | 6 | __metaclass__ = type | ||
240 | 7 | |||
241 | 8 | __all__ = [ | ||
242 | 9 | 'TestOpenIDPersistentIdentity', | ||
243 | 10 | ] | ||
244 | 11 | |||
245 | 12 | from zope.component import adapts | ||
246 | 13 | from zope.interface import implements | ||
247 | 14 | |||
248 | 15 | from canonical.launchpad.interfaces.account import IAccount | ||
249 | 16 | from canonical.launchpad.webapp.vhosts import allvhosts | ||
250 | 17 | |||
251 | 18 | from lp.services.openid.adapters.openid import OpenIDPersistentIdentity | ||
252 | 19 | from lp.testopenid.interfaces.server import ITestOpenIDPersistentIdentity | ||
253 | 20 | |||
254 | 21 | |||
255 | 22 | class TestOpenIDPersistentIdentity(OpenIDPersistentIdentity): | ||
256 | 23 | """See `IOpenIDPersistentIdentity`.""" | ||
257 | 24 | |||
258 | 25 | adapts(IAccount) | ||
259 | 26 | implements(ITestOpenIDPersistentIdentity) | ||
260 | 27 | |||
261 | 28 | @property | ||
262 | 29 | def openid_identity_url(self): | ||
263 | 30 | """See `IOpenIDPersistentIdentity`.""" | ||
264 | 31 | identity_root_url = allvhosts.configs['testopenid'].rooturl | ||
265 | 32 | return identity_root_url + self.openid_identifier.encode('ascii') | ||
266 | 0 | 33 | ||
267 | === added directory 'lib/lp/testopenid/browser' | |||
268 | === added file 'lib/lp/testopenid/browser/__init__.py' | |||
269 | === added file 'lib/lp/testopenid/browser/configure.zcml' | |||
270 | --- lib/lp/testopenid/browser/configure.zcml 1970-01-01 00:00:00 +0000 | |||
271 | +++ lib/lp/testopenid/browser/configure.zcml 2010-02-17 15:49:22 +0000 | |||
272 | @@ -0,0 +1,82 @@ | |||
273 | 1 | <!-- Copyright 2009 Canonical Ltd. This software is licensed under the | ||
274 | 2 | GNU Affero General Public License version 3 (see the file LICENSE). | ||
275 | 3 | --> | ||
276 | 4 | |||
277 | 5 | <configure | ||
278 | 6 | xmlns="http://namespaces.zope.org/zope" | ||
279 | 7 | xmlns:browser="http://namespaces.zope.org/browser" | ||
280 | 8 | xmlns:i18n="http://namespaces.zope.org/i18n" | ||
281 | 9 | i18n_domain="launchpad"> | ||
282 | 10 | |||
283 | 11 | <browser:navigation | ||
284 | 12 | module=".server" | ||
285 | 13 | classes="TestOpenIDApplicationNavigation" | ||
286 | 14 | /> | ||
287 | 15 | |||
288 | 16 | <adapter | ||
289 | 17 | provides="canonical.launchpad.webapp.interfaces.ICanonicalUrlData" | ||
290 | 18 | for="..interfaces.server.ITestOpenIDApplication" | ||
291 | 19 | factory=".server.TestOpenIDRootUrlData" | ||
292 | 20 | /> | ||
293 | 21 | |||
294 | 22 | <browser:defaultView | ||
295 | 23 | for="..interfaces.server.ITestOpenIDApplication" | ||
296 | 24 | name="+index" | ||
297 | 25 | /> | ||
298 | 26 | |||
299 | 27 | <browser:page | ||
300 | 28 | for="..interfaces.server.ITestOpenIDApplication" | ||
301 | 29 | class=".server.TestOpenIDView" | ||
302 | 30 | permission="zope.Public" | ||
303 | 31 | name="+openid" | ||
304 | 32 | /> | ||
305 | 33 | <browser:page | ||
306 | 34 | for="..interfaces.server.ITestOpenIDApplication" | ||
307 | 35 | class=".server.TestOpenIDIndexView" | ||
308 | 36 | permission="zope.Public" | ||
309 | 37 | name="+index" | ||
310 | 38 | /> | ||
311 | 39 | <browser:page | ||
312 | 40 | for="..interfaces.server.ITestOpenIDApplication" | ||
313 | 41 | class=".server.TestOpenIDLoginView" | ||
314 | 42 | permission="zope.Public" | ||
315 | 43 | name="+auth" | ||
316 | 44 | /> | ||
317 | 45 | |||
318 | 46 | <browser:url | ||
319 | 47 | for="..interfaces.server.ITestOpenIDPersistentIdentity" | ||
320 | 48 | path_expression="string:${openid_identifier}" | ||
321 | 49 | parent_utility="..interfaces.server.ITestOpenIDApplication" | ||
322 | 50 | /> | ||
323 | 51 | |||
324 | 52 | <browser:defaultView | ||
325 | 53 | for="..interfaces.server.ITestOpenIDPersistentIdentity" | ||
326 | 54 | name="+index" | ||
327 | 55 | /> | ||
328 | 56 | |||
329 | 57 | <browser:page | ||
330 | 58 | for="..interfaces.server.ITestOpenIDPersistentIdentity" | ||
331 | 59 | name="+index" | ||
332 | 60 | template="../templates/persistentidentity-index.pt" | ||
333 | 61 | permission="zope.Public" | ||
334 | 62 | class=".server.PersistentIdentityView" | ||
335 | 63 | /> | ||
336 | 64 | |||
337 | 65 | <browser:page | ||
338 | 66 | name="" | ||
339 | 67 | for="..interfaces.server.ITestOpenIDApplication" | ||
340 | 68 | class="canonical.launchpad.browser.launchpad.LaunchpadImageFolder" | ||
341 | 69 | permission="zope.Public" | ||
342 | 70 | layer="canonical.launchpad.layers.TestOpenIDLayer" | ||
343 | 71 | /> | ||
344 | 72 | |||
345 | 73 | <!-- A simple view used by the page tests. --> | ||
346 | 74 | <browser:page | ||
347 | 75 | for="..interfaces.server.ITestOpenIDApplication" | ||
348 | 76 | name="+echo" | ||
349 | 77 | permission="zope.Public" | ||
350 | 78 | class="..testing.helpers.EchoView" | ||
351 | 79 | layer="canonical.launchpad.layers.PageTestLayer" | ||
352 | 80 | /> | ||
353 | 81 | |||
354 | 82 | </configure> | ||
355 | 0 | 83 | ||
356 | === added file 'lib/lp/testopenid/browser/server.py' | |||
357 | --- lib/lp/testopenid/browser/server.py 1970-01-01 00:00:00 +0000 | |||
358 | +++ lib/lp/testopenid/browser/server.py 2010-02-17 15:49:22 +0000 | |||
359 | @@ -0,0 +1,285 @@ | |||
360 | 1 | # Copyright 2010 Canonical Ltd. All rights reserved. | ||
361 | 2 | |||
362 | 3 | """Test OpenID server.""" | ||
363 | 4 | |||
364 | 5 | __metaclass__ = type | ||
365 | 6 | __all__ = [ | ||
366 | 7 | 'PersistentIdentityView', | ||
367 | 8 | 'TestOpenIDApplicationNavigation', | ||
368 | 9 | 'TestOpenIDIndexView' | ||
369 | 10 | 'TestOpenIDLoginView', | ||
370 | 11 | 'TestOpenIDRootUrlData', | ||
371 | 12 | 'TestOpenIDView', | ||
372 | 13 | ] | ||
373 | 14 | |||
374 | 15 | from datetime import timedelta | ||
375 | 16 | |||
376 | 17 | from z3c.ptcompat import ViewPageTemplateFile | ||
377 | 18 | from zope.app.security.interfaces import IUnauthenticatedPrincipal | ||
378 | 19 | from zope.component import getUtility | ||
379 | 20 | from zope.interface import implements | ||
380 | 21 | from zope.security.proxy import isinstance as zisinstance | ||
381 | 22 | from zope.session.interfaces import ISession | ||
382 | 23 | |||
383 | 24 | from openid.server.server import CheckIDRequest, Server | ||
384 | 25 | from openid.store.memstore import MemoryStore | ||
385 | 26 | |||
386 | 27 | from canonical.cachedproperty import cachedproperty | ||
387 | 28 | from canonical.launchpad import _ | ||
388 | 29 | from canonical.launchpad.interfaces.account import AccountStatus, IAccountSet | ||
389 | 30 | from canonical.launchpad.webapp import ( | ||
390 | 31 | action, LaunchpadFormView, LaunchpadView) | ||
391 | 32 | from canonical.launchpad.webapp.interfaces import ( | ||
392 | 33 | ICanonicalUrlData, IPlacelessLoginSource, UnexpectedFormData) | ||
393 | 34 | from canonical.launchpad.webapp.login import ( | ||
394 | 35 | allowUnauthenticatedSession, logInPrincipal, logoutPerson) | ||
395 | 36 | from canonical.launchpad.webapp.publisher import Navigation, stepthrough | ||
396 | 37 | from canonical.launchpad.webapp.url import urlappend | ||
397 | 38 | from canonical.launchpad.webapp.vhosts import allvhosts | ||
398 | 39 | |||
399 | 40 | from lp.services.openid.browser.openiddiscovery import ( | ||
400 | 41 | XRDSContentNegotiationMixin) | ||
401 | 42 | from lp.testopenid.interfaces.server import ( | ||
402 | 43 | ITestOpenIDApplication, ITestOpenIDLoginForm, | ||
403 | 44 | ITestOpenIDPersistentIdentity) | ||
404 | 45 | |||
405 | 46 | |||
406 | 47 | OPENID_REQUEST_SESSION_KEY = 'testopenid.request' | ||
407 | 48 | SESSION_PKG_KEY = 'TestOpenID' | ||
408 | 49 | SERVER_URL = urlappend(allvhosts.configs['testopenid'].rooturl, '+openid') | ||
409 | 50 | openid_store = MemoryStore() | ||
410 | 51 | |||
411 | 52 | |||
412 | 53 | class TestOpenIDRootUrlData: | ||
413 | 54 | """`ICanonicalUrlData` for the test OpenID provider.""" | ||
414 | 55 | |||
415 | 56 | implements(ICanonicalUrlData) | ||
416 | 57 | |||
417 | 58 | path = '' | ||
418 | 59 | inside = None | ||
419 | 60 | rootsite = 'testopenid' | ||
420 | 61 | |||
421 | 62 | def __init__(self, context): | ||
422 | 63 | self.context = context | ||
423 | 64 | |||
424 | 65 | |||
425 | 66 | class TestOpenIDApplicationNavigation(Navigation): | ||
426 | 67 | """Navigation for `ITestOpenIDApplication`""" | ||
427 | 68 | usedfor = ITestOpenIDApplication | ||
428 | 69 | |||
429 | 70 | @stepthrough('+id') | ||
430 | 71 | def traverse_id(self, name): | ||
431 | 72 | """Traverse to persistent OpenID identity URLs.""" | ||
432 | 73 | try: | ||
433 | 74 | account = getUtility(IAccountSet).getByOpenIDIdentifier(name) | ||
434 | 75 | except LookupError: | ||
435 | 76 | account = None | ||
436 | 77 | if account is None or account.status != AccountStatus.ACTIVE: | ||
437 | 78 | return None | ||
438 | 79 | return ITestOpenIDPersistentIdentity(account) | ||
439 | 80 | |||
440 | 81 | |||
441 | 82 | class TestOpenIDXRDSContentNegotiationMixin(XRDSContentNegotiationMixin): | ||
442 | 83 | """Custom XRDSContentNegotiationMixin that overrides openid_server_url.""" | ||
443 | 84 | |||
444 | 85 | @property | ||
445 | 86 | def openid_server_url(self): | ||
446 | 87 | """The OpenID Server endpoint URL for Launchpad.""" | ||
447 | 88 | return SERVER_URL | ||
448 | 89 | |||
449 | 90 | |||
450 | 91 | class TestOpenIDIndexView( | ||
451 | 92 | TestOpenIDXRDSContentNegotiationMixin, LaunchpadView): | ||
452 | 93 | template = ViewPageTemplateFile("../templates/application-index.pt") | ||
453 | 94 | xrds_template = ViewPageTemplateFile("../templates/application-xrds.pt") | ||
454 | 95 | |||
455 | 96 | |||
456 | 97 | class OpenIDMixin: | ||
457 | 98 | """A mixin with OpenID helper methods.""" | ||
458 | 99 | |||
459 | 100 | openid_request = None | ||
460 | 101 | |||
461 | 102 | def __init__(self, context, request): | ||
462 | 103 | super(OpenIDMixin, self).__init__(context, request) | ||
463 | 104 | self.server_url = SERVER_URL | ||
464 | 105 | self.openid_server = Server(openid_store, self.server_url) | ||
465 | 106 | |||
466 | 107 | @property | ||
467 | 108 | def user_identity_url(self): | ||
468 | 109 | return ITestOpenIDPersistentIdentity(self.account).openid_identity_url | ||
469 | 110 | |||
470 | 111 | def isIdentityOwner(self): | ||
471 | 112 | """Return True if the user can authenticate as the given ID.""" | ||
472 | 113 | assert self.account is not None, "user should be logged in by now." | ||
473 | 114 | return (self.openid_request.idSelect() or | ||
474 | 115 | self.openid_request.identity == self.user_identity_url) | ||
475 | 116 | |||
476 | 117 | @cachedproperty('_openid_parameters') | ||
477 | 118 | def openid_parameters(self): | ||
478 | 119 | """A dictionary of OpenID query parameters from request.""" | ||
479 | 120 | query = {} | ||
480 | 121 | for key, value in self.request.form.items(): | ||
481 | 122 | if key.startswith('openid.'): | ||
482 | 123 | # All OpenID query args are supposed to be ASCII. | ||
483 | 124 | query[key.encode('US-ASCII')] = value.encode('US-ASCII') | ||
484 | 125 | return query | ||
485 | 126 | |||
486 | 127 | def getSession(self): | ||
487 | 128 | """Get the session data container that stores the OpenID request.""" | ||
488 | 129 | if IUnauthenticatedPrincipal.providedBy(self.request.principal): | ||
489 | 130 | # A dance to assert that we want to break the rules about no | ||
490 | 131 | # unauthenticated sessions. Only after this next line is it | ||
491 | 132 | # safe to set session values. | ||
492 | 133 | allowUnauthenticatedSession( | ||
493 | 134 | self.request, duration=timedelta(minutes=60)) | ||
494 | 135 | return ISession(self.request)[SESSION_PKG_KEY] | ||
495 | 136 | |||
496 | 137 | def restoreRequestFromSession(self): | ||
497 | 138 | """Get the OpenIDRequest from our session.""" | ||
498 | 139 | session = self.getSession() | ||
499 | 140 | try: | ||
500 | 141 | self._openid_parameters = session[OPENID_REQUEST_SESSION_KEY] | ||
501 | 142 | except KeyError: | ||
502 | 143 | raise UnexpectedFormData("No OpenID request in session") | ||
503 | 144 | |||
504 | 145 | # Decode the request parameters and create the request object. | ||
505 | 146 | self.openid_request = self.openid_server.decodeRequest( | ||
506 | 147 | self.openid_parameters) | ||
507 | 148 | assert zisinstance(self.openid_request, CheckIDRequest), ( | ||
508 | 149 | 'Invalid OpenIDRequest in session') | ||
509 | 150 | |||
510 | 151 | def saveRequestInSession(self): | ||
511 | 152 | """Save the OpenIDRequest in our session.""" | ||
512 | 153 | query = self.openid_parameters | ||
513 | 154 | assert query.get('openid.mode') == 'checkid_setup', ( | ||
514 | 155 | 'Can only serialise checkid_setup OpenID requests') | ||
515 | 156 | |||
516 | 157 | session = self.getSession() | ||
517 | 158 | # If this was meant for use in production we'd have to use a nonce | ||
518 | 159 | # as the key when storing the openid request in the session, but as | ||
519 | 160 | # it's meant to run only on development instances we can simplify | ||
520 | 161 | # things a bit by storing the openid request using a well known key. | ||
521 | 162 | session[OPENID_REQUEST_SESSION_KEY] = query | ||
522 | 163 | |||
523 | 164 | def renderOpenIDResponse(self, openid_response): | ||
524 | 165 | """Return a web-suitable response constructed from openid_response.""" | ||
525 | 166 | webresponse = self.openid_server.encodeResponse(openid_response) | ||
526 | 167 | response = self.request.response | ||
527 | 168 | response.setStatus(webresponse.code) | ||
528 | 169 | for header, value in webresponse.headers.items(): | ||
529 | 170 | response.setHeader(header, value) | ||
530 | 171 | return webresponse.body | ||
531 | 172 | |||
532 | 173 | def createPositiveResponse(self): | ||
533 | 174 | """Create a positive assertion OpenIDResponse. | ||
534 | 175 | |||
535 | 176 | This method should be called to create the response to | ||
536 | 177 | successful checkid requests. | ||
537 | 178 | |||
538 | 179 | If the trust root for the request is in openid_sreg_trustroots, | ||
539 | 180 | then additional user information is included with the | ||
540 | 181 | response. | ||
541 | 182 | """ | ||
542 | 183 | assert self.account is not None, ( | ||
543 | 184 | 'Must be logged in for positive OpenID response') | ||
544 | 185 | assert self.openid_request is not None, ( | ||
545 | 186 | 'No OpenID request to respond to.') | ||
546 | 187 | |||
547 | 188 | if not self.isIdentityOwner(): | ||
548 | 189 | return self.createFailedResponse() | ||
549 | 190 | |||
550 | 191 | if self.openid_request.idSelect(): | ||
551 | 192 | response = self.openid_request.answer( | ||
552 | 193 | True, identity=self.user_identity_url) | ||
553 | 194 | else: | ||
554 | 195 | response = self.openid_request.answer(True) | ||
555 | 196 | |||
556 | 197 | return response | ||
557 | 198 | |||
558 | 199 | def createFailedResponse(self): | ||
559 | 200 | """Create a failed assertion OpenIDResponse. | ||
560 | 201 | |||
561 | 202 | This method should be called to create the response to | ||
562 | 203 | unsuccessful checkid requests. | ||
563 | 204 | """ | ||
564 | 205 | assert self.openid_request is not None, ( | ||
565 | 206 | 'No OpenID request to respond to.') | ||
566 | 207 | response = self.openid_request.answer(False, self.server_url) | ||
567 | 208 | return response | ||
568 | 209 | |||
569 | 210 | |||
570 | 211 | class TestOpenIDView(OpenIDMixin, LaunchpadView): | ||
571 | 212 | """An OpenID Provider endpoint for Launchpad. | ||
572 | 213 | |||
573 | 214 | This class implements an OpenID endpoint using the python-openid | ||
574 | 215 | library. In addition to the normal modes of operation, it also | ||
575 | 216 | implements the OpenID 2.0 identifier select mode. | ||
576 | 217 | |||
577 | 218 | Note that the checkid_immediate mode is not supported. | ||
578 | 219 | """ | ||
579 | 220 | |||
580 | 221 | def render(self): | ||
581 | 222 | """Handle all OpenID requests and form submissions.""" | ||
582 | 223 | # NB: Will be None if there are no parameters in the request. | ||
583 | 224 | self.openid_request = self.openid_server.decodeRequest( | ||
584 | 225 | self.openid_parameters) | ||
585 | 226 | |||
586 | 227 | if self.openid_request.mode == 'checkid_setup': | ||
587 | 228 | referer = self.request.get("HTTP_REFERER") | ||
588 | 229 | if referer: | ||
589 | 230 | self.request.response.setCookie("openid_referer", referer) | ||
590 | 231 | |||
591 | 232 | # Log the user out and present the login page so that they can | ||
592 | 233 | # authenticate as somebody else if they want. | ||
593 | 234 | logoutPerson(self.request) | ||
594 | 235 | return self.showLoginPage() | ||
595 | 236 | elif self.openid_request.mode == 'checkid_immediate': | ||
596 | 237 | raise UnexpectedFormData( | ||
597 | 238 | 'We do not handle checkid_immediate requests.') | ||
598 | 239 | else: | ||
599 | 240 | return self.renderOpenIDResponse( | ||
600 | 241 | self.openid_server.handleRequest(self.openid_request)) | ||
601 | 242 | |||
602 | 243 | def showLoginPage(self): | ||
603 | 244 | """Render the login dialog.""" | ||
604 | 245 | self.saveRequestInSession() | ||
605 | 246 | return TestOpenIDLoginView(self.context, self.request)() | ||
606 | 247 | |||
607 | 248 | |||
608 | 249 | class TestOpenIDLoginView(OpenIDMixin, LaunchpadFormView): | ||
609 | 250 | """A view for users to log into the OpenID provider.""" | ||
610 | 251 | |||
611 | 252 | page_title = "Login" | ||
612 | 253 | schema = ITestOpenIDLoginForm | ||
613 | 254 | action_url = '+auth' | ||
614 | 255 | template = ViewPageTemplateFile("../templates/auth.pt") | ||
615 | 256 | |||
616 | 257 | def initialize(self): | ||
617 | 258 | self.restoreRequestFromSession() | ||
618 | 259 | super(TestOpenIDLoginView, self).initialize() | ||
619 | 260 | |||
620 | 261 | def validate(self, data): | ||
621 | 262 | """Check that the email address and password are valid for login.""" | ||
622 | 263 | loginsource = getUtility(IPlacelessLoginSource) | ||
623 | 264 | principal = loginsource.getPrincipalByLogin(data['email']) | ||
624 | 265 | if principal is None or not principal.validate(data['password']): | ||
625 | 266 | self.addError( | ||
626 | 267 | _("Incorrect password for the provided email address.")) | ||
627 | 268 | |||
628 | 269 | @action('Continue', name='continue') | ||
629 | 270 | def continue_action(self, action, data): | ||
630 | 271 | email = data['email'] | ||
631 | 272 | principal = getUtility(IPlacelessLoginSource).getPrincipalByLogin( | ||
632 | 273 | email) | ||
633 | 274 | logInPrincipal(self.request, principal, email) | ||
634 | 275 | # Update the attribute holding the cached user. | ||
635 | 276 | self._account = principal.account | ||
636 | 277 | return self.renderOpenIDResponse(self.createPositiveResponse()) | ||
637 | 278 | |||
638 | 279 | |||
639 | 280 | class PersistentIdentityView( | ||
640 | 281 | TestOpenIDXRDSContentNegotiationMixin, LaunchpadView): | ||
641 | 282 | """Render the OpenID identity page.""" | ||
642 | 283 | |||
643 | 284 | xrds_template = ViewPageTemplateFile( | ||
644 | 285 | "../templates/persistentidentity-xrds.pt") | ||
645 | 0 | 286 | ||
646 | === added file 'lib/lp/testopenid/configure.zcml' | |||
647 | --- lib/lp/testopenid/configure.zcml 1970-01-01 00:00:00 +0000 | |||
648 | +++ lib/lp/testopenid/configure.zcml 2010-02-17 15:49:22 +0000 | |||
649 | @@ -0,0 +1,28 @@ | |||
650 | 1 | <!-- Copyright 2009 Canonical Ltd. This software is licensed under the | ||
651 | 2 | GNU Affero General Public License version 3 (see the file LICENSE). | ||
652 | 3 | --> | ||
653 | 4 | |||
654 | 5 | <configure | ||
655 | 6 | xmlns="http://namespaces.zope.org/zope" | ||
656 | 7 | xmlns:browser="http://namespaces.zope.org/browser" | ||
657 | 8 | xmlns:i18n="http://namespaces.zope.org/i18n" | ||
658 | 9 | i18n_domain="launchpad"> | ||
659 | 10 | |||
660 | 11 | <securedutility | ||
661 | 12 | class="canonical.launchpad.systemhomes.TestOpenIDApplication" | ||
662 | 13 | provides="lp.testopenid.interfaces.server.ITestOpenIDApplication"> | ||
663 | 14 | <allow interface="lp.testopenid.interfaces.server.ITestOpenIDApplication"/> | ||
664 | 15 | </securedutility> | ||
665 | 16 | |||
666 | 17 | <class class=".adapters.openid.TestOpenIDPersistentIdentity"> | ||
667 | 18 | <allow interface=".interfaces.server.ITestOpenIDPersistentIdentity" /> | ||
668 | 19 | </class> | ||
669 | 20 | |||
670 | 21 | <adapter | ||
671 | 22 | factory=".adapters.openid.TestOpenIDPersistentIdentity" | ||
672 | 23 | provides=".interfaces.server.ITestOpenIDPersistentIdentity" /> | ||
673 | 24 | /> | ||
674 | 25 | |||
675 | 26 | <include package=".browser"/> | ||
676 | 27 | |||
677 | 28 | </configure> | ||
678 | 0 | 29 | ||
679 | === added directory 'lib/lp/testopenid/interfaces' | |||
680 | === added file 'lib/lp/testopenid/interfaces/__init__.py' | |||
681 | === added file 'lib/lp/testopenid/interfaces/server.py' | |||
682 | --- lib/lp/testopenid/interfaces/server.py 1970-01-01 00:00:00 +0000 | |||
683 | +++ lib/lp/testopenid/interfaces/server.py 2010-02-17 15:49:22 +0000 | |||
684 | @@ -0,0 +1,29 @@ | |||
685 | 1 | # Copyright 2010 Canonical Ltd. All rights reserved. | ||
686 | 2 | |||
687 | 3 | __metaclass__ = type | ||
688 | 4 | __all__ = [ | ||
689 | 5 | 'ITestOpenIDApplication', | ||
690 | 6 | 'ITestOpenIDLoginForm', | ||
691 | 7 | 'ITestOpenIDPersistentIdentity', | ||
692 | 8 | ] | ||
693 | 9 | |||
694 | 10 | from zope.interface import Interface | ||
695 | 11 | from zope.schema import TextLine | ||
696 | 12 | |||
697 | 13 | from canonical.launchpad.fields import PasswordField | ||
698 | 14 | from canonical.launchpad.webapp.interfaces import ILaunchpadApplication | ||
699 | 15 | |||
700 | 16 | from lp.services.openid.interfaces.openid import IOpenIDPersistentIdentity | ||
701 | 17 | |||
702 | 18 | |||
703 | 19 | class ITestOpenIDApplication(ILaunchpadApplication): | ||
704 | 20 | """Launchpad's testing OpenID application root.""" | ||
705 | 21 | |||
706 | 22 | |||
707 | 23 | class ITestOpenIDLoginForm(Interface): | ||
708 | 24 | email = TextLine(title=u'What is your e-mail address?', required=True) | ||
709 | 25 | password = PasswordField(title=u'Password', required=True) | ||
710 | 26 | |||
711 | 27 | |||
712 | 28 | class ITestOpenIDPersistentIdentity(IOpenIDPersistentIdentity): | ||
713 | 29 | """Marker interface for IOpenIDPersistentIdentity on testopenid.""" | ||
714 | 0 | 30 | ||
715 | === added directory 'lib/lp/testopenid/stories' | |||
716 | === added file 'lib/lp/testopenid/stories/__init__.py' | |||
717 | === added file 'lib/lp/testopenid/stories/basics.txt' | |||
718 | --- lib/lp/testopenid/stories/basics.txt 1970-01-01 00:00:00 +0000 | |||
719 | +++ lib/lp/testopenid/stories/basics.txt 2010-02-17 15:49:22 +0000 | |||
720 | @@ -0,0 +1,163 @@ | |||
721 | 1 | ==================== | ||
722 | 2 | Test OpenID provider | ||
723 | 3 | ==================== | ||
724 | 4 | |||
725 | 5 | Introduction | ||
726 | 6 | ============ | ||
727 | 7 | |||
728 | 8 | Launchpad provides an OpenID provider (under the testopenid.dev | ||
729 | 9 | vhost) for testing purposes and for developers to be able to log into their | ||
730 | 10 | development instances. This provider is only available for development and | ||
731 | 11 | testing. | ||
732 | 12 | |||
733 | 13 | We are going to fake a consumer for these examples. In order to ensure | ||
734 | 14 | that the consumer is being fed the correct replies, we use a view that | ||
735 | 15 | renders the parameters in the response in an easily testable format. | ||
736 | 16 | |||
737 | 17 | >>> anon_browser.open('http://testopenid.dev/+echo?foo=bar') | ||
738 | 18 | >>> print anon_browser.contents | ||
739 | 19 | Request method: GET | ||
740 | 20 | foo:bar | ||
741 | 21 | |||
742 | 22 | |||
743 | 23 | associate Mode | ||
744 | 24 | ============== | ||
745 | 25 | |||
746 | 26 | Establish a shared secret between Consumer and Identity Provider. | ||
747 | 27 | |||
748 | 28 | After determining the URL of the OpenID server, the next thing a consumer | ||
749 | 29 | needs to do is associate with the server and get a shared secret via a | ||
750 | 30 | POST request. | ||
751 | 31 | |||
752 | 32 | >>> from urllib import urlencode | ||
753 | 33 | >>> anon_browser.open( | ||
754 | 34 | ... 'http://testopenid.dev/+openid', data=urlencode({ | ||
755 | 35 | ... 'openid.mode': 'associate', | ||
756 | 36 | ... 'openid.assoc_type': 'HMAC-SHA1'})) | ||
757 | 37 | >>> print anon_browser.headers | ||
758 | 38 | Status: 200 Ok | ||
759 | 39 | ... | ||
760 | 40 | Content-Type: text/plain | ||
761 | 41 | ... | ||
762 | 42 | >>> print anon_browser.contents | ||
763 | 43 | assoc_handle:{HMAC-SHA1}{...}{...} | ||
764 | 44 | assoc_type:HMAC-SHA1 | ||
765 | 45 | expires_in:1209... | ||
766 | 46 | mac_key:... | ||
767 | 47 | <BLANKLINE> | ||
768 | 48 | |||
769 | 49 | Get the association handle, which we will need for later tests. | ||
770 | 50 | |||
771 | 51 | >>> import re | ||
772 | 52 | >>> [assoc_handle] = re.findall('assoc_handle:(.*)', anon_browser.contents) | ||
773 | 53 | |||
774 | 54 | |||
775 | 55 | checkid_setup Mode | ||
776 | 56 | ================== | ||
777 | 57 | |||
778 | 58 | When we go to the OpenID setup URL, we are presented with a login | ||
779 | 59 | form. By entering an email address and password, we are directed back | ||
780 | 60 | to the consumer, completing the OpenID request: | ||
781 | 61 | |||
782 | 62 | >>> args = urlencode({ | ||
783 | 63 | ... 'openid.mode': 'checkid_setup', | ||
784 | 64 | ... 'openid.identity': 'http://testopenid.dev/+id/mark_oid', | ||
785 | 65 | ... 'openid.assoc_handle': assoc_handle, | ||
786 | 66 | ... 'openid.return_to': 'http://testopenid.dev/+echo', | ||
787 | 67 | ... }) | ||
788 | 68 | >>> user_browser.open('http://testopenid.dev/+openid?%s' % args) | ||
789 | 69 | >>> print user_browser.url | ||
790 | 70 | http://testopenid.dev/+openid?... | ||
791 | 71 | >>> print user_browser.title | ||
792 | 72 | Login | ||
793 | 73 | >>> user_browser.getControl(name='field.email').value = 'mark@example.com' | ||
794 | 74 | >>> user_browser.getControl(name='field.password').value = 'test' | ||
795 | 75 | >>> user_browser.getControl('Continue').click() | ||
796 | 76 | |||
797 | 77 | >>> print user_browser.url | ||
798 | 78 | http://testopenid.dev/+echo?... | ||
799 | 79 | >>> print user_browser.contents | ||
800 | 80 | Request method: GET | ||
801 | 81 | openid.assoc_handle:... | ||
802 | 82 | openid.identity:http://testopenid.dev/+id/mark_oid | ||
803 | 83 | openid.mode:id_res... | ||
804 | 84 | openid.op_endpoint:http://testopenid.dev/+openid | ||
805 | 85 | openid.response_nonce:... | ||
806 | 86 | openid.return_to:http://testopenid.dev/+echo | ||
807 | 87 | openid.sig:... | ||
808 | 88 | openid.signed:... | ||
809 | 89 | <BLANKLINE> | ||
810 | 90 | |||
811 | 91 | We will record the signature from this response to use in the next test: | ||
812 | 92 | |||
813 | 93 | >>> [sig] = re.findall('sig:(.*)', user_browser.contents) | ||
814 | 94 | |||
815 | 95 | |||
816 | 96 | check_authentication Mode | ||
817 | 97 | ========================= | ||
818 | 98 | |||
819 | 99 | Ask an Identity Provider if a message is valid. For dumb, stateless | ||
820 | 100 | Consumers or when verifying an invalidate_handle response. | ||
821 | 101 | |||
822 | 102 | If an association handle is stateful (genereted using the associate Mode), | ||
823 | 103 | check_authentication will fail. | ||
824 | 104 | |||
825 | 105 | >>> args = urlencode({ | ||
826 | 106 | ... 'openid.mode': 'check_authentication', | ||
827 | 107 | ... 'openid.assoc_handle': assoc_handle, | ||
828 | 108 | ... 'openid.sig': sig, | ||
829 | 109 | ... 'openid.signed': 'return_to,mode,identity', | ||
830 | 110 | ... 'openid.identity': | ||
831 | 111 | ... 'http://testopenid.dev/+id/mark_oid', | ||
832 | 112 | ... 'openid.return_to': 'http://testopenid.dev/+echo', | ||
833 | 113 | ... }) | ||
834 | 114 | >>> user_browser.open('http://testopenid.dev/+openid?%s' % args) | ||
835 | 115 | >>> print user_browser.contents | ||
836 | 116 | is_valid:false | ||
837 | 117 | <BLANKLINE> | ||
838 | 118 | |||
839 | 119 | If we are a dumb consumer though, we must invoke the check_authentication | ||
840 | 120 | mode, passing back the association handle, signature and values of all | ||
841 | 121 | fields that were signed. | ||
842 | 122 | |||
843 | 123 | >>> args = urlencode({ | ||
844 | 124 | ... 'openid.mode': 'checkid_setup', | ||
845 | 125 | ... 'openid.identity': | ||
846 | 126 | ... 'http://testopenid.dev/+id/mark_oid', | ||
847 | 127 | ... 'openid.return_to': 'http://testopenid.dev/+echo', | ||
848 | 128 | ... }) | ||
849 | 129 | >>> user_browser.open('http://testopenid.dev/+openid?%s' % args) | ||
850 | 130 | >>> user_browser.getControl(name='field.email').value = 'mark@example.com' | ||
851 | 131 | >>> user_browser.getControl(name='field.password').value = 'test' | ||
852 | 132 | >>> user_browser.getControl('Continue').click() | ||
853 | 133 | >>> print user_browser.contents | ||
854 | 134 | Request method: GET | ||
855 | 135 | openid.assoc_handle:... | ||
856 | 136 | openid.identity:http://testopenid.dev/+id/mark_oid | ||
857 | 137 | openid.mode:id_res | ||
858 | 138 | openid.op_endpoint:http://testopenid.dev/+openid | ||
859 | 139 | openid.response_nonce:... | ||
860 | 140 | openid.return_to:http://testopenid.dev/+echo | ||
861 | 141 | openid.sig:... | ||
862 | 142 | openid.signed:... | ||
863 | 143 | <BLANKLINE> | ||
864 | 144 | |||
865 | 145 | >>> fields = dict(line.split(':', 1) | ||
866 | 146 | ... for line in user_browser.contents.splitlines()[1:] | ||
867 | 147 | ... if line.startswith('openid.')) | ||
868 | 148 | >>> signed = ['openid.' + name | ||
869 | 149 | ... for name in fields['openid.signed'].split(',')] | ||
870 | 150 | >>> message = dict((key, value) for (key, value) in fields.items() | ||
871 | 151 | ... if key in signed) | ||
872 | 152 | >>> message.update({ | ||
873 | 153 | ... 'openid.mode': 'check_authentication', | ||
874 | 154 | ... 'openid.assoc_handle': fields['openid.assoc_handle'], | ||
875 | 155 | ... 'openid.sig': fields['openid.sig'], | ||
876 | 156 | ... 'openid.signed': fields['openid.signed'], | ||
877 | 157 | ... }) | ||
878 | 158 | |||
879 | 159 | >>> args = urlencode(message) | ||
880 | 160 | >>> user_browser.open('http://testopenid.dev/+openid', args) | ||
881 | 161 | >>> print user_browser.contents | ||
882 | 162 | is_valid:true | ||
883 | 163 | <BLANKLINE> | ||
884 | 0 | 164 | ||
885 | === added file 'lib/lp/testopenid/stories/logging-in.txt' | |||
886 | --- lib/lp/testopenid/stories/logging-in.txt 1970-01-01 00:00:00 +0000 | |||
887 | +++ lib/lp/testopenid/stories/logging-in.txt 2010-02-17 15:49:22 +0000 | |||
888 | @@ -0,0 +1,64 @@ | |||
889 | 1 | ======================================== | ||
890 | 2 | Logging in using the TestOpenID provider | ||
891 | 3 | ======================================== | ||
892 | 4 | |||
893 | 5 | A user with an existing account may log into Launchpad using the OpenID | ||
894 | 6 | provider available on testopenid.dev. | ||
895 | 7 | |||
896 | 8 | First we will set up the helper view that lets us test the final | ||
897 | 9 | portion of the authentication process: | ||
898 | 10 | |||
899 | 11 | >>> from openid.consumer.consumer import Consumer | ||
900 | 12 | >>> from openid.fetchers import setDefaultFetcher | ||
901 | 13 | >>> from openid.store.memstore import MemoryStore | ||
902 | 14 | >>> from lp.testopenid.testing.helpers import ( | ||
903 | 15 | ... complete_from_browser, make_identifier_select_endpoint, | ||
904 | 16 | ... PublisherFetcher) | ||
905 | 17 | >>> setDefaultFetcher(PublisherFetcher()) | ||
906 | 18 | |||
907 | 19 | The authentication process is started by the relying party issuing a | ||
908 | 20 | checkid_setup request, sending the user to Launchpad: | ||
909 | 21 | |||
910 | 22 | >>> openid_store = MemoryStore() | ||
911 | 23 | >>> consumer = Consumer(session={}, store=openid_store) | ||
912 | 24 | |||
913 | 25 | >>> request = consumer.beginWithoutDiscovery( | ||
914 | 26 | ... make_identifier_select_endpoint()) | ||
915 | 27 | >>> browser.open(request.redirectURL( | ||
916 | 28 | ... 'http://testopenid.dev/', | ||
917 | 29 | ... 'http://testopenid.dev/+echo')) | ||
918 | 30 | |||
919 | 31 | At this point, the user is presented with a login form: | ||
920 | 32 | |||
921 | 33 | >>> print browser.title | ||
922 | 34 | Login | ||
923 | 35 | |||
924 | 36 | As the user already has an account, they can enter their email address and | ||
925 | 37 | password. If the password does not match the given email address, an error is | ||
926 | 38 | shown: | ||
927 | 39 | |||
928 | 40 | >>> browser.getControl(name='field.email').value = 'mark@example.com' | ||
929 | 41 | >>> browser.getControl(name='field.password').value = 'not the password' | ||
930 | 42 | >>> browser.getControl('Continue').click() | ||
931 | 43 | >>> print browser.title | ||
932 | 44 | Login | ||
933 | 45 | >>> for tag in find_tags_by_class(browser.contents, 'error'): | ||
934 | 46 | ... print extract_text(tag) | ||
935 | 47 | There is 1 error. | ||
936 | 48 | Incorrect password for the provided email address. | ||
937 | 49 | |||
938 | 50 | If the email address and password match, the user is logged in and returned to | ||
939 | 51 | the relying party, with the user's identity URL: | ||
940 | 52 | |||
941 | 53 | >>> browser.getControl(name='field.password').value = 'test' | ||
942 | 54 | >>> browser.getControl('Continue').click() | ||
943 | 55 | >>> print browser.url | ||
944 | 56 | http://testopenid.dev/+echo?... | ||
945 | 57 | >>> info = complete_from_browser(consumer, browser) | ||
946 | 58 | >>> print info.status | ||
947 | 59 | success | ||
948 | 60 | >>> print info.endpoint.claimed_id | ||
949 | 61 | http://testopenid.dev/+id/mark_oid | ||
950 | 62 | |||
951 | 63 | # Clean up the changes we did to the openid module. | ||
952 | 64 | >>> setDefaultFetcher(None) | ||
953 | 0 | 65 | ||
954 | === added file 'lib/lp/testopenid/stories/tests.py' | |||
955 | --- lib/lp/testopenid/stories/tests.py 1970-01-01 00:00:00 +0000 | |||
956 | +++ lib/lp/testopenid/stories/tests.py 2010-02-17 15:49:22 +0000 | |||
957 | @@ -0,0 +1,22 @@ | |||
958 | 1 | # Copyright 2004-2008 Canonical Ltd. All rights reserved. | ||
959 | 2 | |||
960 | 3 | import os | ||
961 | 4 | import unittest | ||
962 | 5 | |||
963 | 6 | from canonical.launchpad.testing.pages import PageTestSuite | ||
964 | 7 | |||
965 | 8 | |||
966 | 9 | here = os.path.dirname(os.path.realpath(__file__)) | ||
967 | 10 | |||
968 | 11 | |||
969 | 12 | def test_suite(): | ||
970 | 13 | stories = sorted( | ||
971 | 14 | dir for dir in os.listdir(here) | ||
972 | 15 | if not dir.startswith('.') and os.path.isdir(os.path.join(here, dir))) | ||
973 | 16 | |||
974 | 17 | suite = unittest.TestSuite() | ||
975 | 18 | suite.addTest(PageTestSuite('.')) | ||
976 | 19 | for storydir in stories: | ||
977 | 20 | suite.addTest(PageTestSuite(storydir)) | ||
978 | 21 | |||
979 | 22 | return suite | ||
980 | 0 | 23 | ||
981 | === added directory 'lib/lp/testopenid/templates' | |||
982 | === added file 'lib/lp/testopenid/templates/application-index.pt' | |||
983 | --- lib/lp/testopenid/templates/application-index.pt 1970-01-01 00:00:00 +0000 | |||
984 | +++ lib/lp/testopenid/templates/application-index.pt 2010-02-17 15:49:22 +0000 | |||
985 | @@ -0,0 +1,5 @@ | |||
986 | 1 | <html> | ||
987 | 2 | <body> | ||
988 | 3 | <h1>Test OpenID provider for launchpad.dev</h1> | ||
989 | 4 | </body> | ||
990 | 5 | </html> | ||
991 | 0 | 6 | ||
992 | === added file 'lib/lp/testopenid/templates/application-xrds.pt' | |||
993 | --- lib/lp/testopenid/templates/application-xrds.pt 1970-01-01 00:00:00 +0000 | |||
994 | +++ lib/lp/testopenid/templates/application-xrds.pt 2010-02-17 15:49:22 +0000 | |||
995 | @@ -0,0 +1,14 @@ | |||
996 | 1 | <?xml version="1.0"?> | ||
997 | 2 | <xrds:XRDS | ||
998 | 3 | xmlns="xri://$xrd*($v*2.0)" | ||
999 | 4 | xmlns:xrds="xri://$xrds" | ||
1000 | 5 | xmlns:tal="http://xml.zope.org/namespaces/tal"> | ||
1001 | 6 | <XRD> | ||
1002 | 7 | <Service priority="0"> | ||
1003 | 8 | <Type>http://specs.openid.net/auth/2.0/server</Type> | ||
1004 | 9 | <URI tal:content="view/openid_server_url"> | ||
1005 | 10 | https://login.launchpad.net/+openid | ||
1006 | 11 | </URI> | ||
1007 | 12 | </Service> | ||
1008 | 13 | </XRD> | ||
1009 | 14 | </xrds:XRDS> | ||
1010 | 0 | 15 | ||
1011 | === added file 'lib/lp/testopenid/templates/auth.pt' | |||
1012 | --- lib/lp/testopenid/templates/auth.pt 1970-01-01 00:00:00 +0000 | |||
1013 | +++ lib/lp/testopenid/templates/auth.pt 2010-02-17 15:49:22 +0000 | |||
1014 | @@ -0,0 +1,18 @@ | |||
1015 | 1 | <html | ||
1016 | 2 | xmlns="http://www.w3.org/1999/xhtml" | ||
1017 | 3 | xmlns:tal="http://xml.zope.org/namespaces/tal" | ||
1018 | 4 | xmlns:metal="http://xml.zope.org/namespaces/metal" | ||
1019 | 5 | xmlns:i18n="http://xml.zope.org/namespaces/i18n" | ||
1020 | 6 | xml:lang="en" | ||
1021 | 7 | lang="en" | ||
1022 | 8 | dir="ltr" | ||
1023 | 9 | metal:use-macro="view/macro:page/locationless" | ||
1024 | 10 | i18n:domain="launchpad" | ||
1025 | 11 | > | ||
1026 | 12 | <body> | ||
1027 | 13 | <div metal:fill-slot="main"> | ||
1028 | 14 | <div metal:use-macro="context/@@launchpad_form/form" /> | ||
1029 | 15 | </div> | ||
1030 | 16 | </body> | ||
1031 | 17 | </html> | ||
1032 | 18 | |||
1033 | 0 | 19 | ||
1034 | === added file 'lib/lp/testopenid/templates/persistentidentity-index.pt' | |||
1035 | --- lib/lp/testopenid/templates/persistentidentity-index.pt 1970-01-01 00:00:00 +0000 | |||
1036 | +++ lib/lp/testopenid/templates/persistentidentity-index.pt 2010-02-17 15:49:22 +0000 | |||
1037 | @@ -0,0 +1,14 @@ | |||
1038 | 1 | <html xmlns="http://www.w3.org/1999/xhtml" | ||
1039 | 2 | xmlns:tal="http://xml.zope.org/namespaces/tal"> | ||
1040 | 3 | <head> | ||
1041 | 4 | <link rel="openid.server" | ||
1042 | 5 | tal:attributes="href view/openid_server_url" /> | ||
1043 | 6 | </head> | ||
1044 | 7 | <body> | ||
1045 | 8 | <h1> | ||
1046 | 9 | OpenID Identity URL for | ||
1047 | 10 | <tal:user content="context/account/displayname">Display Name</tal:user> | ||
1048 | 11 | </h1> | ||
1049 | 12 | </body> | ||
1050 | 13 | </html> | ||
1051 | 14 | |||
1052 | 0 | 15 | ||
1053 | === added file 'lib/lp/testopenid/templates/persistentidentity-xrds.pt' | |||
1054 | --- lib/lp/testopenid/templates/persistentidentity-xrds.pt 1970-01-01 00:00:00 +0000 | |||
1055 | +++ lib/lp/testopenid/templates/persistentidentity-xrds.pt 2010-02-17 15:49:22 +0000 | |||
1056 | @@ -0,0 +1,14 @@ | |||
1057 | 1 | <?xml version="1.0"?> | ||
1058 | 2 | <xrds:XRDS | ||
1059 | 3 | xmlns="xri://$xrd*($v*2.0)" | ||
1060 | 4 | xmlns:xrds="xri://$xrds" | ||
1061 | 5 | xmlns:tal="http://xml.zope.org/namespaces/tal"> | ||
1062 | 6 | <XRD> | ||
1063 | 7 | <Service priority="0"> | ||
1064 | 8 | <Type>http://specs.openid.net/auth/2.0/signon</Type> | ||
1065 | 9 | <URI priority="0" tal:content="view/openid_server_url"> | ||
1066 | 10 | https://testopenid.dev/+openid | ||
1067 | 11 | </URI> | ||
1068 | 12 | </Service> | ||
1069 | 13 | </XRD> | ||
1070 | 14 | </xrds:XRDS> | ||
1071 | 0 | 15 | ||
1072 | === added directory 'lib/lp/testopenid/testing' | |||
1073 | === added file 'lib/lp/testopenid/testing/__init__.py' | |||
1074 | === added file 'lib/lp/testopenid/testing/helpers.py' | |||
1075 | --- lib/lp/testopenid/testing/helpers.py 1970-01-01 00:00:00 +0000 | |||
1076 | +++ lib/lp/testopenid/testing/helpers.py 2010-02-17 15:49:22 +0000 | |||
1077 | @@ -0,0 +1,74 @@ | |||
1078 | 1 | # Copyright 2010 Canonical Ltd. All rights reserved. | ||
1079 | 2 | |||
1080 | 3 | """Helpers for TestOpenID page tests.""" | ||
1081 | 4 | |||
1082 | 5 | __metaclass__ = type | ||
1083 | 6 | __all__ = [ | ||
1084 | 7 | 'complete_from_browser', | ||
1085 | 8 | 'EchoView', | ||
1086 | 9 | 'make_identifier_select_endpoint', | ||
1087 | 10 | 'PublisherFetcher', | ||
1088 | 11 | ] | ||
1089 | 12 | |||
1090 | 13 | from StringIO import StringIO | ||
1091 | 14 | import urllib2 | ||
1092 | 15 | |||
1093 | 16 | from zope.testbrowser.testing import PublisherHTTPHandler | ||
1094 | 17 | |||
1095 | 18 | from openid import fetchers | ||
1096 | 19 | from openid.consumer.discover import ( | ||
1097 | 20 | OpenIDServiceEndpoint, OPENID_IDP_2_0_TYPE) | ||
1098 | 21 | |||
1099 | 22 | from canonical.launchpad.webapp import LaunchpadView | ||
1100 | 23 | |||
1101 | 24 | from lp.testopenid.browser.server import SERVER_URL | ||
1102 | 25 | |||
1103 | 26 | |||
1104 | 27 | class EchoView(LaunchpadView): | ||
1105 | 28 | """A view which just echoes its form arguments in the response.""" | ||
1106 | 29 | |||
1107 | 30 | def render(self): | ||
1108 | 31 | out = StringIO() | ||
1109 | 32 | print >> out, 'Request method: %s' % self.request.method | ||
1110 | 33 | keys = sorted(self.request.form.keys()) | ||
1111 | 34 | for key in keys: | ||
1112 | 35 | print >> out, '%s:%s' % (key, self.request.form[key]) | ||
1113 | 36 | return out.getvalue() | ||
1114 | 37 | |||
1115 | 38 | |||
1116 | 39 | class PublisherFetcher(fetchers.Urllib2Fetcher): | ||
1117 | 40 | """An `HTTPFetcher` that passes requests on to the Zope publisher.""" | ||
1118 | 41 | def __init__(self): | ||
1119 | 42 | super(PublisherFetcher, self).__init__() | ||
1120 | 43 | self.opener = urllib2.build_opener(PublisherHTTPHandler) | ||
1121 | 44 | |||
1122 | 45 | def urlopen(self, request): | ||
1123 | 46 | request.add_header('X-zope-handle-errors', True) | ||
1124 | 47 | return self.opener.open(request) | ||
1125 | 48 | |||
1126 | 49 | |||
1127 | 50 | def complete_from_browser(consumer, browser): | ||
1128 | 51 | """Complete OpenID request based on output of +echo. | ||
1129 | 52 | |||
1130 | 53 | :param consumer: an OpenID `Consumer` instance. | ||
1131 | 54 | :param browser: a Zope testbrowser `Browser` instance. | ||
1132 | 55 | |||
1133 | 56 | This function parses the body of the +echo view into a set of query | ||
1134 | 57 | arguments representing the OpenID response. | ||
1135 | 58 | """ | ||
1136 | 59 | assert browser.contents.startswith('Request method'), ( | ||
1137 | 60 | "Browser contents does not look like it came from +echo") | ||
1138 | 61 | # Skip the first line. | ||
1139 | 62 | query = dict(line.split(':', 1) | ||
1140 | 63 | for line in browser.contents.splitlines()[1:]) | ||
1141 | 64 | |||
1142 | 65 | response = consumer.complete(query, browser.url) | ||
1143 | 66 | return response | ||
1144 | 67 | |||
1145 | 68 | |||
1146 | 69 | def make_identifier_select_endpoint(): | ||
1147 | 70 | """Create an endpoint for use in OpenID identifier select mode.""" | ||
1148 | 71 | endpoint = OpenIDServiceEndpoint() | ||
1149 | 72 | endpoint.server_url = SERVER_URL | ||
1150 | 73 | endpoint.type_uris = [OPENID_IDP_2_0_TYPE] | ||
1151 | 74 | return endpoint | ||
1152 | 0 | 75 | ||
1153 | === modified file 'utilities/rocketfuel-setup' | |||
1154 | --- utilities/rocketfuel-setup 2009-12-13 01:38:59 +0000 | |||
1155 | +++ utilities/rocketfuel-setup 2010-02-17 15:49:22 +0000 | |||
1156 | @@ -136,6 +136,7 @@ | |||
1157 | 136 | shipit.edubuntu.dev | 136 | shipit.edubuntu.dev |
1158 | 137 | shipit.kubuntu.dev | 137 | shipit.kubuntu.dev |
1159 | 138 | shipit.ubuntu.dev | 138 | shipit.ubuntu.dev |
1160 | 139 | testopenid.dev | ||
1161 | 139 | translations.launchpad.dev | 140 | translations.launchpad.dev |
1162 | 140 | xmlrpc-private.launchpad.dev | 141 | xmlrpc-private.launchpad.dev |
1163 | 141 | xmlrpc.launchpad.dev | 142 | xmlrpc.launchpad.dev |
This branch adds a new vhost (testopenid) which serves an OpenID
provider that is only enabled when devmode is on. The primary use of
this will be to allow we (developers) to log into launchpad.dev once it
becomes an OpenID RP.
This OpenID provider always requires the user to log in and doesn't ask
users whether or not they authorize the provider to send user details to
the site that's requesting it. The login is always required in order to
make it easier to authenticate as a different user, and the lack of an
authorization step is because it wouldn't make any sense here.
In order to try out the testopenid functionality, we need an openid dev/+login- openid
consumer, which is included (together with this branch) in
lp:~salgado/launchpad/lp-as-openid-rp-loom. To test it you just need to
go to launchpad.
BRANCH.TODO has a bunch of random notes and TODO items for myself; it
will be erased before I land this branch or else a test will fail.