2022-06-29 17:39:46 +00:00
|
|
|
# pylint: disable=redefined-outer-name
|
|
|
|
# pylint: disable=unused-argument
|
|
|
|
# pylint: disable=missing-function-docstring
|
|
|
|
|
2023-11-10 17:10:01 +00:00
|
|
|
import pytest
|
|
|
|
|
|
|
|
from datetime import datetime, timezone
|
|
|
|
|
2023-01-06 10:59:59 +00:00
|
|
|
from tests.common import (
|
|
|
|
generate_api_query,
|
|
|
|
assert_recovery_recent,
|
2023-01-06 13:09:54 +00:00
|
|
|
NearFuture,
|
|
|
|
RECOVERY_KEY_VALIDATION_DATETIME,
|
2023-01-06 10:59:59 +00:00
|
|
|
)
|
2023-01-11 17:02:01 +00:00
|
|
|
|
|
|
|
# Graphql API's output should be timezone-naive
|
2024-01-09 18:58:09 +00:00
|
|
|
from tests.common import ten_minutes_into_future_naive_utc as ten_minutes_into_future
|
|
|
|
from tests.common import ten_minutes_into_future as ten_minutes_into_future_tz
|
|
|
|
from tests.common import ten_minutes_into_past_naive_utc as ten_minutes_into_past
|
2023-01-11 17:02:01 +00:00
|
|
|
|
2023-11-22 18:13:07 +00:00
|
|
|
from tests.test_graphql.common import (
|
2023-01-06 11:57:51 +00:00
|
|
|
assert_empty,
|
2023-11-22 18:13:07 +00:00
|
|
|
get_data,
|
2023-01-06 11:57:51 +00:00
|
|
|
assert_ok,
|
2023-01-06 13:09:54 +00:00
|
|
|
assert_errorcode,
|
2023-01-06 11:57:51 +00:00
|
|
|
assert_token_valid,
|
2023-01-06 13:09:54 +00:00
|
|
|
assert_original,
|
2023-01-06 11:57:51 +00:00
|
|
|
graphql_get_devices,
|
|
|
|
set_client_token,
|
|
|
|
)
|
2022-06-29 17:39:46 +00:00
|
|
|
|
|
|
|
API_RECOVERY_QUERY = """
|
|
|
|
recoveryKey {
|
|
|
|
exists
|
|
|
|
valid
|
|
|
|
creationDate
|
|
|
|
expirationDate
|
|
|
|
usesLeft
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
|
2022-07-07 13:53:19 +00:00
|
|
|
|
2023-01-06 10:34:52 +00:00
|
|
|
def request_recovery_status(client):
|
|
|
|
return client.post(
|
2022-06-29 17:39:46 +00:00
|
|
|
"/graphql",
|
2022-07-07 13:53:19 +00:00
|
|
|
json={"query": generate_api_query([API_RECOVERY_QUERY])},
|
2022-06-29 17:39:46 +00:00
|
|
|
)
|
2023-01-06 10:34:52 +00:00
|
|
|
|
|
|
|
|
|
|
|
def graphql_recovery_status(client):
|
|
|
|
response = request_recovery_status(client)
|
2023-11-22 18:13:07 +00:00
|
|
|
data = get_data(response)
|
2023-01-06 10:34:52 +00:00
|
|
|
|
2023-11-22 18:13:07 +00:00
|
|
|
status = data["api"]["recoveryKey"]
|
2023-01-06 10:34:52 +00:00
|
|
|
assert status is not None
|
|
|
|
return status
|
|
|
|
|
|
|
|
|
2023-01-06 13:09:54 +00:00
|
|
|
def request_make_new_recovery_key(client, expires_at=None, uses=None):
|
|
|
|
json = {"query": API_RECOVERY_KEY_GENERATE_MUTATION}
|
|
|
|
limits = {}
|
|
|
|
|
|
|
|
if expires_at is not None:
|
|
|
|
limits["expirationDate"] = expires_at.isoformat()
|
|
|
|
if uses is not None:
|
|
|
|
limits["uses"] = uses
|
|
|
|
|
|
|
|
if limits != {}:
|
|
|
|
json["variables"] = {"limits": limits}
|
|
|
|
|
|
|
|
response = client.post("/graphql", json=json)
|
|
|
|
return response
|
|
|
|
|
|
|
|
|
|
|
|
def graphql_make_new_recovery_key(client, expires_at=None, uses=None):
|
|
|
|
response = request_make_new_recovery_key(client, expires_at, uses)
|
2023-11-22 18:13:07 +00:00
|
|
|
output = get_data(response)["api"]["getNewRecoveryApiKey"]
|
|
|
|
assert_ok(output)
|
|
|
|
|
|
|
|
key = output["key"]
|
2023-01-06 10:48:59 +00:00
|
|
|
assert key is not None
|
|
|
|
assert key.split(" ").__len__() == 18
|
|
|
|
return key
|
|
|
|
|
|
|
|
|
2023-01-06 13:09:54 +00:00
|
|
|
def request_recovery_auth(client, key, device_name):
|
|
|
|
return client.post(
|
2023-01-06 11:25:53 +00:00
|
|
|
"/graphql",
|
|
|
|
json={
|
|
|
|
"query": API_RECOVERY_KEY_USE_MUTATION,
|
|
|
|
"variables": {
|
|
|
|
"input": {
|
|
|
|
"key": key,
|
|
|
|
"deviceName": device_name,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
2023-01-06 13:09:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
def graphql_use_recovery_key(client, key, device_name):
|
|
|
|
response = request_recovery_auth(client, key, device_name)
|
2023-11-22 18:13:07 +00:00
|
|
|
output = get_data(response)["api"]["useRecoveryApiKey"]
|
|
|
|
assert_ok(output)
|
|
|
|
|
|
|
|
token = output["token"]
|
2023-01-06 11:25:53 +00:00
|
|
|
assert token is not None
|
2023-01-06 11:57:51 +00:00
|
|
|
assert_token_valid(client, token)
|
|
|
|
set_client_token(client, token)
|
2023-01-09 12:39:54 +00:00
|
|
|
assert device_name in [device["name"] for device in graphql_get_devices(client)]
|
2023-01-06 11:25:53 +00:00
|
|
|
return token
|
|
|
|
|
|
|
|
|
2024-01-09 18:58:09 +00:00
|
|
|
def test_graphql_recovery_key_status_unauthorized(client):
|
2023-01-06 10:34:52 +00:00
|
|
|
response = request_recovery_status(client)
|
2023-01-06 09:54:07 +00:00
|
|
|
assert_empty(response)
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2022-07-07 13:53:19 +00:00
|
|
|
|
2024-01-09 18:58:09 +00:00
|
|
|
def test_graphql_recovery_key_status_when_none_exists(authorized_client):
|
2023-01-06 10:34:52 +00:00
|
|
|
status = graphql_recovery_status(authorized_client)
|
|
|
|
assert status["exists"] is False
|
|
|
|
assert status["valid"] is False
|
|
|
|
assert status["creationDate"] is None
|
|
|
|
assert status["expirationDate"] is None
|
|
|
|
assert status["usesLeft"] is None
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2022-07-07 13:53:19 +00:00
|
|
|
|
2022-06-29 17:39:46 +00:00
|
|
|
API_RECOVERY_KEY_GENERATE_MUTATION = """
|
2022-07-08 15:28:08 +00:00
|
|
|
mutation TestGenerateRecoveryKey($limits: RecoveryKeyLimitsInput) {
|
2023-06-21 03:46:56 +00:00
|
|
|
api {
|
|
|
|
getNewRecoveryApiKey(limits: $limits) {
|
|
|
|
success
|
|
|
|
message
|
|
|
|
code
|
|
|
|
key
|
|
|
|
}
|
2022-06-29 17:39:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
|
|
|
|
|
|
|
API_RECOVERY_KEY_USE_MUTATION = """
|
|
|
|
mutation TestUseRecoveryKey($input: UseRecoveryKeyInput!) {
|
2023-06-21 03:46:56 +00:00
|
|
|
api {
|
|
|
|
useRecoveryApiKey(input: $input) {
|
|
|
|
success
|
|
|
|
message
|
|
|
|
code
|
|
|
|
token
|
|
|
|
}
|
2022-06-29 17:39:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
"""
|
2022-07-07 13:53:19 +00:00
|
|
|
|
|
|
|
|
2024-01-09 18:58:09 +00:00
|
|
|
def test_graphql_generate_recovery_key(client, authorized_client):
|
2023-01-06 13:09:54 +00:00
|
|
|
key = graphql_make_new_recovery_key(authorized_client)
|
2023-01-06 10:48:59 +00:00
|
|
|
|
2023-01-06 11:08:53 +00:00
|
|
|
status = graphql_recovery_status(authorized_client)
|
|
|
|
assert status["exists"] is True
|
|
|
|
assert status["valid"] is True
|
|
|
|
assert_recovery_recent(status["creationDate"])
|
|
|
|
assert status["expirationDate"] is None
|
|
|
|
assert status["usesLeft"] is None
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2023-01-06 11:57:51 +00:00
|
|
|
graphql_use_recovery_key(client, key, "new_test_token")
|
|
|
|
# And again
|
|
|
|
graphql_use_recovery_key(client, key, "new_test_token2")
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2022-07-07 13:53:19 +00:00
|
|
|
|
2023-11-10 17:10:01 +00:00
|
|
|
@pytest.mark.parametrize(
|
2024-01-09 18:58:09 +00:00
|
|
|
"expiration_date", [ten_minutes_into_future(), ten_minutes_into_future_tz()]
|
2023-11-10 17:10:01 +00:00
|
|
|
)
|
2022-07-07 13:53:19 +00:00
|
|
|
def test_graphql_generate_recovery_key_with_expiration_date(
|
2024-01-09 18:58:09 +00:00
|
|
|
client, authorized_client, expiration_date: datetime
|
2022-07-07 13:53:19 +00:00
|
|
|
):
|
2023-01-06 13:09:54 +00:00
|
|
|
key = graphql_make_new_recovery_key(authorized_client, expires_at=expiration_date)
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2023-01-06 13:09:54 +00:00
|
|
|
status = graphql_recovery_status(authorized_client)
|
|
|
|
assert status["exists"] is True
|
|
|
|
assert status["valid"] is True
|
|
|
|
assert_recovery_recent(status["creationDate"])
|
2023-11-10 17:10:01 +00:00
|
|
|
|
|
|
|
# timezone-aware comparison. Should pass regardless of server's tz
|
2023-11-13 16:15:12 +00:00
|
|
|
assert datetime.fromisoformat(status["expirationDate"]) == expiration_date.replace(
|
|
|
|
tzinfo=timezone.utc
|
|
|
|
)
|
2023-11-10 17:10:01 +00:00
|
|
|
|
2023-01-06 13:09:54 +00:00
|
|
|
assert status["usesLeft"] is None
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2023-01-06 13:09:54 +00:00
|
|
|
graphql_use_recovery_key(client, key, "new_test_token")
|
|
|
|
# And again
|
|
|
|
graphql_use_recovery_key(client, key, "new_test_token2")
|
2022-06-29 17:39:46 +00:00
|
|
|
|
|
|
|
|
2024-01-09 18:58:09 +00:00
|
|
|
def test_graphql_use_recovery_key_after_expiration(client, authorized_client, mocker):
|
|
|
|
expiration_date = ten_minutes_into_future()
|
2023-01-06 13:09:54 +00:00
|
|
|
key = graphql_make_new_recovery_key(authorized_client, expires_at=expiration_date)
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2023-01-06 13:09:54 +00:00
|
|
|
# Timewarp to after it expires
|
|
|
|
mock = mocker.patch(RECOVERY_KEY_VALIDATION_DATETIME, NearFuture)
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2023-01-06 13:09:54 +00:00
|
|
|
response = request_recovery_auth(client, key, "new_test_token3")
|
2023-11-22 18:13:07 +00:00
|
|
|
output = get_data(response)["api"]["useRecoveryApiKey"]
|
|
|
|
assert_errorcode(output, 404)
|
|
|
|
|
|
|
|
assert output["token"] is None
|
2023-01-06 13:09:54 +00:00
|
|
|
assert_original(authorized_client)
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2023-01-06 13:09:54 +00:00
|
|
|
status = graphql_recovery_status(authorized_client)
|
|
|
|
assert status["exists"] is True
|
|
|
|
assert status["valid"] is False
|
|
|
|
assert_recovery_recent(status["creationDate"])
|
2023-11-10 17:10:01 +00:00
|
|
|
|
|
|
|
# timezone-aware comparison. Should pass regardless of server's tz
|
2023-11-13 16:15:12 +00:00
|
|
|
assert datetime.fromisoformat(status["expirationDate"]) == expiration_date.replace(
|
|
|
|
tzinfo=timezone.utc
|
|
|
|
)
|
2023-01-06 13:09:54 +00:00
|
|
|
assert status["usesLeft"] is None
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2022-07-07 13:53:19 +00:00
|
|
|
|
2024-01-09 18:58:09 +00:00
|
|
|
def test_graphql_generate_recovery_key_with_expiration_in_the_past(authorized_client):
|
|
|
|
expiration_date = ten_minutes_into_past()
|
2023-01-09 12:17:36 +00:00
|
|
|
response = request_make_new_recovery_key(
|
|
|
|
authorized_client, expires_at=expiration_date
|
2022-06-29 17:39:46 +00:00
|
|
|
)
|
2023-01-09 12:17:36 +00:00
|
|
|
|
2023-11-22 18:13:07 +00:00
|
|
|
output = get_data(response)["api"]["getNewRecoveryApiKey"]
|
|
|
|
assert_errorcode(output, 400)
|
|
|
|
|
|
|
|
assert output["key"] is None
|
2023-01-09 12:54:10 +00:00
|
|
|
assert graphql_recovery_status(authorized_client)["exists"] is False
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2022-07-07 13:53:19 +00:00
|
|
|
|
2024-01-09 18:58:09 +00:00
|
|
|
def test_graphql_generate_recovery_key_with_invalid_time_format(authorized_client):
|
2022-06-29 17:39:46 +00:00
|
|
|
expiration_date = "invalid_time_format"
|
|
|
|
expiration_date_str = expiration_date
|
|
|
|
|
|
|
|
response = authorized_client.post(
|
|
|
|
"/graphql",
|
|
|
|
json={
|
|
|
|
"query": API_RECOVERY_KEY_GENERATE_MUTATION,
|
|
|
|
"variables": {
|
|
|
|
"limits": {
|
|
|
|
"expirationDate": expiration_date_str,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
)
|
2023-01-06 09:54:07 +00:00
|
|
|
assert_empty(response)
|
2023-01-09 12:54:10 +00:00
|
|
|
assert graphql_recovery_status(authorized_client)["exists"] is False
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2022-07-07 13:53:19 +00:00
|
|
|
|
2024-01-09 18:58:09 +00:00
|
|
|
def test_graphql_generate_recovery_key_with_limited_uses(authorized_client, client):
|
2023-01-09 12:39:54 +00:00
|
|
|
mnemonic_key = graphql_make_new_recovery_key(authorized_client, uses=2)
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2023-01-09 12:39:54 +00:00
|
|
|
status = graphql_recovery_status(authorized_client)
|
|
|
|
assert status["exists"] is True
|
|
|
|
assert status["valid"] is True
|
|
|
|
assert status["creationDate"] is not None
|
|
|
|
assert status["expirationDate"] is None
|
|
|
|
assert status["usesLeft"] == 2
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2023-01-09 12:39:54 +00:00
|
|
|
graphql_use_recovery_key(client, mnemonic_key, "new_test_token1")
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2023-01-09 12:39:54 +00:00
|
|
|
status = graphql_recovery_status(authorized_client)
|
|
|
|
assert status["exists"] is True
|
|
|
|
assert status["valid"] is True
|
|
|
|
assert status["creationDate"] is not None
|
|
|
|
assert status["expirationDate"] is None
|
|
|
|
assert status["usesLeft"] == 1
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2023-01-09 12:39:54 +00:00
|
|
|
graphql_use_recovery_key(client, mnemonic_key, "new_test_token2")
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2023-01-09 12:39:54 +00:00
|
|
|
status = graphql_recovery_status(authorized_client)
|
|
|
|
assert status["exists"] is True
|
|
|
|
assert status["valid"] is False
|
|
|
|
assert status["creationDate"] is not None
|
|
|
|
assert status["expirationDate"] is None
|
|
|
|
assert status["usesLeft"] == 0
|
|
|
|
|
|
|
|
response = request_recovery_auth(client, mnemonic_key, "new_test_token3")
|
2023-11-22 18:13:07 +00:00
|
|
|
output = get_data(response)["api"]["useRecoveryApiKey"]
|
|
|
|
assert_errorcode(output, 404)
|
2022-07-07 13:53:19 +00:00
|
|
|
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2024-01-09 18:58:09 +00:00
|
|
|
def test_graphql_generate_recovery_key_with_negative_uses(authorized_client):
|
2023-01-09 12:44:48 +00:00
|
|
|
response = request_make_new_recovery_key(authorized_client, uses=-1)
|
|
|
|
|
2023-11-22 18:13:07 +00:00
|
|
|
output = get_data(response)["api"]["getNewRecoveryApiKey"]
|
|
|
|
assert_errorcode(output, 400)
|
|
|
|
assert output["key"] is None
|
|
|
|
assert graphql_recovery_status(authorized_client)["exists"] is False
|
2022-06-29 17:39:46 +00:00
|
|
|
|
2022-07-07 13:53:19 +00:00
|
|
|
|
2024-01-09 18:58:09 +00:00
|
|
|
def test_graphql_generate_recovery_key_with_zero_uses(authorized_client):
|
2023-01-09 12:44:48 +00:00
|
|
|
response = request_make_new_recovery_key(authorized_client, uses=0)
|
|
|
|
|
2023-11-22 18:13:07 +00:00
|
|
|
output = get_data(response)["api"]["getNewRecoveryApiKey"]
|
|
|
|
assert_errorcode(output, 400)
|
|
|
|
assert output["key"] is None
|
2023-01-09 12:54:10 +00:00
|
|
|
assert graphql_recovery_status(authorized_client)["exists"] is False
|