Repository: freedomofpress/securedrop

Issue:
journalist login with long passphrase causes internal server error
## Description

When logging in to the journalist interface, entering a passphrase that's too long raises `models.InvalidPasswordLength`, which is not caught properly, so ends up handled at line 97 of `/var/www/securedrop/journalist_app/__init__.py`, in `_handle_http_exception`, where `AttributeError: 'InvalidPasswordLength' object has no attribute 'code'` results in the plain Apache Internal Server Error page being delivered to the visitor.

## Steps to Reproduce

This can be reproduced with the dev server.
- `make dev`
- visit the journalist interface at http://localhost:8081
- try to log in with a passphrase longer than 128 characters

## Expected Behavior

The server should return a flashed error indicating the login failed.

## Actual Behavior

The improperly handled exception lands the visitor at a dead-end Internal Server Error page.




Discussion:
@rmol Will look into this.
@rmol Wrapping the `raise InvalidPasswordLength(password)` in a `try-catch` block like:
```
try:
    raise InvalidPasswordLength(passphrase)
except InvalidPasswordLength as error:
    str(error)
````
would work I think. 

Update: Exception was just needed to be handled in `journalist_app/utils.py`

Resolving patch:
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
@@ -12,7 +12,8 @@
 from db import db
 from models import (get_one_or_else, Source, Journalist, InvalidUsernameException,
                     WrongPasswordException, FirstOrLastNameError, LoginThrottledException,
-                    BadTokenException, SourceStar, PasswordError, Submission, RevokedToken)
+                    BadTokenException, SourceStar, PasswordError, Submission, RevokedToken,
+                    InvalidPasswordLength)
 from store import add_checksum_for_file
 
 import typing
@@ -77,7 +78,8 @@ def validate_user(username, password, token, error_message=None):
     except (InvalidUsernameException,
             BadTokenException,
             WrongPasswordException,
-            LoginThrottledException) as e:
+            LoginThrottledException,
+            InvalidPasswordLength) as e:
         current_app.logger.error("Login for '{}' failed: {}".format(
             username, e))
         if not error_message:


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
@@ -614,6 +614,21 @@ def test_max_password_length():
                    password=overly_long_password)
 
 
+def test_login_password_too_long(journalist_app, test_journo, mocker):
+    mocked_error_logger = mocker.patch('journalist.app.logger.error')
+    with journalist_app.test_client() as app:
+        resp = app.post(url_for('main.login'),
+                        data=dict(username=test_journo['username'],
+                                  password='a' * (Journalist.MAX_PASSWORD_LEN + 1),
+                                  token=TOTP(test_journo['otp_secret']).now()))
+    assert resp.status_code == 200
+    text = resp.data.decode('utf-8')
+    assert "Login failed" in text
+    mocked_error_logger.assert_called_once_with(
+        "Login for '{}' failed: Password too long (len={})".format(
+            test_journo['username'], Journalist.MAX_PASSWORD_LEN + 1))
+
+
 def test_min_password_length():
     """Creating a Journalist with a password that is smaller than the
        minimum password length should raise an exception. This uses the