diff --git a/endpoint/config.py b/endpoint/config.py index 75e6142..bdbfb6b 100644 --- a/endpoint/config.py +++ b/endpoint/config.py @@ -71,6 +71,7 @@ def getLogLevel(): logLevel = config.get('Settings', 'loglevel', fallback='INFO') return logging.getLevelName(logLevel) - logging.basicConfig(level=getLogLevel(), format='%(asctime)s - %(levelname)s - %(message)s') +# Suppress http-log +logging.getLogger('urllib3').setLevel(logging.INFO) diff --git a/endpoint/register/apple_cryptography.py b/endpoint/register/apple_cryptography.py index d2e74fe..222f6cd 100644 --- a/endpoint/register/apple_cryptography.py +++ b/endpoint/register/apple_cryptography.py @@ -41,13 +41,14 @@ def decode_tag(data): return {'lat': latitude, 'lon': longitude, 'conf': confidence, 'status': status} -def getAuth(regenerate=False, second_factor='sms'): +def getAuth(regenerate=False): if os.path.exists(config.getConfigFile()) and not regenerate: with open(config.getConfigFile(), "r") as f: j = json.load(f) else: + logger.info('Trying to login') mobileme = icloud_login_mobileme( - username=config.getUser(), password=config.getPass(), second_factor=second_factor) + username=config.getUser(), password=config.getPass()) logger.debug('Answer from icloud login') logger.debug(mobileme) @@ -73,4 +74,4 @@ def getAuth(regenerate=False, second_factor='sms'): def registerDevice(): logger.info(f'Trying to register new device.') - getAuth(regenerate=True, second_factor='trusted_device' 'sms') + getAuth(regenerate=True) diff --git a/endpoint/register/pypush_gsa_icloud.py b/endpoint/register/pypush_gsa_icloud.py index 0207197..943d0f8 100644 --- a/endpoint/register/pypush_gsa_icloud.py +++ b/endpoint/register/pypush_gsa_icloud.py @@ -32,12 +32,12 @@ logger = logging.getLogger() -def icloud_login_mobileme(username='', password='', second_factor='sms'): +def icloud_login_mobileme(username='', password=''): if not username: username = input('Apple ID: ') if not password: password = getpass('Password: ') - g = gsa_authenticate(username, password, second_factor) + g = gsa_authenticate(username, password) pet = g["t"]["com.apple.gs.idms.pet"]["token"] adsid = g["adsid"] @@ -55,21 +55,24 @@ def icloud_login_mobileme(username='', password='', second_factor='sms'): } headers.update(generate_anisette_headers()) - r = requests.post( + logger.info("Registering device after login") + resp = requests.post( "https://setup.icloud.com/setup/iosbuddy/loginDelegates", auth=(username, pet), data=data, headers=headers, verify=False, ) - return plist.loads(r.content) + response = f"HTTP-Code: {resp.status_code}\n{resp.text}" + logger.debug(response) + return plist.loads(resp.content) -def gsa_authenticate(username, password, second_factor='sms'): +def gsa_authenticate(username, password): # Password is None as we'll provide it later usr = srp.User(username, bytes(), hash_alg=srp.SHA256, ng_type=srp.NG_2048) _, A = usr.start_authentication() - + logger.info("Authentication request initialization") r = gsa_authenticated_request( {"A2k": A, "ps": ["s2k", "s2k_fo"], "u": username, "o": "init"}) @@ -87,17 +90,21 @@ def gsa_authenticate(username, password, second_factor='sms'): if M is None: logger.error("Failed to process challenge") return - - r = gsa_authenticated_request( + logger.info("Authentication request completion") + resp = gsa_authenticated_request( {"c": r["c"], "M1": M, "u": username, "o": "complete"}) # Make sure that the server's session key matches our session key (and thus that they are not an imposter) - usr.verify_session(r["M2"]) + if "M2" not in resp: + logger.error("Error on authentication") + logger.error(resp) + return + usr.verify_session(resp["M2"]) if not usr.authenticated(): logger.error("Failed to verify session") return - spd = decrypt_cbc(usr, r["spd"]) + spd = decrypt_cbc(usr, resp["spd"]) # For some reason plistlib doesn't accept it without the header... PLISTHEADER = b"""\ @@ -105,18 +112,16 @@ def gsa_authenticate(username, password, second_factor='sms'): """ spd = plist.loads(PLISTHEADER + spd) - if "au" in r["Status"] and r["Status"]["au"] in ["trustedDeviceSecondaryAuth", "secondaryAuth"]: + if "au" in resp["Status"] and resp["Status"]["au"] in ["trustedDeviceSecondaryAuth", "secondaryAuth"]: logger.info("2FA required, requesting code") # Replace bytes with strings for k, v in spd.items(): if isinstance(v, bytes): spd[k] = base64.b64encode(v).decode() - if second_factor == 'sms': - sms_second_factor(spd["adsid"], spd["GsIdmsToken"]) - elif second_factor == 'trusted_device': - trusted_second_factor(spd["adsid"], spd["GsIdmsToken"]) + sms_second_factor(spd["adsid"], spd["GsIdmsToken"]) + return gsa_authenticate(username, password) - elif "au" in r["Status"]: + elif "au" in resp["Status"]: logger.error(f"Unknown auth value {r['Status']['au']}") return else: @@ -144,6 +149,8 @@ def gsa_authenticated_request(parameters): verify=False, timeout=5, ) + response = f"HTTP-Code: {resp.status_code}\n{resp.text}" + logger.debug(response) return plist.loads(resp.content)["Response"] @@ -163,10 +170,6 @@ def generate_cpd(): def generate_anisette_headers(): - - - logger.debug( - f'Querying {config.getAnisetteServer()} for an anisette server') h = json.loads(requests.get(config.getAnisetteServer(), timeout=5).text) a = {"X-Apple-I-MD": h["X-Apple-I-MD"], "X-Apple-I-MD-M": h["X-Apple-I-MD-M"]} @@ -214,48 +217,6 @@ def decrypt_cbc(usr, data): return padder.update(data) + padder.finalize() -def trusted_second_factor(dsid, idms_token): - identity_token = base64.b64encode( - (dsid + ":" + idms_token).encode()).decode() - - headers = { - "Content-Type": "text/x-xml-plist", - "User-Agent": "Xcode", - "Accept": "text/x-xml-plist", - "Accept-Language": "en-us", - "X-Apple-Identity-Token": identity_token, - "X-Apple-App-Info": "com.apple.gs.xcode.auth", - "X-Xcode-Version": "11.2 (11B41)", - "X-Mme-Client-Info": ' ' - } - - headers.update(generate_anisette_headers()) - - # This will trigger the 2FA prompt on trusted devices - # We don't care about the response, it's just some HTML with a form for entering the code - # Easier to just use a text prompt - requests.get( - "https://gsa.apple.com/auth/verify/trusteddevice", - headers=headers, - verify=False, - timeout=10, - ) - - # Prompt for the 2FA code. It's just a string like '123456', no dashes or spaces - code = getpass("Enter 2FA code: ") - headers["security-code"] = code - - # Send the 2FA code to Apple - resp = requests.get( - "https://gsa.apple.com/grandslam/GsService2/validate", - headers=headers, - verify=False, - timeout=10, - ) - if resp.ok: - logger.info("2FA successful") - - def sms_second_factor(dsid, idms_token): identity_token = base64.b64encode( (dsid + ":" + idms_token).encode()).decode() @@ -302,5 +263,13 @@ def sms_second_factor(dsid, idms_token): verify=False, timeout=5, ) - if resp.ok: + header_string = "Header der Antwort:\n" + for header, value in response.headers.items(): + header_string += f"{header}: {value}\n" + response = f"HTTP-Code: {resp.status_code} with {header_string} bytes" + logger.debug(response) + # If the answer was too long, the output is the + if resp.ok and len(resp.text) < 100: logger.info("2FA successful") + else: + raise Exception("2FA unsuccessful. Maybe wrong code or wrong number. Check your account details.") diff --git a/macless_haystack/test/accessory/accessory_registry_test.dart b/macless_haystack/test/accessory/accessory_registry_test.dart index f43687c..8df1785 100644 --- a/macless_haystack/test/accessory/accessory_registry_test.dart +++ b/macless_haystack/test/accessory/accessory_registry_test.dart @@ -111,4 +111,56 @@ void main() { expect(locationHistory.elementAt(2).start, DateTime(2024, 1, 1, 12, 0, 0)); expect(locationHistory.elementAt(2).end, DateTime(2024, 1, 1, 12, 0, 0)); }); + + test('Add same location entries twice should not change latest anything', + () async { + List reports = []; + + // 8 o'clock 1st location + reports.add(FindMyLocationReport.withHash( + 1, + 2, + DateTime(2024, 1, 1, 8, 0, 0), + DateTime.now().microsecondsSinceEpoch.toString())); + //10 o'clock second location + reports.add(FindMyLocationReport.withHash( + 2, + 2, + DateTime(2024, 1, 1, 10, 0, 0), + DateTime.now().microsecondsSinceEpoch.toString())); + // 9 o'clock first location + reports.add(FindMyLocationReport.withHash( + 1, + 2, + DateTime(2024, 1, 1, 9, 0, 0), + DateTime.now().microsecondsSinceEpoch.toString())); + //12 o'clock 1st location + reports.add(FindMyLocationReport.withHash( + 1, + 2, + DateTime(2024, 1, 1, 12, 0, 0), + DateTime.now().microsecondsSinceEpoch.toString())); + await registry.fillLocationHistory(reports, accessory); + reports.shuffle(); + await registry.fillLocationHistory(reports, accessory); + var locationHistory = accessory.locationHistory; + expect(3, locationHistory.length); + + var latest = accessory.datePublished; + var lastLocation = accessory.lastLocation; + var endOfFirstEntry = accessory.latestHistoryEntry(); + + expect(endOfFirstEntry, DateTime(2024, 1, 1, 9, 0, 0)); + expect(latest, DateTime(2024, 1, 1, 12, 0, 0)); + expect(lastLocation, const LatLng(1, 2)); + + expect(locationHistory.elementAt(0).start, DateTime(2024, 1, 1, 8, 0, 0)); + expect(locationHistory.elementAt(0).end, DateTime(2024, 1, 1, 9, 0, 0)); + + expect(locationHistory.elementAt(1).start, DateTime(2024, 1, 1, 10, 0, 0)); + expect(locationHistory.elementAt(1).end, DateTime(2024, 1, 1, 10, 0, 0)); + + expect(locationHistory.elementAt(2).start, DateTime(2024, 1, 1, 12, 0, 0)); + expect(locationHistory.elementAt(2).end, DateTime(2024, 1, 1, 12, 0, 0)); + }); }