Skip to content

Commit

Permalink
Requested revisions
Browse files Browse the repository at this point in the history
  • Loading branch information
mdrose committed Oct 17, 2018
1 parent a49f0b3 commit 4e3b4fc
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 23 deletions.
36 changes: 26 additions & 10 deletions securedrop/journalist_app/api.py
@@ -1,5 +1,6 @@
from datetime import datetime, timedelta
from functools import wraps
from six import string_types
import json
from werkzeug.exceptions import default_exceptions # type: ignore

Expand Down Expand Up @@ -280,23 +281,38 @@ def get_current_user():
user = get_user_object(request)
return jsonify(user.to_json()), 200

@api.route('/account/new-password', methods=['POST'])
@api.route('/user/new-passphrase', methods=['POST'])
@token_required
def new_password():
def set_passphrase():
REQUIRED_ATTRIBUTES = ['old_passphrase', 'two_factor_code', 'new_passphrase']

user = get_user_object(request)
new_password = json.loads(request.data)['new_password']
data = json.loads(request.data)

# Validate request
for attribute in REQUIRED_ATTRIBUTES:
if attribute not in data:
return jsonify({'message':
'Invalid request: {} unspecified'.format(attribute)}), 400
elif not isinstance(data[attribute], string_types):
return jsonify({'message':
'Invalid request: {} must be a string'}.format(attribute)), 400

try:
user.set_password(new_password)
except (InvalidPasswordLength, NonDicewarePassword) as e:
return jsonify({'message': str(e)}), 400
except Exception as e:
return jsonify({'message': 'An error occurred while setting the password. Password unchanged'}), 500
Journalist.login(user.username, data['old_passphrase'], data['two_factor_code'])
except (BadTokenException, WrongPasswordException):
return jsonify({'message':
'Invalid credentials. Passphrase change denied'}), 403

# Set password
try:
user.set_password(data['new_passphrase'])
db.session.commit()
except Exception as e:
return jsonify({'message': 'An error occurred on database commit. Password unchanged.'}), 500
except (InvalidPasswordLength, NonDicewarePassword) as e:
return jsonify({'message': str(e)}), 400
except Exception:
return jsonify({'message':
'An error occurred while setting the passphrase. Passphrase unchanged'}), 500

return jsonify({'message': 'Password changed successfully'}), 200

Expand Down
3 changes: 2 additions & 1 deletion securedrop/models.py
Expand Up @@ -578,7 +578,8 @@ def to_json(self):
'username': self.username,
'last_login': self.last_access.isoformat() + 'Z',
'is_admin': self.is_admin,
'uuid': self.uuid
'uuid': self.uuid,
'set_passphrase_url': url_for('api.set_passphrase')
}
return json_user

Expand Down
77 changes: 65 additions & 12 deletions securedrop/tests/test_journalist_api.py
Expand Up @@ -663,37 +663,90 @@ def test_authorized_user_can_add_reply(journalist_app, journalist_api_token,
assert reply_content == saved_content


def test_new_password_unacceptable_400(journalist_app, journalist_api_token,
test_journo):
def test_set_passphrase_blank_request_400(journalist_app, journalist_api_token):
with journalist_app.test_client() as app:
response = app.post(url_for('api.set_passphrase'),
data='{}',
headers=get_api_headers(journalist_api_token))
assert response.status_code == 400


def test_set_passphrase_nonstring_request_400(journalist_app, journalist_api_token):
with journalist_app.test_client() as app:
data = {'new_passphrase': {}}
response = app.post(url_for('api.set_passphrase'),
data=json.dumps(data),
headers=get_api_headers(journalist_api_token))
assert response.status_code == 400


def test_set_passphrase_wrong_pass_403(journalist_app, journalist_api_token,
test_journo):
topt = test_journo['journalist'].totp
with journalist_app.test_client() as app:
data = {'old_passphrase': test_journo['password'] + 'a',
'two_factor_code': topt.now(),
'new_passphrase': 'aaaa'}
response = app.post(url_for('api.set_passphrase'),
data=json.dumps(data),
headers=get_api_headers(journalist_api_token))

assert response.status_code == 403


def test_set_passphrase_wrong_otp_403(journalist_app, journalist_api_token,
test_journo):
topt = test_journo['journalist'].totp
with journalist_app.test_client() as app:
data = {'old_passphrase': test_journo['password'],
'two_factor_code': topt.now() + '1',
'new_passphrase': 'aaaa'}
response = app.post(url_for('api.set_passphrase'),
data=json.dumps(data),
headers=get_api_headers(journalist_api_token))

assert response.status_code == 403


def test_set_passphrase_unacceptable_400(journalist_app, journalist_api_token,
test_journo):
original_hash = test_journo['journalist'].passphrase_hash
topt = test_journo['journalist'].totp

with journalist_app.test_client() as app:
new_password = 'a' * (Journalist.MIN_PASSWORD_LEN - 1)
data = {'new_password': new_password}
response = app.post(url_for('api.new_password'),
new_passphrase = 'a' * (Journalist.MIN_PASSWORD_LEN - 1)
data = {'old_passphrase': test_journo['password'],
'two_factor_code': topt.now(),
'new_passphrase': new_passphrase}
response = app.post(url_for('api.set_passphrase'),
data=json.dumps(data),
headers=get_api_headers(journalist_api_token))

assert response.status_code == 400
assert (response.get_json()['message'] == str(NonDicewarePassword()) or
response.get_json()['message'] == str(InvalidPasswordLength(new_password))
response.get_json()['message'] == str(InvalidPasswordLength(new_passphrase))
)

with journalist_app.app_context():
user = Journalist.query.get(test_journo['id'])
assert original_hash == user.passphrase_hash


def test_new_password_success_200(journalist_app, journalist_api_token,
test_journo):
def test_set_passphrase_success_200(journalist_app, journalist_api_token,
test_journo):
original_hash = test_journo['journalist'].passphrase_hash
topt = test_journo['journalist'].totp

with journalist_app.test_client() as app:
new_password = 'a ' * max( (Journalist.MIN_PASSWORD_LEN+1) / 2,
Journalist.MIN_PASSWORD_WORDS)
new_passphrase = ('a ' * Journalist.MIN_PASSWORD_WORDS)[:-1]
pass_len_short = Journalist.MIN_PASSWORD_LEN - len(new_passphrase)
if pass_len_short > 0:
new_passphrase += 'a' * pass_len_short

data = {'new_password': new_password}
response = app.post(url_for('api.new_password'),
data = {'old_passphrase': test_journo['password'],
'two_factor_code': topt.now(),
'new_passphrase': new_passphrase}
response = app.post(url_for('api.set_passphrase'),
data=json.dumps(data),
headers=get_api_headers(journalist_api_token))
assert response.status_code == 200
Expand Down

0 comments on commit 4e3b4fc

Please sign in to comment.