#!/usr/bin/env python import cherrypy import datetime import urllib from cherrypy.lib import httpauth from dm.common.constants import dmStatus from dm.common.constants import dmRole from dm.common.constants import dmHttpStatus from dm.common.exceptions.dmException import DmException from dm.common.exceptions.dmHttpError import DmHttpError from dm.common.exceptions.authorizationError import AuthorizationError from dm.common.exceptions.invalidSession import InvalidSession from dm.common.utility.loggingManager import LoggingManager from dm.common.service.dmController import DmController from dm.common.service.auth.authorizationPrincipalManager import AuthorizationPrincipalManager from dm.common.service.auth.singleSignOnManager import SingleSignOnManager class LoginController(DmController): """ Controller to provide login and logout actions. """ SESSION_USERNAME_KEY = '_cp_username' SESSION_USER_KEY = 'user' SESSION_ROLE_KEY = 'role' ORIGINAL_SESSION_ID_KEY = 'originalid' INVALID_SESSION_KEY = 'invalidSession' _cp_config = { 'tools.sessions.on' : True, 'tools.auth.on' : True } def __init__(self): DmController.__init__(self) def onLogin(self, username): """ Called on successful login. """ return def onLogout(self, username): """ Called on logout. """ return @classmethod def getLoginForm(cls, msg='Enter username and password:', username='', fromPage='/'): return """ <html> <body> <form method='post' action='/auth/login'> <input type='hidden' name='fromPage' value='%(fromPage)s' /> <h2>DM Service</h2> <p/> %(msg)s <p/> <table border='0'> <tr> <td>Username:</td><td><input type='text' name="username" value='%(username)s'/></td> </tr> <tr> <td>Password:</td><td><input type='password' name='password' /></td> </tr> <p/> <tr> <td></td> <td><input type='submit' value='Log In' /></td> </tr> </table> </form> </body> </html>""" % locals() @classmethod def parseBasicAuthorizationHeaders(cls): try: username = None password = None authorization = cherrypy.request.headers['authorization'] authorizationHeader = httpauth.parseAuthorization(authorization) if authorizationHeader['auth_scheme'] == 'basic': username = authorizationHeader['username'] password = authorizationHeader['password'] if username and password: return (username, password) else: raise AuthorizationError('Username and/or password not supplied.') except Exception, ex: errorMsg = 'Could not extract username/password from authorization header: %s' % ex raise AuthorizationError(errorMsg) @classmethod def checkCredentials(cls, username, password): """ Verifies credentials for username and password.""" logger = LoggingManager.getInstance().getLogger('LoginController:checkCredentials') logger.debug('Checking credential for User: %s' % (username)) #logger.debug('Checking credential for User: %s, Password: %s' % (username, password)) logger.debug('Session id: %s' % cherrypy.serving.session.id) principal = AuthorizationPrincipalManager.getInstance().getAuthenticatedAuthorizationPrincipal(username, password) #logger.debug('Principal: %s' % (principal)) if principal: cherrypy.session[LoginController.SESSION_ROLE_KEY] = principal.getRole() logger.debug('Successful login from user: %s (role: %s)' % (username, principal.getRole())) # Try adding to SingleSignOnManager sessionId = cherrypy.serving.session.id sessionCache = cherrypy.session.cache sessionInfo = {LoginController.SESSION_ROLE_KEY : principal.getRole()} sessionInfo[LoginController.SESSION_USER_KEY] = principal.getUserInfo() sessionInfo[LoginController.SESSION_USERNAME_KEY] = username ssoManager = SingleSignOnManager.getInstance() ssoManager.addSession(sessionId, sessionInfo) else: logger.debug('Login denied for user: %s' % username) username = cherrypy.session.get(LoginController.SESSION_USERNAME_KEY, None) if username is not None: cherrypy.request.login = None cherrypy.session[LoginController.INVALID_DM_SESSION_KEY] = True raise AuthorizationError('Incorrect username or password.') cherrypy.session[LoginController.SESSION_USER_KEY] = principal.getUserInfo() return principal @classmethod def authCheck(cls, *args, **kwargs): """ A tool that looks in config for 'auth.require'. If found and it is not None, a login is required and the entry is evaluated as a list of conditions that the user must fulfill. """ logger = LoggingManager.getInstance().getLogger('LoginController:authCheck') conditions = cherrypy.request.config.get('auth.require', None) #logger.debug('Headers: %s' % (cherrypy.request.headers)) #logger.debug('Request params: %s' % (cherrypy.request.params)) #logger.debug('Request query string: %s' % (cherrypy.request.query_string)) method = urllib.quote(cherrypy.request.request_line.split()[0]) params = urllib.quote(cherrypy.request.request_line.split()[1]) if conditions is None: logger.debug('No conditions imposed') return sessionId = cherrypy.serving.session.id sessionCache = cherrypy.session.cache # If session cache does not have current session id, reuse original # session id if not sessionCache.has_key(sessionId) and cherrypy.serving.session.__dict__.has_key(LoginController.ORIGINAL_SESSION_ID_KEY): logger.debug('Reusing original session id: %s' % sessionId) sessionId = cherrypy.serving.session.__dict__.get(LoginController.ORIGINAL_SESSION_ID_KEY) #logger.debug('Session: %s' % ((cherrypy.session.__dict__))) logger.debug('Session cache length: %s' % (len(sessionCache))) #logger.debug('Session cache: %s' % (sessionCache)) # Check session. # Try SingleSignOnManager first ssoManager = SingleSignOnManager.getInstance() # SSO Manager returns session info sessionInfo = ssoManager.checkSession(sessionId) if not sessionInfo: # Cache has tuple (sessionInfo, updateTime) sessionTuple = sessionCache.get(sessionId) if sessionTuple: sessionInfo = sessionTuple[0] logger.debug('Retrieved session info from cache: %s' % sessionInfo) else: logger.debug('Retrieved session %s from SSO Manager' % sessionId) sessionCache[sessionId] = (sessionInfo, datetime.datetime.now()) if not sessionInfo: errorMsg = 'Invalid or expired session id: %s.' % sessionId logger.debug(errorMsg) raise DmHttpError(dmHttpStatus.DM_HTTP_UNAUTHORIZED, 'User Not Authorized', InvalidSession(errorMsg)) username = sessionInfo.get(LoginController.SESSION_USERNAME_KEY) cherrypy.session[LoginController.SESSION_ROLE_KEY] = sessionInfo[LoginController.SESSION_ROLE_KEY] logger.debug('Session id %s is valid (username: %s)' % (sessionId, username)) if username: cherrypy.request.login = username for condition in conditions: # A condition is just a callable that returns true or false if not condition(): logger.debug('Authorization check %s() failed for username %s' % (condition.func_name, username)) errorMsg = 'Authorization check %s() failed for user %s.' % (condition.func_name, username) raise DmHttpError(dmHttpStatus.DM_HTTP_UNAUTHORIZED, 'User Not Authorized', AuthorizationError(errorMsg)) else: logger.debug('Username is not supplied') raise DmHttpError(dmHttpStatus.DM_HTTP_UNAUTHORIZED, 'User Not Authorized', ex) @cherrypy.expose def login(self, username=None, password=None, fromPage='/'): self.logger.debug('Attempting login from username %s' % (username)) try: if username is None or password is None: self.logger.debug('Parsing auth headers for username %s' % (username)) (username, password) = LoginController.parseBasicAuthorizationHeaders() self.logger.debug('Retrieving principal for username %s' % (username)) principal = LoginController.checkCredentials(username, password) except DmHttpError, ex: raise except DmException, ex: self.logger.debug('Authorization failed (username %s): %s' % (username, ex)) self.addDmExceptionHeaders(ex) raise DmHttpError(dmHttpStatus.DM_HTTP_UNAUTHORIZED, 'User Not Authorized', ex) # Authorization worked. cherrypy.session[LoginController.SESSION_USERNAME_KEY] = cherrypy.request.login = username self.onLogin(username) self.addDmSessionRoleHeaders(principal.getRole()) self.addDmResponseHeaders() @cherrypy.expose def logout(self, fromPage='/'): sess = cherrypy.session username = sess.get(LoginController.SESSION_USERNAME_KEY, None) if username: del sess[LoginController.SESSION_USERNAME_KEY] cherrypy.request.login = None self.onLogout(username)