From 4075c41ecc07f03e1994ae1d0996907d767bd2d3 Mon Sep 17 00:00:00 2001 From: Andreas Thienemann Date: Thu, 13 Aug 2020 13:46:55 +0200 Subject: [PATCH] Add debugging example to CODE_SAMPLES.md (#295) Add an example invocation to CODE_SAMPLES that would allow for MITM interception as well as debug prints to the console. Drive-By: blacken the code samples. Drive-By: Make code samples compliant with python3 (print->print()) Fixes: #270 --- CODE_SAMPLES.md | 163 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 137 insertions(+), 26 deletions(-) diff --git a/CODE_SAMPLES.md b/CODE_SAMPLES.md index ad9b9fb..77448b1 100644 --- a/CODE_SAMPLES.md +++ b/CODE_SAMPLES.md @@ -25,56 +25,55 @@ import datetime from pyicloud import PyiCloudService -print('Setup Time Zone') -time.strftime('%X %x %Z') -os.environ['TZ'] = 'America/New_York' +print("Setup Time Zone") +time.strftime("%X %x %Z") +os.environ["TZ"] = "America/New_York" - -print('Py iCloud Services') -api = PyiCloudService('your@me.com', 'password') +print("Py iCloud Services") +api = PyiCloudService("your@me.com", "password") if api.requires_2fa: - print "Two-factor authentication required. Your trusted devices are:" + print("Two-factor authentication required. Your trusted devices are:") devices = api.trusted_devices for i, device in enumerate(devices): - print " %s: %s" % (i, device.get('deviceName', "SMS to %s" % - device.get('phoneNumber'))) + print( + " %s: %s" + % (i, device.get("deviceName", "SMS to %s" % device.get("phoneNumber"))) + ) - device = click.prompt('Which device would you like to use?', default=0) + device = click.prompt("Which device would you like to use?", default=0) device = devices[device] if not api.send_verification_code(device): - print "Failed to send verification code" + print("Failed to send verification code") sys.exit(1) - code = click.prompt('Please enter validation code') + code = click.prompt("Please enter validation code") if not api.validate_verification_code(device, code): - print "Failed to verify verification code" + print("Failed to verify verification code") sys.exit(1) - + # # Devices # -print('Devices') +print("Devices") print(api.devices) print(api.devices[0]) print(api.iphone) - # # Location # -print('Location') +print("Location") print(api.iphone.location()) - # # Status # -print('Status') +print("Status") print(api.iphone.status()) # @@ -86,7 +85,7 @@ print(api.iphone.status()) # # Events # -print('Events') +print("Events") print(api.calendar.events()) from_dt = datetime.date(2018, 1, 1) to_dt = datetime.date(2018, 1, 31) @@ -96,17 +95,129 @@ print(api.calendar.events(from_dt, to_dt)) # ======== # Contacts # ======== -print('Contacts') +print("Contacts") for c in api.contacts.all(): - print c.get('firstName'), c.get('phones') - - + print(c.get("firstName"), c.get("phones")) + + # ======================= # File Storage (Ubiquity) # ======================= -# You can access documents stored in your iCloud account by using the +# You can access documents stored in your iCloud account by using the # ``files`` property's ``dir`` method: -print('File Storage') +print("File Storage") print(api.files.dir()) ``` + +## From [@ixs](https://github.com/ixs) + +### Debug build of pyicloud + +This example allows to use tools like mitmproxy, fiddler, charles or similiar +things to debug the data sent on the wire. + +In addition, the underlying requests module and the http.client are asked, to +output all data sent and received to stdout. + +This uses code taken from [How do I disable the security certificate check in Python requests](https://stackoverflow.com/questions/15445981/how-do-i-disable-the-security-certificate-check-in-python-requests) +and [Log all requests from the python-requests module](https://stackoverflow.com/questions/16337511/log-all-requests-from-the-python-requests-module) + + +```python +#!/usr/bin/env python3 + +import contextlib +import http.client +import logging +import requests +import warnings +from pprint import pprint +from pyicloud import PyiCloudService +from urllib3.exceptions import InsecureRequestWarning + +# Handle certificate warnings by ignoring them +old_merge_environment_settings = requests.Session.merge_environment_settings + + +@contextlib.contextmanager +def no_ssl_verification(): + opened_adapters = set() + + def merge_environment_settings(self, url, proxies, stream, verify, cert): + # Verification happens only once per connection so we need to close + # all the opened adapters once we're done. Otherwise, the effects of + # verify=False persist beyond the end of this context manager. + opened_adapters.add(self.get_adapter(url)) + + settings = old_merge_environment_settings( + self, url, proxies, stream, verify, cert + ) + settings["verify"] = False + + return settings + + requests.Session.merge_environment_settings = merge_environment_settings + + try: + with warnings.catch_warnings(): + warnings.simplefilter("ignore", InsecureRequestWarning) + yield + finally: + requests.Session.merge_environment_settings = old_merge_environment_settings + + for adapter in opened_adapters: + try: + adapter.close() + except: + pass + + +# Monkeypatch the http client for full debugging output +httpclient_logger = logging.getLogger("http.client") + + +def httpclient_logging_patch(level=logging.DEBUG): + """Enable HTTPConnection debug logging to the logging framework""" + + def httpclient_log(*args): + httpclient_logger.log(level, " ".join(args)) + + # mask the print() built-in in the http.client module to use + # logging instead + http.client.print = httpclient_log + # enable debugging + http.client.HTTPConnection.debuglevel = 1 + + +# Enable general debug logging +logging.basicConfig(level=logging.DEBUG) + +httpclient_logging_patch() + +api = PyiCloudService(username, password) +if api.requires_2sa: + print("Two-factor authentication required. Your trusted devices are:") + + devices = api.trusted_devices + for i, device in enumerate(devices): + print( + " %s: %s" + % (i, device.get("deviceName", "SMS to %s") % device.get("phoneNumber")) + ) + + device = click.prompt("Which device would you like to use?", default=0) + device = devices[device] + if not api.send_verification_code(device): + print("Failed to send verification code") + sys.exit(1) + + code = click.prompt("Please enter validation code") + if not api.validate_verification_code(device, code): + print("Failed to verify verification code") + sys.exit(1) + +# This request will not fail, even if using intercepting proxies. +with no_ssl_verification(): + pprint(api.account) +```