summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames E. Blair <corvus@gnu.org>2009-08-23 14:19:52 -0700
committerJames E. Blair <corvus@gnu.org>2009-08-23 14:19:52 -0700
commita9c8b10d07f910b01af32e21afd34e8b6c6ebf6f (patch)
tree1975ef445db24ca81032027f25d4fb9b75873138
parentdbfb8c00f1118c3645e18b65eca2461e736799e2 (diff)
Addd OpenID controllers to support being an OpenID provider.
-rw-r--r--quoins/blog_controllers.py8
-rw-r--r--quoins/controllers.py32
-rw-r--r--quoins/openid_controllers.py186
-rw-r--r--quoins/templates/oid_confirm.html72
-rw-r--r--quoins/utils.py17
5 files changed, 286 insertions, 29 deletions
diff --git a/quoins/blog_controllers.py b/quoins/blog_controllers.py
deleted file mode 100644
index 40c27ae..0000000
--- a/quoins/blog_controllers.py
+++ /dev/null
@@ -1,8 +0,0 @@
1from tg import TGController, tmpl_context
2from tg import expose, flash, require, url, request, redirect
3
4class BlogController(TGController):
5 @expose(template="genshi:quoinstemplates.test1")
6 def test(self):
7 return dict()
8
diff --git a/quoins/controllers.py b/quoins/controllers.py
index 6aac7a2..541d90b 100644
--- a/quoins/controllers.py
+++ b/quoins/controllers.py
@@ -41,31 +41,18 @@ import openid.consumer.consumer
41import openid.server.server 41import openid.server.server
42import openid.extensions.sreg 42import openid.extensions.sreg
43from openid.store.sqlstore import MySQLStore 43from openid.store.sqlstore import MySQLStore
44import MySQLdb
45import sqlalchemy.engine.url
46import types 44import types
47 45
48import xmlrpclib, sys, re 46import xmlrpclib, sys, re
49from linkback import LinkBackHandler, PingBackURI, TrackBackURI 47from linkback import LinkBackHandler, PingBackURI, TrackBackURI
50import base64 48import base64
51import urllib 49import urllib
50import openid_controllers
51from utils import get_oid_connection
52 52
53def b64encode(x): 53def b64encode(x):
54 return base64.encodestring(x)[:-1] 54 return base64.encodestring(x)[:-1]
55 55
56def get_oid_connection(config=None):
57 if config is None:
58 config = tg.config
59 backupuri = config.get('sqlalchemy.url')
60 uri = config.get('openid.store', backupuri)
61 u = sqlalchemy.engine.url.make_url(uri)
62 pw = u.password or ''
63 conn = MySQLdb.connect (host = u.host,
64 user = u.username,
65 passwd = pw,
66 db = u.database)
67 return conn
68
69def fix_url(url): 56def fix_url(url):
70 parts = urlsplit(url) 57 parts = urlsplit(url)
71 if not parts[0]: 58 if not parts[0]:
@@ -335,6 +322,15 @@ class BlogController(TGController):
335 feed = Feed() 322 feed = Feed()
336 pingback = Pingback() 323 pingback = Pingback()
337 324
325 def __init__(self, *args, **kw):
326 self.path = kw.pop('path', '/')
327 self.post_paginate = kw.pop('paginate', 5)
328 get_name_from_id = kw.pop('get_name_from_id', lambda x: x)
329 self.openid = openid_controllers.OpenIDController(path=self.path+'/openid/',
330 get_name_from_id = get_name_from_id)
331 self.feed.blog_controller = self
332 super(BlogController, self).__init__(*args, **kw)
333
338 def url(self, obj=None): 334 def url(self, obj=None):
339 if obj is None: 335 if obj is None:
340 u = tg.url(self.path) 336 u = tg.url(self.path)
@@ -428,12 +424,6 @@ Comment:
428 t = Thread(target=send_email, args=(msg, fromaddr, toaddr)) 424 t = Thread(target=send_email, args=(msg, fromaddr, toaddr))
429 t.start() 425 t.start()
430 426
431 def __init__(self, *args, **kw):
432 self.path = kw.pop('path', '/')
433 self.post_paginate = kw.pop('paginate', 5)
434 self.feed.blog_controller = self
435 super(BlogController, self).__init__(*args, **kw)
436
437 @expose(template="genshi:quoinstemplates.index") 427 @expose(template="genshi:quoinstemplates.index")
438 def index(self, start=0): 428 def index(self, start=0):
439 pylons.response.headers['X-XRDS-Location']=self.absolute_url('/yadis') 429 pylons.response.headers['X-XRDS-Location']=self.absolute_url('/yadis')
diff --git a/quoins/openid_controllers.py b/quoins/openid_controllers.py
new file mode 100644
index 0000000..9491195
--- /dev/null
+++ b/quoins/openid_controllers.py
@@ -0,0 +1,186 @@
1# Quoins - A TurboGears blogging system.
2# Copyright (C) 2008-2009 James E. Blair <corvus@gnu.org>
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation, either version 3 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17from tg import controllers, expose, flash, require, validate, request, redirect, session
18from tg import TGController
19import tg
20import pylons
21from repoze.what import predicates
22import openid.consumer.consumer
23import openid.server.server
24import openid.extensions.sreg
25from openid.store.sqlstore import MySQLStore
26import MySQLdb
27import sqlalchemy.engine.url
28import types
29import os.path
30
31from utils import get_oid_connection
32
33class DecideController(object):
34 def __init__(self, oid_controller):
35 self.oid_controller = oid_controller
36
37 @expose(template="genshi:quoinstemplates.oid_confirm")
38 @require(predicates.not_anonymous())
39 def index(self, **kw):
40 oid_request = request.environ['oid_request']
41 self.oid_controller._validate_identity(oid_request.identity)
42
43 sreg_req = openid.extensions.sreg.SRegRequest.fromOpenIDRequest(oid_request)
44 user = request.identity['user']
45
46 sr_required = [self.oid_controller.getSRegValue(user,x) for x in sreg_req.required]
47 sr_optional = [self.oid_controller.getSRegValue(user,x) for x in sreg_req.optional]
48
49 session['oid_request']=oid_request
50 session.save()
51
52 return dict(openid = self.oid_controller,
53 oid_request = oid_request,
54 required = sr_required,
55 optional = sr_optional,
56 )
57
58class ResponseController(object):
59 def __init__(self, oid_controller):
60 self.oid_controller = oid_controller
61
62 @expose()
63 def index(self, **kw):
64 webresponse = request.environ['oid_webresponse']
65
66 pylons.response.status = webresponse.code
67 pylons.response.headers.update(webresponse.headers)
68 return webresponse.body
69
70
71class OpenIDController(TGController):
72 sreg_friendly = {
73 'nickname': 'Nickname',
74 'email': 'Email address',
75 'fullname': 'Full name',
76 'dob': 'Date of birth',
77 'gender': 'Gender',
78 'postcode': 'Postal code',
79 'country': 'Country',
80 'language': 'Language',
81 'timezone': 'Time zone',
82 }
83
84 def url(self, obj=None):
85 if obj is None:
86 u = tg.url(self.path)
87 elif isinstance(obj, basestring):
88 if obj.startswith('/'): obj = obj[1:]
89 u = tg.url(os.path.join(self.path, obj))
90 return u
91
92 def absolute_url(self, obj):
93 port = tg.config.get('server.webport', '')
94 if port:
95 port = ':'+port
96 return 'http://%s%s%s'%(tg.config.get('server.webhost'), port,
97 self.url(obj))
98
99 def __init__(self, *args, **kw):
100 self.get_name_from_id = kw.pop('get_name_from_id', lambda x: x)
101 self.path = kw.pop('path', '/')
102 super(OpenIDController, self).__init__(*args, **kw)
103
104 def getSRegValue(self, user, field):
105 val = None
106 if field=='nickname': val = user.user_name
107 if field=='fullname': val = user.display_name
108 if field=='email': val = user.email_address
109 return (field, self.sreg_friendly[field], val)
110
111 def isAuthorized(self, id, trust_root):
112 if not request.identity: return False
113 name = self.get_name_from_id(id)
114 if request.identity['user'].user_name == name:
115 # check to see if the user has set preferences for this trust
116 # root, and return True. also, sreg. XXX
117 return False
118 return False
119
120 def _validate_identity(self, id):
121 name = self.get_name_from_id(id)
122 if request.identity['user'].user_name != name:
123 raise Exception("Not your ID")
124
125 @expose()
126 def finish(self, **kw):
127 oid_request=session['oid_request']
128 self._validate_identity(oid_request.identity)
129
130 sreg_req = openid.extensions.sreg.SRegRequest.fromOpenIDRequest(oid_request)
131 user = request.identity['user']
132
133 store = MySQLStore(get_oid_connection())
134 oserver = openid.server.server.Server(store,
135 self.absolute_url('/openid/server'))
136
137 data = {}
138 for field in sreg_req.required+sreg_req.optional:
139 if kw.has_key(field):
140 data[field]=self.getSRegValue(user, field)[2]
141
142 sreg_resp = openid.extensions.sreg.SRegResponse.extractResponse(sreg_req, data)
143
144 if kw.get('continue', None):
145 response = oid_request.answer(True)
146 response.addExtension(sreg_resp)
147 else:
148 response = oid_request.answer(False)
149
150 webresponse = oserver.encodeResponse(response)
151
152 pylons.response.status = webresponse.code
153 pylons.response.headers.update(webresponse.headers)
154
155 del session['oid_request']
156 session.save()
157 return webresponse.body
158
159 @expose()
160 def lookup(self, *remainder):
161 store = MySQLStore(get_oid_connection())
162 oserver = openid.server.server.Server(store,
163 self.absolute_url('/server'))
164 oid_request = oserver.decodeRequest(request.params)
165 request.environ['oid_request']=oid_request
166
167 if oid_request.mode in ['checkid_immediate', 'checkid_setup']:
168 if hasattr(oid_request, 'returnToVerified'):
169 if not (oid_request.trustRootValid()
170 and oid_request.returnToVerified()):
171 raise Exception("Not trusted")
172 else:
173 if not (oid_request.trustRootValid()):
174 raise Exception("Not trusted")
175 if self.isAuthorized(oid_request.identity, oid_request.trust_root):
176 response = oid_request.answer(True)
177 elif oid_request.immediate:
178 response = oid_request.answer(False)
179 else:
180 return (DecideController(self), ())
181 else:
182 response = oserver.handleRequest(oid_request)
183
184 webresponse = oserver.encodeResponse(response)
185 request.environ['oid_webresponse']=webresponse
186 return (ResponseController(self), ())
diff --git a/quoins/templates/oid_confirm.html b/quoins/templates/oid_confirm.html
new file mode 100644
index 0000000..4fcf1de
--- /dev/null
+++ b/quoins/templates/oid_confirm.html
@@ -0,0 +1,72 @@
1<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
2 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
3<html xmlns="http://www.w3.org/1999/xhtml"
4 xmlns:xi="http://www.w3.org/2001/XInclude"
5 xmlns:py="http://genshi.edgewall.org/">
6 <xi:include href="master.html" />
7 <xi:include href="blog-master.html" />
8
9 <head>
10 <meta content="text/html; charset=UTF-8" http-equiv="content-type" py:replace="''"/>
11 <title>${blog.title}</title>
12 </head>
13
14
15 <body>
16 <div id="main">
17 <div id="blog-content">
18
19 <form action="${openid.url('/finish')}" method="post">
20
21 <p>
22 The site <b>${oid_request.trust_root}</b>
23 wants to verify that you are identified by <b>${request.identity['user']}</b>
24 and has requested the following information:
25 </p>
26
27
28 <div py:if="required">
29 <h3> Required information </h3>
30 <ul>
31 <li py:for="field, name, value in required">
32 <label>
33 <input type="checkbox" name="${field}" checked="true" />
34 ${name}: ${value}
35 </label>
36 </li>
37 </ul>
38 </div>
39 <div py:if="optional">
40 <h3> Optional information </h3>
41 <ul>
42 <li py:for="field, name, value in optional">
43 <label>
44 <input type="checkbox" name="${field}" checked="true" />
45 ${name}: ${value}
46 </label>
47 </li>
48 </ul>
49 </div>
50
51 <!--
52 <p>
53 <label>
54 <input type="checkbox" name="remember" />
55 Remember these choices and don't prompt for this site in the future.
56 </label>
57 </p>
58 -->
59
60 <p>
61 <input type="submit" name="continue" value="Continue" />
62 &nbsp;
63 <input type="submit" name="cancel" value="Cancel" />
64 </p>
65
66 </form>
67
68 </div> <!-- blog-content -->
69 </div> <!-- main -->
70
71 </body>
72</html>
diff --git a/quoins/utils.py b/quoins/utils.py
new file mode 100644
index 0000000..2382a71
--- /dev/null
+++ b/quoins/utils.py
@@ -0,0 +1,17 @@
1import tg
2import sqlalchemy.engine.url
3import MySQLdb
4
5def get_oid_connection(config=None):
6 if config is None:
7 config = tg.config
8 backupuri = config.get('sqlalchemy.url')
9 uri = config.get('openid.store', backupuri)
10 u = sqlalchemy.engine.url.make_url(uri)
11 pw = u.password or ''
12 conn = MySQLdb.connect (host = u.host,
13 user = u.username,
14 passwd = pw,
15 db = u.database)
16 return conn
17