Mercurial > moin > 1.9
changeset 2025:d919b7b7b3e9
auth framework: login() methods return an object now
This avoids overloading the returned tuple and simplifies code since
they can now just instantiate ContinueLogin(), CancelLogin() etc.
author | Johannes Berg <johannes AT sipsolutions DOT net> |
---|---|
date | Tue, 24 Apr 2007 12:04:19 +0200 |
parents | b73858101d66 |
children | 5aa49f81db45 |
files | MoinMoin/auth/__init__.py MoinMoin/auth/interwiki.py MoinMoin/auth/ldap_login.py MoinMoin/auth/log.py MoinMoin/auth/mysql_group.py MoinMoin/auth/smb_mount.py MoinMoin/request/__init__.py |
diffstat | 7 files changed, 136 insertions(+), 52 deletions(-) [+] |
line wrap: on
line diff
--- a/MoinMoin/auth/__init__.py Mon Apr 23 23:28:03 2007 +0200 +++ b/MoinMoin/auth/__init__.py Tue Apr 24 12:04:19 2007 +0200 @@ -28,21 +28,30 @@ and 'continue' is a boolean to indicate whether the next authentication method should be tried. - The 'login' method must return a tuple - (user_obj, continue, multistage, message). + The 'login' method must return an instance of MoinMoin.auth.LoginReturn + which contains the members + * user_obj + * continue_flag + * multistage + * message + * redirect_to - The user_obj and continue values have the same semantics as for the request - and logout methods. + There are some helpful subclasses derived from this class for the most + common cases, namely ContinueLogin(), CancelLogin(), MultistageFormLogin() + and MultistageRedirectLogin(). + + The user_obj and continue_flag members have the same semantics as for the + request and logout methods. The messages that are returned by the various auth methods will be displayed to the user, since they will all be displayed usually auth methods will use the message feature only along with returning False for the continue flag. - The multistage item in the tuple must evaluate to false or be callable. - If it is callable, this indicates that the authentication method requires - a second login stage. In that case, the multistage item will be called - with the request as the only parameter. It should return an instance of + The multistage member must evaluate to false or be callable. If it is + callable, this indicates that the authentication method requires a second + login stage. In that case, the multistage item will be called with the + request as the only parameter. It should return an instance of MoinMoin.widget.html.FORM and the generic code will append some required hidden fields to it. It is also permissible to return some valid HTML, but that feature has very limited use since it breaks the authentication @@ -54,10 +63,18 @@ methods should take care to recheck everything and not assume the user has gone through all previous stages. - After the user has submitted the required form, execution of the auth - login methods resumes with the auth item that requested the multistage - login and its login method is called with the 'multistage' keyword - parameter set to True. + If the multistage login requires querying an external site that involves + a redirect, the redirect_to member may be set instead of the multistage + member. If this is set it must be a URL that user should be redirected to. + Since the user must be able to come back to the authentication, any + "%return" in the URL is replaced with the url-encoded form of the URL + to the next authentication stage, any "%return_form" is replaced with + the url-plus-encoded form (spaces encoded as +) of the same URL. + + After the user has submitted the required form or has been redirected back + from the external site, execution of the auth login methods resumes with + the auth item that requested the multistage login and its login method is + called with the 'multistage' keyword parameter set to True. Each authentication method instance must also contain the members * login_inputs: a list of required inputs, currently supported are @@ -93,7 +110,60 @@ @license: GNU GPL, see COPYING for details. """ -from MoinMoin import user +from MoinMoin import user, wikiutil + + +def get_multistage_continuation_url(request, auth_name, extra_fields={}): + """get_continuation_url - return a multistage continuation URL + + This function returns a URL that when loaded continues a multistage + authentication at the auth method requesting it (parameter auth_name.) + Additional fields are added to the URL from the extra_fields dict. + + @param request: the Moin request + @param auth_name: name of the auth method requesting the continuation + @param extra_fields: extra GET fields to add to the URL + """ + # logically, this belongs to request, but semantically it should + # live in auth so people do auth.get_multistage_continuation_url() + fields = {'action': 'login', + 'login': '1', + 'stage': auth_name} + fields.update(extra_fields) + qstr = wikiutil.makeQueryString(fields) + return ''.join([request.getBaseURL(), '?', qstr]) + + +class LoginReturn(object): + """ LoginReturn - base class for auth method login() return value""" + def __init__(self, user_obj, continue_flag, message=None, multistage=None, + redirect_to=None): + self.user_obj = user_obj + self.continue_flag = continue_flag + self.message = message + self.multistage = multistage + self.redirect_to = redirect_to + +class ContinueLogin(LoginReturn): + """ ContinueLogin - helper for auth method login that just continues """ + def __init__(self, user_obj, message=None): + LoginReturn.__init__(self, user_obj, True, message=message) + +class CancelLogin(LoginReturn): + """ CancelLogin - cancel login showing a message """ + def __init__(self, message): + LoginReturn.__init__(self, None, False, message=message) + +class MultistageFormLogin(LoginReturn): + """ MultistageFormLogin - require user to fill in another form """ + def __init__(self, multistage): + LoginReturn.__init__(self, None, False, multistage=multistage) + +class MultistageRedirectLogin(LoginReturn): + """ MultistageRedirectLogin - redirect user to another site before continuing login """ + def __init__(self, url): + LoginReturn.__init__(self, None, False, redirect_to=url) + class BaseAuth: name = None @@ -102,7 +172,7 @@ def __init__(self): pass def login(self, request, user_obj, **kw): - return user_obj, True, None, None + return ContinueLogin(user_obj) def request(self, request, user_obj, **kw): return user_obj, True def logout(self, request, user_obj, **kw): @@ -126,10 +196,10 @@ # simply continue if something else already logged in successfully if user_obj and user_obj.valid: - return user_obj, True, None, None + return ContinueLogin(user_obj) if not username and not password: - return user_obj, True, None, None + return ContinueLogin(user_obj) _ = request.getText @@ -138,12 +208,12 @@ if verbose: request.log("moin_login performing login action") if username and not password: - return user_obj, True, None, _('Missing password. Please enter user name and password.') + return ContinueLogin(user_obj, _('Missing password. Please enter user name and password.')) u = user.User(request, name=username, password=password, auth_method=self.name) if u.valid: if verbose: request.log("moin_login got valid user...") - return u, True, None, None + return ContinueLogin(u) else: if verbose: request.log("moin_login not valid, previous valid=%d." % user_obj.valid) - return user_obj, True, None, _("Invalid username or password.") + return ContinueLogin(user_obj, _("Invalid username or password."))
--- a/MoinMoin/auth/interwiki.py Mon Apr 23 23:28:03 2007 +0200 +++ b/MoinMoin/auth/interwiki.py Tue Apr 24 12:04:19 2007 +0200 @@ -11,7 +11,7 @@ import xmlrpclib from MoinMoin import auth, wikiutil, user -from MoinMoin.auth import BaseAuth +from MoinMoin.auth import BaseAuth, ContinueLogin, CancelLogin class InterwikiAuth(BaseAuth): name = 'interwiki' @@ -27,7 +27,7 @@ password = kw.get('password') if not username or not password: - return user_obj, True, None, None + return ContinueLogin(user_obj) if verbose: request.log("interwiki auth: trying to auth %r" % username) username = username.replace(' ', ':', 1) # Hack because ':' is not allowed in name field @@ -35,13 +35,13 @@ if verbose: request.log("interwiki auth: resolve wiki returned: %r %r %r %r" % (wikitag, wikiurl, name, err)) if err or wikitag not in self.trusted_wikis: - return user_obj, True, None, None + return ContinueLogin(user_obj) homewiki = xmlrpclib.Server(wikiurl + "?action=xmlrpc2") account_data = homewiki.getUser(name, password) if isinstance(account_data, str): if verbose: request.log("interwiki auth: %r wiki said: %s" % (wikitag, account_data)) - return user_obj, True, None, account_data + return ContinueLogin(None, account_data) # TODO: check remote auth_attribs u = user.User(request, name=name, auth_method=self.name, auth_attribs=('name', 'aliasname', 'password', 'email', )) @@ -51,4 +51,4 @@ u.valid = True u.create_or_update(True) if verbose: request.log("interwiki: successful auth for %r" % name) - return u, True, None, None + return ContinueLogin(u)
--- a/MoinMoin/auth/ldap_login.py Mon Apr 23 23:28:03 2007 +0200 +++ b/MoinMoin/auth/ldap_login.py Tue Apr 24 12:04:19 2007 +0200 @@ -16,7 +16,7 @@ import ldap from MoinMoin import user -from MoinMoin.auth import BaseAuth +from MoinMoin.auth import BaseAuth, CancelLogin, ContinueLogin class LDAPAuth(BaseAuth): """ get authentication data from form, authenticate against LDAP (or Active Directory), @@ -32,6 +32,7 @@ def login(self, request, user_obj, **kw): username = kw.get('username') password = kw.get('password') + _ = request.getText cfg = request.cfg verbose = cfg.ldap_verbose @@ -39,7 +40,7 @@ # we require non-empty password as ldap bind does a anon (not password # protected) bind if the password is empty and SUCCEEDS! if not password: - return user_obj, True, None, None + return ContinueLogin(user_obj, _('Missing password. Please enter user name and password.')) try: try: @@ -93,7 +94,7 @@ request.log("LDAP: Search found more than one (%d) matches for %s." % (result_length, filterstr)) if result_length == 0: if verbose: request.log("LDAP: Search found no matches for %s." % (filterstr, )) - return None, False, None # if ldap returns unusable results, we veto the user and don't let him in + return CancelLogin(_("Invalid username or password.")) dn, ldap_dict = lusers[0] if verbose: request.log("LDAP: DN found is %s, trying to bind with pw" % dn) @@ -124,16 +125,16 @@ except ldap.INVALID_CREDENTIALS, err: request.log("LDAP: invalid credentials (wrong password?) for dn %s (username: %s)" % (dn, username)) - return None, False, None, None # if ldap says no, we veto the user and don't let him in + return CancelLogin(_("Invalid username or password.")) if u: u.create_or_update(True) - return u, True, None, None + return ContinueLogin(u) except: import traceback info = sys.exc_info() request.log("LDAP: caught an exception, traceback follows...") request.log(''.join(traceback.format_exception(*info))) - return None, False, None, None # something went completely wrong, in doubt we veto the login + return CancelLogin(None)
--- a/MoinMoin/auth/log.py Mon Apr 23 23:28:03 2007 +0200 +++ b/MoinMoin/auth/log.py Tue Apr 24 12:04:19 2007 +0200 @@ -9,7 +9,7 @@ @license: GNU GPL, see COPYING for details. """ -from MoinMoin.auth import BaseAuth +from MoinMoin.auth import BaseAuth, ContinueLogin class AuthLog(BaseAuth): """ just log the call, do nothing else """ @@ -20,7 +20,7 @@ def login(self, request, user_obj, **kw): self.log(request, 'login', user_obj, kw) - return user_obj, True, None, None + return ContinueLogin(user_obj) def request(self, request, user_obj, **kw): self.log(request, 'session', user_obj, kw)
--- a/MoinMoin/auth/mysql_group.py Mon Apr 23 23:28:03 2007 +0200 +++ b/MoinMoin/auth/mysql_group.py Tue Apr 24 12:04:19 2007 +0200 @@ -8,7 +8,7 @@ """ import MySQLdb -from MoinMoin.auth import BaseAuth +from MoinMoin.auth import BaseAuth, CancelLogin, ContinueLogin class MysqlGroupAuth(BaseAuth): """ Authorize via MySQL group DB. @@ -37,7 +37,7 @@ # No other method succeeded, so we cannot authorize # but maybe some following auth methods can still "fix" that. if verbose: request.log("auth.mysql_group did not get valid user from previous auth method") - return user_obj, True, None, None + return ContinueLogin(user_obj) # Got a valid user object - we can do stuff! if verbose: @@ -56,7 +56,7 @@ info = sys.exc_info() request.log("auth.mysql_group: authorization failed due to exception connecting to DB, traceback follows...") request.log(''.join(traceback.format_exception(*info))) - return None, False, None, _('Failed to connect to database.') + return CancelLogin(_('Failed to connect to database.')) c = m.cursor() c.execute(self.mysql_group_query, user_obj.auth_username) @@ -64,13 +64,13 @@ if results: # Checked out OK if verbose: request.log("auth.mysql_group got %d results -- authorized!" % len(results)) - return user_obj, True, None, None # we make continuing possible, e.g. for smbmount + return ContinueLogin(user_obj) else: if verbose: request.log("auth.mysql_group did not get match from DB -- not authorized") - return None, False, None, None + return CancelLogin(_("Invalid username or password.")) # XXX do we really want this? could it be enough to check when they log in? # of course then when you change the DB people who are logged in can still do stuff... def request(self, request, user_obj, **kw): - u, cont, multi, msg = self.login(request, user_obj, **kw) - return u, cont + retval = self.login(request, user_obj, **kw) + return retval.user_obj, retval.continue_flag
--- a/MoinMoin/auth/smb_mount.py Mon Apr 23 23:28:03 2007 +0200 +++ b/MoinMoin/auth/smb_mount.py Tue Apr 24 12:04:19 2007 +0200 @@ -11,7 +11,7 @@ @license: GNU GPL, see COPYING for details. """ -from MoinMoin.auth import BaseAuth +from MoinMoin.auth import BaseAuth, CancelLogin, ContinueLogin class SMBMount(BaseAuth): """ auth plugin for (un)mounting an smb share """ @@ -75,7 +75,7 @@ password = kw.get('password') if user_obj and user_obj.valid: do_smb(request, username, password, True) - return user_obj, True, None, None + return ContinueLogin(user_obj) def logout(self, request, user_obj, **kw): if user_obj and not user_obj.valid:
--- a/MoinMoin/request/__init__.py Mon Apr 23 23:28:03 2007 +0200 +++ b/MoinMoin/request/__init__.py Tue Apr 24 12:04:19 2007 +0200 @@ -20,6 +20,8 @@ from MoinMoin import config, wikiutil, user, caching, error from MoinMoin.config import multiconfig from MoinMoin.util import IsWin9x +from MoinMoin import auth +from urllib import quote, quote_plus # umask setting -------------------------------------------------------- def set_umask(new_mask=0777^config.umask): @@ -139,6 +141,8 @@ self._finishers = [] + self._auth_redirected = False + # Decode values collected by sub classes self.path_info = self.decodePagename(self.path_info) @@ -617,26 +621,35 @@ del self.session['setuid'] return user_obj - for auth in self.cfg.auth: + for authmethod in self.cfg.auth: if logout: - user_obj, cont = auth.logout(self, user_obj, **extra) + user_obj, cont = authmethod.logout(self, user_obj, **extra) elif login: - if stage and auth.name != stage: + if stage and authmethod.name != stage: continue - user_obj, cont, multistage, msg = auth.login(self, - user_obj, - **extra) + ret = authmethod.login(self, user_obj, **extra) + user_obj = ret.user_obj + cont = ret.continue_flag if stage: stage = None del extra['multistage'] - if multistage: - self._login_multistage = multistage - self._login_multistage_name = auth.name + if ret.multistage: + self._login_multistage = ret.multistage + self._login_multistage_name = authmethod.name return user_obj + if ret.redirect_to: + nextstage = auth.get_multistage_continuation_url(self, authmethod.name) + url = ret.redirect_to + url = url.replace('%return_form', quote_plus(nextstage)) + url = url.replace('%return', quote(nextstage)) + self._auth_redirected = True + self.http_redirect(url) + return user_obj + msg = ret.message if msg and not msg in login_msgs: login_msgs.append(msg) else: - user_obj, cont = auth.request(self, user_obj, **extra) + user_obj, cont = authmethod.request(self, user_obj, **extra) if not cont: break @@ -1096,7 +1109,7 @@ def run(self): # Exit now if __init__ failed or request is forbidden - if self.failed or self.forbidden: + if self.failed or self.forbidden or self._auth_redirected: # Don't sleep() here, it binds too much of our resources! return self.finish()