~ubuntu-branches/ubuntu/natty/moin/natty-updates

« back to all changes in this revision

Viewing changes to MoinMoin/auth/openidrp.py

  • Committer: Bazaar Package Importer
  • Author(s): Jonas Smedegaard
  • Date: 2008-06-22 21:17:13 UTC
  • mfrom: (0.9.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20080622211713-fpo2zrq3s5dfecxg
Tags: 1.7.0-3
Simplify /etc/moin/wikilist format: "USER URL" (drop unneeded middle
CONFIG_DIR that was wrongly advertised as DATA_DIR).  Make
moin-mass-migrate handle both formats and warn about deprecation of
the old one.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- coding: iso-8859-1 -*-
 
2
"""
 
3
    MoinMoin - OpenID authorization
 
4
 
 
5
    @copyright: 2007 MoinMoin:JohannesBerg
 
6
    @license: GNU GPL, see COPYING for details.
 
7
"""
 
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
 
18
 
 
19
 
 
20
class OpenIDAuth(BaseAuth):
 
21
    login_inputs = ['openid_identifier']
 
22
    name = 'openid'
 
23
    logout_possible = True
 
24
 
 
25
    def __init__(self, modify_request=None,
 
26
                       update_user=None,
 
27
                       create_user=None,
 
28
                       forced_service=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
 
34
        if forced_service:
 
35
            self.login_inputs = ['special_no_input']
 
36
 
 
37
    def _handle_user_data(self, request, u):
 
38
        create = not u
 
39
        if create:
 
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'])
 
43
            # invalid name
 
44
            u.name = ''
 
45
            u = self._create_user(request.session['openid.info'], u)
 
46
 
 
47
        if u:
 
48
            self._update_user(request.session['openid.info'], u)
 
49
 
 
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))):
 
53
                return None
 
54
 
 
55
            if not hasattr(u, 'openids'):
 
56
                u.openids = []
 
57
            if not request.session['openid.id'] in u.openids:
 
58
                u.openids.append(request.session['openid.id'])
 
59
 
 
60
            u.save()
 
61
 
 
62
            del request.session['openid.id']
 
63
            del request.session['openid.info']
 
64
 
 
65
        return u
 
66
 
 
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)
 
72
        _ = request.getText
 
73
        form.append(html.INPUT(type='hidden', name='oidstage', value='2'))
 
74
        table = html.TABLE(border='0')
 
75
        form.append(table)
 
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))
 
81
        if msg:
 
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))
 
85
        td1 = html.TD()
 
86
        td1.append(html.STRONG().append(html.Raw(_('Name'))))
 
87
        td2 = html.TD()
 
88
        td2.append(html.INPUT(type='text', name='username'))
 
89
        table.append(html.TR().append(td1).append(td2))
 
90
        td1 = html.TD()
 
91
        td2 = html.TD()
 
92
        td2.append(html.INPUT(type='submit', name='submit',
 
93
                              value=_('Choose this name')))
 
94
        table.append(html.TR().append(td1).append(td2))
 
95
 
 
96
    def _get_account_name_inval_user(self, request, form):
 
97
        _ = request.getText
 
98
        msg = _('This is not a valid username, choose a different one.')
 
99
        return self._get_account_name(request, form, msg=msg)
 
100
 
 
101
    def _associate_account(self, request, form, accountname, msg=None):
 
102
        _ = request.getText
 
103
 
 
104
        form.append(html.INPUT(type='hidden', name='oidstage', value='3'))
 
105
        table = html.TABLE(border='0')
 
106
        form.append(table)
 
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))
 
113
        if msg:
 
114
            td.append(html.P().append(html.STRONG().append(html.Raw(msg))))
 
115
        td1 = html.TD()
 
116
        td1.append(html.STRONG().append(html.Raw(_('Name'))))
 
117
        td2 = html.TD()
 
118
        td2.append(html.INPUT(type='text', name='username', value=accountname))
 
119
        table.append(html.TR().append(td1).append(td2))
 
120
        td1 = html.TD()
 
121
        td1.append(html.STRONG().append(html.Raw(_('Password'))))
 
122
        td2 = html.TD()
 
123
        td2.append(html.INPUT(type='password', name='password'))
 
124
        table.append(html.TR().append(td1).append(td2))
 
125
        td1 = html.TD()
 
126
        td2 = html.TD()
 
127
        td2.append(html.INPUT(type='submit', name='submit',
 
128
                              value=_('Associate this name')))
 
129
        table.append(html.TR().append(td1).append(td2))
 
130
 
 
131
    def _handle_verify_continuation(self, request):
 
132
        _ = request.getText
 
133
        oidconsumer = consumer.Consumer(request.session,
 
134
                                        MoinOpenIDStore(request))
 
135
        query = {}
 
136
        for key in request.form:
 
137
            query[key] = request.form[key][0]
 
138
        return_to = get_multistage_continuation_url(request, self.name,
 
139
                                                    {'oidstage': '1'})
 
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
 
148
 
 
149
            # try to find user object
 
150
            uid = user.getUserIdByOpenId(request, info.identity_url)
 
151
            if uid:
 
152
                u = user.User(request, id=uid, auth_method=self.name,
 
153
                              auth_username=info.identity_url)
 
154
            else:
 
155
                u = None
 
156
 
 
157
            # create or update the user according to the registration data
 
158
            u = self._handle_user_data(request, u)
 
159
            if u:
 
160
                return ContinueLogin(u)
 
161
 
 
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)
 
166
        else:
 
167
            return CancelLogin(_('OpenID failure.'))
 
168
 
 
169
    def _handle_name_continuation(self, request):
 
170
        if not 'openid.id' in request.session:
 
171
            return CancelLogin(None)
 
172
 
 
173
        _ = request.getText
 
174
        newname = request.form.get('username', [''])[0]
 
175
        if not newname:
 
176
            return MultistageFormLogin(self._get_account_name)
 
177
        if not user.isValidName(request, newname):
 
178
            return MultistageFormLogin(self._get_account_name_inval_user)
 
179
        uid = None
 
180
        if newname:
 
181
            uid = user.getUserId(request, newname)
 
182
        if not uid:
 
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'])
 
186
            u.name = newname
 
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)
 
193
 
 
194
    def _handle_associate_continuation(self, request):
 
195
        if not 'openid.id' in request.session:
 
196
            return CancelLogin()
 
197
 
 
198
        _ = request.getText
 
199
        username = request.form.get('username', [''])[0]
 
200
        password = request.form.get('password', [''])[0]
 
201
        if not password:
 
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'])
 
206
        if u.valid:
 
207
            self._handle_user_data(request, u)
 
208
            return ContinueLogin(u, _('Your account is now associated to your OpenID.'))
 
209
        else:
 
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)
 
213
 
 
214
    def _handle_continuation(self, request):
 
215
        oidstage = request.form.get('oidstage', [0])[0]
 
216
        if oidstage == '1':
 
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)
 
222
        return CancelLogin()
 
223
 
 
224
    def _openid_form(self, request, form, oidhtml):
 
225
        _ = request.getText
 
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">
 
229
<!--//
 
230
document.getElementById("openid_message").submit();
 
231
//-->
 
232
</script>
 
233
"""
 
234
        return ''.join([txt, oidhtml, submitjs])
 
235
 
 
236
    def login(self, request, user_obj, **kw):
 
237
        continuation = kw.get('multistage')
 
238
 
 
239
        if continuation:
 
240
            return self._handle_continuation(request)
 
241
 
 
242
        # openid is designed to work together with other auths
 
243
        if user_obj and user_obj.valid:
 
244
            return ContinueLogin(user_obj)
 
245
 
 
246
        openid_id = kw.get('openid_identifier')
 
247
 
 
248
        # nothing entered? continue...
 
249
        if not self._forced_service and not openid_id:
 
250
            return ContinueLogin(user_obj)
 
251
 
 
252
        _ = request.getText
 
253
 
 
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.'))
 
258
 
 
259
        oidconsumer = consumer.Consumer(request.session,
 
260
                                        MoinOpenIDStore(request))
 
261
 
 
262
        try:
 
263
            fserv = self._forced_service
 
264
            if fserv:
 
265
                if isinstance(fserv, str) or isinstance(fserv, unicode):
 
266
                    oidreq = oidconsumer.begin(fserv)
 
267
                else:
 
268
                    oidreq = oidconsumer.beginWithoutDiscovery(fserv)
 
269
            else:
 
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.'))
 
275
        else:
 
276
            if oidreq is None:
 
277
                return ContinueLogin(None, _('No OpenID.'))
 
278
 
 
279
            self._modify_request(oidreq)
 
280
 
 
281
            return_to = get_multistage_continuation_url(request, self.name,
 
282
                                                        {'oidstage': '1'})
 
283
            trust_root = request.getBaseURL()
 
284
            if oidreq.shouldSendRedirect():
 
285
                redirect_url = oidreq.redirectURL(trust_root, return_to)
 
286
                return MultistageRedirectLogin(redirect_url)
 
287
            else:
 
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)
 
293
                return ret
 
294
 
 
295
    def login_hint(self, request):
 
296
        _ = request.getText
 
297
        return _("If you do not have an account yet, you can still log in "
 
298
                 "with your OpenID and create one during login.")