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

Update icloudpd code with latest from PyiCloud, add 2FA auth option #733

Open
wants to merge 12 commits into
base: master
Choose a base branch
from

Conversation

cfurrow
Copy link

@cfurrow cfurrow commented Dec 9, 2023

This is a work-in-progress (WIP) PR. Not ready for merging yet.

I've not followed all guidelines in https://github.com/icloud-photos-downloader/icloud_photos_downloader/blob/master/CONTRIBUTING.md#pull-request-process yet

Purpose

Start to debug #729 and gather information on a fix. It seems that the issues may be around the latest code from PyiCloud not being included into pyicloud-ipd.

  • Python is not my primary language
  • I'm not familiar with PyiCloud or icloudpd

A PR can be easier to review than diffs/patches or branches, so I'm creating a WIP PR to track my investigation progress.

Summary

  • Pulled down latest code changes from PyiCloud and inserted into the src/pyicloud_ipd directory.
  • Updated code paths to use new PyiCloud changes.
  • Added new code path for entering 2FA codes directly, without selecting SMS/2FA. There's apparently a non-trivial difference between the PyiCloud's treatment of 2SA (2-step auth) vs 2FA (2-factor auth). For my own setup, I required 2FA over 2SA.

TODO

  • Removed "domain" selection code, so this will only work with icloud.com, and not icloud.cn. Will need to address this in this PR, or in a follow-up PR.
  • Identify and fix broken/outdated automated tests (and VCR cassettes)
  • Add new automated tests for 2FA
  • Update USAGE/docs/etc to remove mentions of library selection (does not seem to be supported anymore)
  • Address/Remove any TODO comments I added.

Manual Testing

docker build . -t icloudpd_dev

docker run -it \
  --rm --name icloudpd \
  -v ~/icloudpd-download:/data \
  -e TZ=America/New_York \
  icloudpd_dev \
    icloudpd \
    --directory /data \
    --username [email protected] \
    --watch-with-interval 3600

2023-12-09 16:23:42 DEBUG    Authenticating...
iCloud Password:
2023-12-09 16:24:00 INFO     Two-factor authentication is required
Please enter two-factor authentication code: 123456
2023-12-09 16:24:07 WARNING  Failed to parse response with JSON mimetype
2023-12-09 16:24:10 INFO     Great, you're all set up. The script can now be run without user interaction until 2SA expires.
You can set up email notifications for when the two-step authentication expires.
(Use --help to view information about SMTP options.)
2023-12-09 16:24:13 DEBUG    Looking up all photos and videos from album All Photos...
2023-12-09 16:24:13 INFO     Downloading 81324 original photos and videos to /data ...
2023-12-09 16:24:16 DEBUG    Downloading /data/2023/12/09/IMG_1083.MOV...
2023-12-09 16:24:18 INFO     Downloaded /data/2023/12/09/IMG_1083.MOV
2023-12-09 16:24:18 DEBUG    Downloading /data/2023/12/09/IMG_1082.JPG...

...snipped...

@AndreyNikiforov
Copy link
Collaborator

I've also not followed the guidelines re: updating pyicloud_ipd:

If you need to make any changes to the pyicloud library, icloudpd uses a fork of this library that has been renamed to pyicloud-ipd. Please clone my pyicloud fork and check out the pyicloud-ipd branch. PRs should be based on the pyicloud-ipd branch and submitted to icloud-photos-downloader/pyicloud.

It is not longer the case, as you probably figured out yourself: pyicloud_ipd is just subfolder now, not a separate lib (I updated contribution doc). One option is to make it a lib again and reference original pyicloud.

Context: pyicloud was forked because original was dormant. Once work in original pyicloud resurrected, fork was never reconciled with original, mostly because all attempts were facing many broken tests in icloudpd.

@cfurrow cfurrow force-pushed the update-icloudpd-code-with-latest branch from 5f5a83f to b89b99e Compare December 9, 2023 19:08
@cfurrow
Copy link
Author

cfurrow commented Dec 9, 2023

It is not longer the case, as you probably figured out yourself: pyicloud_ipd is just subfolder now, not a separate lib (I updated contribution doc). One option is to make it a lib again and reference original pyicloud.

I wasn't sure if the other fork was used, and then copied into this subfolder. I can keep my changes as-is for now inside this repo's subfolder. Thanks!

Context: pyicloud was forked because original was dormant. Once work in original pyicloud resurrected, fork was never reconciled with original, mostly because all attempts were facing many broken tests in icloudpd.

Ugh, I bet. I've not gone back to look at the tests I've broken yet, as it seems like it'll be a pain once I get to that stage. VCR cassette updates, expectation updates, etc. That's a problem for future me 😉

@cfurrow cfurrow force-pushed the update-icloudpd-code-with-latest branch 2 times, most recently from 97fa083 to bb7ba68 Compare December 9, 2023 20:06
@cfurrow
Copy link
Author

cfurrow commented Dec 9, 2023

Got a little further along after doing a copy/paste of upstream icloudpd files into the src/icloud_ipd folder.

Now my docker output is this after asking for password, then 2FA code. I entered in my email, password, and valid 2FA code after being prompted on my mac:

scripts/build_docker

docker run -it \
  --rm --name icloudpd \
  -v ~/icloudpd-download:/data \
  -e TZ=America/New_York \
  icloudpd/icloudpd:latest \
    icloudpd \
    --directory /data \
    --username [email protected] \
    --watch-with-interval 3600

2023-12-09 15:05:57 DEBUG    Authenticating...
iCloud Password:
2023-12-09 15:06:02 INFO     Two-step/two-factor authentication is required
  0: SMS to *******
  1: Enter two-factor authentication code
Please choose an option: [0]: 1
Please enter two-factor authentication code: 123456 
2023-12-09 15:06:10 ERROR    Not Found (404)
2023-12-09 15:06:10 ERROR    Session trust failed.
2023-12-09 15:06:10 ERROR    Failed to verify two-factor authentication code

@cfurrow
Copy link
Author

cfurrow commented Dec 9, 2023

Ok, some more progress:

docker build . -t icloudpd_test

docker run -it \
  --rm --name icloudpd_test \
  -v ~/icloudpd-download:/data \
  -e TZ=America/New_York \
  icloudpd/icloudpd:latest \
    icloudpd \
    --directory /data \
    --username [email protected] \
    --watch-with-interval 3600

2023-12-09 16:23:42 DEBUG    Authenticating...
iCloud Password:
2023-12-09 16:24:00 INFO     Two-factor authentication is required
Please enter two-factor authentication code: 123456
2023-12-09 16:24:07 WARNING  Failed to parse response with JSON mimetype
2023-12-09 16:24:10 INFO     Great, you're all set up. The script can now be run without user interaction until 2SA expires.
You can set up email notifications for when the two-step authentication expires.
(Use --help to view information about SMTP options.)
2023-12-09 16:24:13 DEBUG    Looking up all photos and videos from album All Photos...
2023-12-09 16:24:13 INFO     Downloading 81324 original photos and videos to /data ...
2023-12-09 16:24:16 DEBUG    Downloading /data/2023/12/09/IMG_1083.MOV...
2023-12-09 16:24:18 INFO     Downloaded /data/2023/12/09/IMG_1083.MOV
2023-12-09 16:24:18 DEBUG    Downloading /data/2023/12/09/IMG_1082.JPG...

...then I stopped it with Control-C...

specify log level with --log (default is WARNING)
allow passing of individual test files
…/base.py

this removes the domain logic to switch between China and US icloud domains, which will have to be added back in later
… arguments

updated arg list to remove domain, and set password=password.
if 2fa is required, that is not the same as 2sa being required.
required to filter if photo or movie, etc
@cfurrow cfurrow force-pushed the update-icloudpd-code-with-latest branch from cfd9b6b to c706dc5 Compare December 9, 2023 21:34
Comment on lines -6 to +8
[[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
[[ $1 = /* ]] && echo "$1" || echo "$PWD/${1#./}"
Copy link
Author

@cfurrow cfurrow Dec 9, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My editor and I will have words about this... Ugh. Not quite sure what is off here for my tabs/spaces/whatever. Does not look this bad in my editor.

@@ -50,6 +57,22 @@ def authenticate_(
return authenticate_


def request_2fa(icloud: pyicloud_ipd.PyiCloudService, logger: logging.Logger):
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is new, and significant. Before this was here, the request_2sa codepath was executed, and I'd select that I wanted a 2fa sent to my devices vs an SMS message. I'd get the 2FA prompt on my laptop, and enter the 2FA code into the icloudpd prompt, but it would always fail to validate.

But, after I added this code, it seems to work as I'd expect. I enter my password, and get immediately prompted on my macos to validate the new device, and then given a 2FA code that I enter in to icloudpd, which then validates it and starts downloading photos/videos.

I do not completely understand the difference in API calls between 2sa and 2fa, but, it's significant enough to get it to pass one way (2fa) and fail the other (2sa). 🤷🏻

@@ -87,7 +110,7 @@ def request_2sa(icloud: pyicloud_ipd.PyiCloudService, logger: logging.Logger):

code = click.prompt("Please enter two-factor authentication code")
if not icloud.validate_verification_code(device, code):
logger.error("Failed to verify two-factor authentication code")
logger.error(f"Failed to verify two-factor authentication code for device \"{device.get('deviceName')}\"")
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll remove this extra device info--I added for debugging, but it wasn't helpful because it was returning None.

libraries_dict = icloud.photos.libraries
library_names = libraries_dict.keys()
print(*library_names, sep="\n")
# TODO: selecting library not supported anymore! Need to adjust USAGE, docs, etc
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is another significant change. The latest PyiCloud does not support libraries, so we have to use the default photos.all. I'll need to update any CLI args/USAGE docs to remove the ability to select libraries, as it's not supported anymore... Or so it would seem.

Copy link
Contributor

@scaraebeus scaraebeus Dec 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually a case where the pyicloud_ipd fork added functionality that was never implemented in the original 'pyicloud' project. There is PR picklepete/pyicloud#407 to merge this into the 'pyicloud' project, but that seems to be a bit of a dead project now. There is a bit of cross merging that needs to occur to truly get the latest features on both sides. I wouldn't necessarily remove this feature from this project.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wondered if it was a patch added after the fact.

The intention was not to leave out all of the things I removed (domain selection, library selection, etc), the intention was to address the bug first, and then figure out what we can do about the rest once the project was in a more working state again.

@cfurrow
Copy link
Author

cfurrow commented Dec 9, 2023

Stepping away for now.
Open to ideas/help in fixing tests, and adding back support for library selection, domain selection (.com/.cn), etc.

Regarding domains, that may be something that could be nice to include upstream in PyiCloud, and not have to patch it here. Maybe they've already discussed it there, and decided against supporting both.

@cfurrow cfurrow changed the title Update icloudpd code with latest from PyiCloud Update icloudpd code with latest from PyiCloud, add 2FA auth option Dec 9, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants