2
0
mirror of https://github.com/xcat2/confluent.git synced 2024-11-22 17:43:14 +00:00

Add facility for Anti-CSRF measures

Enable a WebUI to request an auth token.  This will allow it to indicate it is running in a browser and have the server implement protections such that
other software in the browser cannot send arbitrary requests into the server API.

This is implemented in a backward compatible fashion, allowing, for example, purely non-browser clients to ignore the CSRF protection as
it doesn't apply to that use case.
This commit is contained in:
Jarrod Johnson 2016-09-22 11:09:05 -04:00
parent d183ef768d
commit 1286f8af3c
2 changed files with 44 additions and 8 deletions

View File

@ -214,6 +214,33 @@ def _should_skip_authlog(env):
return True
return False
def _csrf_valid(env, session):
# This could be simplified into a statement, but this is more readable
# to have it broken out
if (env['REQUEST_METHOD'] == 'GET' and
env['PATH_INFO'] == '/sessions/current/info'):
# Provide a web client a safe hook to request the CSRF token
# This means that we consider GET of /sessions/current/info to be
# a safe thing to inflict via CSRF, since CORS should prevent
# hypothetical attacker from reading the data and it has no
# side effects to speak of
return True
if 'csrftoken' not in session:
# The client has not (yet) requested CSRF protection
# so we return true
if 'HTTP_CONFLUENTAUTHTOKEN' in env:
# The client has requested CSRF countermeasures,
# oblige the request and apply a new token to the
# session
session['csrftoken'] = util.randomstring(32)
return True
# The session has CSRF protection enabled, only mark valid if
# the client has provided an auth token and that token matches the
# value protecting the session
return ('HTTP_CONFLUENTAUTHTOKEN' in env and
env['HTTP_CONFLUENTAUTHTOKEN'] == session['csrftoken'])
def _authorize_request(env, operation):
"""Grant/Deny access based on data from wsgi env
@ -228,6 +255,7 @@ def _authorize_request(env, operation):
cc.load(env['HTTP_COOKIE'])
if 'confluentsessionid' in cc:
sessionid = cc['confluentsessionid'].value
sessid = sessionid
if sessionid in httpsessions:
if env['PATH_INFO'] == '/sessions/current/logout':
targets = []
@ -237,11 +265,12 @@ def _authorize_request(env, operation):
eventlet.greenthread.kill(mythread)
del httpsessions[sessionid]
return ('logout',)
httpsessions[sessionid]['expiry'] = time.time() + 90
name = httpsessions[sessionid]['name']
authdata = auth.authorize(
name, element=None,
skipuserobj=httpsessions[sessionid]['skipuserobject'])
if _csrf_valid(env, httpsessions[sessionid]):
httpsessions[sessionid]['expiry'] = time.time() + 90
name = httpsessions[sessionid]['name']
authdata = auth.authorize(
name, element=None,
skipuserobj=httpsessions[sessionid]['skipuserobject'])
if (not authdata) and 'HTTP_AUTHORIZATION' in env:
if env['PATH_INFO'] == '/sessions/current/logout':
return ('logout',)
@ -256,6 +285,8 @@ def _authorize_request(env, operation):
httpsessions[sessid] = {'name': name, 'expiry': time.time() + 90,
'skipuserobject': authdata[4],
'inflight': set([])}
if 'HTTP_CONFLUENTAUTHTOKEN' in env:
httpsessions[sessid]['csrftoken'] = util.randomstring(32)
cookie['confluentsessionid'] = sessid
cookie['confluentsessionid']['secure'] = 1
cookie['confluentsessionid']['httponly'] = 1
@ -280,6 +311,8 @@ def _authorize_request(env, operation):
authinfo['sessionid'] = sessionid
if not skiplog:
auditlog.log(auditmsg)
if 'csrftoken' in httpsessions[sessid]:
authinfo['authtoken'] = httpsessions[sessid]['csrftoken']
return authinfo
else:
return {'code': 401}
@ -352,7 +385,7 @@ def resourcehandler_backend(env, start_response):
start_response('200 Successful logout', headers)
yield('{"result": "200 - Successful logout"}')
return
if 'HTTP_SUPPRESSAUTHHEADER' in env:
if 'HTTP_SUPPRESSAUTHHEADER' in env or 'HTTP_CONFLUENTAUTHTOKEN' in env:
badauth = [('Content-type', 'text/plain')]
else:
badauth = [('Content-type', 'text/plain'),
@ -530,7 +563,10 @@ def resourcehandler_backend(env, start_response):
url = url.replace('.html', '')
if url == '/sessions/current/info':
start_response('200 OK', headers)
yield json.dumps({'username': authorized['username']})
sessinfo = {'username': authorized['username']}
if 'authtoken' in authorized:
sessinfo['authtoken'] = authorized['authtoken']
yield json.dumps(sessinfo)
return
resource = '.' + url[url.rindex('/'):]
lquerydict = copy.deepcopy(querydict)

View File

@ -176,7 +176,7 @@ def send_response(responses, connection):
def process_request(connection, request, cfm, authdata, authname, skipauth):
if not isinstance(request, dict):
raise ValueError
raise exc.InvalidArgumentException
operation = request['operation']
path = request['path']
params = request.get('parameters', {})