Skip to content

Instantly share code, notes, and snippets.

@dd32
Last active July 21, 2019 03:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dd32/e1a6e434cb9b5721cc086e51751f8c44 to your computer and use it in GitHub Desktop.
Save dd32/e1a6e434cb9b5721cc086e51751f8c44 to your computer and use it in GitHub Desktop.
A Trac plugin to block anonymous cookies being sent
"""WordPress.org: Block Anonymous Cookies."""
import re
from trac.core import Component, implements
from trac.web.api import IRequestFilter
from trac.web.session import Session, PURGE_AGE
class WPOrgBlockAnonymousCookies(Component):
"""Prevents Trac from setting cookies for unauthenticated requests.
This is to aid in Trac cachability, where the trac_form_token cookie
prevents proper nginx caching of responses.
"""
implements(IRequestFilter)
def pre_process_request(self, req, handler):
if not self.is_authenticated(req):
# Override the Request session and form token handler with our own.
req.callbacks.update({
'session': self._get_session,
'form_token': self._get_form_token
})
# Prevent access to /prefs with a redirect to home
if re.match('/prefs(?:/([^/]+))?$', req.path_info):
req.redirect( req.base_url )
# If an unauthenticated request has a form token, discard the token.
if '__FORM_TOKEN' in req.args:
del req.args['__FORM_TOKEN']
# Disable CSRF protection for the `POST /query` endpoint when logged out.
# The /query endpoint is the only unauthenticated POST endpoint that
# This is done by pretending the users form contained a form token that matches the Requests token.
if req.method == 'POST' and req.path_info == '/query':
req.args['__FORM_TOKEN'] = req.form_token
return handler
def post_process_request(self, req, template, data, content_type):
if not self.is_authenticated(req):
# Clear out any cookies Trac was intending to send.
req.outcookie.clear()
# Expire any trac_* cookies if no longer authenticated.
for name in list(req.incookie):
if name.startswith('trac_') and req.incookie[ name ].value:
req.outcookie[ name ] = req.incookie[ name ]
req.outcookie[ name ].update({
'value': '',
'expires': -1000,
})
return (template, data, content_type)
def _get_session(self, req):
# Prevent anonymous being saved to DB with our own Session instance
return UnsavedSession( self.env, req )
def _get_form_token(self, req):
# Return a Null-ish form token for unauthenciated requests, it's simply a static value for comparisons
return None
def is_authenticated(self, req):
# Polyfil for Request.is_authenticated() which is only available in Trac 1.3.2
try:
return req.is_authenticated
except AttributeError:
return req.authname and req.authname != 'anonymous'
class UnsavedSession(Session):
"""An implementation of the Trac Session handler which doesn't save Session data or set cookies.
Should only be used for unauthenticated requests.
"""
def __init__(self, env, req):
super(Session, self).__init__(env, None)
self.req = req
def save(self):
# Don't save any session attributes for this session.
pass
def bake_cookie(self, expires=PURGE_AGE):
# Don't output any cookies for this session.
pass
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment