diff --git a/confluent_server/confluent/httpapi.py b/confluent_server/confluent/httpapi.py index 74ed89c5..610c90b8 100644 --- a/confluent_server/confluent/httpapi.py +++ b/confluent_server/confluent/httpapi.py @@ -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) diff --git a/confluent_server/confluent/sockapi.py b/confluent_server/confluent/sockapi.py index 6203f198..6681a67f 100644 --- a/confluent_server/confluent/sockapi.py +++ b/confluent_server/confluent/sockapi.py @@ -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', {})