Browse code
init
Ed L authored on 05/03/2012 03:52:52
Showing 3 changed files
Showing 3 changed files
2 | 2 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,354 @@ |
1 |
+#!/usr/bin/env python |
|
2 |
+""" |
|
3 |
+Simple example for an OpenID consumer. |
|
4 |
+ |
|
5 |
+Once you understand this example you'll know the basics of OpenID |
|
6 |
+and using the Python OpenID library. You can then move on to more |
|
7 |
+robust examples, and integrating OpenID into your application. |
|
8 |
+""" |
|
9 |
+__copyright__ = 'Copyright 2012, Edward Langley' |
|
10 |
+ |
|
11 |
+import imp, os.path |
|
12 |
+import cgi |
|
13 |
+import urlparse |
|
14 |
+import cgitb |
|
15 |
+from twisted.web.resource import Resource |
|
16 |
+import sys |
|
17 |
+ |
|
18 |
+import openid |
|
19 |
+from openid.consumer import consumer |
|
20 |
+from openid.oidutil import appendArgs |
|
21 |
+from openid.extensions import pape, sreg |
|
22 |
+ |
|
23 |
+import mako.template |
|
24 |
+from openidmongodb import MongoDBStore |
|
25 |
+from zope.interface import Interface, Attribute, implements |
|
26 |
+from twisted.python.components import registerAdapter |
|
27 |
+from twisted.web.server import Session |
|
28 |
+from twisted.web.resource import Resource |
|
29 |
+import collections |
|
30 |
+ |
|
31 |
+################### Customization hooks |
|
32 |
+class EventHandler(object): |
|
33 |
+ store = MongoDBStore() |
|
34 |
+ |
|
35 |
+ def __init__(self, resource): |
|
36 |
+ self.resource = resource |
|
37 |
+ |
|
38 |
+ @property |
|
39 |
+ def template(self): |
|
40 |
+ with file(get_filename('twisted_openid', 'page.mako')) as f: |
|
41 |
+ return mako.template.Template(f.read()) |
|
42 |
+ |
|
43 |
+ def cancel(self, *args, **kwargs): |
|
44 |
+ pass |
|
45 |
+ |
|
46 |
+ def fail(self, *args, **kwargs): |
|
47 |
+ pass |
|
48 |
+ |
|
49 |
+ def setup_needed(self, *args, **kwargs): |
|
50 |
+ pass |
|
51 |
+ |
|
52 |
+ def success(self, *args, **kwargs): |
|
53 |
+ pass |
|
54 |
+ |
|
55 |
+################################################# |
|
56 |
+ |
|
57 |
+def get_filename(package, resource=None): |
|
58 |
+ """Get the absolute path to a file inside a given Python package""" |
|
59 |
+ d = sys.modules[package].__file__ |
|
60 |
+ if resource: |
|
61 |
+ d = os.path.dirname(d) |
|
62 |
+ d = os.path.abspath(d) |
|
63 |
+ return os.path.join(d, resource) |
|
64 |
+ return d |
|
65 |
+ |
|
66 |
+def quoteattr(s): |
|
67 |
+ qs = cgi.escape(s, 1) |
|
68 |
+ return '"%s"' % (qs,) |
|
69 |
+ |
|
70 |
+ |
|
71 |
+ |
|
72 |
+ |
|
73 |
+# Used with an OpenID provider affiliate program. |
|
74 |
+OPENID_PROVIDER_NAME = 'MyOpenID' |
|
75 |
+OPENID_PROVIDER_URL ='https://www.myopenid.com/affiliate_signup?affiliate_id=39' |
|
76 |
+ |
|
77 |
+ |
|
78 |
+ |
|
79 |
+class OpenIDResource(Resource): |
|
80 |
+ """Request handler that knows how to verify an OpenID identity.""" |
|
81 |
+ SESSION_COOKIE_NAME = 'pyoidconsexsid' |
|
82 |
+ |
|
83 |
+ eventhandler = EventHandler |
|
84 |
+ session = None |
|
85 |
+ isLeaf = True |
|
86 |
+ |
|
87 |
+ |
|
88 |
+ def __init__(self, *args, **kw): |
|
89 |
+ Resource.__init__(self, *args, **kw) |
|
90 |
+ self.eventhandler = self.eventhandler(self) |
|
91 |
+ |
|
92 |
+ def getConsumer(self, txrequest, stateless=False): |
|
93 |
+ if stateless: |
|
94 |
+ store = None |
|
95 |
+ else: |
|
96 |
+ store = self.eventhandler.store |
|
97 |
+ return consumer.Consumer(IOID_Session(txrequest.getSession()), store) |
|
98 |
+ |
|
99 |
+ |
|
100 |
+ def render(self, txrequest): |
|
101 |
+ """Dispatching logic. There are three paths defined: |
|
102 |
+ |
|
103 |
+ / - Display an empty form asking for an identity URL to |
|
104 |
+ verify |
|
105 |
+ /verify - Handle form submission, initiating OpenID verification |
|
106 |
+ /process - Handle a redirect from an OpenID server |
|
107 |
+ |
|
108 |
+ Any other path gets a 404 response. This function also parses |
|
109 |
+ the query parameters. |
|
110 |
+ |
|
111 |
+ If an exception occurs in this function, a traceback is |
|
112 |
+ written to the requesting browser. |
|
113 |
+ """ |
|
114 |
+ o_txrequest_write = txrequest.write |
|
115 |
+ def txrequest_write(msg, *a, **kw): |
|
116 |
+ if isinstance(msg, unicode): msg = msg.encode('utf-8') |
|
117 |
+ return o_txrequest_write(msg, *a, **kw) |
|
118 |
+ txrequest.write = txrequest_write |
|
119 |
+ try: |
|
120 |
+ self.parsed_uri = urlparse.urlparse(txrequest.uri) |
|
121 |
+ |
|
122 |
+ path = '/%s' % '/'.join(txrequest.postpath) |
|
123 |
+ print path |
|
124 |
+ if path == '/': |
|
125 |
+ self.renderPage(txrequest) |
|
126 |
+ elif path == '/verify': |
|
127 |
+ self.doVerify(txrequest) |
|
128 |
+ elif path == '/process': |
|
129 |
+ self.doProcess(txrequest) |
|
130 |
+ elif path == '/affiliate': |
|
131 |
+ self.doAffiliate(txrequest) |
|
132 |
+ elif path == '/favicon.ico': |
|
133 |
+ return '' |
|
134 |
+ else: |
|
135 |
+ self.notFound(txrequest) |
|
136 |
+ |
|
137 |
+ except (KeyboardInterrupt, SystemExit): |
|
138 |
+ raise |
|
139 |
+ except: |
|
140 |
+ txrequest.setResponseCode(500) |
|
141 |
+ txrequest.setHeader('Content-type', 'text/html') |
|
142 |
+ txrequest.getSession() |
|
143 |
+ return cgitb.html(sys.exc_info(), context=10) |
|
144 |
+ return '' |
|
145 |
+ |
|
146 |
+ def doVerify(self, txrequest): |
|
147 |
+ """Process the form submission, initating OpenID verification. |
|
148 |
+ """ |
|
149 |
+ |
|
150 |
+ # First, make sure that the user entered something |
|
151 |
+ openid_url = txrequest.args.get('openid_identifier') |
|
152 |
+ if not openid_url: |
|
153 |
+ self.renderPage(txrequest, 'Enter an OpenID Identifier to verify.', |
|
154 |
+ css_class='error', form_contents=openid_url) |
|
155 |
+ return |
|
156 |
+ else: |
|
157 |
+ openid_url = openid_url[0] |
|
158 |
+ |
|
159 |
+ immediate = 'immediate' in txrequest.args |
|
160 |
+ use_stateless = 'use_stateless' in txrequest.args |
|
161 |
+ |
|
162 |
+ oidconsumer = self.getConsumer(txrequest, stateless = use_stateless) |
|
163 |
+ try: |
|
164 |
+ request = oidconsumer.begin(openid_url) |
|
165 |
+ except consumer.DiscoveryFailure, exc: |
|
166 |
+ fetch_error_string = 'Error in discovery: %s' % (cgi.escape(str(exc[0]))) |
|
167 |
+ self.renderPage(txrequest, fetch_error_string, css_class='error', form_contents=openid_url) |
|
168 |
+ else: |
|
169 |
+ if request is None: |
|
170 |
+ msg = 'No OpenID services found for <code>%s</code>' % ( |
|
171 |
+ cgi.escape(openid_url),) |
|
172 |
+ self.renderPage(txrequest, msg, css_class='error', form_contents=openid_url) |
|
173 |
+ else: |
|
174 |
+ trust_root = txrequest.prePathURL() |
|
175 |
+ return_to = self.buildURL(txrequest, 'process') |
|
176 |
+ |
|
177 |
+ if request.shouldSendRedirect(): |
|
178 |
+ redirect_url = request.redirectURL(trust_root, return_to, immediate=immediate) |
|
179 |
+ txrequest.redirect(redirect_url) |
|
180 |
+ else: |
|
181 |
+ form_html = request.htmlMarkup( |
|
182 |
+ trust_root, return_to, |
|
183 |
+ form_tag_attrs={'id':'openid_message'}, |
|
184 |
+ immediate=immediate) |
|
185 |
+ |
|
186 |
+ txrequest.write(form_html) |
|
187 |
+ |
|
188 |
+ def requestRegistrationData(self, request): |
|
189 |
+ sreg_request = sreg.SRegRequest( |
|
190 |
+ required=['nickname'], optional=['fullname', 'email']) |
|
191 |
+ request.addExtension(sreg_request) |
|
192 |
+ |
|
193 |
+ def requestPAPEDetails(self, request): |
|
194 |
+ pape_request = pape.Request([pape.AUTH_PHISHING_RESISTANT]) |
|
195 |
+ request.addExtension(pape_request) |
|
196 |
+ |
|
197 |
+ def doProcess(self, txrequest): |
|
198 |
+ """Handle the redirect from the OpenID server. |
|
199 |
+ """ |
|
200 |
+ oidconsumer = self.getConsumer(txrequest) |
|
201 |
+ |
|
202 |
+ # Ask the library to check the response that the server sent |
|
203 |
+ # us. Status is a code indicating the response type. info is |
|
204 |
+ # either None or a string containing more information about |
|
205 |
+ # the return type. |
|
206 |
+ url = 'http://'+txrequest.getHeader('Host')+txrequest.path |
|
207 |
+ query = { k: a[0] if len(a) == 1 else a for k,a in txrequest.args.iteritems() } |
|
208 |
+ info = oidconsumer.complete(query, url) |
|
209 |
+ |
|
210 |
+ sreg_resp = None |
|
211 |
+ pape_resp = None |
|
212 |
+ css_class = 'error' |
|
213 |
+ display_identifier = info.getDisplayIdentifier() |
|
214 |
+ |
|
215 |
+ message = None |
|
216 |
+ cb = None |
|
217 |
+ kwargs = {} |
|
218 |
+ if info.status == consumer.FAILURE and display_identifier: |
|
219 |
+ txrequest.getSession().expire() |
|
220 |
+ cb = self.eventhandler.fail |
|
221 |
+ elif info.status == consumer.SUCCESS: |
|
222 |
+ kwargs['canonicalID'] = info.endpoint.canonicalID |
|
223 |
+ kwargs['display_identifier'] = display_identifier |
|
224 |
+ kwargs['identity_url'] = info.identity_url |
|
225 |
+ session = IOID_Session(txrequest.getSession()) |
|
226 |
+ print 'id(session.items)', id(session) |
|
227 |
+ session.update(kwargs) |
|
228 |
+ cb = self.eventhandler.success |
|
229 |
+ elif info.status == consumer.CANCEL: |
|
230 |
+ txrequest.getSession().expire() |
|
231 |
+ cb = self.eventhandler.cancel |
|
232 |
+ elif info.status == consumer.SETUP_NEEDED: |
|
233 |
+ cb = self.eventhandler.setup_needed |
|
234 |
+ else: |
|
235 |
+ txrequest.getSession().expire() |
|
236 |
+ message = 'Verification failed.' |
|
237 |
+ |
|
238 |
+ if cb is not None: |
|
239 |
+ cb(txrequest, message, **kwargs) |
|
240 |
+ else: |
|
241 |
+ self.renderPage(txrequest, message, display_identifier, |
|
242 |
+ sreg_data=sreg_resp, pape_data=pape_resp) |
|
243 |
+ |
|
244 |
+#### Untested !!! |
|
245 |
+# def doAffiliate(self): |
|
246 |
+# """Direct the user sign up with an affiliate OpenID provider.""" |
|
247 |
+# sreg_req = sreg.SRegRequest(['nickname'], ['fullname', 'email']) |
|
248 |
+# href = sreg_req.toMessage().toURL(OPENID_PROVIDER_URL) |
|
249 |
+# |
|
250 |
+# message = """Get an OpenID at <a href=%s>%s</a>""" % ( |
|
251 |
+# quoteattr(href), OPENID_PROVIDER_NAME) |
|
252 |
+# self.renderPage(txrequest, message) |
|
253 |
+# |
|
254 |
+# def renderSREG(self, sreg_data, txrequest): |
|
255 |
+# if not sreg_data: |
|
256 |
+# txrequest.write('<div class="alert">No registration data was returned</div>') |
|
257 |
+# else: |
|
258 |
+# sreg_list = sreg_data.items() |
|
259 |
+# sreg_list.sort() |
|
260 |
+# txrequest.write( |
|
261 |
+# '<h2>Registration Data</h2>' |
|
262 |
+# '<table class="sreg">' |
|
263 |
+# '<thead><tr><th>Field</th><th>Value</th></tr></thead>' |
|
264 |
+# '<tbody>') |
|
265 |
+# |
|
266 |
+# odd = ' class="odd"' |
|
267 |
+# for k, v in sreg_list: |
|
268 |
+# field_name = sreg.data_fields.get(k, k) |
|
269 |
+# value = cgi.escape(v.encode('UTF-8')) |
|
270 |
+# txrequest.write( |
|
271 |
+# '<tr%s><td>%s</td><td>%s</td></tr>' % (odd, field_name, value)) |
|
272 |
+# if odd: |
|
273 |
+# odd = '' |
|
274 |
+# else: |
|
275 |
+# odd = ' class="odd"' |
|
276 |
+# |
|
277 |
+# txrequest.write('</tbody></table>') |
|
278 |
+ |
|
279 |
+ def renderPAPE(self, pape_data, txrequest): |
|
280 |
+ if not pape_data: |
|
281 |
+ txrequest.write( |
|
282 |
+ '<div class="alert">No PAPE data was returned</div>') |
|
283 |
+ else: |
|
284 |
+ txrequest.write('<div class="alert">Effective Auth Policies<ul>') |
|
285 |
+ |
|
286 |
+ for policy_uri in pape_data.auth_policies: |
|
287 |
+ txrequest.write('<li><tt>%s</tt></li>' % (cgi.escape(policy_uri),)) |
|
288 |
+ |
|
289 |
+ if not pape_data.auth_policies: |
|
290 |
+ txrequest.write('<li>No policies were applied.</li>') |
|
291 |
+ |
|
292 |
+ txrequest.write('</ul></div>') |
|
293 |
+ |
|
294 |
+ def buildURL(self, txrequest, action, **query): |
|
295 |
+ """Build a URL relative to the server base_url, with the given |
|
296 |
+ query parameters added.""" |
|
297 |
+ base = urlparse.urljoin(txrequest.prePathURL()+'/', action) |
|
298 |
+ print 'buildURL',base |
|
299 |
+ return appendArgs(base, query) |
|
300 |
+ |
|
301 |
+ def notFound(self): |
|
302 |
+ """Render a page with a 404 return code and a message.""" |
|
303 |
+ fmt = 'The path <q>%s</q> was not understood by this server.' |
|
304 |
+ msg = fmt % (self.path,) |
|
305 |
+ openid_url = txrequest.args.get('openid_identifier') |
|
306 |
+ self.renderPage(txrequest, msg, 'error', openid_url, status=404) |
|
307 |
+ |
|
308 |
+ def renderPage(self, txrequest, message=None, css_class='alert', form_contents=None, |
|
309 |
+ status=200, title="Python OpenID Consumer Example", |
|
310 |
+ sreg_data=None, pape_data=None): |
|
311 |
+ """Render a page.""" |
|
312 |
+ form_contents = form_contents or '' |
|
313 |
+ |
|
314 |
+ txrequest.setResponseCode(status) |
|
315 |
+ session = txrequest.getSession() |
|
316 |
+ print 'id(session)', id(session) |
|
317 |
+ print 'session id:', session.uid |
|
318 |
+ counter = IOID_Session(session) |
|
319 |
+ print 'id(session.items)', id(counter) |
|
320 |
+ |
|
321 |
+ result = self.eventhandler.template.render(**dict( |
|
322 |
+ css_class = css_class, |
|
323 |
+ action = quoteattr(self.buildURL(txrequest, 'verify')), |
|
324 |
+ openid = quoteattr(form_contents), |
|
325 |
+ message = message or 'session details: %s' % counter.items, |
|
326 |
+ )) |
|
327 |
+ txrequest.write(result); |
|
328 |
+ |
|
329 |
+################################# Twisted Session Management ####################################### |
|
330 |
+ |
|
331 |
+class IOID_Session(Interface): |
|
332 |
+ items = Attribute("An int value which counts up once per page view.") |
|
333 |
+ |
|
334 |
+ |
|
335 |
+class OID_Session(collections.MutableMapping): |
|
336 |
+ implements(IOID_Session) |
|
337 |
+ def __init__(self, session): |
|
338 |
+ self.items = {} |
|
339 |
+ def __len__(self): |
|
340 |
+ return len(self.items) |
|
341 |
+ def __iter__(self): |
|
342 |
+ return iter(self.items) |
|
343 |
+ def __getitem__(self, name): |
|
344 |
+ return self.items[name] |
|
345 |
+ def __setitem__(self, name, value): |
|
346 |
+ self.items[name] = value |
|
347 |
+ def __delitem__(self, name): |
|
348 |
+ del self.items[name] |
|
349 |
+ def get(self, name, default=None): |
|
350 |
+ return self.items.get(name, default) |
|
351 |
+ |
|
352 |
+ |
|
353 |
+registerAdapter(OID_Session, Session, IOID_Session) |
|
354 |
+ |
0 | 355 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,8 @@ |
1 |
+<div id="verify-form"> |
|
2 |
+%if message: |
|
3 |
+ <div class="{css_class}">${message}</div> |
|
4 |
+%endif |
|
5 |
+<form method="get" accept-charset="UTF-8" action=${action}> |
|
6 |
+ <input type="text" name="openid_identifier" value=${openid} /> |
|
7 |
+ <input type="submit" value="Verify" /><br /> |
|
8 |
+</form> </div> |