def _check(self): registered = self._registered_objects() if len(registered) > 0 and \ not IDisableCSRFProtection.providedBy(self.request): # Okay, we're writing here, we need to protect! try: check(self.request) return True except ComponentLookupError: # okay, it's possible we're at the zope root and the KeyManager # hasn't been installed yet. Let's check and carry on # if this is the case if IApplication.providedBy(self.getContext()): LOGGER.info('skipping csrf protection on zope root until ' 'keymanager gets installed') return True raise except Forbidden: if self.request.REQUEST_METHOD != 'GET': # only try to be "smart" with GET requests raise # XXX # okay, so right now, we're going to check if the current # registered objects to write, are just portlet assignments. # I don't know why, but when a site is created, these # cause some writes on read. ALL, registered objects # need to be portlet assignments. XXX needs to be fixed # somehow... safe = True for obj in registered: if (not IPortletAssignment.providedBy(obj) and not getattr(obj, '_v_safe_write', False)): safe = False break if not safe: LOGGER.info('aborting transaction due to no CSRF ' 'protection on url %s' % self.request.URL) transaction.abort() # conditions for doing the confirm form are: # 1. 301, 302 response code # 2. text/html response # 3. getSite could be none, zope root maybe, carry on # otherwise, # just abort with a log entry because we tried to # write on read, without a POST request and we don't # know what to do with it gracefully. resp = self.request.response ct = resp.headers.get('content-type') if self.site and ( resp.status in (301, 302) or 'text/html' in ct): data = self.request.form.copy() data['original_url'] = self.request.URL resp.redirect('%s/@@confirm-action?%s' % ( self.site.absolute_url(), urlencode(data) )) return False return True
def _check(self): registered = self._registered_objects() if len(registered) > 0 and \ not IDisableCSRFProtection.providedBy(self.request): # Okay, we're writing here, we need to protect! try: check(self.request) return True except ComponentLookupError: # okay, it's possible we're at the zope root and the KeyManager # hasn't been installed yet. Let's check and carry on # if this is the case if IApplication.providedBy(self.getContext()): LOGGER.info('skipping csrf protection on zope root until ' 'keymanager gets installed') return True raise except Forbidden: if self.request.REQUEST_METHOD != 'GET': # only try to be "smart" with GET requests raise # XXX # okay, so right now, we're going to check if the current # registered objects to write, are just portlet assignments. # I don't know why, but when a site is created, these # cause some writes on read. ALL, registered objects # need to be portlet assignments. XXX needs to be fixed # somehow... safe = True for obj in registered: if (not IPortletAssignment.providedBy(obj) and not getattr(obj, '_v_safe_write', False)): safe = False break if not safe: LOGGER.info('aborting transaction due to no CSRF ' 'protection on url %s' % self.request.URL) transaction.abort() # conditions for doing the confirm form are: # 1. 301, 302 response code # 2. text/html response # 3. getSite could be none, zope root maybe, carry on # otherwise, # just abort with a log entry because we tried to # write on read, without a POST request and we don't # know what to do with it gracefully. resp = self.request.response ct = resp.headers.get('content-type') if self.site and (resp.status in (301, 302) or 'text/html' in ct): data = self.request.form.copy() data['original_url'] = self.request.URL resp.redirect( '%s/@@confirm-action?%s' % (self.site.absolute_url(), urlencode(data))) return False return True
def _check(self): registered = self._registered_objects() if len(registered) > 0 and \ not IDisableCSRFProtection.providedBy(self.request): # Okay, we're writing here, we need to protect! try: check(self.request, manager=self.key_manager) return True except ComponentLookupError: LOGGER.info('Can not find key manager for CSRF protection. ' 'This should not happen.') raise except Forbidden: # XXX # okay, so right now, we're going to check if the current # registered objects to write, are just portlet assignments. # I don't know why, but when a site is created, these # cause some writes on read. ALL, registered objects # need to be portlet assignments. XXX needs to be fixed # somehow... safe_oids = [] if SAFE_WRITE_KEY in getattr(self.request, 'environ', {}): safe_oids = self.request.environ[SAFE_WRITE_KEY] safe = True for obj in registered: if (not IPortletAssignment.providedBy(obj) and getattr(obj, '_p_oid', False) not in safe_oids): safe = False break if not safe: if self.request.REQUEST_METHOD != 'GET': # only try to be "smart" with GET requests raise LOGGER.info('%s\naborting transaction due to no CSRF ' 'protection on url %s'%(traceback.print_stack(), self.request.URL)) transaction.abort() # conditions for doing the confirm form are: # 1. 301, 302 response code # 2. text/html response # 3. getSite could be none, zope root maybe, carry on # otherwise, # just abort with a log entry because we tried to # write on read, without a POST request and we don't # know what to do with it gracefully. resp = self.request.response ct = resp.getHeader('Content-Type', '') or '' if self.site and ( resp.status in (301, 302) or 'text/html' in ct): data = self.request.form.copy() data['original_url'] = self.request.URL resp.redirect('%s/@@confirm-action?%s' % ( self.site.absolute_url(), urlencode(data) )) return False return True
def _abort_txn_on_confirm_action_view(self): if self._get_current_view() == '@@confirm-action': if len(self._registered_objects()) > 0 and \ not IDisableCSRFProtection.providedBy(self.request): transaction.abort() LOG.error( "Error checking for CSRF. Transaction was modified when " "visiting @@confirm-action view. Transaction aborted!)") return True
def _check(self): registered = self._registered_objects() if len(registered) > 0 and \ not IDisableCSRFProtection.providedBy(self.request): # Okay, we're writing here, we need to protect! try: check(self.request, manager=self.key_manager) return True except ComponentLookupError: LOGGER.info('Can not find key manager for CSRF protection. ' 'This should not happen.') raise except Forbidden: # XXX # okay, so right now, we're going to check if the current # registered objects to write, are just portlet assignments. # I don't know why, but when a site is created, these # cause some writes on read. ALL, registered objects # need to be portlet assignments. XXX needs to be fixed # somehow... safe_oids = [] if SAFE_WRITE_KEY in getattr(self.request, 'environ', {}): safe_oids = self.request.environ[SAFE_WRITE_KEY] safe = True for obj in registered: if (not IPortletAssignment.providedBy(obj) and getattr( obj, '_p_oid', False) not in safe_oids): safe = False break if not safe: if self.request.REQUEST_METHOD != 'GET': # only try to be "smart" with GET requests raise LOGGER.info('aborting transaction due to no CSRF ' 'protection on url %s' % self.request.URL) transaction.abort() # conditions for doing the confirm form are: # 1. 301, 302 response code # 2. text/html response # 3. getSite could be none, zope root maybe, carry on # otherwise, # just abort with a log entry because we tried to # write on read, without a POST request and we don't # know what to do with it gracefully. resp = self.request.response ct = resp.getHeader('Content-Type', '') or '' if self.site and (resp.status in (301, 302) or 'text/html' in ct): data = self.request.form.copy() data['original_url'] = self.request.URL resp.redirect( '%s/@@confirm-action?%s' % (self.site.absolute_url(), urlencode(data))) return False return True
def test_after_successfully_credentials_authentication_the_request_provides_the_disable_csrf_protection_layer(self): ip = '192.168.1.233' create(Builder('admin_unit') .id('client1') .having(title=u'Client1', ip_address=ip)) self.set_params_for_remote_request(client_ip=ip) creds = extract_user(self.portal, self.portal.REQUEST) authenticate_credentials(self.portal, creds) self.assertTrue(IDisableCSRFProtection.providedBy(self.portal.REQUEST))
def test_after_successfully_credentials_authentication_the_request_provides_the_disable_csrf_protection_layer( self): ip = '192.168.1.233' create( Builder('admin_unit').id('client1').having(title=u'Client1', ip_address=ip)) self.set_params_for_remote_request(client_ip=ip) creds = extract_user(self.portal, self.portal.REQUEST) authenticate_credentials(self.portal, creds) self.assertTrue(IDisableCSRFProtection.providedBy(self.portal.REQUEST))
def transform(self, result, encoding): from plone.protect.interfaces import IDisableCSRFProtection # clickjacking protection from plone.protect if X_FRAME_OPTIONS: if not self.request.response.getHeader('X-Frame-Options'): self.request.response.setHeader('X-Frame-Options', X_FRAME_OPTIONS) # drop X-Tile-Url if 'x-tile-url' in self.request.response.headers: del self.request.response.headers['x-tile-url'] # ESI requests are always GET request and should not mutate DB # unless they provide IDisableCSRFProtection if not IDisableCSRFProtection.providedBy(self.request): transaction.abort() return None
def transform(self, result, encoding): from plone.protect.interfaces import IDisableCSRFProtection # clickjacking protection from plone.protect if X_FRAME_OPTIONS: if not self.request.response.getHeader('X-Frame-Options'): self.request.response.setHeader( 'X-Frame-Options', X_FRAME_OPTIONS) # drop X-Tile-Url if 'x-tile-url' in self.request.response.headers: del self.request.response.headers['x-tile-url'] # ESI requests are always GET request and should not mutate DB # unless they provide IDisableCSRFProtection if not IDisableCSRFProtection.providedBy(self.request): transaction.abort() return None
def __call__(self): form = self.request.form context = aq_inner(self.context) request = context.REQUEST authenticator = getMultiAdapter((context, request), name=u"authenticator") # CSRF should be disabled during tests if (not IDisableCSRFProtection.providedBy(request) and not authenticator.verify()): raise Unauthorized if not self.memship.checkPermission('Poi: Add Response', context): raise Unauthorized response_text = form.get('response', u'') new_response = Response(response_text) new_response.mimetype = self.mimetype new_response.type = self.determine_response_type(new_response) issue_has_changed = False transition = form.get('transition', u'') if transition and transition in self.available_transitions: wftool = getToolByName(context, 'portal_workflow') before = wftool.getInfoFor(context, 'review_state') before = wftool.getTitleForStateOnType(before, 'PoiIssue') wftool.doActionFor(context, transition) after = wftool.getInfoFor(context, 'review_state') after = wftool.getTitleForStateOnType(after, 'PoiIssue') new_response.add_change('review_state', _(u'Issue state'), before, after) issue_has_changed = True options = [ ('severity', _(u'Severity'), 'available_severities'), ('current_assignee', _(u'Assignee'), 'available_assignees'), ('targetRelease', _(u'Target release'), 'available_releases'), ] for option, title, vocab in options: new = form.get(option, u'') if new and new in self.__getattribute__(vocab): current = self.__getattribute__(option) if current == new: continue new_response.add_change(option, title, current, new) issue_has_changed = True if option == 'severity': context.severity = new elif option == 'targetRelease': context.target_release = new elif option == 'current_assignee': context.assignee = new if len(response_text) == 0 and not issue_has_changed: status = IStatusMessage(self.request) msg = _(u"No response text added and no issue changes made.") msg = translate(msg, 'Poi', context=self.request) status.addStatusMessage(msg, type='error') else: # Add response self.folder.add(new_response) redirect_url = "{0}?_authenticator={1}".format(context.absolute_url(), authenticator.token()) self.request.response.redirect(redirect_url)
def transform(self, result, encoding): site_url = 'foobar' if self.site: site_url = self.site.absolute_url() registered = self._registered_objects() if len(registered) > 0 and \ not IDisableCSRFProtection.providedBy(self.request): # in plone 4, we need to do some more trickery to # prevent write on read errors annotation_keys = ('plone.contentrules.localassignments', 'syndication_settings', 'plone.portlets.contextassignments') for obj in registered: if isinstance(obj, OOBTree): safe = False for key in annotation_keys: if key in obj: safe = True break if safe: safeWrite(obj) elif isinstance(obj, ATBlob): # writing scales is fine safeWrite(obj) # check referrer/origin header as a backstop to check # against false positives for write on read errors referrer = self.request.environ.get('HTTP_REFERER') if referrer: if referrer.startswith(site_url): alsoProvides(self.request, IDisableCSRFProtection) else: origin = self.request.environ.get('HTTP_ORIGIN') if origin and origin == site_url: alsoProvides(self.request, IDisableCSRFProtection) result = self.parseTree(result, encoding) if result is None: return None root = result.tree.getroot() try: token = createToken(manager=self.key_manager) except ComponentLookupError: return if self.site is not None: body = root.cssselect('body')[0] protect_script = etree.Element("script") protect_script.attrib.update({ 'type': "text/javascript", 'src': "%s/++resource++protect.js" % site_url, 'data-site-url': site_url, 'data-token': token, 'id': 'protect-script' }) body.append(protect_script) # guess zmi, if it is, rewrite all links last_path = self.request.URL.split('/')[-1] if last_path == 'manage' or last_path.startswith('manage_'): root.make_links_absolute(self.request.URL) def rewrite_func(url): return addTokenToUrl(url, self.request, manager=self.key_manager) root.rewrite_links(rewrite_func) # Links to add token to so we don't trigger the csrf # warnings for anchor in root.cssselect(_add_rule_token_selector): url = anchor.attrib.get('href') # addTokenToUrl only converts urls on the same site anchor.attrib['href'] = addTokenToUrl(url, self.request, manager=self.key_manager) return result
if exception_handler is not None: exception_handler(response, e) else: response.exception() return response finally: if SAFE_WRITE_KEY in request.environ: # append this list of safe oids to parent request if SAFE_WRITE_KEY not in parent_request.environ: parent_request.environ[SAFE_WRITE_KEY] = [] new_keys = ( set(request.environ[SAFE_WRITE_KEY]) - set(parent_request.environ[SAFE_WRITE_KEY]) ) parent_request.environ[SAFE_WRITE_KEY].extend(new_keys) if IDisableCSRFProtection.providedBy(request): alsoProvides(parent_request, IDisableCSRFProtection) request.clear() setRequest(parent_request) setSite(parent_site) setSecurityManager(security_manager) def unauthorized_exception_handler(response, exception): """exception handler for subrequests delegating Unauthorized to a 401, but raising all other exceptions (resulting later in a 500). """ if not isinstance(exception, Unauthorized): return response.exception() response.setStatus = 401
def subrequest(url, root=None, stdout=None, exception_handler=None): assert url is not None, 'You must pass a url' if six.PY2 and isinstance(url, six.text_type): url = url.encode('utf-8') _, _, path, query, _ = urlsplit(url) parent_request = getRequest() assert parent_request is not None, \ 'Unable to get request, perhaps zope.globalrequest is not configured.' parent_site = getSite() security_manager = getSecurityManager() parent_app = parent_request.PARENTS[-1] if path.startswith('/'): path = normpath(path) vurl_parts = parent_request.get('VIRTUAL_URL_PARTS') if vurl_parts is not None: # Use the virtual host root path_past_root = unquote(vurl_parts[-1]) root_path = normpath( parent_request['PATH_INFO'] ).rstrip('/')[:-len(path_past_root) or None] if root is None: path = root_path + path else: path = '{0}/{1}{2}'.format( root_path, root.virtual_url_path(), path ) elif root is not None: path = '/{0}{1}'.format(root.virtual_url_path(), path) else: try: parent_url = parent_request['URL'] if isinstance(parent_url, six.binary_type): parent_url = parent_url.encode('utf-8') # extra is the hidden part of the url, e.g. a default view extra = unquote( parent_url[len(parent_request['ACTUAL_URL']):] ) except KeyError: extra = '' here = parent_request['PATH_INFO'] + extra path = urljoin(here, path) path = normpath(path) request = parent_request.clone() for name, parent_value in parent_request.other.items(): if name in OTHER_IGNORE \ or OTHER_IGNORE_RE.match(name) \ or name.startswith('_'): continue request.other[name] = parent_value for key, value in parent_request.response.cookies.items(): request.cookies[key] = value['value'] request['PARENT_REQUEST'] = parent_request alsoProvides(request, ISubRequest) try: setRequest(request) request_container = RequestContainer(REQUEST=request) app = aq_base(parent_app).__of__(request_container) request['PARENTS'] = [app] response = request.response response.__class__ = SubResponse response.stderr = None # only used on retry it seems if stdout is None: stdout = StringIO() # It might be possible to optimize this response.stdout = stdout environ = request.environ environ['PATH_INFO'] = path environ['QUERY_STRING'] = query # Clean up the request. for header in CONDITIONAL_HEADERS: environ.pop(header, None) try: request.processInputs() traversed = request.traverse(path) result = mapply( traversed, positional=request.args, keyword=request, debug=None, maybe=1, missing_name=missing_name, handle_class=dont_publish_class, context=request, bind=1 ) if result is not response: response.setBody(result) for key, value in request.response.cookies.items(): parent_request.response.cookies[key] = value except Exception as e: logger.exception(u'Error handling subrequest to {0}'.format(url)) if exception_handler is not None: exception_handler(response, e) else: view = queryMultiAdapter((e, request), name=u'index.html') if view is not None: v = view() response.setBody(v) else: response.exception() return response finally: if SAFE_WRITE_KEY in request.environ: # append this list of safe oids to parent request if SAFE_WRITE_KEY not in parent_request.environ: parent_request.environ[SAFE_WRITE_KEY] = [] new_keys = ( set(request.environ[SAFE_WRITE_KEY]) - set(parent_request.environ[SAFE_WRITE_KEY]) ) parent_request.environ[SAFE_WRITE_KEY].extend(new_keys) if IDisableCSRFProtection.providedBy(request): alsoProvides(parent_request, IDisableCSRFProtection) request.clear() setRequest(parent_request) setSite(parent_site) setSecurityManager(security_manager)
def transform(self, result, encoding): site_url = 'foobar' if self.site: site_url = self.site.absolute_url() registered = self._registered_objects() if len(registered) > 0 and \ not IDisableCSRFProtection.providedBy(self.request): # in plone 4, we need to do some more trickery to # prevent write on read errors annotation_keys = ( 'plone.contentrules.localassignments', 'syndication_settings', 'plone.portlets.contextassignments') for obj in registered: if isinstance(obj, OOBTree): safe = False for key in annotation_keys: try: if key in obj: safe = True break except TypeError: pass if safe: safeWrite(obj) elif isinstance(obj, ATBlob): # writing scales is fine safeWrite(obj) # check referrer/origin header as a backstop to check # against false positives for write on read errors referrer = self.request.environ.get('HTTP_REFERER') if referrer: if referrer.startswith(site_url + '/'): alsoProvides(self.request, IDisableCSRFProtection) else: origin = self.request.environ.get('HTTP_ORIGIN') if origin and origin == site_url: alsoProvides(self.request, IDisableCSRFProtection) result = self.parseTree(result, encoding) if result is None: return None root = result.tree.getroot() try: token = createToken(manager=self.key_manager) except ComponentLookupError: return if self.site is not None: body = root.cssselect('body')[0] protect_script = etree.Element("script") protect_script.attrib.update({ 'type': "application/javascript", 'src': "%s/++resource++protect.js" % site_url, 'data-site-url': site_url, 'data-token': token, 'id': 'protect-script' }) body.append(protect_script) # guess zmi, if it is, rewrite all links last_path = self.request.URL.split('/')[-1] if last_path == 'manage' or last_path.startswith('manage_'): root.make_links_absolute(self.request.URL) def rewrite_func(url): return addTokenToUrl( url, self.request, manager=self.key_manager) root.rewrite_links(rewrite_func) # Links to add token to so we don't trigger the csrf # warnings for anchor in root.cssselect(_add_rule_token_selector): url = anchor.attrib.get('href') # addTokenToUrl only converts urls on the same site anchor.attrib['href'] = addTokenToUrl( url, self.request, manager=self.key_manager) return result
def subrequest(url, root=None, stdout=None, exception_handler=None): assert url is not None, 'You must pass a url' if isinstance(url, six.text_type): url = url.encode('utf-8') _, _, path, query, _ = urlsplit(url) parent_request = getRequest() assert parent_request is not None, \ 'Unable to get request, perhaps zope.globalrequest is not configured.' parent_site = getSite() security_manager = getSecurityManager() parent_app = parent_request.PARENTS[-1] if path.startswith('/'): path = normpath(path) vurl_parts = parent_request.get('VIRTUAL_URL_PARTS') if vurl_parts is not None: # Use the virtual host root path_past_root = unquote(vurl_parts[-1]) root_path = normpath(parent_request['PATH_INFO']).rstrip( '/')[:-len(path_past_root) or None] if root is None: path = root_path + path else: path = '{0}/{1}{2}'.format(root_path, root.virtual_url_path(), path) elif root is not None: path = '/{0}{1}'.format(root.virtual_url_path(), path) else: try: parent_url = parent_request['URL'] if isinstance(parent_url, six.text_type): parent_url = parent_url.encode('utf-8') # extra is the hidden part of the url, e.g. a default view extra = unquote(parent_url[len(parent_request['ACTUAL_URL']):]) except KeyError: extra = '' here = parent_request['PATH_INFO'] + extra path = urljoin(here, path) path = normpath(path) request = parent_request.clone() for name, parent_value in parent_request.other.items(): if name in OTHER_IGNORE \ or OTHER_IGNORE_RE.match(name) \ or name.startswith('_'): continue request.other[name] = parent_value for key, value in parent_request.response.cookies.items(): request.cookies[key] = value['value'] request['PARENT_REQUEST'] = parent_request alsoProvides(request, ISubRequest) try: setRequest(request) request_container = RequestContainer(REQUEST=request) app = aq_base(parent_app).__of__(request_container) request['PARENTS'] = [app] response = request.response response.__class__ = SubResponse response.stderr = None # only used on retry it seems if stdout is None: stdout = StringIO() # It might be possible to optimize this response.stdout = stdout environ = request.environ environ['PATH_INFO'] = path environ['QUERY_STRING'] = query # Clean up the request. for header in CONDITIONAL_HEADERS: environ.pop(header, None) try: request.processInputs() traversed = request.traverse(path) result = mapply(traversed, positional=request.args, keyword=request, debug=None, maybe=1, missing_name=missing_name, handle_class=dont_publish_class, context=request, bind=1) if result is not response: response.setBody(result) for key, value in request.response.cookies.items(): parent_request.response.cookies[key] = value except Exception as e: logger.exception('Error handling subrequest to {0}'.format(url)) if exception_handler is not None: exception_handler(response, e) else: view = queryMultiAdapter((e, request), name=u'index.html') if view is not None: v = view() response.setBody(v) else: response.exception() return response finally: if SAFE_WRITE_KEY in request.environ: # append this list of safe oids to parent request if SAFE_WRITE_KEY not in parent_request.environ: parent_request.environ[SAFE_WRITE_KEY] = [] new_keys = (set(request.environ[SAFE_WRITE_KEY]) - set(parent_request.environ[SAFE_WRITE_KEY])) parent_request.environ[SAFE_WRITE_KEY].extend(new_keys) if IDisableCSRFProtection.providedBy(request): alsoProvides(parent_request, IDisableCSRFProtection) request.clear() setRequest(parent_request) setSite(parent_site) setSecurityManager(security_manager)
def __call__(self): form = self.request.form context = aq_inner(self.context) request = context.REQUEST authenticator = getMultiAdapter((context, request), name=u"authenticator") # CSRF should be disabled during tests if (not IDisableCSRFProtection.providedBy(request) and not authenticator.verify()): raise Unauthorized if not self.memship.checkPermission('Poi: Add Response', context): raise Unauthorized response_text = form.get('response', u'') new_response = Response(response_text) new_response.mimetype = self.mimetype new_response.type = self.determine_response_type(new_response) issue_has_changed = False transition = form.get('transition', u'') if transition and transition in self.available_transitions: wftool = getToolByName(context, 'portal_workflow') before = wftool.getInfoFor(context, 'review_state') before = wftool.getTitleForStateOnType(before, 'PoiIssue') wftool.doActionFor(context, transition) after = wftool.getInfoFor(context, 'review_state') after = wftool.getTitleForStateOnType(after, 'PoiIssue') new_response.add_change('review_state', _(u'Issue state'), before, after) issue_has_changed = True options = [ ('severity', _(u'Severity'), 'available_severities'), ('current_assignee', _(u'Assignee'), 'available_assignees'), ('targetRelease', _(u'Target release'), 'available_releases'), ] for option, title, vocab in options: new = form.get(option, u'') if new and new in self.__getattribute__(vocab): current = self.__getattribute__(option) if current == new: continue new_response.add_change(option, title, current, new) issue_has_changed = True if option == 'severity': context.severity = new elif option == 'targetRelease': context.target_release = new elif option == 'current_assignee': context.assignee = new if len(response_text) == 0 and not issue_has_changed: status = IStatusMessage(self.request) msg = _(u"No response text added and no issue changes made.") msg = translate(msg, 'Poi', context=self.request) status.addStatusMessage(msg, type='error') else: # Add response self.folder.add(new_response) context.reindexObject() redirect_url = "{0}?_authenticator={1}".format(context.absolute_url(), authenticator.token()) self.request.response.redirect(redirect_url)