Mercurial > moin > 1.9
view MoinMoin/wsgiapp.py @ 6121:1563d6db198c
security: fix XSS in GUI editor's attachment dialogue CVE-2016-7146
author | Thomas Waldmann <tw AT waldmann-edv DOT de> |
---|---|
date | Fri, 28 Oct 2016 21:33:38 +0200 |
parents | 236f7a9370c4 |
children |
line wrap: on
line source
# -*- coding: iso-8859-1 -*- """ MoinMoin - WSGI application @copyright: 2003-2008 MoinMoin:ThomasWaldmann, 2008-2008 MoinMoin:FlorianKrupicka @license: GNU GPL, see COPYING for details. """ import os from MoinMoin import log logging = log.getLogger(__name__) from MoinMoin.web.contexts import AllContext, Context, XMLRPCContext from MoinMoin.web.exceptions import HTTPException from MoinMoin.web.request import Request, MoinMoinFinish, HeaderSet from MoinMoin.web.utils import check_forbidden, check_surge_protect, fatal_response, \ redirect_last_visited from MoinMoin.Page import Page from MoinMoin import auth, config, i18n, user, wikiutil, xmlrpc, error from MoinMoin.action import get_names, get_available_actions from MoinMoin.util.abuse import log_attempt def set_umask(new_mask=0777^config.umask): """ Set the OS umask value (and ignore potential failures on OSes where this is not supported). Default: the bitwise inverted value of config.umask """ try: old_mask = os.umask(new_mask) except: # maybe we are on win32? pass def init(request): """ Wraps an incoming WSGI request in a Context object and initializes several important attributes. """ set_umask() # do it once per request because maybe some server # software sets own umask if isinstance(request, Context): context, request = request, request.request else: context = AllContext(request) context.clock.start('total') context.clock.start('init') context.lang = setup_i18n_preauth(context) context.session = context.cfg.session_service.get_session(context) context.user = setup_user(context, context.session) context.lang = setup_i18n_postauth(context) def finish(): pass context.finish = finish context.reset() context.clock.stop('init') return context def run(context): """ Run a context trough the application. """ context.clock.start('run') request = context.request # preliminary access checks (forbidden, bots, surge protection) try: try: check_forbidden(context) check_surge_protect(context) action_name = context.action # handle XMLRPC calls if action_name == 'xmlrpc': response = xmlrpc.xmlrpc(XMLRPCContext(request)) elif action_name == 'xmlrpc2': response = xmlrpc.xmlrpc2(XMLRPCContext(request)) else: response = dispatch(request, context, action_name) context.cfg.session_service.finalize(context, context.session) return response except MoinMoinFinish: return request finally: context.finish() context.clock.stop('run') def remove_prefix(path, prefix=None): """ Remove an url prefix from the path info and return shortened path. """ # we can have all action URLs like this: /action/ActionName/PageName?action=ActionName&... # this is just for robots.txt being able to forbid them for crawlers if prefix is not None: prefix = '/%s/' % prefix # e.g. '/action/' if path.startswith(prefix): # remove prefix and action name path = path[len(prefix):] action, path = (path.split('/', 1) + ['', ''])[:2] path = '/' + path return path def dispatch(request, context, action_name='show'): cfg = context.cfg # The last component in path_info is the page name, if any path = remove_prefix(request.path, cfg.url_prefix_action) if path.startswith('/'): pagename = wikiutil.normalize_pagename(path, cfg) else: pagename = None # need to inform caches that content changes based on: # * cookie (even if we aren't sending one now) # * User-Agent (because a bot might be denied and get no content) # * Accept-Language (except if moin is told to ignore browser language) hs = HeaderSet(('Cookie', 'User-Agent')) if not cfg.language_ignore_browser: hs.add('Accept-Language') request.headers['Vary'] = str(hs) # Handle request. We have these options: # 1. jump to page where user left off if not pagename and context.user.remember_last_visit and action_name == 'show': response = redirect_last_visited(context) # 2. handle action else: response = handle_action(context, pagename, action_name) if isinstance(response, Context): response = response.request return response def handle_action(context, pagename, action_name='show'): """ Actual dispatcher function for non-XMLRPC actions. Also sets up the Page object for this request, normalizes and redirects to canonical pagenames and checks for non-allowed actions. """ _ = context.getText cfg = context.cfg # pagename could be empty after normalization e.g. '///' -> '' # Use localized FrontPage if pagename is empty if not pagename: context.page = wikiutil.getFrontPage(context) else: context.page = Page(context, pagename) if '_' in pagename and not context.page.exists(): pagename = pagename.replace('_', ' ') page = Page(context, pagename) if page.exists(): url = page.url(context) return context.http_redirect(url) msg = None # Complain about unknown actions if not action_name in get_names(cfg): msg = _("Unknown action %(action_name)s.") % { 'action_name': wikiutil.escape(action_name), } # Disallow non available actions elif action_name[0].isupper() and not action_name in \ get_available_actions(cfg, context.page, context.user): msg = _("You are not allowed to do %(action_name)s on this page.") % { 'action_name': wikiutil.escape(action_name), } if context.user.valid: log_attempt(action_name + '/action unavailable', False, context.request, context.user.name, pagename=pagename) else: log_attempt(action_name + '/action unavailable', False, context.request, pagename=pagename) # Suggest non valid user to login msg += " " + _("Login and try again.") if msg: context.theme.add_msg(msg, "error") context.page.send_page() # Try action else: from MoinMoin import action handler = action.getHandler(context, action_name) if handler is None: msg = _("You are not allowed to do %(action_name)s on this page.") % { 'action_name': wikiutil.escape(action_name), } if context.user.valid: log_attempt(action_name + '/no handler', False, context.request, context.user.name, pagename=pagename) else: log_attempt(action_name + '/no handler', False, context.request, pagename=pagename) # Suggest non valid user to login msg += " " + _("Login and try again.") context.theme.add_msg(msg, "error") context.page.send_page() else: handler(context.page.page_name, context) return context def setup_user(context, session): """ Try to retrieve a valid user object from the request, be it either through the session or through a login. """ # first try setting up from session userobj = auth.setup_from_session(context, session) userobj, olduser = auth.setup_setuid(context, userobj) context._setuid_real_user = olduser # then handle login/logout forms form = context.request.values if 'login' in form: params = { 'username': form.get('name'), 'password': form.get('password'), 'attended': True, 'openid_identifier': form.get('openid_identifier'), 'stage': form.get('stage') } userobj = auth.handle_login(context, userobj, **params) elif 'logout' in form: userobj = auth.handle_logout(context, userobj) else: userobj = auth.handle_request(context, userobj) # if we still have no user obj, create a dummy: if not userobj: userobj = user.User(context, auth_method='invalid') return userobj def setup_i18n_preauth(context): """ Determine language for the request in absence of any user info. """ if i18n.languages is None: i18n.i18n_init(context) lang = i18n.requestLanguage(context) return lang def setup_i18n_postauth(context): """ Determine language for the request after user-id is established. """ lang = i18n.userLanguage(context) or context.lang return lang class Application(object): def __init__(self, app_config=None): class AppRequest(Request): given_config = app_config self.Request = AppRequest def __call__(self, environ, start_response): try: request = None request = self.Request(environ) context = init(request) try: response = run(context) finally: context.clock.stop('total') if context.cfg.log_timing: dt = context.clock.timings['total'] logging.info("timing: %s %s %s %3.3f %s" % ( request.remote_addr, request.url, request.referrer, dt, "!" * int(dt) or ".")) except HTTPException, e: response = e except error.ConfigurationError, e: # this is stuff the user should see on the web interface: response = fatal_response(e) except Exception, e: # we avoid raising more exceptions here to preserve the original exception url_info = request and ' [%s]' % request.url or '' # have exceptions logged within the moin logging framework: logging.exception("An exception has occurred%s." % url_info) # re-raise exception, so e.g. the debugger middleware gets it raise return response(environ, start_response) #XXX: default application using the default config from disk application = Application()