import Component from Component import ServletComponent, Component from WebUtils.Funcs import htmlEncode, urlEncode try: # backward compatibility for Python < 2.3 True, False except NameError: True, False = 1, 0 try: from paste.httpexceptions import * HTTPAuthenticationRequired = HTTPUnauthorized hasExceptions = True except ImportError: try: from WebKit.HTTPExceptions import * hasExceptions = True except ImportError: hasExceptions = False import base64 class UserServletComponent(ServletComponent): """ Handles user login, creation, etc. UserComponent should be used in your class definition. ``__init__`` takes a UserManager class (see the ``usermanager`` module), and optionally the name of the servlet that implements the login page. If httpLogin is true, then simple HTTP authentication is used instead of a login form. If `saveUsername` is true, then the username will be saved in a cookie on login, and will be filled in when the user comes back to the site. ``loginEnvironKey`` is a key that is looked in for the username (like ``'REMOTE_USER'``). It adds the method ``user()`` to the servlet, which returns the logged in user or ``None`` (i.e., no logged in user). ``logout()`` logs the user out, and ``simpleLoginForm()`` returns as a string a simple login form (that you might embed in a sidebar). To ask a visitor to login, raise ``HTTPAuthenticationError``, or if the servlet defines the method ``loginRequired()``, and it returns true, then the user will be required to login first. If ``userPermitted(user)`` is defined, then that will be called with the logged-in user, and ``HTTPForbidden`` will be raised if this method returns false. If ``permittedRoles()`` is defined, then a match between those roles and the user's roles is searched for, and if not found ``HTTPForbidden`` will again be raised. If redirectAfterLogin is true (the default) and you login via a form (i.e., not HTTP authentication), then it may try to redirect to the page the user tried to get to originally. You must add a key _actionLogin_REQUEST_METHOD to your form, with a value of 'GET', as you can't redirect to a POST resource (in that case, no redirection will be done). Any variables should be put in hidden fields, and (besides _actionLogin variables) they will be appended to the redirect URL. """ def __init__(self, userManager, loginServlet='/Login', httpLogin=False, realm="Secure Site", saveUsername=True, redirectAfterLogin=True, loginEnvironKey=None): self._userManager = userManager self._loginServlet = loginServlet self._httpLogin = httpLogin self._realm = realm self._saveUsername = saveUsername self._redirectAfterLogin = redirectAfterLogin self._loginEnvironKey = loginEnvironKey _servletMethods = ['user', 'setUser', 'simpleLoginForm', 'logout', 'userManager'] if hasExceptions: _handleExceptions = [HTTPAuthenticationRequired] else: _handleExceptions = [] def awakeEvent(self, trans): try: # If we already tried to authenticate, and this # is the login page, then we won't retry the # authentication if self.servlet().request()._loginForwarded: return True except AttributeError: pass req = self.servlet().request() if req.hasField('_actionLogout'): self.logout() if req.hasField('_actionLoginUsername_'): self.actionLogin() if (req.environ().get('HTTP_AUTHORIZATION') or req.environ().get('HTTP_CGI_AUTHORIZATION')): self.actionHTTPLogin() if self._loginEnvironKey: self.actionEnvironLogin() if self.optionalMethod('loginRequired', True): self.loginRequired() if not self.optionalMethod('userPermitted', True, self.user()): self.forbidden() roles = self.optionalMethod('permittedRoles', None) if roles is not None: if self.user() is None: self.actionLogin() if not self.roleAllowed(user, roles): self.forbidden() return True def forbidden(self): if hasExceptions: raise HTTPForbidden else: self.servlet().sendResponseAndEnd('403 Forbidden') def roleAllowed(self, role, allowedRoles): if role in allowedRoles: return True for subRole in role.roles(): if self.roleAllowed(subRole, allowedRoles): return True return False def actionLogin(self): req = self.servlet().request() field = req.field username = field('_actionLoginUsername_') password = field('_actionLoginPassword_') if self._userManager.loginCorrect(username, password, req): userID = self._userManager.userIDForUsername(username) self.setUser(userID, username) self.servlet().session().setValue('userID', userID) self.optionalMethod('message', None, 'Logged in as %s' % username) else: self.optionalMethod('message', None, 'Username or password incorrect') servlet = self.servlet() res = servlet.response() req = servlet.request() if (self._redirectAfterLogin and req.field('_actionLogin_REQUEST_METHOD', None) == 'GET'): url = servlet.linkToSelf() fields = [] for name, value in req.fields().items(): if (name.startswith('_actionLogin') or name.startswith('_actionLogout')): continue if isinstance(value, list): for sub in value: fields.append((name, sub)) else: fields.append((name, value)) if fields: url += '?' + '&'.join( ['%s=%s' % (urlEncode(name), urlEncode(value)) for name, value in fields]) res.setHeader('Content-type', 'text/html') res.setHeader('Status', '303 Found') res.setHeader('Location', url) res.setHeader('Content-type', 'text/html') res.write('