#!/usr/bin/env python3 """Regression tests for FIFA 17's account-scoped phishing/security gate.""" import importlib import json import os import sys import tempfile TOOLS = os.path.dirname(os.path.abspath(__file__)) if TOOLS not in sys.path: sys.path.insert(0, TOOLS) DEVICE_ID = "1" * 32 TRANSFORMED_ANSWER = "a" * 32 # sanitized replay value, not a real answer class Request: def __init__(self, method, path, sid=None): self.command = method self.path = path self.headers = {"X-UT-SID": sid} if sid is not None else {} self._body = b"" def request(utas_server, method, suffix, sid=None): sid = utas_server.SID if sid is None else sid h = Request(method, "/ut/game/fifa17/phishing/" + suffix, sid) return utas_server.security_question_route(h) def profile(state, persona_id): path = os.path.join(state, "accounts", str(persona_id), "fifa17_profile.json") with open(path) as f: return json.load(f) def main(): with tempfile.TemporaryDirectory() as state: os.environ["FUT_ACCOUNT_PATH"] = os.path.join(state, "active_account.json") os.environ["FUT_PROFILE_ROOT"] = os.path.join(state, "accounts") os.environ.pop("FUT_PROFILE", None) import fut_account import fut_store import fut_accounts import utas_server importlib.reload(fut_account) importlib.reload(fut_store) importlib.reload(fut_accounts) importlib.reload(utas_server) # New/missing state: launcher account selection initializes one account only. fut_accounts.activate({"personaId": 771001, "personaName": "SEC_A"}) p = profile(state, 771001) assert p["securityQuestion"] == {"version": 1, "verified": True} # Actual trusted-device response fields parsed by CardsDLL 0x18012a170. code, body = request( utas_server, "GET", "trusteddevice?deviceId=" + DEVICE_ID) assert code == 200 assert body == { "changed": False, "exists": True, "locked": False, "trusted": True, } # Existing initialized state survives a fresh Store instance/process view. reopened = fut_store.Store(fut_store.profile_path_for(771001)) assert reopened.profile()["securityQuestion"] == { "version": 1, "verified": True} # FIFA's observed repeat-session request: POST, empty body, opaque 32-hex # deviceId and transformed answer in the query string. The answer is accepted # for OpenFUT compatibility but never persisted. code, body = request( utas_server, "POST", "validate?deviceId=%s&answer=%s" % (DEVICE_ID, TRANSFORMED_ANSWER), ) assert (code, body) == (200, {}) saved = profile(state, 771001) assert TRANSFORMED_ANSWER not in json.dumps(saved) # Question lookup uses the three fields parsed by CardsDLL 0x180129850. code, body = request( utas_server, "GET", "question?deviceId=" + DEVICE_ID) assert code == 200 assert set(body) == {"question", "attempts", "recoverAttempts"} assert all(isinstance(body[k], int) for k in body) # Malformed values/methods and missing sessions fail explicitly. code, _ = request(utas_server, "POST", "validate?deviceId=bad&answer=bad") assert code == 400 code, _ = request( utas_server, "DELETE", "trusteddevice?deviceId=" + DEVICE_ID) assert code == 405 h = Request( "GET", "/ut/game/fifa17/phishing/trusteddevice?deviceId=" + DEVICE_ID) code, _ = utas_server.security_question_route(h) assert code == 400 # Ordinary request logging must redact answer query values. raw_path = "/ut/game/fifa17/phishing/validate?deviceId=%s&answer=%s" % ( DEVICE_ID, TRANSFORMED_ANSWER) safe_path = utas_server.safe_request_path(raw_path) assert TRANSFORMED_ANSWER not in safe_path assert "answer=%5BREDACTED%5D" in safe_path # Multiple profiles receive independent persisted state; selecting B must not # alter A's initialized record. fut_accounts.activate({"personaId": 771002, "personaName": "SEC_B"}) assert profile(state, 771002)["securityQuestion"] == { "version": 1, "verified": True} assert profile(state, 771001)["securityQuestion"] == { "version": 1, "verified": True} # Legacy profile with the field removed is repaired once and persisted. b_path = os.path.join(state, "accounts", "771002", "fifa17_profile.json") b = profile(state, 771002) b.pop("securityQuestion") with open(b_path, "w") as f: json.dump(b, f) fut_store.STORE._p = None code, body = request( utas_server, "GET", "trusteddevice?deviceId=" + DEVICE_ID) assert code == 200 and body["exists"] and body["trusted"] assert profile(state, 771002)["securityQuestion"]["verified"] is True print("security-question compatibility: PASS") if __name__ == "__main__": main()