Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion python/exporter/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,9 +480,15 @@ async def sign_in(arguments: argparse.Namespace):

# Named as it will actually appear. The model and OS come from FindMy.py, which presents as a
# MacBook Pro, so somebody who was only told the serial goes looking for a device they own.
#
# **Read off the account rather than written here.** Serials are drawn per install, so a
# literal in this sentence names a device the user does not have and will not find - which is
# worse than saying nothing, because they go looking and conclude the entry is somebody
# else's. The same mistake as the screen this replaced in the Android app.
print("\nSigning in registers this exporter as a device on your Apple account. It appears in",
file=sys.stderr)
print("your device list as a MacBook Pro on macOS 13.4.1, serial 0PENTAGXPORT - that is this",
print("your device list as a MacBook Pro on macOS 13.4.1, serial "
f"{account.serial} - that is this",
file=sys.stderr)
print("program, not a Mac you own. Remove it any time at account.apple.com > Devices.\n",
file=sys.stderr)
Expand Down
6 changes: 5 additions & 1 deletion python/exporter/wizard.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,10 +466,14 @@ async def _read_icloud(self, asker: Asker):
# Said before the password is asked for, and naming the entry as it will appear: the
# model and OS come from FindMy.py, which presents as a MacBook Pro, so somebody told
# only the serial goes looking for a Mac they own.
#
# **Read off the account rather than written here.** Serials are drawn per install, so
# a literal names a device this user does not have, and they go looking for it and
# conclude the entry belongs to somebody else.
asker.ask(lambda: messagebox.showinfo(
"Signing in registers a device",
"Signing in adds an entry to your Apple account's device list: a MacBook Pro on"
" macOS 13.4.1, serial 0PENTAGXPORT.\n\n"
f" macOS 13.4.1, serial {account.serial}.\n\n"
"That is this program, not a Mac you own. You can remove it at any time at"
" account.apple.com under Devices, from any browser.",
))
Expand Down
28 changes: 28 additions & 0 deletions python/test/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,8 +285,19 @@ async def _nothing_ticked(*_args, **_kwargs):
class FakeAccount:
"""Enough of an `AsyncAppleAccount` for the sign-in flow: it can be closed, and it says so."""

SERIAL = "0PENTAGXK7QX"
"""
A drawn serial, deliberately not `0PENTAGXPORT`.

The notice printed before the password prompt used to write the constant into the sentence by
hand, and this fake had no `serial` at all - so nothing could tell the difference between the
sentence naming the account and the sentence naming a literal. It named a literal for as long
as serials were constant, and kept naming it afterwards.
"""

def __init__(self) -> None:
self.closed = False
self.serial = FakeAccount.SERIAL

async def close(self) -> None:
self.closed = True
Expand Down Expand Up @@ -348,6 +359,23 @@ class TestRememberingTheDevice:
not recognise. See `exporter.device`.
"""

def test_the_notice_names_the_serial_this_run_presents(self, apple, capsys):
"""
The sentence shown before the password prompt tells the user what to look for.

**It used to be a literal**, written when every install shared one serial and left there
when they stopped. A user on a drawn serial was told to look for `0PENTAGXPORT`, would not
find it in their device list, and would reasonably conclude the entry they did find
belonged to somebody else - which is the belief that gets it removed, taking the session
with it. The same defect was fixed on the Android side's registered-device screen.
"""
asyncio.run(cli.sign_in(signing_in()))

notice = capsys.readouterr().err

assert FakeAccount.SERIAL in notice
assert "0PENTAGXPORT" not in notice

def test_an_ordinary_sign_in_is_remembered(self, apple):
# The path almost every run takes, and the one that was storing nothing: the call sat in
# the terms handler, so only an account with unaccepted terms kept its identity.
Expand Down
Loading