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:
parent
d183ef768d
commit
1286f8af3c
@ -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)
|
||||
|
@ -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', {})
|
||||
|
Loading…
Reference in New Issue
Block a user