git.fiddlerwoaroof.com
Ed L authored on 05/03/2012 03:52:52
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>