Repository: freedomofpress/securedrop

Issue:
Journalist API responses include Set-Cookie/Vary headers
## Description

The API includes the `Set-Cookie` header and `Vary: Cookie` when they should not as they are not relevant to accessing the API.

## Steps to Reproduce

In one terminal: `make dev`.
In another: `http HEAD localhost:8081/api/v1`

## Expected Behavior

```http
HTTP/1.0 200 OK
Content-Length: 198
Content-Type: application/json
Date: Mon, 15 Oct 2018 13:06:32 GMT
Server: Werkzeug/0.14.1 Python/2.7.6
```

## Actual Behavior

```http
HTTP/1.0 200 OK
Content-Length: 198
Content-Type: application/json
Date: Mon, 15 Oct 2018 13:06:32 GMT
Server: Werkzeug/0.14.1 Python/2.7.6
Set-Cookie: js=eyJleHBpcmVzIjp7IiBkIjoiTW9uLCAxNSBPY3QgMjAxOCAxNTowNjozMiBHTVQifX0.DqYiWA.-hH2qepnrLDYF8HV79vsn29TfhY; HttpOnly; Path=/
Vary: Cookie
```

## Comments

I have tried implementing and override to `SecureSessionInterface` that doesn't set a cookie if the endpoint matches the API endpoint, but... no dice so far.


Discussion:
@heartsucker are you still working on this or i can give it a try?

I'm not actively working on it, so if you'd like to give it a try that'd be very helpful!
batman on it.
I am not able to give it as much time as i thought i would. So, if anyone wants to take this, they are free to

Resolving patch:
diff --git a/securedrop/journalist_app/__init__.py b/securedrop/journalist_app/__init__.py
--- a/securedrop/journalist_app/__init__.py
+++ b/securedrop/journalist_app/__init__.py
@@ -16,7 +16,8 @@
 from crypto_util import CryptoUtil
 from db import db
 from journalist_app import account, admin, api, main, col
-from journalist_app.utils import get_source, logged_in
+from journalist_app.utils import (get_source, logged_in,
+                                  JournalistInterfaceSessionInterface)
 from models import Journalist
 from store import Storage
 
@@ -40,6 +41,7 @@ def create_app(config):
 
     app.config.from_object(config.JournalistInterfaceFlaskConfig)
     app.sdconfig = config
+    app.session_interface = JournalistInterfaceSessionInterface()
 
     csrf = CSRFProtect(app)
     Environment(app)
diff --git a/securedrop/journalist_app/utils.py b/securedrop/journalist_app/utils.py
--- a/securedrop/journalist_app/utils.py
+++ b/securedrop/journalist_app/utils.py
@@ -2,7 +2,7 @@
 
 from datetime import datetime
 from flask import (g, flash, current_app, abort, send_file, redirect, url_for,
-                   render_template, Markup)
+                   render_template, Markup, sessions, request)
 from flask_babel import gettext, ngettext
 import hashlib
 from sqlalchemy.sql.expression import false
@@ -337,3 +337,16 @@ def serve_file_with_etag(source, filename):
     response.headers['Etag'] = '"sha256:{}"'.format(
         hashlib.sha256(response.get_data()).hexdigest())
     return response
+
+
+class JournalistInterfaceSessionInterface(
+        sessions.SecureCookieSessionInterface):
+    """A custom session interface that skips storing sessions for api requests but
+    otherwise just uses the default behaviour."""
+    def save_session(self, app, session, response):
+        # If this is an api request do not save the session
+        if request.path.split("/")[1] == "api":
+            return
+        else:
+            super(JournalistInterfaceSessionInterface, self).save_session(
+                app, session, response)


Test patch:
diff --git a/securedrop/tests/test_journalist.py b/securedrop/tests/test_journalist.py
--- a/securedrop/tests/test_journalist.py
+++ b/securedrop/tests/test_journalist.py
@@ -1959,3 +1959,12 @@ def test_source_with_null_last_updated(journalist_app,
                     test_journo['otp_secret'])
         resp = app.get(url_for('main.index'))
         assert resp.status_code == 200
+
+
+def test_does_set_cookie_headers(journalist_app, test_journo):
+    with journalist_app.test_client() as app:
+        response = app.get(url_for('main.login'))
+
+        observed_headers = response.headers
+        assert 'Set-Cookie' in observed_headers.keys()
+        assert 'Cookie' in observed_headers['Vary']
diff --git a/securedrop/tests/test_journalist_api.py b/securedrop/tests/test_journalist_api.py
--- a/securedrop/tests/test_journalist_api.py
+++ b/securedrop/tests/test_journalist_api.py
@@ -850,3 +850,13 @@ def test_set_reply_uuid(journalist_app, journalist_api_token, test_source):
                         data=json.dumps(req_data),
                         headers=get_api_headers(journalist_api_token))
         assert resp.status_code == 400
+
+
+def test_api_does_not_set_cookie_headers(journalist_app, test_journo):
+    with journalist_app.test_client() as app:
+        response = app.get(url_for('api.get_endpoints'))
+
+        observed_headers = response.headers
+        assert 'Set-Cookie' not in observed_headers.keys()
+        if 'Vary' in observed_headers.keys():
+            assert 'Cookie' not in observed_headers['Vary']