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
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ chaquopy {
// wheel for desktop platforms and a pure-Python `py3-none-any` one as well.
// There is no Android wheel, so pip falls back to the pure-Python build - which
// is correct but markedly slower. The messages here are small enough not to care.
install("git+https://github.com/parawanderer/FindMy.py@eda897ba995a21ceaeac427efd8edcd8625f3618")
install("git+https://github.com/parawanderer/FindMy.py@d956fc8b2679be56b0f4b0053940c5091fc0f1bb")

install("NSKeyedUnArchiver==1.5")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,45 @@ public void appleDecliningIsNotBlamedOnThePasswordOrTheNetwork() {
not(withText(containsString("Grand Slam")))));
}

/**
* <b>iCloud being refused for the account is not reported as terms of service.</b>
*
* <p><b>Issue #221.</b> Apple took the password and the code, then its {@code mobileme}
* delegate refused the account on its own {@code status} with nothing on the
* {@code localizedError} channel terms arrive on. Every delegate failure was classified as
* terms pending, so the reporter - whose terms were fine, as iCloud on the web confirmed -
* was sent to a document list with nothing in it and told to accept something.
*
* <p>Asserted on four wrong turns, because each sends somebody somewhere different: not the
* terms sentence, not "Apple declined" (which says to wait, and here waiting does not work),
* not the raw delegate text, and the Anisette-server offer stays hidden - a different machine
* identity changes nothing about an account Apple will not open.
*/
@Test
public void icloudBeingRefusedIsNotReportedAsTermsOfService() {
this.apple = FakeAppleAuthService.icloudIsRefusedForTheAccount();
AppDependencies.replaceAuthService(this.apple);

launch();
signIn();

final android.content.Context context = getInstrumentation().getTargetContext();

Eventually.check(() -> onView(withId(R.id.login_error_message_text)).check(matches(
withText(context.getString(R.string.login_failed_icloud_refused)))));

onView(withId(R.id.login_error_message_text)).check(matches(
not(withText(context.getString(R.string.login_failed_terms)))));
onView(withId(R.id.login_error_message_text)).check(matches(
not(withText(context.getString(R.string.login_failed_apple_declined)))));
onView(withId(R.id.login_error_message_text)).check(matches(
not(withText(containsString("com.apple.mobileme")))));

// The server route is for a refused sign-in, and this sign-in was not refused.
onView(withId(R.id.login_error_try_remote_anisette))
.check(matches(not(isDisplayed())));
}

/**
* <b>When Apple names a wait, the screen gives it rather than "try again shortly".</b>
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,29 @@ public static FakeAppleAuthService appleIsDeclining() {
return fake;
}

/**
* Apple signs the user in and then will not open iCloud for the account.
*
* <p><b>Issue #221.</b> The message is the real one, word for word off the reporter's screen:
* the delegate's own {@code status}, with nothing on the {@code localizedError} channel that
* terms arrive on. Every delegate failure used to be reported as terms pending, so this
* account - whose terms were fine - was shown an empty document list and told to accept
* something.
*
* <p>Carries no account, which is what keeps it out of the terms flow: {@code
* hasTermsToAccept()} needs both the reason and the session.
*/
public static FakeAppleAuthService icloudIsRefusedForTheAccount() {
final FakeAppleAuthService fake =
new FakeAppleAuthService(LOGIN_STATE.LOGGED_OUT, null);
fake.loginFailsWith = new PythonAccountLoginException(
"The com.apple.mobileme delegate request failed, reporting status=1,"
+ " status-message='A server problem is blocking Apple ID sign in."
+ " Try signing in later.'",
PythonAccountLoginException.REASON_ICLOUD_REFUSED);
return fake;
}

/**
* Apple declining, and saying how long to leave it - a {@code Retry-After} on the refusal.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,13 @@ private String describeLoginFailure(final Throwable error) {
return this.getString(R.string.login_failed_apple_declined);
}

// Apple took the password and the code, then would not open iCloud for the account. Kept
// apart from the terms sentence because the remedy is elsewhere entirely, and apart from
// "Apple declined" because that one says to wait - see REASON_ICLOUD_REFUSED.
if (PythonAccountLoginException.REASON_ICLOUD_REFUSED.equals(reason)) {
return this.getString(R.string.login_failed_icloud_refused);
}

// Reached when the terms path was tried and produced nothing to accept, so the sentence
// says what Apple said and then that accepting terms will not fix something else -
// rather than asserting a cause that has not been established.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,25 @@ public class PythonAccountLoginException extends RuntimeException {
*/
public static final String REASON_TERMS = "terms";

/** Anything not recognised. The detail is shown as-is rather than guessed at. */
/**
* Apple signed the user in and then refused to open iCloud. Matches
* {@code REASON_ICLOUD_REFUSED}.
*
* <p><b>Split out of {@link #REASON_TERMS}, which used to swallow it.</b> Every delegate
* failure was reported as terms pending, because terms were the only cause with a remedy.
* The response has two error channels and terms arrive on only one; when that channel is
* empty, the delegate refused the account itself and the terms flow has nothing to show.
* Issue #221 is somebody whose terms were fine being shown an empty document list.
*
* <p>Carries no account, deliberately: unlike terms, there is nothing further to do with
* the session from here.
*
* <p><b>Not {@link #REASON_APPLE_DECLINED}</b>, which advises waiting. Apple's own wording
* here says to try later, and across the clients sharing this sign-in path it does not
* clear on its own - so repeating that advice sends somebody to retry forever.
*/
public static final String REASON_ICLOUD_REFUSED = "icloud_refused";

/**
* Apple answered and refused to serve. Matches {@code REASON_APPLE_DECLINED}.
*
Expand All @@ -42,6 +60,7 @@ public class PythonAccountLoginException extends RuntimeException {
*/
public static final String REASON_APPLE_DECLINED = "apple_declined";

/** Anything not recognised. The detail is shown as-is rather than guessed at. */
public static final String REASON_UNKNOWN = "unknown";

private final String reason;
Expand Down
43 changes: 42 additions & 1 deletion app/src/main/python/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,34 @@ def assertAnisetteIsSupported(serializedAccountData: str) -> str | None:
screen that keeps refusing them for a reason nothing tells them.
"""

REASON_ICLOUD_REFUSED = "icloud_refused"
"""
Apple accepted the sign-in and then refused to open iCloud for this account.

**Authentication worked.** The password was right, the second factor was right, and the
`com.apple.mobileme` delegate exchange that follows them is what failed - so nothing on the
sign-in screen is wrong and re-entering it changes nothing.

**Split out of :data:`REASON_TERMS`, which used to swallow it.** Every `MobileMeDelegateError`
was reported as terms pending, because terms were the only cause anybody had a remedy for. The
response has two independent error channels and terms arrive on only one of them: when
`localizedError` is absent, the delegate refused the account on its own `status` and the terms
flow has nothing to show. Reported as issue #221, where somebody whose terms were fine was shown
an empty document list and told to accept some.

**What it does mean is not established, so the screen does not assert one.** It is met across
every client sharing this sign-in path, on Apple IDs that have never been used with an Apple
device - macless-haystack#84, #86 and #87, where the same delegate status arrives both with
"A server problem is blocking Apple ID sign in" and with "Account limit reached". The advice
that circulates there is to fill the account out at appleid.apple.com, and that is offered as
the thing to try rather than as the answer.

**Deliberately not :data:`REASON_APPLE_DECLINED`.** That one says wait and try again, which is
also what Apple's own wording here says - and across those clients it does not clear on its own.
Sending somebody back to retry an unchanged sign-in indefinitely is worse than saying plainly
that the account looks like the problem.
"""

REASON_APPLE_DECLINED = "apple_declined"
"""
Apple answered, and refused to serve the request. Not the password, and not the network.
Expand Down Expand Up @@ -506,8 +534,21 @@ def classifyLoginFailure(error: BaseException) -> str:
# "terms pending" is not established**, so this reports the possibility rather than asserting
# it - the screen offers to fetch them and says plainly that if the cause is something else,
# accepting terms will not fix it. The desktop CLI makes the same judgement the same way.
#
# **But only when the response used that channel at all.** `localizedError` is where a
# response explains itself in words, and where terms arrive; the delegate's own `status`
# fails independently of it. Treating both as terms sent somebody with perfectly good terms
# to an empty document list and told them to accept something - issue #221. `status`-only
# failures are a refusal of the account, and get their own sentence.
#
# **Reads `localized_error` rather than `names_a_localized_error`, on purpose.** The property
# is the nicer spelling and says exactly this, but it landed in the fork after the commit
# pinned in `app/build.gradle.kts`, and the attribute behind it has been there all along. So
# this works on the pinned version rather than requiring the pin to move first - which would
# drag rule 14's four files into a fix that does not otherwise need them. Switching to the
# property is safe whenever the pin next moves; both spellings coexist.
if isinstance(error, MobileMeDelegateError):
return REASON_TERMS
return REASON_TERMS if error.localized_error is not None else REASON_ICLOUD_REFUSED

# Before the network checks, and not because of ordering hazards - it is a RuntimeError and
# collides with none of them. It is here because it reads as the same thing to a user and is
Expand Down
1 change: 1 addition & 0 deletions app/src/main/res/values-de/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -389,4 +389,5 @@ Es wurde kein Verlauf importiert und bereits gespeicherte Daten wurden nicht ge
Wenn Apple die Anfrage lediglich abgelehnt hat, lohnt es sich, zu warten und es erneut zu versuchen. Schlägt es weiterhin fehl, macht ein Bericht mit diesem Protokoll es behebbar.</string>
<string name="login_failed_apple_asked_to_wait">Apple hat die Anmeldung abgelehnt und bittet, %1$s bis zum nächsten Versuch zu warten. Das ist eine Ablehnung durch Apple und kein Problem mit deiner Apple-ID oder deinem Passwort.</string>
<string name="login_try_remote_anisette">Remote-Server versuchen</string>
<string name="login_failed_icloud_refused">Dein Passwort und dein Code wurden akzeptiert, danach hat Apple iCloud für dieses Konto nicht freigegeben. Es geht dabei nicht um Nutzungsbedingungen. Andere Apps, die sich auf diesem Weg anmelden, sehen das bei Apple-IDs, die noch nie mit einem Apple-Gerät verwendet wurden, und sich bei appleid.apple.com anzumelden und die Kontodaten zu vervollständigen — etwa eine Zahlungsmethode hinzuzufügen — behebt es meistens. Bloßes Warten hilft in der Regel nicht.</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-en/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -389,4 +389,5 @@ No history was imported and nothing already stored was changed.</string>
If Apple simply refused the request, waiting and trying again is worth doing first. If it keeps failing, a report with this log attached is what makes it fixable.</string>
<string name="login_failed_apple_asked_to_wait">Apple refused the sign-in and asked for %1$s before trying again. This is Apple declining, not a problem with your Apple ID or password.</string>
<string name="login_try_remote_anisette">Try a remote server</string>
<string name="login_failed_icloud_refused">Your password and code were accepted, and then Apple would not open iCloud for this account. This is not about terms of service. Other apps that sign in this way see it on Apple IDs that have never been used with an Apple device, and signing in at appleid.apple.com and completing the account details — adding a payment method, for instance — is what usually clears it. Waiting on its own generally does not.</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-fr/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -389,4 +389,5 @@ Aucun historique n’a été importé et les données déjà enregistrées n’o
Si Apple a simplement refusé la requête, il vaut mieux attendre et réessayer. Si l\'échec persiste, un rapport accompagné de ce journal permet de corriger le problème.</string>
<string name="login_failed_apple_asked_to_wait">Apple a refusé la connexion et demande d\'attendre %1$s avant de réessayer. C\'est un refus d\'Apple, pas un problème avec votre identifiant ou votre mot de passe.</string>
<string name="login_try_remote_anisette">Essayer un serveur distant</string>
<string name="login_failed_icloud_refused">Votre mot de passe et votre code ont été acceptés, puis Apple n\'a pas ouvert iCloud pour ce compte. Il ne s\'agit pas des conditions d\'utilisation. D\'autres applications qui se connectent de cette façon le rencontrent sur des identifiants Apple qui n\'ont jamais été utilisés avec un appareil Apple, et se connecter sur appleid.apple.com puis compléter les informations du compte — ajouter un moyen de paiement, par exemple — suffit généralement à débloquer la situation. Attendre seul n\'y suffit pas en général.</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-ja/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -389,4 +389,5 @@
Appleが要求を拒否しただけなら、少し待ってからもう一度お試しください。それでも失敗する場合は、このログを添えて報告すると修正できます。</string>
<string name="login_failed_apple_asked_to_wait">Apple がサインインを拒否し、再試行まで %1$s 待つよう求めています。Apple ID やパスワードの問題ではなく、Apple 側の拒否です。</string>
<string name="login_try_remote_anisette">リモートサーバーを試す</string>
<string name="login_failed_icloud_refused">パスワードと確認コードは受け付けられましたが、その後 Apple がこのアカウントの iCloud を開きませんでした。これは利用規約の問題ではありません。同じ方法でサインインする他のアプリでも、Apple 製デバイスで一度も使われたことのない Apple ID でこれが起きると報告されています。appleid.apple.com にサインインし、支払い方法の追加などアカウント情報を入力すると解消することがほとんどです。待つだけでは通常は解消しません。</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-ko/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -389,4 +389,5 @@
Apple이 요청을 거부한 것뿐이라면 잠시 기다렸다가 다시 시도해 보세요. 계속 실패한다면 이 로그를 첨부해 신고하면 해결할 수 있습니다.</string>
<string name="login_failed_apple_asked_to_wait">Apple이 로그인을 거부했으며, 다시 시도하기 전에 %1$s 기다려 달라고 요청했습니다. Apple ID나 비밀번호의 문제가 아니라 Apple 쪽의 거부입니다.</string>
<string name="login_try_remote_anisette">원격 서버 시도</string>
<string name="login_failed_icloud_refused">비밀번호와 인증 코드는 통과했지만, 그 뒤에 Apple이 이 계정의 iCloud를 열어 주지 않았습니다. 약관과는 관계가 없습니다. 같은 방식으로 로그인하는 다른 앱에서도 Apple 기기에서 한 번도 사용한 적이 없는 Apple ID에서 이런 일이 보고되며, appleid.apple.com에 로그인해 결제 수단 추가처럼 계정 정보를 채우면 대개 해결됩니다. 기다리는 것만으로는 보통 해결되지 않습니다.</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-nl/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -389,4 +389,5 @@ Er is geen geschiedenis geïmporteerd en eerder opgeslagen gegevens zijn niet ge
Als Apple het verzoek simpelweg weigerde, is even wachten en opnieuw proberen het eerste om te doen. Blijft het mislukken, dan maakt een melding met dit logboek het oplosbaar.</string>
<string name="login_failed_apple_asked_to_wait">Apple heeft de aanmelding geweigerd en vraagt %1$s te wachten voor je het opnieuw probeert. Dit is een weigering van Apple, geen probleem met je Apple ID of wachtwoord.</string>
<string name="login_try_remote_anisette">Externe server proberen</string>
<string name="login_failed_icloud_refused">Je wachtwoord en code zijn geaccepteerd, waarna Apple iCloud voor dit account niet heeft geopend. Dit gaat niet over de voorwaarden. Andere apps die op deze manier inloggen zien dit bij Apple ID\'s die nooit met een Apple-apparaat zijn gebruikt, en inloggen op appleid.apple.com en de accountgegevens aanvullen — bijvoorbeeld een betaalmethode toevoegen — lost het meestal op. Alleen wachten helpt doorgaans niet.</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-ru/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -389,4 +389,5 @@
Если Apple просто отклонила запрос, сначала стоит подождать и попробовать снова. Если ошибка повторяется, отчёт с этим журналом позволит её исправить.</string>
<string name="login_failed_apple_asked_to_wait">Apple отклонила вход и просит подождать %1$s, прежде чем повторить попытку. Это отказ со стороны Apple, а не проблема с вашим Apple ID или паролем.</string>
<string name="login_try_remote_anisette">Попробовать удалённый сервер</string>
<string name="login_failed_icloud_refused">Пароль и код были приняты, но затем Apple не открыла iCloud для этой учётной записи. Дело не в условиях использования. Другие приложения, которые входят таким же образом, сталкиваются с этим на Apple ID, ни разу не использовавшихся с устройством Apple, и вход на appleid.apple.com с заполнением данных учётной записи — например, добавлением способа оплаты — обычно решает проблему. Просто подождать, как правило, не помогает.</string>
</resources>
1 change: 1 addition & 0 deletions app/src/main/res/values-zh-rCN/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -389,4 +389,5 @@
如果只是 Apple 拒绝了请求,先等一会儿再试。若持续失败,附上此日志的报告才能让问题得到修复。</string>
<string name="login_failed_apple_asked_to_wait">Apple 拒绝了此次登录,并要求等待 %1$s 后再试。这是 Apple 的拒绝,不是你的 Apple ID 或密码有问题。</string>
<string name="login_try_remote_anisette">尝试远程服务器</string>
<string name="login_failed_icloud_refused">密码和验证码都通过了,但随后 Apple 没有为此账户开放 iCloud。这与服务条款无关。以同样方式登录的其他应用也报告,从未在 Apple 设备上使用过的 Apple ID 会出现这种情况;登录 appleid.apple.com 并补全账户信息——例如添加付款方式——通常就能解决。仅仅等待通常没有用。</string>
</resources>
Loading
Loading