Mercurial > public > src > moin > 1.9
changeset 2299:39d11cf4af6c
OpenID relying party (client) support
author | Johannes Berg <johannes AT sipsolutions DOT net> |
---|---|
date | Tue, 10 Jul 2007 19:11:46 +0200 |
parents | c81b9c983b1d |
children | 95e57cb59bb0 |
files | MoinMoin/auth/__init__.py MoinMoin/auth/openidrp.py MoinMoin/request/__init__.py MoinMoin/user.py MoinMoin/userform/login.py wiki/htdocs/classic/css/screen.css wiki/htdocs/common/openid.png wiki/htdocs/modern/css/screen.css wiki/htdocs/rightsidebar/css/screen.css |
diffstat | 9 files changed, 280 insertions(+), 3 deletions(-) [+] |
line wrap: on
line diff
--- a/MoinMoin/auth/__init__.py Tue Jul 10 19:09:14 2007 +0200 +++ b/MoinMoin/auth/__init__.py Tue Jul 10 19:11:46 2007 +0200 @@ -20,6 +20,8 @@ that the browser sent multistage: boolean indicating multistage login continuation [may not be present, login only] + openid_identifier: the OpenID identifier we got from the form + (or None) [login only] More may be added. @@ -85,6 +87,7 @@ * login_inputs: a list of required inputs, currently supported are - 'username': username entry field - 'password': password entry field + - 'openid_identifier': OpenID entry field * logout_possible: boolean indicating whether this auth methods supports logging out * name: name of the auth method, must be the same as given as the
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/MoinMoin/auth/openidrp.py Tue Jul 10 19:11:46 2007 +0200 @@ -0,0 +1,235 @@ +# -*- coding: iso-8859-1 -*- +""" + MoinMoin - OpenID authorization + + @copyright: 2007 MoinMoin:JohannesBerg + @license: GNU GPL, see COPYING for details. +""" +from MoinMoin.util.moinoid import MoinOpenIDStore +from MoinMoin import user +from MoinMoin.auth import BaseAuth +from openid.consumer import consumer +from openid.yadis.discover import DiscoveryFailure +from openid.fetchers import HTTPFetchingError +from MoinMoin import wikiutil +from MoinMoin.widget import html +from MoinMoin.auth import (CancelLogin, ContinueLogin, MultistageFormLogin, + MultistageRedirectLogin, get_multistage_continuation_url) + +class OpenIDAuth(BaseAuth): + login_inputs = ['openid_identifier'] + name = 'openid' + logout_possible = True + + def _get_account_name(self, request, form, msg=None): + # now we need to ask the user for a new username + # that they want to use on this wiki + # XXX: request nickname from OP and suggest using it + # (if it isn't in use yet) + _ = request.getText + form.append(html.INPUT(type='hidden', name='oidstage', value='2')) + table = html.TABLE(border='0') + form.append(table) + td = html.TD(colspan=2) + td.append(html.Raw(_("""Please choose an account name now. +If you choose an existing account name you will be asked for the +password and be able to associate the account with your OpenID."""))) + table.append(html.TR().append(td)) + if msg: + td = html.TD(colspan='2') + td.append(html.P().append(html.STRONG().append(html.Raw(msg)))) + table.append(html.TR().append(td)) + td1 = html.TD() + td1.append(html.STRONG().append(html.Raw(_('Name')))) + td2 = html.TD() + td2.append(html.INPUT(type='text', name='username')) + table.append(html.TR().append(td1).append(td2)) + td1 = html.TD() + td2 = html.TD() + td2.append(html.INPUT(type='submit', name='submit', + value=_('Choose this name'))) + table.append(html.TR().append(td1).append(td2)) + + def _associate_account(self, request, form, accountname, msg=None): + _ = request.getText + + form.append(html.INPUT(type='hidden', name='oidstage', value='3')) + table = html.TABLE(border='0') + form.append(table) + td = html.TD(colspan=2) + td.append(html.Raw(_("""The username you have chosen is already +taken. If it is your username, enter your password below to associate +the username with your OpenID. Otherwise, please choose a different +username and leave the password field blank."""))) + table.append(html.TR().append(td)) + if msg: + td.append(html.P().append(html.STRONG().append(html.Raw(msg)))) + td1 = html.TD() + td1.append(html.STRONG().append(html.Raw(_('Name')))) + td2 = html.TD() + td2.append(html.INPUT(type='text', name='username', value=accountname)) + table.append(html.TR().append(td1).append(td2)) + td1 = html.TD() + td1.append(html.STRONG().append(html.Raw(_('Password')))) + td2 = html.TD() + td2.append(html.INPUT(type='password', name='password')) + table.append(html.TR().append(td1).append(td2)) + td1 = html.TD() + td2 = html.TD() + td2.append(html.INPUT(type='submit', name='submit', + value=_('Associate this name'))) + table.append(html.TR().append(td1).append(td2)) + + def _handle_verify_continuation(self, request): + _ = request.getText + oidconsumer = consumer.Consumer(request.session, + MoinOpenIDStore(request)) + query = {} + for key in request.form: + query[key] = request.form[key][0] + return_to = get_multistage_continuation_url(request, self.name, + {'oidstage': '1'}) + info = oidconsumer.complete(query, return_to=return_to) + if info.status == consumer.FAILURE: + return CancelLogin(_('OpenID error: %s.') % info.message) + elif info.status == consumer.CANCEL: + return CancelLogin(_('Verification canceled.')) + elif info.status == consumer.SUCCESS: + # try to find user object + uid = user.getUserIdByOpenId(request, info.identity_url) + if uid: + u = user.User(request, id=uid, auth_method=self.name, + auth_username=info.identity_url) + return ContinueLogin(u) + # if no user found, then we need to ask for a username, + # possibly associating an existing account. + request.session['openid.id'] = info.identity_url + return MultistageFormLogin(self._get_account_name) + else: + return CancelLogin(_('OpenID failure.')) + + def _handle_name_continuation(self, request): + if not 'openid.id' in request.session: + return CancelLogin(None) + + _ = request.getText + newname = request.form.get('username', [''])[0] + if not newname: + return MultistageFormLogin(self._get_account_name) + if not user.isValidName(request, newname): + return MultistageFormLogin(self._get_account_name, + _('This is not a valid username, choose a different one.')) + uid = None + if newname: + uid = user.getUserId(request, newname) + if not uid: + # we can create a new user with this name :) + u = user.User(request, id=uid, auth_method=self.name, + auth_username=request.session['openid.id']) + u.name = newname + u.openids = [request.session['openid.id']] + u.aliasname = request.session['openid.id'] + del request.session['openid.id'] + u.save() + return ContinueLogin(u) + # requested username already exists. if they know the password, + # they can associate that account with the openid. + assoc = lambda req, form: self._associate_account(req, form, newname) + return MultistageFormLogin(assoc) + + def _handle_associate_continuation(self, request): + if not 'openid.id' in request.session: + return CancelLogin() + + _ = request.getText + username = request.form.get('username', [''])[0] + password = request.form.get('password', [''])[0] + if not password: + return self._handle_name_continuation(request) + u = user.User(request, name=username, password=password, + auth_method=self.name, + auth_username=request.session['openid.id']) + if u.valid: + if not hasattr(u, 'openids'): + u.openids = [] + u.openids.append(request.session['openid.id']) + if not u.aliasname: + u.aliasname = request.session['openid.id'] + u.save() + del request.session['openid.id'] + return ContinueLogin(u, _('Your account is now associated to your OpenID.')) + else: + msg = _('The password you entered is not valid.') + assoc = lambda req, form: self._associate_account(req, form, username, msg=msg) + return MultistageFormLogin(assoc) + + def _handle_continuation(self, request): + oidstage = request.form.get('oidstage', [0])[0] + if oidstage == '1': + return self._handle_verify_continuation(request) + elif oidstage == '2': + return self._handle_name_continuation(request) + elif oidstage == '3': + return self._handle_associate_continuation(request) + return CancelLogin() + + def _openid_form(self, request, form, oidhtml): + _ = request.getText + txt = _('OpenID verification requires that you click this button:') + # create JS to automatically submit the form if possible + submitjs = """<script type="text/javascript"> +<!--// +document.getElementById("openid_message").submit(); +//--> +</script> +""" + return ''.join([txt, oidhtml, submitjs]) + + def login(self, request, user_obj, **kw): + continuation = kw.get('multistage') + + if continuation: + return self._handle_continuation(request) + + # openid is designed to work together with other auths + if user_obj and user_obj.valid: + return ContinueLogin(user_obj) + + openid_id = kw.get('openid_identifier') + # nothing entered? continue... + if not openid_id: + return ContinueLogin(user_obj) + + _ = request.getText + + # user entered something but the session can't be stored + if not request.session.is_stored: + return ContinueLogin(user_obj, + _('Anonymous sessions need to be enabled for OpenID login.')) + + oidconsumer = consumer.Consumer(request.session, + MoinOpenIDStore(request)) + + try: + oidreq = oidconsumer.begin(openid_id) + except HTTPFetchingError: + return ContinueLogin(None, _('Failed to resolve OpenID.')) + except DiscoveryFailure: + return ContinueLogin(None, _('OpenID discovery failure, not a valid OpenID.')) + else: + if oidreq is None: + return ContinueLogin(None, _('No OpenID.')) + + return_to = get_multistage_continuation_url(request, self.name, + {'oidstage': '1'}) + trust_root = request.getBaseURL() + if oidreq.shouldSendRedirect(): + redirect_url = oidreq.redirectURL(trust_root, return_to) + return MultistageRedirectLogin(redirect_url) + else: + form_html = oidreq.formMarkup(trust_root, return_to, + form_tag_attrs={'id': 'openid_message'}) + mcall = lambda request, form:\ + self._openid_form(request, form, form_html) + ret = MultistageFormLogin(mcall) + return ret
--- a/MoinMoin/request/__init__.py Tue Jul 10 19:09:14 2007 +0200 +++ b/MoinMoin/request/__init__.py Tue Jul 10 19:11:46 2007 +0200 @@ -603,16 +603,18 @@ def _handle_auth_form(self, user_obj): username = self.form.get('name', [None])[0] password = self.form.get('password', [None])[0] + oid = self.form.get('openid_identifier', [None])[0] login = 'login' in self.form logout = 'logout' in self.form stage = self.form.get('stage', [None])[0] return self.handle_auth(user_obj, attended=True, username=username, password=password, login=login, logout=logout, - stage=stage) + stage=stage, openid_identifier=oid) def handle_auth(self, user_obj, attended=False, **kw): username = kw.get('username') password = kw.get('password') + oid = kw.get('openid_identifier') login = kw.get('login') logout = kw.get('logout') stage = kw.get('stage') @@ -623,6 +625,7 @@ extra['attended'] = attended extra['username'] = username extra['password'] = password + extra['openid_identifier'] = oid if stage: extra['multistage'] = True login_msgs = []
--- a/MoinMoin/user.py Tue Jul 10 19:09:14 2007 +0200 +++ b/MoinMoin/user.py Tue Jul 10 19:11:46 2007 +0200 @@ -88,7 +88,11 @@ u = User(request, id=userid) if hasattr(u, key): value = getattr(u, key) - _key2id[value] = userid + if isinstance(value, list): + for val in value: + _key2id[val] = userid + else: + _key2id[value] = userid arena = 'user' cache = caching.CacheEntry(request, arena, cachekey, scope='wiki', use_pickle=True) try: @@ -109,6 +113,16 @@ return _getUserIdByKey(request, 'name', searchName) +def getUserIdByOpenId(request, openid): + """ Get the user ID for a specific OpenID. + + @param openid: the openid to look up + @rtype: string + @return: the corresponding user ID or None + """ + return _getUserIdByKey(request, 'openids', openid) + + def getUserIdentification(request, username=None): """ Return user name or IP or '<unknown>' indicator.
--- a/MoinMoin/userform/login.py Tue Jul 10 19:09:14 2007 +0200 +++ b/MoinMoin/userform/login.py Tue Jul 10 19:11:46 2007 +0200 @@ -70,6 +70,14 @@ ), ]) + if 'openid_identifier' in cfg.auth_login_inputs: + self.make_row(_('OpenID'), [ + html.INPUT( + type="text", size="32", name="openid_identifier", + id="openididentifier" + ), + ]) + self.make_row('', [ html.INPUT( type="submit", name='login', value=_('Login')
--- a/wiki/htdocs/classic/css/screen.css Tue Jul 10 19:09:14 2007 +0200 +++ b/wiki/htdocs/classic/css/screen.css Tue Jul 10 19:11:46 2007 +0200 @@ -395,3 +395,8 @@ font-weight: bold; } +#openididentifier { + background: url(../../common/openid.png) no-repeat; + background-position: 0 50%; + padding-left: 18px; +}
--- a/wiki/htdocs/modern/css/screen.css Tue Jul 10 19:09:14 2007 +0200 +++ b/wiki/htdocs/modern/css/screen.css Tue Jul 10 19:11:46 2007 +0200 @@ -457,4 +457,8 @@ margin: 2px; } - +#openididentifier { + background: url(../../common/openid.png) no-repeat; + background-position: 0 50%; + padding-left: 18px; +}
--- a/wiki/htdocs/rightsidebar/css/screen.css Tue Jul 10 19:09:14 2007 +0200 +++ b/wiki/htdocs/rightsidebar/css/screen.css Tue Jul 10 19:11:46 2007 +0200 @@ -347,3 +347,8 @@ font-weight: bold; } +#openididentifier { + background: url(../../common/openid.png) no-repeat; + background-position: 0 50%; + padding-left: 18px; +}