def buildAW(ak, onlyPublic=False): aw = AccessWrapper() aw.setIP(str(request.remote_addr)) if ak and not onlyPublic: # If we have an authenticated request, require HTTPS # Dirty hack: Google calendar converts HTTP API requests from https to http # Therefore, not working with Indico setup (requiring https for HTTP API authenticated) if not request.is_secure and api_settings.get('require_https') and request.user_agent.browser != 'google': raise HTTPAPIError('HTTPS is required', 403) aw.setUser(ak.user.as_avatar) return aw
def buildAW(ak, req, onlyPublic=False): aw = AccessWrapper() aw.setIP(str(req.get_remote_ip())) if ak and not onlyPublic: # If we have an authenticated request, require HTTPS minfo = HelperMaKaCInfo.getMaKaCInfoInstance() # Dirty hack: Google calendar converts HTTP API requests from https to http # Therefore, not working with Indico setup (requiring https for HTTP API authenticated) if not request.is_secure and minfo.isAPIHTTPSRequired() and request.user_agent.browser != 'google': raise HTTPAPIError('HTTPS is required', 403) aw.setUser(ak.getUser()) return aw
def buildAW(ak, onlyPublic=False): aw = AccessWrapper() aw.setIP(str(request.remote_addr)) if ak and not onlyPublic: # If we have an authenticated request, require HTTPS # Dirty hack: Google calendar converts HTTP API requests from https to http # Therefore, not working with Indico setup (requiring https for HTTP API authenticated) if not request.is_secure and api_settings.get( 'require_https') and request.user_agent.browser != 'google': raise HTTPAPIError('HTTPS is required', 403) aw.setUser(ak.user.as_avatar) return aw
def buildAW(ak, onlyPublic=False): aw = AccessWrapper() aw.setIP(str(request.remote_addr)) if ak and not onlyPublic: # If we have an authenticated request, require HTTPS minfo = HelperMaKaCInfo.getMaKaCInfoInstance() # Dirty hack: Google calendar converts HTTP API requests from https to http # Therefore, not working with Indico setup (requiring https for HTTP API authenticated) if not request.is_secure and minfo.isAPIHTTPSRequired( ) and request.user_agent.browser != 'google': raise HTTPAPIError('HTTPS is required', 403) aw.setUser(ak.getUser()) return aw
class RH(RequestHandlerBase): """This class is the base for request handlers of the application. A request handler will be instantiated when a web request arrives to mod_python; the mp layer will forward the request to the corresponding request handler which will know which action has to be performed (displaying a web page or performing some operation and redirecting to another page). Request handlers will be responsible for parsing the parameters coming from a mod_python request, handle the errors which occurred during the action to perform, managing the sessions, checking security for each operation (thus they implement the access control system of the web interface). It is important to encapsulate all this here as in case of changing the web application framework we'll just need to adapt this layer (the rest of the system wouldn't need any change). Attributes: _uh - (URLHandler) Associated URLHandler which points to the current rh. _req - (mod_python.Request) mod_python request received for the current rh. _requestStarted - (bool) Flag which tells whether a DB transaction has been started or not. _websession - ( webinterface.session.sessionManagement.PSession ) Web session associated to the HTTP request. _aw - (AccessWrapper) Current access information for the rh. _target - (Locable) Reference to an object which is the destination of the operations needed to carry out the rh. If set it must provide (through the standard Locable interface) the methods to get the url parameters in order to reproduce the access to the rh. _reqParams - (dict) Dictionary containing the received HTTP parameters (independently of the method) transformed into python data types. The key is the parameter name while the value should be the received paramter value (or values). """ _tohttps = False # set this value to True for the RH that must be HTTPS when there is a BaseSecureURL _doNotSanitizeFields = [] def __init__( self, req ): """Constructor. Initialises the rh setting up basic attributes so it is able to process the request. Parameters: req - (mod_python.Request) mod_python request received for the current rh. """ RequestHandlerBase.__init__(self, req) self._requestStarted = False self._websession = None self._aw = AccessWrapper() #Fill in the aw instance with the current information self._target = None self._reqParams = {} self._startTime = None self._endTime = None self._tempFilesToDelete = [] self._doProcess = True #Flag which indicates whether the RH process # must be carried out; this is useful for # the checkProtection methods when they # detect that an inmediate redirection is # needed # Methods ============================================================= def getTarget( self ): return self._target def _setSession( self ): """Sets up a reference to the corresponding web session. It uses the session manager to retrieve the session corresponding to the received request and makes sure it is a valid one. In case of having an invalid session it reset client settings and creates a new one. """ if not self._websession: sm = session.getSessionManager() try: self._websession = sm.get_session( self._req ) except session.SessionError: sm.revoke_session_cookie( self._req ) self._websession = sm.get_session( self._req ) def _getSession( self ): """Returns the web session associated to the received mod_python request. """ if not self._websession: self._setSession() return self._websession def _setSessionUser( self ): """ """ self._aw.setUser( self._getSession().getUser() ) def _getRequestParams( self ): return self._reqParams def getRequestParams( self ): return self._getRequestParams() def _disableCaching(self): """ Disables caching, i.e. for materials """ # IE doesn't seem to like 'no-cache' Cache-Control headers... if (re.match(r'.*MSIE.*', self._req.headers_in.get("User-Agent",""))): # actually, the only way to safely disable caching seems to be this one self._req.headers_out["Cache-Control"] = "private" self._req.headers_out["Expires"] = "-1" else: self._req.headers_out["Cache-Control"] = "no-store, no-cache, must-revalidate" self._req.headers_out["Pragma"] = "no-cache" def _redirect( self, targetURL, noCache=False, status=apache.HTTP_SEE_OTHER ): """Utility for redirecting the client web browser to the specified URL. Params: newURL - Target URL of the redirection """ #check if there is no \r\n character to avoid http header injection if str(targetURL): if "\r" in str(targetURL) or "\n" in str(targetURL): raise MaKaCError(_("http header CRLF injection detected")) self._req.headers_out["Location"] = str(targetURL) if noCache: self._disableCaching() try: self._req.status = status except NameError: pass def _checkHttpsRedirect(self): """ If HTTPS must be used but it is not, redirect! """ if self.use_https() and not self._req.is_https(): self._redirect(self.getRequestURL(secure=True)) return True else: return False def _normaliseListParam( self, param ): if not isinstance(param, list): return [ param ] return param def _processError( self, ex ): """ """ raise def _checkParams( self, params ): """ """ pass def _process( self ): """ """ pass def _processGeneralError(self,e): """Treats general errors occured during the process of a RH. """ Logger.get('requestHandler').info('Request %s finished with: "%s"' % (id(self._req), e)) p=errors.WPGenericError(self) return p.display() def _processUnexpectedError(self,e): """Unexpected errors """ Logger.get('requestHandler').exception('Request %s failed: "%s"' % (id(self._req), e)) p=errors.WPUnexpectedError(self) return p.display() def _processHostnameResolveError(self,e): """Unexpected errors """ Logger.get('requestHandler').exception('Request %s failed: "%s"' % (id(self._req), e)) p=errors.WPHostnameResolveError(self) return p.display() def _processAccessError(self,e): """Treats access errors occured during the process of a RH. """ Logger.get('requestHandler').info('Request %s finished with AccessError: "%s"' % (id(self._req), e)) self._req.status = apache.HTTP_FORBIDDEN p=errors.WPAccessError(self) return p.display() def _processKeyAccessError(self,e): """Treats access errors occured during the process of a RH. """ Logger.get('requestHandler').info('Request %s finished with KeyAccessError: "%s"' % (id(self._req), e)) self._req.status = apache.HTTP_FORBIDDEN # We are going to redirect to the page asking for access key # and so it must be https if there is a BaseSecureURL. And that's # why we set _tohttps to True. self._tohttps = True if self._checkHttpsRedirect(): return p=errors.WPKeyAccessError(self) return p.display() def _processModificationError(self,e): """Treats modification errors occured during the process of a RH. """ Logger.get('requestHandler').info('Request %s finished with ModificationError: "%s"' % (id(self._req), e)) p=errors.WPModificationError(self) return p.display() def _processConferenceClosedError(self,e): """Treats access to modification pages for conferences when they are closed. """ p = WPConferenceModificationClosed( self, e._conf ) return p.display() def _processTimingError(self,e): """Treats timing errors occured during the process of a RH. """ Logger.get('requestHandler').info('Request %s finished with TimingError: "%s"' % (id(self._req), e)) p=errors.WPTimingError(self,e) return p.display() def _processNoReportError(self,e): """Process errors without reporting """ Logger.get('requestHandler').info('Request %s finished with NoReportError: "%s"' % (id(self._req), e)) p=errors.WPNoReportError(self,e) return p.display() def _processNotFoundError(self,e): """Process not found error; uses NoReportError template """ Logger.get('requestHandler').info('Request %s finished with NotFoundError: "%s"' % (id(self._req), e)) try: self._req.status = apache.HTTP_NOT_FOUND except NameError: pass p=errors.WPNoReportError(self,e) return p.display() def _processParentTimingError(self,e): """Treats timing errors occured during the process of a RH. """ Logger.get('requestHandler').info('Request %s finished with ParentTimingError: "%s"' % (id(self._req), e)) p=errors.WPParentTimingError(self,e) return p.display() def _processEntryTimingError(self,e): """Treats timing errors occured during the process of a RH. """ Logger.get('requestHandler').info('Request %s finished with EntryTimingError: "%s"' % (id(self._req), e)) p=errors.WPEntryTimingError(self,e) return p.display() def _processFormValuesError(self,e): """Treats user input related errors occured during the process of a RH. """ Logger.get('requestHandler').info('Request %s finished with FormValuesError: "%s"' % (id(self._req), e)) p=errors.WPFormValuesError(self,e) return p.display() def _processHtmlScriptError(self, e): Logger.get('requestHandler').info('Request %s finished with ProcessHtmlScriptError: "%s"' % (id(self._req), e)) p=errors.WPHtmlScriptError(self, escape(str(e))) return p.display() def _processRestrictedHTML(self, e): Logger.get('requestHandler').info('Request %s finished with ProcessRestrictedHTMLError: "%s"' % (id(self._req), e)) p=errors.WPRestrictedHTML(self, escape(str(e))) return p.display() def process( self, params ): """ """ profile = Config.getInstance().getProfile() proffilename = "" res = "" MAX_RETRIES = 10 retry = MAX_RETRIES textLog = [] self._startTime = datetime.now() # clear the context ContextManager.destroy() ContextManager.set('currentRH', self) #redirect to https if necessary if self._checkHttpsRedirect(): return DBMgr.getInstance().startRequest() self._startRequestSpecific2RH() # I.e. implemented by Room Booking request handlers textLog.append("%s : Database request started"%(datetime.now() - self._startTime)) Logger.get('requestHandler').info('[pid=%s] Request %s started (%s)' % (os.getpid(),id(self._req), self._req.unparsed_uri)) # notify components that the request has started self._notify('requestStarted', self._req) forcedConflicts = Config.getInstance().getForceConflicts() try: while retry>0: if retry < MAX_RETRIES: # notify components that the request is being retried self._notify('requestRetry', self._req, MAX_RETRIES - retry) try: Logger.get('requestHandler').info('\t[pid=%s] from host %s' % (os.getpid(), self.getHostIP())) try: # clear the fossile cache at the start of each request fossilize.clearCache() # delete all queued emails GenericMailer.flushQueue(False) DBMgr.getInstance().sync() # keep a link to the web session in the access wrapper # this is used for checking access/modification key existence # in the user session self._aw.setIP( self.getHostIP() ) self._aw.setSession(self._getSession()) #raise(str(dir(self._websession))) self._setSessionUser() self._setLang(params) if self._getAuth(): if self._getUser(): Logger.get('requestHandler').info('Request %s identified with user %s (%s)' % (id(self._req), self._getUser().getFullName(), self._getUser().getId())) if not self._tohttps and Config.getInstance().getAuthenticatedEnforceSecure(): self._tohttps = True if self._checkHttpsRedirect(): return #if self._getUser() != None and self._getUser().getId() == "893": # profile = True self._reqParams = copy.copy( params ) self._checkParams( self._reqParams ) self._checkProtection() security.Sanitization.sanitizationCheck(self._target, self._reqParams, self._aw, self._doNotSanitizeFields) if self._doProcess: if profile: import profile, pstats proffilename = os.path.join(Config.getInstance().getTempDir(), "stone%s.prof" % str(random.random())) result = [None] profile.runctx("result[0] = self._process()", globals(), locals(), proffilename) res = result[0] else: res = self._process() # Save web session, just when needed sm = session.getSessionManager() sm.maintain_session(self._req, self._websession) # notify components that the request has finished self._notify('requestFinished', self._req) # Raise a conflict error if enabled. This allows detecting conflict-related issues easily. if retry > (MAX_RETRIES - forcedConflicts): raise ConflictError self._endRequestSpecific2RH( True ) # I.e. implemented by Room Booking request handlers DBMgr.getInstance().endRequest( True ) Logger.get('requestHandler').info('Request %s successful' % (id(self._req))) #request succesfull, now, doing tas that must be done only once try: GenericMailer.flushQueue(True) # send emails self._deleteTempFiles() except: Logger.get('mail').exception('Mail sending operation failed') pass break except MaKaCError, e: #DBMgr.getInstance().endRequest(False) res = self._processError(e) except (ConflictError, POSKeyError): import traceback # only log conflict if it wasn't forced if retry <= (MAX_RETRIES - forcedConflicts): Logger.get('requestHandler').warning('Conflict in Database! (Request %s)\n%s' % (id(self._req), traceback.format_exc())) self._abortSpecific2RH() DBMgr.getInstance().abort() retry -= 1 continue except ClientDisconnected: Logger.get('requestHandler').warning('Client Disconnected! (Request %s)' % id(self._req) ) self._abortSpecific2RH() DBMgr.getInstance().abort() retry -= 1 time.sleep(10-retry) continue except KeyAccessError, e: #Key Access error treatment res = self._processKeyAccessError( e ) self._endRequestSpecific2RH( False ) DBMgr.getInstance().endRequest(False) except AccessError, e: #Access error treatment res = self._processAccessError( e ) self._endRequestSpecific2RH( False ) DBMgr.getInstance().endRequest(False)
class RH(RequestHandlerBase): """This class is the base for request handlers of the application. A request handler will be instantiated when a web request arrives to mod_python; the mp layer will forward the request to the corresponding request handler which will know which action has to be performed (displaying a web page or performing some operation and redirecting to another page). Request handlers will be responsible for parsing the parameters coming from a mod_python request, handle the errors which occurred during the action to perform, managing the sessions, checking security for each operation (thus they implement the access control system of the web interface). It is important to encapsulate all this here as in case of changing the web application framework we'll just need to adapt this layer (the rest of the system wouldn't need any change). Attributes: _uh - (URLHandler) Associated URLHandler which points to the current rh. _req - UNUSED/OBSOLETE, always None _requestStarted - (bool) Flag which tells whether a DB transaction has been started or not. _aw - (AccessWrapper) Current access information for the rh. _target - (Locable) Reference to an object which is the destination of the operations needed to carry out the rh. If set it must provide (through the standard Locable interface) the methods to get the url parameters in order to reproduce the access to the rh. _reqParams - (dict) Dictionary containing the received HTTP parameters (independently of the method) transformed into python data types. The key is the parameter name while the value should be the received paramter value (or values). """ _tohttps = False # set this value to True for the RH that must be HTTPS when there is a BaseSecureURL _doNotSanitizeFields = [] _isMobile = True # this value means that the generated web page can be mobile HTTP_VERBS = frozenset(('GET', 'POST', 'PUT', 'DELETE')) def __init__(self): self._responseUtil = ResponseUtil() self._requestStarted = False self._aw = AccessWrapper() # Fill in the aw instance with the current information self._target = None self._reqParams = {} self._startTime = None self._endTime = None self._tempFilesToDelete = [] self._redisPipeline = None self._doProcess = True # Flag which indicates whether the RH process # must be carried out; this is useful for # the checkProtection methods when they # detect that an immediate redirection is # needed # Methods ============================================================= def getTarget(self): return self._target def isMobile(self): return self._isMobile def _setSessionUser(self): self._aw.setUser(session.user) @property def csrf_token(self): return session.csrf_token def _getRequestParams(self): return self._reqParams def getRequestParams(self): return self._getRequestParams() def _disableCaching(self): """Disables caching""" # IE doesn't seem to like 'no-cache' Cache-Control headers... if request.user_agent.browser == 'msie': # actually, the only way to safely disable caching seems to be this one self._responseUtil.headers["Cache-Control"] = "private" self._responseUtil.headers["Expires"] = "-1" else: self._responseUtil.headers["Cache-Control"] = "no-store, no-cache, must-revalidate" self._responseUtil.headers["Pragma"] = "no-cache" def _redirect(self, targetURL, status=303): targetURL = str(targetURL) if "\r" in targetURL or "\n" in targetURL: raise MaKaCError(_("http header CRLF injection detected")) self._responseUtil.redirect = (targetURL, status) def _changeRH(self, rh, params): """Calls the specified RH after processing this one""" self._responseUtil.call = lambda: rh().process(params) def _checkHttpsRedirect(self): """If HTTPS must be used but it is not, redirect!""" if self.use_https() and not request.is_secure: self._redirect(self.getRequestURL(secure=True)) return True else: return False def _normaliseListParam(self, param): if not isinstance(param, list): return [param] return param def _processError(self, e): raise def _checkParams(self, params): """This method is called before _checkProtection and is a good place to assign variables from request params to member variables. Note that in any new code the params argument SHOULD be IGNORED. Use the following objects provided by Flask instead: from flask import request request.view_args (URL route params) request.args (GET params (from the query string)) request.form (POST params) request.values (GET+POST params - use only if ABSOLUTELY NECESSARY) If you only want to run some code for GET or POST requests, you can create a method named e.g. _checkParams_POST which will be executed AFTER this one. The method is called without any arguments (except self). """ pass def _process(self): """The default process method dispatches to a method containing the HTTP verb used for the current request, e.g. _process_POST. When implementing this please consider that you most likely want/need only GET and POST - the other verbs are not supported everywhere! """ method = getattr(self, '_process_' + request.method, None) if method is None: valid_methods = [m for m in self.HTTP_VERBS if hasattr(self, '_process_' + m)] raise MethodNotAllowed(valid_methods) return method() def _checkCSRF(self): # Check referer for POST requests. We do it here so we can properly use indico's error handling if Config.getInstance().getCSRFLevel() < 3 or request.method != 'POST': return referer = request.referrer # allow empty - otherwise we might lock out paranoid users blocking referers if not referer: return # valid http referer if referer.startswith(Config.getInstance().getBaseURL()): return # valid https referer - if https is enabled base_secure = Config.getInstance().getBaseSecureURL() if base_secure and referer.startswith(base_secure): return raise BadRefererError('This operation is not allowed from an external referer.') @jsonify_error def _processGeneralError(self, e): """Treats general errors occured during the process of a RH.""" if Config.getInstance().getPropagateAllExceptions(): raise return errors.WPGenericError(self).display() @jsonify_error(status=500, logging_level='exception') def _processUnexpectedError(self, e): """Unexpected errors""" self._responseUtil.redirect = None if Config.getInstance().getEmbeddedWebserver() or Config.getInstance().getPropagateAllExceptions(): raise return errors.WPUnexpectedError(self).display() @jsonify_error def _processHostnameResolveError(self, e): """Unexpected errors""" return errors.WPHostnameResolveError(self).display() @jsonify_error(status=403) def _processForbidden(self, e): message = _("Access Denied") if e.description == Forbidden.description: explanation = _("You are not allowed to access this page.") else: explanation = e.description return WErrorWSGI((message, explanation)).getHTML() @jsonify_error(status=403) def _processAccessError(self, e): """Treats access errors occured during the process of a RH.""" return errors.WPAccessError(self).display() @jsonify_error def _processKeyAccessError(self, e): """Treats access errors occured during the process of a RH.""" # We are going to redirect to the page asking for access key # and so it must be https if there is a BaseSecureURL. And that's # why we set _tohttps to True. self._tohttps = True if self._checkHttpsRedirect(): return return errors.WPKeyAccessError(self).display() @jsonify_error def _processModificationError(self, e): """Handles modification errors occured during the process of a RH.""" # Redirect to HTTPS in case the user is logged in self._tohttps = True if self._checkHttpsRedirect(): return return errors.WPModificationError(self).display() @jsonify_error(status=400) def _processBadRequestKeyError(self, e): """Request lacks a necessary key for processing""" msg = _('Required argument missing: %s') % e.message return errors.WPFormValuesError(self, msg).display() # TODO: check this method to integrate with jsonify error def _processOAuthError(self, e): res = json.dumps(e.fossilize()) header = oauth.build_authenticate_header(realm=Config.getInstance().getBaseSecureURL()) self._responseUtil.headers.extend(header) self._responseUtil.content_type = 'application/json' self._responseUtil.status = e.code return res @jsonify_error def _processConferenceClosedError(self, e): """Treats access to modification pages for conferences when they are closed.""" return WPConferenceModificationClosed(self, e._conf).display() @jsonify_error def _processTimingError(self, e): """Treats timing errors occured during the process of a RH.""" return errors.WPTimingError(self, e).display() @jsonify_error def _processNoReportError(self, e): """Process errors without reporting""" return errors.WPNoReportError(self, e).display() @jsonify_error(status=404) def _processNotFoundError(self, e): if isinstance(e, NotFound): message = _("Page not found") # that's a bit nicer than "404: Not Found" if e.description == NotFound.description: explanation = _("The page you are looking for doesn't exist.") else: explanation = e.description else: message = e.getMessage() explanation = e.getExplanation() return WErrorWSGI((message, explanation)).getHTML() @jsonify_error def _processParentTimingError(self, e): """Treats timing errors occured during the process of a RH.""" return errors.WPParentTimingError(self, e).display() @jsonify_error def _processEntryTimingError(self, e): """Treats timing errors occured during the process of a RH.""" return errors.WPEntryTimingError(self, e).display() @jsonify_error def _processFormValuesError(self, e): """Treats user input related errors occured during the process of a RH.""" return errors.WPFormValuesError(self, e).display() @jsonify_error def _processLaTeXError(self, e): """Treats access errors occured during the process of a RH.""" return errors.WPLaTeXError(self, e).display() @jsonify_error def _processRestrictedHTML(self, e): return errors.WPRestrictedHTML(self, escape(str(e))).display() @jsonify_error def _processHtmlScriptError(self, e): """ TODO """ return errors.WPHtmlScriptError(self, escape(str(e))).display() @jsonify_error def _processHtmlForbiddenTag(self, e): """ TODO """ return errors.WPRestrictedHTML(self, escape(str(e))).display() def _process_retry_setup(self): # clear the fossile cache at the start of each request fossilize.clearCache() # clear after-commit queue flush_after_commit_queue(False) # delete all queued emails GenericMailer.flushQueue(False) # clear the existing redis pipeline if self._redisPipeline: self._redisPipeline.reset() def _process_retry_auth_check(self, params): # keep a link to the web session in the access wrapper # this is used for checking access/modification key existence # in the user session self._aw.setIP(request.remote_addr) self._setSessionUser() if self._getAuth(): if self._getUser(): Logger.get('requestHandler').info('Request %s identified with user %s (%s)' % ( request, self._getUser().getFullName(), self._getUser().getId())) if not self._tohttps and Config.getInstance().getAuthenticatedEnforceSecure(): self._tohttps = True if self._checkHttpsRedirect(): return self._responseUtil.make_redirect() self._checkCSRF() self._reqParams = copy.copy(params) def _process_retry_do(self, profile): profile_name, res = '', '' try: # old code gets parameters from call # new code utilizes of flask.request if len(inspect.getargspec(self._checkParams).args) < 2: cp_result = self._checkParams() else: cp_result = self._checkParams(self._reqParams) if isinstance(cp_result, (current_app.response_class, Response)): return '', cp_result func = getattr(self, '_checkParams_' + request.method, None) if func: cp_result = func() if isinstance(cp_result, (current_app.response_class, Response)): return '', cp_result except NoResultFound: # sqlalchemy .one() not finding anything raise NotFoundError(_('The specified item could not be found.'), title=_('Item not found')) self._checkProtection() func = getattr(self, '_checkProtection_' + request.method, None) if func: func() security.Sanitization.sanitizationCheck(self._target, self._reqParams, self._aw, self._doNotSanitizeFields) if self._doProcess: if profile: profile_name = os.path.join(Config.getInstance().getTempDir(), 'stone{}.prof'.format(random.random())) result = [None] profiler.runctx('result[0] = self._process()', globals(), locals(), profile_name) res = result[0] else: res = self._process() return profile_name, res def _process_retry(self, params, retry, profile, forced_conflicts): self._process_retry_setup() self._process_retry_auth_check(params) DBMgr.getInstance().sync() return self._process_retry_do(profile) def _process_success(self): Logger.get('requestHandler').info('Request {} successful'.format(request)) # request is succesfull, now, doing tasks that must be done only once try: flush_after_commit_queue(True) GenericMailer.flushQueue(True) # send emails self._deleteTempFiles() except: Logger.get('mail').exception('Mail sending operation failed') # execute redis pipeline if we have one if self._redisPipeline: try: self._redisPipeline.execute() except RedisError: Logger.get('redis').exception('Could not execute pipeline') def process(self, params): if request.method not in self.HTTP_VERBS: # Just to be sure that we don't get some crappy http verb we don't expect raise BadRequest cfg = Config.getInstance() forced_conflicts, max_retries, profile = cfg.getForceConflicts(), cfg.getMaxRetries(), cfg.getProfile() profile_name, res, textLog = '', '', [] self._startTime = datetime.now() # clear the context ContextManager.destroy() ContextManager.set('currentRH', self) g.rh = self #redirect to https if necessary if self._checkHttpsRedirect(): return self._responseUtil.make_redirect() DBMgr.getInstance().startRequest() textLog.append("%s : Database request started" % (datetime.now() - self._startTime)) Logger.get('requestHandler').info('[pid=%s] Request %s started' % ( os.getpid(), request)) try: for i, retry in enumerate(transaction.attempts(max_retries)): with retry: if i > 0: signals.before_retry.send() try: Logger.get('requestHandler').info('\t[pid=%s] from host %s' % (os.getpid(), request.remote_addr)) profile_name, res = self._process_retry(params, i, profile, forced_conflicts) signals.after_process.send() if i < forced_conflicts: # raise conflict error if enabled to easily handle conflict error case raise ConflictError transaction.commit() DBMgr.getInstance().endRequest(commit=False) break except (ConflictError, POSKeyError): transaction.abort() import traceback # only log conflict if it wasn't forced if i >= forced_conflicts: Logger.get('requestHandler').warning('Conflict in Database! (Request %s)\n%s' % (request, traceback.format_exc())) except ClientDisconnected: transaction.abort() Logger.get('requestHandler').warning('Client Disconnected! (Request {})'.format(request)) time.sleep(i) self._process_success() except Exception as e: transaction.abort() res = self._getMethodByExceptionName(e)(e) totalTime = (datetime.now() - self._startTime) textLog.append('{} : Request ended'.format(totalTime)) # log request timing if profile and totalTime > timedelta(0, 1) and os.path.isfile(profile_name): rep = Config.getInstance().getTempDir() stats = pstats.Stats(profile_name) stats.strip_dirs() stats.sort_stats('cumulative', 'time', 'calls') stats.dump_stats(os.path.join(rep, 'IndicoRequestProfile.log')) output = StringIO.StringIO() sys.stdout = output stats.print_stats(100) sys.stdout = sys.__stdout__ s = output.getvalue() f = file(os.path.join(rep, 'IndicoRequest.log'), 'a+') f.write('--------------------------------\n') f.write('URL : {}\n'.format(request.url)) f.write('{} : start request\n'.format(self._startTime)) f.write('params:{}'.format(params)) f.write('\n'.join(textLog)) f.write('\n') f.write('retried : {}\n'.format(10-retry)) f.write(s) f.write('--------------------------------\n\n') f.close() if profile and profile_name and os.path.exists(profile_name): os.remove(profile_name) if self._responseUtil.call: return self._responseUtil.make_call() # In case of no process needed, we should return empty string to avoid erroneous output # specially with getVars breaking the JS files. if not self._doProcess or res is None: return self._responseUtil.make_empty() return self._responseUtil.make_response(res) def _getMethodByExceptionName(self, e): exception_name = { 'NotFound': 'NotFoundError', 'MaKaCError': 'GeneralError', 'IndicoError': 'GeneralError', 'ValueError': 'UnexpectedError', 'Exception': 'UnexpectedError', 'AccessControlError': 'AccessError' }.get(type(e).__name__, type(e).__name__) return getattr(self, '_process{}'.format(exception_name), self._processUnexpectedError) def _deleteTempFiles(self): if len(self._tempFilesToDelete) > 0: for f in self._tempFilesToDelete: if f is not None: os.remove(f) relativeURL = None
class RH(RequestHandlerBase): """This class is the base for request handlers of the application. A request handler will be instantiated when a web request arrives to mod_python; the mp layer will forward the request to the corresponding request handler which will know which action has to be performed (displaying a web page or performing some operation and redirecting to another page). Request handlers will be responsible for parsing the parameters coming from a mod_python request, handle the errors which occurred during the action to perform, managing the sessions, checking security for each operation (thus they implement the access control system of the web interface). It is important to encapsulate all this here as in case of changing the web application framework we'll just need to adapt this layer (the rest of the system wouldn't need any change). Attributes: _uh - (URLHandler) Associated URLHandler which points to the current rh. _req - UNUSED/OBSOLETE, always None _requestStarted - (bool) Flag which tells whether a DB transaction has been started or not. _aw - (AccessWrapper) Current access information for the rh. _target - (Locable) Reference to an object which is the destination of the operations needed to carry out the rh. If set it must provide (through the standard Locable interface) the methods to get the url parameters in order to reproduce the access to the rh. _reqParams - (dict) Dictionary containing the received HTTP parameters (independently of the method) transformed into python data types. The key is the parameter name while the value should be the received paramter value (or values). """ _tohttps = False # set this value to True for the RH that must be HTTPS when there is a BaseSecureURL _doNotSanitizeFields = [] _isMobile = True # this value means that the generated web page can be mobile HTTP_VERBS = frozenset(('GET', 'POST', 'PUT', 'DELETE')) def __init__(self): self._responseUtil = ResponseUtil() self._requestStarted = False self._aw = AccessWrapper( ) # Fill in the aw instance with the current information self._target = None self._reqParams = {} self._startTime = None self._endTime = None self._tempFilesToDelete = [] self._redisPipeline = None self._doProcess = True # Flag which indicates whether the RH process # must be carried out; this is useful for # the checkProtection methods when they # detect that an immediate redirection is # needed # Methods ============================================================= def getTarget(self): return self._target def isMobile(self): return self._isMobile def _setSessionUser(self): self._aw.setUser(session.avatar) @property def csrf_token(self): return session.csrf_token def _getRequestParams(self): return self._reqParams def getRequestParams(self): return self._getRequestParams() def _disableCaching(self): """Disables caching""" # IE doesn't seem to like 'no-cache' Cache-Control headers... if request.user_agent.browser == 'msie': # actually, the only way to safely disable caching seems to be this one self._responseUtil.headers["Cache-Control"] = "private" self._responseUtil.headers["Expires"] = "-1" else: self._responseUtil.headers[ "Cache-Control"] = "no-store, no-cache, must-revalidate" self._responseUtil.headers["Pragma"] = "no-cache" def _redirect(self, targetURL, status=303): targetURL = str(targetURL) if "\r" in targetURL or "\n" in targetURL: raise MaKaCError(_("http header CRLF injection detected")) self._responseUtil.redirect = (targetURL, status) def _changeRH(self, rh, params): """Calls the specified RH after processing this one""" self._responseUtil.call = lambda: rh().process(params) def _checkHttpsRedirect(self): """If HTTPS must be used but it is not, redirect!""" if self.use_https() and not request.is_secure: self._redirect(self.getRequestURL(secure=True)) return True else: return False def _normaliseListParam(self, param): if not isinstance(param, list): return [param] return param def _processError(self, e): raise def _checkParams(self, params): """This method is called before _checkProtection and is a good place to assign variables from request params to member variables. Note that in any new code the params argument SHOULD be IGNORED. Use the following objects provided by Flask instead: from flask import request request.view_args (URL route params) request.args (GET params (from the query string)) request.form (POST params) request.values (GET+POST params - use only if ABSOLUTELY NECESSARY) If you only want to run some code for GET or POST requests, you can create a method named e.g. _checkParams_POST which will be executed AFTER this one. The method is called without any arguments (except self). """ pass def _process(self): """The default process method dispatches to a method containing the HTTP verb used for the current request, e.g. _process_POST. When implementing this please consider that you most likely want/need only GET and POST - the other verbs are not supported everywhere! """ method = getattr(self, '_process_' + request.method, None) if method is None: valid_methods = [ m for m in self.HTTP_VERBS if hasattr(self, '_process_' + m) ] raise MethodNotAllowed(valid_methods) return method() def _checkCSRF(self): # Check referer for POST requests. We do it here so we can properly use indico's error handling if Config.getInstance().getCSRFLevel() < 3 or request.method != 'POST': return referer = request.referrer # allow empty - otherwise we might lock out paranoid users blocking referers if not referer: return # valid http referer if referer.startswith(Config.getInstance().getBaseURL()): return # valid https referer - if https is enabled base_secure = Config.getInstance().getBaseSecureURL() if base_secure and referer.startswith(base_secure): return raise BadRefererError( 'This operation is not allowed from an external referer.') @jsonify_error def _processGeneralError(self, e): """Treats general errors occured during the process of a RH.""" if Config.getInstance().getPropagateAllExceptions(): raise return errors.WPGenericError(self).display() @jsonify_error(status=500, logging_level='exception') def _processUnexpectedError(self, e): """Unexpected errors""" self._responseUtil.redirect = None if Config.getInstance().getEmbeddedWebserver() or Config.getInstance( ).getPropagateAllExceptions(): raise return errors.WPUnexpectedError(self).display() @jsonify_error def _processHostnameResolveError(self, e): """Unexpected errors""" return errors.WPHostnameResolveError(self).display() @jsonify_error(status=403) def _processForbidden(self, e): message = _("Access Denied") if e.description == Forbidden.description: explanation = _("You are not allowed to access this page.") else: explanation = e.description return WErrorWSGI((message, explanation)).getHTML() @jsonify_error(status=400) def _processBadRequest(self, e): message = _("Bad Request") return WErrorWSGI((message, e.description)).getHTML() @jsonify_error(status=400) def _processBadData(self, e): message = _("Invalid or expired token") return WErrorWSGI((message, e.message)).getHTML() @jsonify_error(status=403) def _processAccessError(self, e): """Treats access errors occured during the process of a RH.""" return errors.WPAccessError(self).display() @jsonify_error def _processKeyAccessError(self, e): """Treats access errors occured during the process of a RH.""" # We are going to redirect to the page asking for access key # and so it must be https if there is a BaseSecureURL. And that's # why we set _tohttps to True. self._tohttps = True if self._checkHttpsRedirect(): return return errors.WPKeyAccessError(self).display() @jsonify_error def _processModificationError(self, e): """Handles modification errors occured during the process of a RH.""" # Redirect to HTTPS in case the user is logged in self._tohttps = True if self._checkHttpsRedirect(): return return errors.WPModificationError(self).display() @jsonify_error(status=400) def _processBadRequestKeyError(self, e): """Request lacks a necessary key for processing""" msg = _('Required argument missing: %s') % e.message return errors.WPFormValuesError(self, msg).display() # TODO: check this method to integrate with jsonify error def _processOAuthError(self, e): res = json.dumps(e.fossilize()) header = oauth.build_authenticate_header( realm=Config.getInstance().getBaseSecureURL()) self._responseUtil.headers.extend(header) self._responseUtil.content_type = 'application/json' self._responseUtil.status = e.code return res @jsonify_error def _processConferenceClosedError(self, e): """Treats access to modification pages for conferences when they are closed.""" return WPConferenceModificationClosed(self, e._conf).display() @jsonify_error def _processTimingError(self, e): """Treats timing errors occured during the process of a RH.""" return errors.WPTimingError(self, e).display() @jsonify_error def _processNoReportError(self, e): """Process errors without reporting""" return errors.WPNoReportError(self, e).display() @jsonify_error(status=404) def _processNotFoundError(self, e): if isinstance(e, NotFound): message = _( "Page not found") # that's a bit nicer than "404: Not Found" if e.description == NotFound.description: explanation = _("The page you are looking for doesn't exist.") else: explanation = e.description else: message = e.getMessage() explanation = e.getExplanation() return WErrorWSGI((message, explanation)).getHTML() @jsonify_error def _processParentTimingError(self, e): """Treats timing errors occured during the process of a RH.""" return errors.WPParentTimingError(self, e).display() @jsonify_error def _processEntryTimingError(self, e): """Treats timing errors occured during the process of a RH.""" return errors.WPEntryTimingError(self, e).display() @jsonify_error def _processFormValuesError(self, e): """Treats user input related errors occured during the process of a RH.""" return errors.WPFormValuesError(self, e).display() @jsonify_error def _processLaTeXError(self, e): """Treats access errors occured during the process of a RH.""" return errors.WPLaTeXError(self, e).display() @jsonify_error def _processRestrictedHTML(self, e): return errors.WPRestrictedHTML(self, escape(str(e))).display() @jsonify_error def _processHtmlScriptError(self, e): """ TODO """ return errors.WPHtmlScriptError(self, escape(str(e))).display() @jsonify_error def _processHtmlForbiddenTag(self, e): """ TODO """ return errors.WPRestrictedHTML(self, escape(str(e))).display() def _process_retry_setup(self): # clear the fossile cache at the start of each request fossilize.clearCache() # clear after-commit queue flush_after_commit_queue(False) # delete all queued emails GenericMailer.flushQueue(False) # clear the existing redis pipeline if self._redisPipeline: self._redisPipeline.reset() def _process_retry_auth_check(self, params): # keep a link to the web session in the access wrapper # this is used for checking access/modification key existence # in the user session self._aw.setIP(request.remote_addr) self._setSessionUser() if self._getAuth(): if self._getUser(): Logger.get('requestHandler').info( 'Request %s identified with user %s (%s)' % (request, self._getUser().getFullName(), self._getUser().getId())) if not self._tohttps and Config.getInstance( ).getAuthenticatedEnforceSecure(): self._tohttps = True if self._checkHttpsRedirect(): return self._responseUtil.make_redirect() self._checkCSRF() self._reqParams = copy.copy(params) def _process_retry_do(self, profile): profile_name, res = '', '' try: # old code gets parameters from call # new code utilizes of flask.request if len(inspect.getargspec(self._checkParams).args) < 2: cp_result = self._checkParams() else: cp_result = self._checkParams(self._reqParams) if isinstance(cp_result, (current_app.response_class, Response)): return '', cp_result func = getattr(self, '_checkParams_' + request.method, None) if func: cp_result = func() if isinstance(cp_result, (current_app.response_class, Response)): return '', cp_result except NoResultFound: # sqlalchemy .one() not finding anything raise NotFoundError(_('The specified item could not be found.'), title=_('Item not found')) self._checkProtection() func = getattr(self, '_checkProtection_' + request.method, None) if func: func() security.Sanitization.sanitizationCheck(self._target, self._reqParams, self._aw, self._doNotSanitizeFields) if self._doProcess: if profile: profile_name = os.path.join( Config.getInstance().getTempDir(), 'stone{}.prof'.format(random.random())) result = [None] profiler.runctx('result[0] = self._process()', globals(), locals(), profile_name) res = result[0] else: res = self._process() return profile_name, res def _process_retry(self, params, retry, profile, forced_conflicts): self._process_retry_setup() self._process_retry_auth_check(params) DBMgr.getInstance().sync() return self._process_retry_do(profile) def _process_success(self): Logger.get('requestHandler').info( 'Request {} successful'.format(request)) # request is succesfull, now, doing tasks that must be done only once try: flush_after_commit_queue(True) GenericMailer.flushQueue(True) # send emails self._deleteTempFiles() except: Logger.get('mail').exception('Mail sending operation failed') # execute redis pipeline if we have one if self._redisPipeline: try: self._redisPipeline.execute() except RedisError: Logger.get('redis').exception('Could not execute pipeline') def process(self, params): if request.method not in self.HTTP_VERBS: # Just to be sure that we don't get some crappy http verb we don't expect raise BadRequest cfg = Config.getInstance() forced_conflicts, max_retries, profile = cfg.getForceConflicts( ), cfg.getMaxRetries(), cfg.getProfile() profile_name, res, textLog = '', '', [] self._startTime = datetime.now() # clear the context ContextManager.destroy() ContextManager.set('currentRH', self) g.rh = self #redirect to https if necessary if self._checkHttpsRedirect(): return self._responseUtil.make_redirect() DBMgr.getInstance().startRequest() textLog.append("%s : Database request started" % (datetime.now() - self._startTime)) Logger.get('requestHandler').info('[pid=%s] Request %s started' % (os.getpid(), request)) try: for i, retry in enumerate(transaction.attempts(max_retries)): with retry: if i > 0: signals.before_retry.send() try: Logger.get('requestHandler').info( '\t[pid=%s] from host %s' % (os.getpid(), request.remote_addr)) profile_name, res = self._process_retry( params, i, profile, forced_conflicts) signals.after_process.send() if i < forced_conflicts: # raise conflict error if enabled to easily handle conflict error case raise ConflictError transaction.commit() DBMgr.getInstance().endRequest(commit=False) break except (ConflictError, POSKeyError): transaction.abort() import traceback # only log conflict if it wasn't forced if i >= forced_conflicts: Logger.get('requestHandler').warning( 'Conflict in Database! (Request %s)\n%s' % (request, traceback.format_exc())) except ClientDisconnected: transaction.abort() Logger.get('requestHandler').warning( 'Client Disconnected! (Request {})'.format( request)) time.sleep(i) self._process_success() except Exception as e: transaction.abort() res = self._getMethodByExceptionName(e)(e) totalTime = (datetime.now() - self._startTime) textLog.append('{} : Request ended'.format(totalTime)) # log request timing if profile and totalTime > timedelta( 0, 1) and os.path.isfile(profile_name): rep = Config.getInstance().getTempDir() stats = pstats.Stats(profile_name) stats.strip_dirs() stats.sort_stats('cumulative', 'time', 'calls') stats.dump_stats(os.path.join(rep, 'IndicoRequestProfile.log')) output = StringIO.StringIO() sys.stdout = output stats.print_stats(100) sys.stdout = sys.__stdout__ s = output.getvalue() f = file(os.path.join(rep, 'IndicoRequest.log'), 'a+') f.write('--------------------------------\n') f.write('URL : {}\n'.format(request.url)) f.write('{} : start request\n'.format(self._startTime)) f.write('params:{}'.format(params)) f.write('\n'.join(textLog)) f.write('\n') f.write('retried : {}\n'.format(10 - retry)) f.write(s) f.write('--------------------------------\n\n') f.close() if profile and profile_name and os.path.exists(profile_name): os.remove(profile_name) if self._responseUtil.call: return self._responseUtil.make_call() # In case of no process needed, we should return empty string to avoid erroneous output # specially with getVars breaking the JS files. if not self._doProcess or res is None: return self._responseUtil.make_empty() return self._responseUtil.make_response(res) def _getMethodByExceptionName(self, e): exception_name = { 'NotFound': 'NotFoundError', 'MaKaCError': 'GeneralError', 'IndicoError': 'GeneralError', 'ValueError': 'UnexpectedError', 'Exception': 'UnexpectedError', 'AccessControlError': 'AccessError' }.get(type(e).__name__, type(e).__name__) if isinstance(e, BadData): # we also want its subclasses exception_name = 'BadData' return getattr(self, '_process{}'.format(exception_name), self._processUnexpectedError) def _deleteTempFiles(self): if len(self._tempFilesToDelete) > 0: for f in self._tempFilesToDelete: if f is not None: os.remove(f) relativeURL = None
class ServiceBase(RequestHandlerBase): """ The ServiceBase class is the basic class for services. """ UNICODE_PARAMS = False def __init__(self, params): if not self.UNICODE_PARAMS: params = unicode_struct_to_utf8(params) self._reqParams = self._params = params self._requestStarted = False # Fill in the aw instance with the current information self._aw = AccessWrapper() self._aw.setIP(request.remote_addr) self._aw.setUser(session.avatar) self._target = None self._startTime = None self._tohttps = request.is_secure self._endTime = None self._doProcess = True #Flag which indicates whether the RH process # must be carried out; this is useful for # the checkProtection methods self._tempFilesToDelete = [] self._redisPipeline = None # Methods ============================================================= def _checkParams(self): """ Checks the request parameters (normally overloaded) """ pass def _checkProtection(self): """ Checks protection when accessing resources (normally overloaded) """ pass def _processError(self): """ Treats errors occured during the process of a RH, returning an error string. @param e: the exception @type e: An Exception-derived type """ trace = traceback.format_exception(*sys.exc_info()) return ''.join(trace) def _deleteTempFiles(self): if len(self._tempFilesToDelete) > 0: for file in self._tempFilesToDelete: os.remove(file) def process(self): """ Processes the request, analyzing the parameters, and feeding them to the _getAnswer() method (implemented by derived classes) """ ContextManager.set('currentRH', self) self._checkParams() self._checkProtection() try: security.Sanitization.sanitizationCheck(self._target, self._params, self._aw) except HtmlForbiddenTag as e: raise HTMLSecurityError('ERR-X0', 'HTML Security problem. %s ' % str(e)) if self._doProcess: if Config.getInstance().getProfile(): import profile, pstats, random proffilename = os.path.join(Config.getInstance().getTempDir(), "service%s.prof" % random.random()) result = [None] profile.runctx("result[0] = self._getAnswer()", globals(), locals(), proffilename) answer = result[0] rep = Config.getInstance().getTempDir() stats = pstats.Stats(proffilename) stats.strip_dirs() stats.sort_stats('cumulative', 'time', 'calls') stats.dump_stats( os.path.join(rep, "IndicoServiceRequestProfile.log")) os.remove(proffilename) else: answer = self._getAnswer() self._deleteTempFiles() return answer def _getAnswer(self): """ To be overloaded. It should contain the code that does the actual business logic and returns a result (python JSON-serializable object). If this method is not overloaded, an exception will occur. If you don't want to return an answer, you should still implement this method with 'pass'. """ # This exception will happen if the _getAnswer method is not implemented in a derived class raise MaKaCError("No answer was returned")
class ServiceBase(RequestHandlerBase): """ The ServiceBase class is the basic class for services. """ def __init__(self, params, session, req): """ Constructor. Initializes provate variables @param req: HTTP Request provided by the previous layer """ RequestHandlerBase.__init__(self, req) self._reqParams = self._params = params self._requestStarted = False self._websession = session # Fill in the aw instance with the current information self._aw = AccessWrapper() self._aw.setIP(self.getHostIP()) self._aw.setSession(session) self._aw.setUser(session.getUser()) self._target = None self._startTime = None self._tohttps = self._req.is_https() self._endTime = None self._doProcess = True #Flag which indicates whether the RH process # must be carried out; this is useful for # the checkProtection methods self._tempFilesToDelete = [] # Methods ============================================================= def _getSession( self ): """ Returns the web session associated to the received mod_python request. """ return self._websession def _checkParams(self): """ Checks the request parameters (normally overloaded) """ pass def _checkProtection( self ): """ Checks protection when accessing resources (normally overloaded) """ pass def _processError(self): """ Treats errors occured during the process of a RH, returning an error string. @param e: the exception @type e: An Exception-derived type """ trace = traceback.format_exception(*sys.exc_info()) return ''.join(trace) def _deleteTempFiles( self ): if len(self._tempFilesToDelete) > 0: for file in self._tempFilesToDelete: os.remove(file) def process(self): """ Processes the request, analyzing the parameters, and feeding them to the _getAnswer() method (implemented by derived classes) """ ContextManager.set('currentRH', self) self._setLang() self._checkParams() self._checkProtection() try: security.Sanitization.sanitizationCheck(self._target, self._params, self._aw) except (HtmlScriptError, HtmlForbiddenTag), e: raise HTMLSecurityError('ERR-X0','HTML Security problem. %s ' % str(e)) if self._doProcess: if Config.getInstance().getProfile(): import profile, pstats, random proffilename = os.path.join(Config.getInstance().getTempDir(), "service%s.prof" % random.random()) result = [None] profile.runctx("result[0] = self._getAnswer()", globals(), locals(), proffilename) answer = result[0] rep = Config.getInstance().getTempDir() stats = pstats.Stats(proffilename) stats.strip_dirs() stats.sort_stats('cumulative', 'time', 'calls') stats.dump_stats(os.path.join(rep, "IndicoServiceRequestProfile.log")) os.remove(proffilename) else: answer = self._getAnswer() self._deleteTempFiles() return answer
class ServiceBase(RequestHandlerBase): """ The ServiceBase class is the basic class for services. """ def __init__(self, params, session, req): """ Constructor. Initializes provate variables @param req: HTTP Request provided by the previous layer """ RequestHandlerBase.__init__(self, req) self._reqParams = self._params = params self._requestStarted = False self._websession = session # Fill in the aw instance with the current information self._aw = AccessWrapper() self._aw.setIP(self.getHostIP()) self._aw.setSession(session) self._aw.setUser(session.getUser()) self._target = None self._startTime = None self._tohttps = self._req.is_https() self._endTime = None self._doProcess = True #Flag which indicates whether the RH process # must be carried out; this is useful for # the checkProtection methods self._tempFilesToDelete = [] # Methods ============================================================= def _getSession(self): """ Returns the web session associated to the received mod_python request. """ return self._websession def _checkParams(self): """ Checks the request parameters (normally overloaded) """ pass def _checkProtection(self): """ Checks protection when accessing resources (normally overloaded) """ pass def _processError(self): """ Treats errors occured during the process of a RH, returning an error string. @param e: the exception @type e: An Exception-derived type """ trace = traceback.format_exception(*sys.exc_info()) return ''.join(trace) def _deleteTempFiles(self): if len(self._tempFilesToDelete) > 0: for file in self._tempFilesToDelete: os.remove(file) def process(self): """ Processes the request, analyzing the parameters, and feeding them to the _getAnswer() method (implemented by derived classes) """ ContextManager.set('currentRH', self) self._setLang() self._checkParams() self._checkProtection() try: security.Sanitization.sanitizationCheck(self._target, self._params, self._aw) except (HtmlScriptError, HtmlForbiddenTag), e: raise HTMLSecurityError('ERR-X0', 'HTML Security problem. %s ' % str(e)) if self._doProcess: if Config.getInstance().getProfile(): import profile, pstats, random proffilename = os.path.join(Config.getInstance().getTempDir(), "service%s.prof" % random.random()) result = [None] profile.runctx("result[0] = self._getAnswer()", globals(), locals(), proffilename) answer = result[0] rep = Config.getInstance().getTempDir() stats = pstats.Stats(proffilename) stats.strip_dirs() stats.sort_stats('cumulative', 'time', 'calls') stats.dump_stats( os.path.join(rep, "IndicoServiceRequestProfile.log")) os.remove(proffilename) else: answer = self._getAnswer() self._deleteTempFiles() return answer
class ServiceBase(RequestHandlerBase): """ The ServiceBase class is the basic class for services. """ def __init__(self, params, remoteHost, session): """ Constructor. Initializes provate variables @param req: HTTP Request provided by the previous layer """ self._params = params self._requestStarted = False self._websession = session # Fill in the aw instance with the current information self._aw = AccessWrapper() self._aw.setIP(remoteHost) self._aw.setSession(session) self._aw.setUser(session.getUser()) self._target = None self._startTime = None self._endTime = None self._doProcess = True #Flag which indicates whether the RH process # must be carried out; this is useful for # the checkProtection methods self._tempFilesToDelete = [] # Methods ============================================================= def _getSession(self): """ Returns the web session associated to the received mod_python request. """ return self._websession def _checkParams(self): """ Checks the request parameters (normally overloaded) """ pass def _checkProtection(self): """ Checks protection when accessing resources (normally overloaded) """ pass def _processError(self): """ Treats errors occured during the process of a RH, returning an error string. @param e: the exception @type e: An Exception-derived type """ trace = traceback.format_exception(*sys.exc_info()) return ''.join(trace) def _sendEmails(self): if hasattr(self, "_emailsToBeSent"): for email in self._emailsToBeSent: GenericMailer.send(GenericNotification(email)) def _deleteTempFiles(self): if len(self._tempFilesToDelete) > 0: for file in self._tempFilesToDelete: os.remove(file) def process(self): """ Processes the request, analyzing the parameters, and feeding them to the _getAnswer() method (implemented by derived classes) """ self._setLang() self._checkParams() self._checkProtection() try: security.sanitizationCheck(self._target, self._params, self._aw) except (htmlScriptError, htmlForbiddenTag), e: raise HTMLSecurityError( 'ERR-X0', 'HTML Security problem - you might be using forbidden tags: %s ' % str(e)) if self._doProcess: answer = self._getAnswer() self._sendEmails() self._deleteTempFiles() return answer
class ServiceBase(RequestHandlerBase): """ The ServiceBase class is the basic class for services. """ UNICODE_PARAMS = False def __init__(self, params): RequestHandlerBase.__init__(self) if not self.UNICODE_PARAMS: params = unicode_struct_to_utf8(params) self._reqParams = self._params = params self._requestStarted = False # Fill in the aw instance with the current information self._aw = AccessWrapper() self._aw.setIP(request.remote_addr) self._aw.setUser(session.user) self._target = None self._startTime = None self._tohttps = request.is_secure self._endTime = None self._doProcess = True #Flag which indicates whether the RH process # must be carried out; this is useful for # the checkProtection methods self._tempFilesToDelete = [] self._redisPipeline = None # Methods ============================================================= def _checkParams(self): """ Checks the request parameters (normally overloaded) """ pass def _checkProtection( self ): """ Checks protection when accessing resources (normally overloaded) """ pass def _processError(self): """ Treats errors occured during the process of a RH, returning an error string. @param e: the exception @type e: An Exception-derived type """ trace = traceback.format_exception(*sys.exc_info()) return ''.join(trace) def _deleteTempFiles( self ): if len(self._tempFilesToDelete) > 0: for file in self._tempFilesToDelete: os.remove(file) def process(self): """ Processes the request, analyzing the parameters, and feeding them to the _getAnswer() method (implemented by derived classes) """ ContextManager.set('currentRH', self) self._setLang() self._checkParams() self._checkProtection() try: security.Sanitization.sanitizationCheck(self._target, self._params, self._aw) except HtmlForbiddenTag as e: raise HTMLSecurityError('ERR-X0','HTML Security problem. %s ' % str(e)) if self._doProcess: if Config.getInstance().getProfile(): import profile, pstats, random proffilename = os.path.join(Config.getInstance().getTempDir(), "service%s.prof" % random.random()) result = [None] profile.runctx("result[0] = self._getAnswer()", globals(), locals(), proffilename) answer = result[0] rep = Config.getInstance().getTempDir() stats = pstats.Stats(proffilename) stats.strip_dirs() stats.sort_stats('cumulative', 'time', 'calls') stats.dump_stats(os.path.join(rep, "IndicoServiceRequestProfile.log")) os.remove(proffilename) else: answer = self._getAnswer() self._deleteTempFiles() return answer def _getAnswer(self): """ To be overloaded. It should contain the code that does the actual business logic and returns a result (python JSON-serializable object). If this method is not overloaded, an exception will occur. If you don't want to return an answer, you should still implement this method with 'pass'. """ # This exception will happen if the _getAnswer method is not implemented in a derived class raise MaKaCError("No answer was returned")
class RH(RequestHandlerBase): """This class is the base for request handlers of the application. A request handler will be instantiated when a web request arrives to mod_python; the mp layer will forward the request to the corresponding request handler which will know which action has to be performed (displaying a web page or performing some operation and redirecting to another page). Request handlers will be responsible for parsing the parameters coming from a mod_python request, handle the errors which occurred during the action to perform, managing the sessions, checking security for each operation (thus they implement the access control system of the web interface). It is important to encapsulate all this here as in case of changing the web application framework we'll just need to adapt this layer (the rest of the system wouldn't need any change). Attributes: _uh - (URLHandler) Associated URLHandler which points to the current rh. _req - UNUSED/OBSOLETE, always None _requestStarted - (bool) Flag which tells whether a DB transaction has been started or not. _aw - (AccessWrapper) Current access information for the rh. _target - (Locable) Reference to an object which is the destination of the operations needed to carry out the rh. If set it must provide (through the standard Locable interface) the methods to get the url parameters in order to reproduce the access to the rh. _reqParams - (dict) Dictionary containing the received HTTP parameters (independently of the method) transformed into python data types. The key is the parameter name while the value should be the received paramter value (or values). """ _tohttps = False # set this value to True for the RH that must be HTTPS when there is a BaseSecureURL _doNotSanitizeFields = [] _isMobile = True # this value means that the generated web page can be mobile HTTP_VERBS = frozenset(('GET', 'POST', 'PUT', 'DELETE')) def __init__(self, req=None): """Constructor. Initialises the rh setting up basic attributes so it is able to process the request. Parameters: req - OBSOLETE, MUST BE NONE """ RequestHandlerBase.__init__(self, req) self._responseUtil = ResponseUtil() self._requestStarted = False self._aw = AccessWrapper() #Fill in the aw instance with the current information self._target = None self._reqParams = {} self._startTime = None self._endTime = None self._tempFilesToDelete = [] self._redisPipeline = None self._doProcess = True #Flag which indicates whether the RH process # must be carried out; this is useful for # the checkProtection methods when they # detect that an inmediate redirection is # needed # Methods ============================================================= def getTarget(self): return self._target def isMobile(self): return self._isMobile def _setSessionUser(self): self._aw.setUser(session.user) @property def csrf_token(self): return session.csrf_token def _getRequestParams(self): return self._reqParams def getRequestParams(self): return self._getRequestParams() def _disableCaching(self): """Disables caching""" # IE doesn't seem to like 'no-cache' Cache-Control headers... if request.user_agent.browser == 'msie': # actually, the only way to safely disable caching seems to be this one self._responseUtil.headers["Cache-Control"] = "private" self._responseUtil.headers["Expires"] = "-1" else: self._responseUtil.headers["Cache-Control"] = "no-store, no-cache, must-revalidate" self._responseUtil.headers["Pragma"] = "no-cache" def _redirect(self, targetURL, status=303): targetURL = str(targetURL) if "\r" in targetURL or "\n" in targetURL: raise MaKaCError(_("http header CRLF injection detected")) self._responseUtil.redirect = (targetURL, status) def _changeRH(self, rh, params): """Calls the specified RH after processing this one""" self._responseUtil.call = lambda: rh(None).process(params) def _checkHttpsRedirect(self): """If HTTPS must be used but it is not, redirect!""" if self.use_https() and not request.is_secure: self._redirect(self.getRequestURL(secure=True)) return True else: return False def _normaliseListParam(self, param): if not isinstance(param, list): return [param] return param def _processError(self, ex): raise def _checkParams(self, params): """This method is called before _checkProtection and is a good place to assign variables from request params to member variables. Note that in any new code the params argument SHOULD be IGNORED. Use the following objects provided by Flask instead: from flask import request request.view_args (URL route params) request.args (GET params (from the query string)) request.form (POST params) request.values (GET+POST params - use only if ABSOLUTELY NECESSARY) If you only want to run some code for GET or POST requests, you can create a method named e.g. _checkParams_POST which will be executed AFTER this one. The method is called without any arguments (except self). """ pass def _process(self): """The default process method dispatches to a method containing the HTTP verb used for the current request, e.g. _process_POST. When implementing this please consider that you most likely want/need only GET and POST - the other verbs are not supported everywhere! """ method = getattr(self, '_process_' + request.method, None) if method is None: valid_methods = [m for m in self.HTTP_VERBS if hasattr(self, '_process_' + m)] raise MethodNotAllowed(valid_methods) return method() def _checkCSRF(self): # Check referer for POST requests. We do it here so we can properly use indico's error handling if Config.getInstance().getCSRFLevel() < 3 or request.method != 'POST': return referer = request.referrer # allow empty - otherwise we might lock out paranoid users blocking referers if not referer: return # valid http referer if referer.startswith(Config.getInstance().getBaseURL()): return # valid https referer - if https is enabled base_secure = Config.getInstance().getBaseSecureURL() if base_secure and referer.startswith(base_secure): return raise BadRefererError('This operation is not allowed from an external referer.') def _processGeneralError(self, e): """Treats general errors occured during the process of a RH. """ Logger.get('requestHandler').info('Request %s finished with: "%s"' % (request, e)) p=errors.WPGenericError(self) return p.display() def _processUnexpectedError(self, e): """Unexpected errors""" Logger.get('requestHandler').exception('Request %s failed: "%s"' % (request, e)) p=errors.WPUnexpectedError(self) return p.display() def _processHostnameResolveError(self, e): """Unexpected errors""" Logger.get('requestHandler').exception('Request %s failed: "%s"' % (request, e)) p=errors.WPHostnameResolveError(self) return p.display() def _processAccessError(self, e): """Treats access errors occured during the process of a RH.""" Logger.get('requestHandler').info('Request %s finished with AccessError: "%s"' % (request, e)) self._responseUtil.status = 403 p=errors.WPAccessError(self) return p.display() def _processKeyAccessError(self, e): """Treats access errors occured during the process of a RH.""" Logger.get('requestHandler').info('Request %s finished with KeyAccessError: "%s"' % (request, e)) # We are going to redirect to the page asking for access key # and so it must be https if there is a BaseSecureURL. And that's # why we set _tohttps to True. self._tohttps = True if self._checkHttpsRedirect(): return p=errors.WPKeyAccessError(self) return p.display() def _processModificationError(self, e): """Treats modification errors occured during the process of a RH.""" Logger.get('requestHandler').info('Request %s finished with ModificationError: "%s"' % (request, e)) p=errors.WPModificationError(self) return p.display() def _processConferenceClosedError(self, e): """Treats access to modification pages for conferences when they are closed.""" p = WPConferenceModificationClosed( self, e._conf ) return p.display() def _processTimingError(self, e): """Treats timing errors occured during the process of a RH.""" Logger.get('requestHandler').info('Request %s finished with TimingError: "%s"' % (request, e)) p = errors.WPTimingError(self, e) return p.display() def _processNoReportError(self, e): """Process errors without reporting""" Logger.get('requestHandler').info('Request %s finished with NoReportError: "%s"' % (request, e)) p=errors.WPNoReportError(self,e) return p.display() def _processNotFoundError(self, e): """Process not found error; uses NoReportError template""" Logger.get('requestHandler').info('Request %s finished with NotFoundError: "%s"' % (request, e)) self._responseUtil.status = 404 p=errors.WPNoReportError(self,e) return p.display() def _processParentTimingError(self, e): """Treats timing errors occured during the process of a RH.""" Logger.get('requestHandler').info('Request %s finished with ParentTimingError: "%s"' % (request, e)) p=errors.WPParentTimingError(self,e) return p.display() def _processEntryTimingError(self, e): """Treats timing errors occured during the process of a RH.""" Logger.get('requestHandler').info('Request %s finished with EntryTimingError: "%s"' % (request, e)) p=errors.WPEntryTimingError(self,e) return p.display() def _processFormValuesError(self, e): """Treats user input related errors occured during the process of a RH.""" Logger.get('requestHandler').info('Request %s finished with FormValuesError: "%s"' % (request, e)) p=errors.WPFormValuesError(self,e) return p.display() def _processHtmlScriptError(self, e): Logger.get('requestHandler').info('Request %s finished with ProcessHtmlScriptError: "%s"' % (request, e)) p=errors.WPHtmlScriptError(self, escape(str(e))) return p.display() def _processRestrictedHTML(self, e): Logger.get('requestHandler').info('Request %s finished with ProcessRestrictedHTMLError: "%s"' % (request, e)) p=errors.WPRestrictedHTML(self, escape(str(e))) return p.display() def process(self, params): if request.method not in self.HTTP_VERBS: # Just to be sure that we don't get some crappy http verb we don't expect raise BadRequest profile = Config.getInstance().getProfile() proffilename = "" res = "" MAX_RETRIES = 10 retry = MAX_RETRIES textLog = [] self._startTime = datetime.now() # clear the context ContextManager.destroy() ContextManager.set('currentRH', self) #redirect to https if necessary if self._checkHttpsRedirect(): return self._responseUtil.make_redirect() DBMgr.getInstance().startRequest() self._startRequestSpecific2RH() # I.e. implemented by Room Booking request handlers textLog.append("%s : Database request started" % (datetime.now() - self._startTime)) Logger.get('requestHandler').info('[pid=%s] Request %s started' % ( os.getpid(), request)) # notify components that the request has started self._notify('requestStarted') forcedConflicts = Config.getInstance().getForceConflicts() try: while retry>0: if retry < MAX_RETRIES: # notify components that the request is being retried self._notify('requestRetry', MAX_RETRIES - retry) try: Logger.get('requestHandler').info('\t[pid=%s] from host %s' % (os.getpid(), request.remote_addr)) try: # clear the fossile cache at the start of each request fossilize.clearCache() # delete all queued emails GenericMailer.flushQueue(False) # clear the existing redis pipeline if self._redisPipeline: self._redisPipeline.reset() DBMgr.getInstance().sync() # keep a link to the web session in the access wrapper # this is used for checking access/modification key existence # in the user session self._aw.setIP(request.remote_addr) self._setSessionUser() self._setLang(params) if self._getAuth(): if self._getUser(): Logger.get('requestHandler').info('Request %s identified with user %s (%s)' % ( request, self._getUser().getFullName(), self._getUser().getId())) if not self._tohttps and Config.getInstance().getAuthenticatedEnforceSecure(): self._tohttps = True if self._checkHttpsRedirect(): return self._responseUtil.make_redirect() self._checkCSRF() self._reqParams = copy.copy(params) self._checkParams(self._reqParams) func = getattr(self, '_checkParams_' + request.method, None) if func: func() self._checkProtection() func = getattr(self, '_checkProtection_' + request.method, None) if func: func() security.Sanitization.sanitizationCheck(self._target, self._reqParams, self._aw, self._doNotSanitizeFields) if self._doProcess: if profile: import profile, pstats proffilename = os.path.join(Config.getInstance().getTempDir(), "stone%s.prof" % str(random.random())) result = [None] profile.runctx("result[0] = self._process()", globals(), locals(), proffilename) res = result[0] else: res = self._process() # notify components that the request has finished self._notify('requestFinished') # Raise a conflict error if enabled. This allows detecting conflict-related issues easily. if retry > (MAX_RETRIES - forcedConflicts): raise ConflictError self._endRequestSpecific2RH( True ) # I.e. implemented by Room Booking request handlers DBMgr.getInstance().endRequest( True ) Logger.get('requestHandler').info('Request %s successful' % request) #request succesfull, now, doing tas that must be done only once try: GenericMailer.flushQueue(True) # send emails self._deleteTempFiles() except: Logger.get('mail').exception('Mail sending operation failed') pass # execute redis pipeline if we have one if self._redisPipeline: try: self._redisPipeline.execute() except RedisError: Logger.get('redis').exception('Could not execute pipeline') break except MaKaCError, e: #DBMgr.getInstance().endRequest(False) res = self._processError(e) except (ConflictError, POSKeyError): import traceback # only log conflict if it wasn't forced if retry <= (MAX_RETRIES - forcedConflicts): Logger.get('requestHandler').warning('Conflict in Database! (Request %s)\n%s' % (request, traceback.format_exc())) self._abortSpecific2RH() DBMgr.getInstance().abort() retry -= 1 continue except ClientDisconnected: Logger.get('requestHandler').warning('Client Disconnected! (Request %s)' % request) self._abortSpecific2RH() DBMgr.getInstance().abort() retry -= 1 time.sleep(10-retry) continue except KeyAccessError, e: #Key Access error treatment res = self._processKeyAccessError( e ) self._endRequestSpecific2RH( False ) DBMgr.getInstance().endRequest(False) except AccessError, e: #Access error treatment res = self._processAccessError( e ) self._endRequestSpecific2RH( False ) DBMgr.getInstance().endRequest(False)
class RH(RequestHandlerBase): """This class is the base for request handlers of the application. A request handler will be instantiated when a web request arrives to mod_python; the mp layer will forward the request to the corresponding request handler which will know which action has to be performed (displaying a web page or performing some operation and redirecting to another page). Request handlers will be responsible for parsing the parameters coming from a mod_python request, handle the errors which occurred during the action to perform, managing the sessions, checking security for each operation (thus they implement the access control system of the web interface). It is important to encapsulate all this here as in case of changing the web application framework we'll just need to adapt this layer (the rest of the system wouldn't need any change). Attributes: _uh - (URLHandler) Associated URLHandler which points to the current rh. _req - (mod_python.Request) mod_python request received for the current rh. _requestStarted - (bool) Flag which tells whether a DB transaction has been started or not. _websession - ( webinterface.session.sessionManagement.PSession ) Web session associated to the HTTP request. _aw - (AccessWrapper) Current access information for the rh. _target - (Locable) Reference to an object which is the destination of the operations needed to carry out the rh. If set it must provide (through the standard Locable interface) the methods to get the url parameters in order to reproduce the access to the rh. _reqParams - (dict) Dictionary containing the received HTTP parameters (independently of the method) transformed into python data types. The key is the parameter name while the value should be the received paramter value (or values). """ _tohttps = False # set this value to True for the RH that must be HTTPS when there is a BaseSecureURL _doNotSanitizeFields = [] def __init__(self, req): """Constructor. Initialises the rh setting up basic attributes so it is able to process the request. Parameters: req - (mod_python.Request) mod_python request received for the current rh. """ RequestHandlerBase.__init__(self, req) self._requestStarted = False self._websession = None self._aw = AccessWrapper( ) #Fill in the aw instance with the current information self._target = None self._reqParams = {} self._startTime = None self._endTime = None self._tempFilesToDelete = [] self._doProcess = True #Flag which indicates whether the RH process # must be carried out; this is useful for # the checkProtection methods when they # detect that an inmediate redirection is # needed # Methods ============================================================= def getTarget(self): return self._target def _setSession(self): """Sets up a reference to the corresponding web session. It uses the session manager to retrieve the session corresponding to the received request and makes sure it is a valid one. In case of having an invalid session it reset client settings and creates a new one. """ if not self._websession: sm = session.getSessionManager() try: self._websession = sm.get_session(self._req) except session.SessionError: sm.revoke_session_cookie(self._req) self._websession = sm.get_session(self._req) def _getSession(self): """Returns the web session associated to the received mod_python request. """ if not self._websession: self._setSession() return self._websession def _setSessionUser(self): """ """ self._aw.setUser(self._getSession().getUser()) def _getRequestParams(self): return self._reqParams def getRequestParams(self): return self._getRequestParams() def _disableCaching(self): """ Disables caching, i.e. for materials """ # IE doesn't seem to like 'no-cache' Cache-Control headers... if (re.match(r'.*MSIE.*', self._req.headers_in.get("User-Agent", ""))): # actually, the only way to safely disable caching seems to be this one self._req.headers_out["Cache-Control"] = "private" self._req.headers_out["Expires"] = "-1" else: self._req.headers_out[ "Cache-Control"] = "no-store, no-cache, must-revalidate" self._req.headers_out["Pragma"] = "no-cache" def _redirect(self, targetURL, noCache=False, status=apache.HTTP_SEE_OTHER): """Utility for redirecting the client web browser to the specified URL. Params: newURL - Target URL of the redirection """ #check if there is no \r\n character to avoid http header injection if str(targetURL): if "\r" in str(targetURL) or "\n" in str(targetURL): raise MaKaCError(_("http header CRLF injection detected")) self._req.headers_out["Location"] = str(targetURL) if noCache: self._disableCaching() try: self._req.status = status except NameError: pass def _checkHttpsRedirect(self): """ If HTTPS must be used but it is not, redirect! """ if self.use_https() and not self._req.is_https(): self._redirect(self.getRequestURL(secure=True)) return True else: return False def _normaliseListParam(self, param): if not isinstance(param, list): return [param] return param def _processError(self, ex): """ """ raise def _checkParams(self, params): """ """ pass def _process(self): """ """ pass def _processGeneralError(self, e): """Treats general errors occured during the process of a RH. """ Logger.get('requestHandler').info('Request %s finished with: "%s"' % (id(self._req), e)) p = errors.WPGenericError(self) return p.display() def _processUnexpectedError(self, e): """Unexpected errors """ Logger.get('requestHandler').exception('Request %s failed: "%s"' % (id(self._req), e)) p = errors.WPUnexpectedError(self) return p.display() def _processHostnameResolveError(self, e): """Unexpected errors """ Logger.get('requestHandler').exception('Request %s failed: "%s"' % (id(self._req), e)) p = errors.WPHostnameResolveError(self) return p.display() def _processAccessError(self, e): """Treats access errors occured during the process of a RH. """ Logger.get('requestHandler').info( 'Request %s finished with AccessError: "%s"' % (id(self._req), e)) self._req.status = apache.HTTP_FORBIDDEN p = errors.WPAccessError(self) return p.display() def _processKeyAccessError(self, e): """Treats access errors occured during the process of a RH. """ Logger.get('requestHandler').info( 'Request %s finished with KeyAccessError: "%s"' % (id(self._req), e)) self._req.status = apache.HTTP_FORBIDDEN # We are going to redirect to the page asking for access key # and so it must be https if there is a BaseSecureURL. And that's # why we set _tohttps to True. self._tohttps = True if self._checkHttpsRedirect(): return p = errors.WPKeyAccessError(self) return p.display() def _processModificationError(self, e): """Treats modification errors occured during the process of a RH. """ Logger.get('requestHandler').info( 'Request %s finished with ModificationError: "%s"' % (id(self._req), e)) p = errors.WPModificationError(self) return p.display() def _processConferenceClosedError(self, e): """Treats access to modification pages for conferences when they are closed. """ p = WPConferenceModificationClosed(self, e._conf) return p.display() def _processTimingError(self, e): """Treats timing errors occured during the process of a RH. """ Logger.get('requestHandler').info( 'Request %s finished with TimingError: "%s"' % (id(self._req), e)) p = errors.WPTimingError(self, e) return p.display() def _processNoReportError(self, e): """Process errors without reporting """ Logger.get('requestHandler').info( 'Request %s finished with NoReportError: "%s"' % (id(self._req), e)) p = errors.WPNoReportError(self, e) return p.display() def _processNotFoundError(self, e): """Process not found error; uses NoReportError template """ Logger.get('requestHandler').info( 'Request %s finished with NotFoundError: "%s"' % (id(self._req), e)) try: self._req.status = apache.HTTP_NOT_FOUND except NameError: pass p = errors.WPNoReportError(self, e) return p.display() def _processParentTimingError(self, e): """Treats timing errors occured during the process of a RH. """ Logger.get('requestHandler').info( 'Request %s finished with ParentTimingError: "%s"' % (id(self._req), e)) p = errors.WPParentTimingError(self, e) return p.display() def _processEntryTimingError(self, e): """Treats timing errors occured during the process of a RH. """ Logger.get('requestHandler').info( 'Request %s finished with EntryTimingError: "%s"' % (id(self._req), e)) p = errors.WPEntryTimingError(self, e) return p.display() def _processFormValuesError(self, e): """Treats user input related errors occured during the process of a RH. """ Logger.get('requestHandler').info( 'Request %s finished with FormValuesError: "%s"' % (id(self._req), e)) p = errors.WPFormValuesError(self, e) return p.display() def _processHtmlScriptError(self, e): Logger.get('requestHandler').info( 'Request %s finished with ProcessHtmlScriptError: "%s"' % (id(self._req), e)) p = errors.WPHtmlScriptError(self, escape(str(e))) return p.display() def _processRestrictedHTML(self, e): Logger.get('requestHandler').info( 'Request %s finished with ProcessRestrictedHTMLError: "%s"' % (id(self._req), e)) p = errors.WPRestrictedHTML(self, escape(str(e))) return p.display() def process(self, params): """ """ profile = Config.getInstance().getProfile() proffilename = "" res = "" MAX_RETRIES = 10 retry = MAX_RETRIES textLog = [] self._startTime = datetime.now() # clear the context ContextManager.destroy() ContextManager.set('currentRH', self) #redirect to https if necessary if self._checkHttpsRedirect(): return DBMgr.getInstance().startRequest() self._startRequestSpecific2RH( ) # I.e. implemented by Room Booking request handlers textLog.append("%s : Database request started" % (datetime.now() - self._startTime)) Logger.get('requestHandler').info( '[pid=%s] Request %s started (%s)' % (os.getpid(), id(self._req), self._req.unparsed_uri)) # notify components that the request has started self._notify('requestStarted', self._req) forcedConflicts = Config.getInstance().getForceConflicts() try: while retry > 0: if retry < MAX_RETRIES: # notify components that the request is being retried self._notify('requestRetry', self._req, MAX_RETRIES - retry) try: Logger.get('requestHandler').info( '\t[pid=%s] from host %s' % (os.getpid(), self.getHostIP())) try: # clear the fossile cache at the start of each request fossilize.clearCache() # delete all queued emails GenericMailer.flushQueue(False) DBMgr.getInstance().sync() # keep a link to the web session in the access wrapper # this is used for checking access/modification key existence # in the user session self._aw.setIP(self.getHostIP()) self._aw.setSession(self._getSession()) #raise(str(dir(self._websession))) self._setSessionUser() self._setLang(params) if self._getAuth(): if self._getUser(): Logger.get('requestHandler').info( 'Request %s identified with user %s (%s)' % (id(self._req), self._getUser().getFullName(), self._getUser().getId())) if not self._tohttps and Config.getInstance( ).getAuthenticatedEnforceSecure(): self._tohttps = True if self._checkHttpsRedirect(): return #if self._getUser() != None and self._getUser().getId() == "893": # profile = True self._reqParams = copy.copy(params) self._checkParams(self._reqParams) self._checkProtection() security.Sanitization.sanitizationCheck( self._target, self._reqParams, self._aw, self._doNotSanitizeFields) if self._doProcess: if profile: import profile, pstats proffilename = os.path.join( Config.getInstance().getTempDir(), "stone%s.prof" % str(random.random())) result = [None] profile.runctx("result[0] = self._process()", globals(), locals(), proffilename) res = result[0] else: res = self._process() # Save web session, just when needed sm = session.getSessionManager() sm.maintain_session(self._req, self._websession) # notify components that the request has finished self._notify('requestFinished', self._req) # Raise a conflict error if enabled. This allows detecting conflict-related issues easily. if retry > (MAX_RETRIES - forcedConflicts): raise ConflictError self._endRequestSpecific2RH( True ) # I.e. implemented by Room Booking request handlers DBMgr.getInstance().endRequest(True) Logger.get('requestHandler').info( 'Request %s successful' % (id(self._req))) #request succesfull, now, doing tas that must be done only once try: GenericMailer.flushQueue(True) # send emails self._deleteTempFiles() except: Logger.get('mail').exception( 'Mail sending operation failed') pass break except MaKaCError, e: #DBMgr.getInstance().endRequest(False) res = self._processError(e) except (ConflictError, POSKeyError): import traceback # only log conflict if it wasn't forced if retry <= (MAX_RETRIES - forcedConflicts): Logger.get('requestHandler').warning( 'Conflict in Database! (Request %s)\n%s' % (id(self._req), traceback.format_exc())) self._abortSpecific2RH() DBMgr.getInstance().abort() retry -= 1 continue except ClientDisconnected: Logger.get('requestHandler').warning( 'Client Disconnected! (Request %s)' % id(self._req)) self._abortSpecific2RH() DBMgr.getInstance().abort() retry -= 1 time.sleep(10 - retry) continue except KeyAccessError, e: #Key Access error treatment res = self._processKeyAccessError(e) self._endRequestSpecific2RH(False) DBMgr.getInstance().endRequest(False) except AccessError, e: #Access error treatment res = self._processAccessError(e) self._endRequestSpecific2RH(False) DBMgr.getInstance().endRequest(False)
class ServiceBase(RequestHandlerBase): """ The ServiceBase class is the basic class for services. """ def __init__(self, params, remoteHost, session): """ Constructor. Initializes provate variables @param req: HTTP Request provided by the previous layer """ self._params = params self._requestStarted = False self._websession = session # Fill in the aw instance with the current information self._aw = AccessWrapper() self._aw.setIP(remoteHost) self._aw.setSession(session) self._aw.setUser(session.getUser()) self._target = None self._startTime = None self._endTime = None self._doProcess = True #Flag which indicates whether the RH process # must be carried out; this is useful for # the checkProtection methods self._tempFilesToDelete = [] # Methods ============================================================= def _getSession( self ): """ Returns the web session associated to the received mod_python request. """ return self._websession def _checkParams(self): """ Checks the request parameters (normally overloaded) """ pass def _checkProtection( self ): """ Checks protection when accessing resources (normally overloaded) """ pass def _processError(self): """ Treats errors occured during the process of a RH, returning an error string. @param e: the exception @type e: An Exception-derived type """ trace = traceback.format_exception(*sys.exc_info()) return ''.join(trace) def _sendEmails( self ): if hasattr( self, "_emailsToBeSent" ): for email in self._emailsToBeSent: GenericMailer.send(GenericNotification(email)) def _deleteTempFiles( self ): if len(self._tempFilesToDelete) > 0: for file in self._tempFilesToDelete: os.remove(file) def process(self): """ Processes the request, analyzing the parameters, and feeding them to the _getAnswer() method (implemented by derived classes) """ self._setLang() self._checkParams() self._checkProtection() try: security.sanitizationCheck(self._target, self._params, self._aw) except (htmlScriptError, htmlForbiddenTag), e: raise HTMLSecurityError('ERR-X0','HTML Security problem - you might be using forbidden tags: %s ' % str(e)) if self._doProcess: answer = self._getAnswer() self._sendEmails() self._deleteTempFiles() return answer