Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Task/add specific failed comment #1994

Merged
merged 7 commits into from
Nov 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,7 @@ class Config(object):
ONE_OFF_MESSAGE_FILENAME = "Report"
MAX_VERIFY_CODE_COUNT = 10
JOBS_MAX_SCHEDULE_HOURS_AHEAD = 96
FAILED_LOGIN_LIMIT = os.getenv("FAILED_LOGIN_LIMIT", 10)

# be careful increasing this size without being sure that we won't see slowness in pysftp
MAX_LETTER_PDF_ZIP_FILESIZE = 40 * 1024 * 1024 # 40mb
Expand Down Expand Up @@ -689,6 +690,7 @@ class Test(Development):
FF_EMAIL_DAILY_LIMIT = False
CRM_GITHUB_PERSONAL_ACCESS_TOKEN = "test-token"
CRM_ORG_LIST_URL = "https://test-url.com"
FAILED_LOGIN_LIMIT = 0


class Production(Config):
Expand Down
7 changes: 6 additions & 1 deletion app/user/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,12 @@ def verify_user_password(user_id):
return jsonify({}), 204
else:
increment_failed_login_count(user_to_verify)
message = "Incorrect password"
if user_to_verify.failed_login_count >= current_app.config["FAILED_LOGIN_LIMIT"]:
message = "Failed login: Incorrect password for user_id {user_id} failed_login {failed_login_count} times".format(
user_id=user_id, failed_login_count=user_to_verify.failed_login_count
)
else:
message = "Incorrect password for user_id {user_id}".format(user_id=user_id)
errors = {"password": [message]}
raise InvalidRequest(errors, status_code=400)

Expand Down
31 changes: 31 additions & 0 deletions tests/app/user/test_rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1544,3 +1544,34 @@ def test_update_user_blocked(admin_request, sample_user, account_change_template

assert resp["data"]["id"] == str(sample_user.id)
assert resp["data"]["blocked"]


class TestFailedLogin:
def test_update_user_password_saves_correctly(self, client, sample_service, mocker):
sample_user = sample_service.users[0]
new_password = "tQETOgIO8yzDMyCsDjLZIEVZHAvkFArYfmSI1KTsJnlnPohI2tfIa8kfng7bxCm"
data = {"_password": new_password}
auth_header = create_authorization_header()
headers = [("Content-Type", "application/json"), auth_header]
resp = client.post(
url_for("user.update_password", user_id=sample_user.id),
data=json.dumps(data),
headers=headers,
)
assert resp.status_code == 200

json_resp = json.loads(resp.get_data(as_text=True))
assert json_resp["data"]["password_changed_at"] is not None
data = {"password": new_password}
auth_header = create_authorization_header()
headers = [("Content-Type", "application/json"), auth_header]
# We force a the password to fail on login
mocker.patch("app.models.User.check_password", return_value=False)

resp = client.post(
url_for("user.verify_user_password", user_id=str(sample_user.id)),
data=json.dumps(data),
headers=headers,
)
assert resp.status_code == 400
assert "Incorrect password for user_id" in resp.json["message"]["password"][0]
63 changes: 33 additions & 30 deletions tests/app/user/test_rest_verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from app.dao.users_dao import create_user_code
from app.models import EMAIL_TYPE, SMS_TYPE, Notification, User, VerifyCode
from tests import create_authorization_header
from tests.conftest import set_config_values


@freeze_time("2016-01-01T12:00:00")
Expand Down Expand Up @@ -111,21 +112,22 @@ def test_user_verify_password_creates_login_event(client, sample_user):
assert len(events) == 1


def test_user_verify_password_invalid_password(client, sample_user):
def test_user_verify_password_invalid_password(client, sample_user, mocker):
data = json.dumps({"password": "bad password"})
auth_header = create_authorization_header()

assert sample_user.failed_login_count == 0

resp = client.post(
url_for("user.verify_user_password", user_id=sample_user.id),
data=data,
headers=[("Content-Type", "application/json"), auth_header],
)
assert resp.status_code == 400
json_resp = json.loads(resp.get_data(as_text=True))
assert "Incorrect password" in json_resp["message"]["password"]
assert sample_user.failed_login_count == 1
with set_config_values(current_app, {"FAILED_LOGIN_LIMIT": 10}):
resp = client.post(
url_for("user.verify_user_password", user_id=sample_user.id),
data=data,
headers=[("Content-Type", "application/json"), auth_header],
)
assert resp.status_code == 400
json_resp = json.loads(resp.get_data(as_text=True))
assert "Incorrect password" in json_resp["message"]["password"][0]
assert sample_user.failed_login_count == 1


def test_user_verify_password_valid_password_resets_failed_logins(client, sample_user):
Expand All @@ -134,27 +136,28 @@ def test_user_verify_password_valid_password_resets_failed_logins(client, sample

assert sample_user.failed_login_count == 0

resp = client.post(
url_for("user.verify_user_password", user_id=sample_user.id),
data=data,
headers=[("Content-Type", "application/json"), auth_header],
)
assert resp.status_code == 400
json_resp = json.loads(resp.get_data(as_text=True))
assert "Incorrect password" in json_resp["message"]["password"]

assert sample_user.failed_login_count == 1

data = json.dumps({"password": "password"})
auth_header = create_authorization_header()
resp = client.post(
url_for("user.verify_user_password", user_id=sample_user.id),
data=data,
headers=[("Content-Type", "application/json"), auth_header],
)
with set_config_values(current_app, {"FAILED_LOGIN_LIMIT": 10}):
resp = client.post(
url_for("user.verify_user_password", user_id=sample_user.id),
data=data,
headers=[("Content-Type", "application/json"), auth_header],
)
assert resp.status_code == 400
json_resp = json.loads(resp.get_data(as_text=True))
assert "Incorrect password" in json_resp["message"]["password"][0]

assert sample_user.failed_login_count == 1

data = json.dumps({"password": "password"})
auth_header = create_authorization_header()
resp = client.post(
url_for("user.verify_user_password", user_id=sample_user.id),
data=data,
headers=[("Content-Type", "application/json"), auth_header],
)

assert resp.status_code == 204
assert sample_user.failed_login_count == 0
assert resp.status_code == 204
assert sample_user.failed_login_count == 0


def test_user_verify_password_missing_password(client, sample_user):
Expand Down
Loading
Loading