1
# -*- coding: iso-8859-1 -*-
3
MoinMoin - OpenID authorization
5
@copyright: 2007 MoinMoin:JohannesBerg
6
@license: GNU GPL, see COPYING for details.
8
from MoinMoin.util.moinoid import MoinOpenIDStore
9
from MoinMoin import user
10
from MoinMoin.auth import BaseAuth
11
from openid.consumer import consumer
12
from openid.yadis.discover import DiscoveryFailure
13
from openid.fetchers import HTTPFetchingError
14
from MoinMoin.widget import html
15
from MoinMoin.auth import CancelLogin, ContinueLogin
16
from MoinMoin.auth import MultistageFormLogin, MultistageRedirectLogin
17
from MoinMoin.auth import get_multistage_continuation_url
20
class OpenIDAuth(BaseAuth):
21
login_inputs = ['openid_identifier']
23
logout_possible = True
25
def __init__(self, modify_request=None,
29
BaseAuth.__init__(self)
30
self._modify_request = modify_request or (lambda x: None)
31
self._update_user = update_user or (lambda i, u: None)
32
self._create_user = create_user or (lambda i, u: None)
33
self._forced_service = forced_service
35
self.login_inputs = ['special_no_input']
37
def _handle_user_data(self, request, u):
40
# pass in a created but unsaved user object
41
u = user.User(request, auth_method=self.name,
42
auth_username=request.session['openid.id'])
45
u = self._create_user(request.session['openid.info'], u)
48
self._update_user(request.session['openid.info'], u)
50
# just in case the wiki admin screwed up
51
if (not user.isValidName(request, u.name) or
52
(create and user.getUserId(request, u.name))):
55
if not hasattr(u, 'openids'):
57
if not request.session['openid.id'] in u.openids:
58
u.openids.append(request.session['openid.id'])
62
del request.session['openid.id']
63
del request.session['openid.info']
67
def _get_account_name(self, request, form, msg=None):
68
# now we need to ask the user for a new username
69
# that they want to use on this wiki
70
# XXX: request nickname from OP and suggest using it
71
# (if it isn't in use yet)
73
form.append(html.INPUT(type='hidden', name='oidstage', value='2'))
74
table = html.TABLE(border='0')
76
td = html.TD(colspan=2)
77
td.append(html.Raw(_("""Please choose an account name now.
78
If you choose an existing account name you will be asked for the
79
password and be able to associate the account with your OpenID.""")))
80
table.append(html.TR().append(td))
82
td = html.TD(colspan='2')
83
td.append(html.P().append(html.STRONG().append(html.Raw(msg))))
84
table.append(html.TR().append(td))
86
td1.append(html.STRONG().append(html.Raw(_('Name'))))
88
td2.append(html.INPUT(type='text', name='username'))
89
table.append(html.TR().append(td1).append(td2))
92
td2.append(html.INPUT(type='submit', name='submit',
93
value=_('Choose this name')))
94
table.append(html.TR().append(td1).append(td2))
96
def _get_account_name_inval_user(self, request, form):
98
msg = _('This is not a valid username, choose a different one.')
99
return self._get_account_name(request, form, msg=msg)
101
def _associate_account(self, request, form, accountname, msg=None):
104
form.append(html.INPUT(type='hidden', name='oidstage', value='3'))
105
table = html.TABLE(border='0')
107
td = html.TD(colspan=2)
108
td.append(html.Raw(_("""The username you have chosen is already
109
taken. If it is your username, enter your password below to associate
110
the username with your OpenID. Otherwise, please choose a different
111
username and leave the password field blank.""")))
112
table.append(html.TR().append(td))
114
td.append(html.P().append(html.STRONG().append(html.Raw(msg))))
116
td1.append(html.STRONG().append(html.Raw(_('Name'))))
118
td2.append(html.INPUT(type='text', name='username', value=accountname))
119
table.append(html.TR().append(td1).append(td2))
121
td1.append(html.STRONG().append(html.Raw(_('Password'))))
123
td2.append(html.INPUT(type='password', name='password'))
124
table.append(html.TR().append(td1).append(td2))
127
td2.append(html.INPUT(type='submit', name='submit',
128
value=_('Associate this name')))
129
table.append(html.TR().append(td1).append(td2))
131
def _handle_verify_continuation(self, request):
133
oidconsumer = consumer.Consumer(request.session,
134
MoinOpenIDStore(request))
136
for key in request.form:
137
query[key] = request.form[key][0]
138
return_to = get_multistage_continuation_url(request, self.name,
140
info = oidconsumer.complete(query, return_to=return_to)
141
if info.status == consumer.FAILURE:
142
return CancelLogin(_('OpenID error: %s.') % info.message)
143
elif info.status == consumer.CANCEL:
144
return CancelLogin(_('Verification canceled.'))
145
elif info.status == consumer.SUCCESS:
146
request.session['openid.id'] = info.identity_url
147
request.session['openid.info'] = info
149
# try to find user object
150
uid = user.getUserIdByOpenId(request, info.identity_url)
152
u = user.User(request, id=uid, auth_method=self.name,
153
auth_username=info.identity_url)
157
# create or update the user according to the registration data
158
u = self._handle_user_data(request, u)
160
return ContinueLogin(u)
162
# if no user found, then we need to ask for a username,
163
# possibly associating an existing account.
164
request.session['openid.id'] = info.identity_url
165
return MultistageFormLogin(self._get_account_name)
167
return CancelLogin(_('OpenID failure.'))
169
def _handle_name_continuation(self, request):
170
if not 'openid.id' in request.session:
171
return CancelLogin(None)
174
newname = request.form.get('username', [''])[0]
176
return MultistageFormLogin(self._get_account_name)
177
if not user.isValidName(request, newname):
178
return MultistageFormLogin(self._get_account_name_inval_user)
181
uid = user.getUserId(request, newname)
183
# we can create a new user with this name :)
184
u = user.User(request, auth_method=self.name,
185
auth_username=request.session['openid.id'])
187
u = self._handle_user_data(request, u)
188
return ContinueLogin(u)
189
# requested username already exists. if they know the password,
190
# they can associate that account with the openid.
191
assoc = lambda req, form: self._associate_account(req, form, newname)
192
return MultistageFormLogin(assoc)
194
def _handle_associate_continuation(self, request):
195
if not 'openid.id' in request.session:
199
username = request.form.get('username', [''])[0]
200
password = request.form.get('password', [''])[0]
202
return self._handle_name_continuation(request)
203
u = user.User(request, name=username, password=password,
204
auth_method=self.name,
205
auth_username=request.session['openid.id'])
207
self._handle_user_data(request, u)
208
return ContinueLogin(u, _('Your account is now associated to your OpenID.'))
210
msg = _('The password you entered is not valid.')
211
assoc = lambda req, form: self._associate_account(req, form, username, msg=msg)
212
return MultistageFormLogin(assoc)
214
def _handle_continuation(self, request):
215
oidstage = request.form.get('oidstage', [0])[0]
217
return self._handle_verify_continuation(request)
218
elif oidstage == '2':
219
return self._handle_name_continuation(request)
220
elif oidstage == '3':
221
return self._handle_associate_continuation(request)
224
def _openid_form(self, request, form, oidhtml):
226
txt = _('OpenID verification requires that you click this button:')
227
# create JS to automatically submit the form if possible
228
submitjs = """<script type="text/javascript">
230
document.getElementById("openid_message").submit();
234
return ''.join([txt, oidhtml, submitjs])
236
def login(self, request, user_obj, **kw):
237
continuation = kw.get('multistage')
240
return self._handle_continuation(request)
242
# openid is designed to work together with other auths
243
if user_obj and user_obj.valid:
244
return ContinueLogin(user_obj)
246
openid_id = kw.get('openid_identifier')
248
# nothing entered? continue...
249
if not self._forced_service and not openid_id:
250
return ContinueLogin(user_obj)
254
# user entered something but the session can't be stored
255
if not request.session.is_stored:
256
return ContinueLogin(user_obj,
257
_('Anonymous sessions need to be enabled for OpenID login.'))
259
oidconsumer = consumer.Consumer(request.session,
260
MoinOpenIDStore(request))
263
fserv = self._forced_service
265
if isinstance(fserv, str) or isinstance(fserv, unicode):
266
oidreq = oidconsumer.begin(fserv)
268
oidreq = oidconsumer.beginWithoutDiscovery(fserv)
270
oidreq = oidconsumer.begin(openid_id)
271
except HTTPFetchingError:
272
return ContinueLogin(None, _('Failed to resolve OpenID.'))
273
except DiscoveryFailure:
274
return ContinueLogin(None, _('OpenID discovery failure, not a valid OpenID.'))
277
return ContinueLogin(None, _('No OpenID.'))
279
self._modify_request(oidreq)
281
return_to = get_multistage_continuation_url(request, self.name,
283
trust_root = request.getBaseURL()
284
if oidreq.shouldSendRedirect():
285
redirect_url = oidreq.redirectURL(trust_root, return_to)
286
return MultistageRedirectLogin(redirect_url)
288
form_html = oidreq.formMarkup(trust_root, return_to,
289
form_tag_attrs={'id': 'openid_message'})
290
mcall = lambda request, form:\
291
self._openid_form(request, form, form_html)
292
ret = MultistageFormLogin(mcall)
295
def login_hint(self, request):
297
return _("If you do not have an account yet, you can still log in "
298
"with your OpenID and create one during login.")