|
"""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 |