Skip to content

Commit f38a12d

Browse files
authored
Merge pull request #249 from parawanderer/fix/248-refused-before-the-password
Offer the remote server when Apple refuses before the password
2 parents 39f4a23 + 5f1a00a commit f38a12d

20 files changed

Lines changed: 128 additions & 11 deletions

File tree

‎app/build.gradle.kts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -447,7 +447,7 @@ chaquopy {
447447
// wheel for desktop platforms and a pure-Python `py3-none-any` one as well.
448448
// There is no Android wheel, so pip falls back to the pure-Python build - which
449449
// is correct but markedly slower. The messages here are small enough not to care.
450-
install("git+https://github.com/parawanderer/FindMy.py@7969003ca785658369b650f75d9e7ca519566bf6")
450+
install("git+https://github.com/parawanderer/FindMy.py@00fd962a0e8ec9f5601fe0b9db3f16ccea252a3c")
451451

452452
install("NSKeyedUnArchiver==1.5")
453453

‎app/src/androidTest/java/dev/wander/android/opentagviewer/ForceRemoteAnisetteAfterDeclineTest.java‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import static androidx.test.espresso.intent.matcher.IntentMatchers.hasComponent;
1212
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
1313
import static androidx.test.espresso.matcher.ViewMatchers.withId;
14+
import static androidx.test.espresso.matcher.ViewMatchers.withText;
1415
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
1516
import static org.hamcrest.Matchers.not;
1617
import static org.junit.Assert.assertEquals;
@@ -169,6 +170,29 @@ public void theRetryGoesThroughTheServerAndIsRecordedAsRemote() {
169170
UserSettings.ANISETTE_REMOTE, storedSettings().getAnisetteMode());
170171
}
171172

173+
/**
174+
* Issue #248: refused before the password was used, on a device whose local Anisette works.
175+
*
176+
* <p>The button appears, and the sentence says the password is not the problem while keeping
177+
* Apple's text, which carries the {@code ec} a report needs.
178+
*/
179+
@Test
180+
public void aRefusalBeforeThePasswordOffersTheRemoteServer() {
181+
AppDependencies.replaceAuthService(
182+
this.apple = FakeAppleAuthService.refusesBeforeThePasswordOnceThenSignsIn());
183+
184+
launch();
185+
signInExpectingAnotherLoginCall();
186+
187+
Eventually.check(() -> onView(withId(R.id.login_error_try_remote_anisette))
188+
.perform(scrollTo()).check(matches(isDisplayed())));
189+
190+
final String expected = getInstrumentation().getTargetContext().getString(
191+
R.string.login_failed_refused_before_password,
192+
"Email verification failed (ec=-1): This action cannot be completed at this time.");
193+
onView(withId(R.id.login_error_message_text)).check(matches(withText(expected)));
194+
}
195+
172196
/**
173197
* A wrong password does not offer it: a different Anisette identity would not fix a bad
174198
* credential, and saying otherwise sends people to change a setting that cannot help.

‎app/src/androidTest/java/dev/wander/android/opentagviewer/python/FakeAppleAuthService.java‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,21 @@ public static FakeAppleAuthService declinesOnceThenSignsIn() {
121121
return fake;
122122
}
123123

124+
/**
125+
* Apple refuses the first step of the sign-in, before the password is used, then serves the
126+
* next - issue #248's shape, followed by the retry through a server getting past it.
127+
*
128+
* <p>Fails as {@code REFUSED_BEFORE_PASSWORD}, which the remote-server button is also gated on.
129+
*/
130+
public static FakeAppleAuthService refusesBeforeThePasswordOnceThenSignsIn() {
131+
final FakeAppleAuthService fake = new FakeAppleAuthService(LOGIN_STATE.LOGGED_IN, null);
132+
fake.loginFailsWith = new PythonAccountLoginException(
133+
"Email verification failed (ec=-1): This action cannot be completed at this time.",
134+
PythonAccountLoginException.REASON_REFUSED_BEFORE_PASSWORD);
135+
fake.loginFailuresRemaining = 1;
136+
return fake;
137+
}
138+
124139
/** The password is wrong, or Apple is unreachable. */
125140
public static FakeAppleAuthService rejectsTheSignIn(final String message) {
126141
final FakeAppleAuthService fake =

‎app/src/main/java/dev/wander/android/opentagviewer/AppleLoginActivity.java‎

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -747,15 +747,18 @@ private void showLoginFailure(final Throwable error) {
747747
((TextView) this.findViewById(R.id.login_error_message_text))
748748
.setText(this.describeLoginFailure(error));
749749

750-
// Offer the server route only when Apple itself refused the sign-in - a 429 or 503 -
751-
// and not for a wrong password or a dead network, where a different Anisette identity
752-
// changes nothing. That is the one failure where local Anisette worked and Apple still
753-
// said no, so switching the machine identity a server presents is the thing left to try.
750+
// Offer the server route only when Apple itself refused the sign-in - a 429 or 503, or
751+
// a refusal before the password was used (#248) - and not for a wrong password or a dead
752+
// network, where a different Anisette identity changes nothing. Those are the failures
753+
// where local Anisette worked and Apple still said no, so switching the machine identity
754+
// a server presents is the thing left to try.
754755
final String reason = error instanceof PythonAccountLoginException
755756
? ((PythonAccountLoginException) error).getReason()
756757
: PythonAccountLoginException.REASON_UNKNOWN;
758+
final boolean appleRefused = PythonAccountLoginException.REASON_APPLE_DECLINED.equals(reason)
759+
|| PythonAccountLoginException.REASON_REFUSED_BEFORE_PASSWORD.equals(reason);
757760
this.findViewById(R.id.login_error_try_remote_anisette).setVisibility(
758-
PythonAccountLoginException.REASON_APPLE_DECLINED.equals(reason) ? VISIBLE : GONE);
761+
appleRefused ? VISIBLE : GONE);
759762
}
760763

761764
/**
@@ -825,6 +828,13 @@ private String describeLoginFailure(final Throwable error) {
825828
return this.getString(R.string.login_failed_apple_declined);
826829
}
827830

831+
// Refused before the password was used (#248), so the sentence says that retyping it
832+
// will not help. Apple's own text follows, because it carries the ec a report needs.
833+
if (PythonAccountLoginException.REASON_REFUSED_BEFORE_PASSWORD.equals(reason)) {
834+
return this.getString(R.string.login_failed_refused_before_password,
835+
error.getLocalizedMessage());
836+
}
837+
828838
// Apple took the password and the code, then would not open iCloud for the account. Kept
829839
// apart from the terms sentence because the remedy is elsewhere entirely, and apart from
830840
// "Apple declined" because that one says to wait - see REASON_ICLOUD_REFUSED.

‎app/src/main/java/dev/wander/android/opentagviewer/python/PythonAccountLoginException.java‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,17 @@ public class PythonAccountLoginException extends RuntimeException {
6060
*/
6161
public static final String REASON_APPLE_DECLINED = "apple_declined";
6262

63+
/**
64+
* Apple refused the first step of the sign-in, before the password was used. Matches
65+
* {@code REASON_REFUSED_BEFORE_PASSWORD}.
66+
*
67+
* <p>Issue #248: shown verbatim as "Email verification failed", which reads as a typo in the
68+
* Apple ID or password and is neither. Like {@link #REASON_APPLE_DECLINED} it offers the
69+
* remote-server route, because local Anisette worked and Apple still said no - so the machine
70+
* identity is the thing left to change. Unlike it, nothing here says to wait.
71+
*/
72+
public static final String REASON_REFUSED_BEFORE_PASSWORD = "refused_before_password";
73+
6374
/** Anything not recognised. The detail is shown as-is rather than guessed at. */
6475
public static final String REASON_UNKNOWN = "unknown";
6576

‎app/src/main/python/main.py‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import NSKeyedUnArchiver
1111

1212
from findmy import FindMyAccessory, MobileMeDelegateError
13-
from findmy.errors import AppleServiceUnavailableError
13+
from findmy.errors import AppleServiceUnavailableError, GrandSlamRefusedError
1414
from findmy.accessory import FixedRollingKeyPairAccessory
1515
from findmy.keys import KeyPairType
1616
from findmy.reports import (
@@ -511,6 +511,25 @@ def assertAnisetteIsSupported(serializedAccountData: str) -> str | None:
511511
end is wasted effort.
512512
"""
513513

514+
REASON_REFUSED_BEFORE_PASSWORD = "refused_before_password"
515+
"""
516+
Grand Slam refused the first step of the sign-in, which carries the username and the Anisette
517+
headers and no password.
518+
519+
**So the password is not the problem, and the screen must not suggest it is.** This arrived as
520+
an `InvalidCredentialsError` reading "Email verification failed", shown verbatim, on a device
521+
whose local Anisette was working - OpenTagViewer#248. What is left is the account itself, or the
522+
machine identity the Anisette data presents, and the second is the one thing this app can change:
523+
the screen offers the remote-server route for it, as it does for :data:`REASON_APPLE_DECLINED`.
524+
525+
**Which `ec` means what is not established.** Apple's text here is generic, and the code was not
526+
kept until the fork started carrying it, so nothing branches on a value. The detail keeps the
527+
code, so a report carries it.
528+
529+
**A wrong password is not this.** That is refused at the second step, `complete`, and stays
530+
:data:`REASON_UNKNOWN` - a different Anisette identity cannot fix a bad credential.
531+
"""
532+
514533
REASON_UNKNOWN = "unknown"
515534
"""Anything else. The detail is shown as-is, because a wrong guess is worse than raw text."""
516535

@@ -557,6 +576,11 @@ def classifyLoginFailure(error: BaseException) -> str:
557576
if isinstance(error, AppleServiceUnavailableError):
558577
return REASON_APPLE_DECLINED
559578

579+
# Only the first step. The same type reports a wrong password at the second, and that one
580+
# must not offer a different Anisette server as though it could help.
581+
if isinstance(error, GrandSlamRefusedError) and error.before_password:
582+
return REASON_REFUSED_BEFORE_PASSWORD
583+
560584
if isinstance(error, (asyncio.TimeoutError, asyncio.CancelledError)):
561585
return REASON_NETWORK
562586
if isinstance(error, _NETWORK_ERRORS):

‎app/src/main/res/values-de/strings.xml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,4 +393,5 @@ Wenn Apple die Anfrage lediglich abgelehnt hat, lohnt es sich, zu warten und es
393393
<string name="anisette_login_crashes_here">Apples Anmeldebibliothek stürzt auf diesem Handy ab, daher übernimmt ein Server diesen Schritt. Alles andere funktioniert normal. Warum das auf manchen Handys passiert, wissen wir noch nicht – ein Bericht mit deinem Modell hilft.</string>
394394
<string name="anisette_report_crash">Dieses Handy melden</string>
395395
<string name="anisette_crash_report_body">Apples Anmeldebibliothek stürzt auf diesem Handy ab, sobald sie geladen wird. Auf den meisten Handys funktioniert sie, und wir wissen noch nicht, was die betroffenen gemeinsam haben.\n\nOpenTagViewer nutzt für diesen Schritt jetzt einen Server, du kannst dich also anmelden und die App normal verwenden. Ein Bericht mit den Angaben unten hilft uns, die Ursache zu finden.</string>
396+
<string name="login_failed_refused_before_password">Apple hat die Anmeldung mit dieser Apple-ID abgelehnt, bevor dein Passwort geprüft wurde. Es erneut einzugeben hilft also nicht. Prüfe, ob du dich auf account.apple.com anmelden kannst und ob Apple dort etwas von dir verlangt. Ein Remote-Server stellt sich Apple als anderes Gerät vor, einen Versuch ist das wert. Apple meldete: %1$s</string>
396397
</resources>

‎app/src/main/res/values-en/strings.xml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,4 +393,5 @@ If Apple simply refused the request, waiting and trying again is worth doing fir
393393
<string name="anisette_login_crashes_here">Apple\'s sign-in library crashes on this phone, so a server does this step instead. Everything else works normally. We don\'t know why it happens on some phones yet, and a report naming yours helps.</string>
394394
<string name="anisette_report_crash">Report this phone</string>
395395
<string name="anisette_crash_report_body">Apple\'s sign-in library crashes as soon as it loads on this phone. It works on most phones, and we do not know yet what the affected ones have in common.\n\nOpenTagViewer now uses a server for that step, so you can sign in and use the app normally. A report with the details below is what helps us find the cause.</string>
396+
<string name="login_failed_refused_before_password">Apple refused to start signing in to this Apple ID before your password was checked, so retyping it will not help. Check that you can sign in at account.apple.com and whether Apple asks anything of you there. Trying a remote server presents Apple with a different device, which is worth a try. Apple said: %1$s</string>
396397
</resources>

‎app/src/main/res/values-fr/strings.xml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,4 +393,5 @@ Si Apple a simplement refusé la requête, il vaut mieux attendre et réessayer.
393393
<string name="anisette_login_crashes_here">La bibliothèque de connexion d\'Apple plante sur ce téléphone, un serveur se charge donc de cette étape. Tout le reste fonctionne normalement. Nous ne savons pas encore pourquoi cela arrive sur certains téléphones : un rapport indiquant le vôtre nous aide.</string>
394394
<string name="anisette_report_crash">Signaler ce téléphone</string>
395395
<string name="anisette_crash_report_body">La bibliothèque de connexion d\'Apple plante dès son chargement sur ce téléphone. Elle fonctionne sur la plupart des téléphones, et nous ne savons pas encore ce que les appareils concernés ont en commun.\n\nOpenTagViewer utilise désormais un serveur pour cette étape : vous pouvez vous connecter et utiliser l\'app normalement. Un rapport avec les informations ci-dessous nous aide à trouver la cause.</string>
396+
<string name="login_failed_refused_before_password">Apple a refusé de commencer la connexion à cet identifiant Apple avant même de vérifier votre mot de passe : le saisir à nouveau ne changera rien. Vérifiez que vous pouvez vous connecter sur account.apple.com et si Apple vous y demande quelque chose. Un serveur distant présente un autre appareil à Apple, cela vaut la peine d\'essayer. Apple a répondu : %1$s</string>
396397
</resources>

‎app/src/main/res/values-ja/strings.xml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -393,4 +393,5 @@ Appleが要求を拒否しただけなら、少し待ってからもう一度お
393393
<string name="anisette_login_crashes_here">Apple のサインイン用ライブラリがこの端末ではクラッシュするため、この手順はサーバーが代わりに行います。その他の機能は通常どおり使えます。一部の端末で起きる原因はまだ分かっていません。お使いの機種を添えて報告していただけると助かります。</string>
394394
<string name="anisette_report_crash">この端末を報告</string>
395395
<string name="anisette_crash_report_body">この端末では、Apple のサインイン用ライブラリが読み込み直後にクラッシュします。ほとんどの端末では問題なく動作しており、影響を受ける端末の共通点はまだ分かっていません。\n\nOpenTagViewer はこの手順にサーバーを使うようになったため、サインインもアプリの利用も通常どおりできます。下の情報を添えて報告していただくと、原因の特定に役立ちます。</string>
396+
<string name="login_failed_refused_before_password">Apple はパスワードを確認する前に、この Apple ID へのサインインの開始を拒否しました。パスワードを入力し直しても解決しません。account.apple.com にサインインできるか、そこで Apple から何か求められていないかを確認してください。リモートサーバーを使うと Apple には別のデバイスとして見えるので、試す価値があります。Apple の応答: %1$s</string>
396397
</resources>

0 commit comments

Comments
 (0)