mirror of
https://github.com/fenleon/open-madrid.git
synced 2026-10-22 16:12:35 +00:00
gsa: prefpane/login sends the vendored V1 AuthRequest body (apple-id/client-id/delegates/password, no Basic header) — RE'd from the vendored open_absinthe .so (V2 symbols absent); v2 endpoint keeps the recorded V2 shape
This commit is contained in:
1 parent
2537a80629
commit
863a2d82cb
2 files changed
+63
-18
No files matched your search
@@ -130,8 +130,10 @@ data class IdsDelegateCredentials(
|
||||
)
|
||||
|
||||
/**
|
||||
* The delegate login of spec §1.5: POST `https://setup.icloud.com/setup/signin/v2/login` with
|
||||
* Basic `username:PET` auth and the recorded header set; the response's `com.apple.private.ids`
|
||||
* The delegate login of spec §1.5: POST the configured endpoint (the recorded
|
||||
* `/setup/signin/v2/login` with the V2 body + Basic auth; the vendored reference's
|
||||
* `/setup/prefpane/login` with the V1 `AuthRequest` body — credentials in the body,
|
||||
* RE 2026-10-13) with the recorded header set; the response's `com.apple.private.ids`
|
||||
* delegate serviceData yields the IDS `authToken`/`profileId`.
|
||||
*/
|
||||
class GsaDelegateLoginClient(
|
||||
@@ -144,29 +146,45 @@ class GsaDelegateLoginClient(
|
||||
logEvent("gsa: iCloud sign-in")
|
||||
val basic = Base64.getEncoder()
|
||||
.encodeToString("${request.username}:${request.pet}".toByteArray(Charsets.UTF_8))
|
||||
val delegates = linkedMapOf<String, Any?>(
|
||||
IDS_DELEGATE_BUNDLE_ID to linkedMapOf("protocol-version" to "4"),
|
||||
MOBILEME_DELEGATE_BUNDLE_ID to linkedMapOf<String, Any?>(),
|
||||
)
|
||||
// The vendored reference (open_absinthe, RE 2026-10-13) ships ONLY the V1 request
|
||||
// shape on this endpoint: kebab-case `AuthRequest {apple-id, client-id, delegates,
|
||||
// password}` with the PET in the body and NO Authorization header — the .so has no
|
||||
// V2AuthRequest/protocolVersion/userInfo strings at all. The V2 wrapper is kept for
|
||||
// the recorded v2 endpoint (§1.5 C59).
|
||||
val prefpane = endpoint == PREFPANE_ENDPOINT
|
||||
val body = if (prefpane) {
|
||||
linkedMapOf<String, Any?>(
|
||||
"apple-id" to request.username,
|
||||
"client-id" to request.clientId.lowercase(),
|
||||
"delegates" to delegates,
|
||||
"password" to request.pet,
|
||||
)
|
||||
} else {
|
||||
linkedMapOf<String, Any?>(
|
||||
"delegates" to delegates,
|
||||
"protocolVersion" to PROTOCOL_VERSION,
|
||||
"userInfo" to linkedMapOf<String, Any?>(
|
||||
// rev 26 (live 2026-10-04): the sign-in body's client id rides kebab-case
|
||||
// (`client-id` — the recorded upstream identity struct is kebab-case on the
|
||||
// wire); "clientId" was the one body delta vs the live request capture.
|
||||
"client-id" to request.clientId,
|
||||
"language" to "en-US",
|
||||
"timezone" to request.timezone,
|
||||
),
|
||||
)
|
||||
}
|
||||
val headers = buildMap {
|
||||
putAll(request.anisette)
|
||||
put("Authorization", "Basic $basic")
|
||||
if (!prefpane) put("Authorization", "Basic $basic")
|
||||
put(HEADER_NAS_QUALIFY, Base64.getEncoder().encodeToString(request.validationData))
|
||||
put(HEADER_ADSID, request.adsid)
|
||||
put(HEADER_CLIENT_INFO, request.clientInfo)
|
||||
put("User-Agent", SIGNIN_USER_AGENT)
|
||||
}
|
||||
val body = linkedMapOf<String, Any?>(
|
||||
"delegates" to linkedMapOf<String, Any?>(
|
||||
IDS_DELEGATE_BUNDLE_ID to linkedMapOf("protocol-version" to "4"),
|
||||
MOBILEME_DELEGATE_BUNDLE_ID to linkedMapOf<String, Any?>(),
|
||||
),
|
||||
"protocolVersion" to PROTOCOL_VERSION,
|
||||
"userInfo" to linkedMapOf<String, Any?>(
|
||||
// rev 26 (live 2026-10-04): the sign-in body's client id rides kebab-case
|
||||
// (`client-id` — the recorded upstream identity struct is kebab-case on the
|
||||
// wire); "clientId" was the one body delta vs the live request capture.
|
||||
"client-id" to request.clientId,
|
||||
"language" to "en-US",
|
||||
"timezone" to request.timezone,
|
||||
),
|
||||
)
|
||||
val encoded = XmlPlist.encode(body)
|
||||
val response = http.post(endpoint, headers, encoded, CONTENT_TYPE)
|
||||
if (response.status != 200) {
|
||||
|
||||
@@ -99,6 +99,33 @@ class GsaDelegateLoginTest {
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `prefpane endpoint posts the vendored V1 body without Basic auth`() = runBlocking {
|
||||
val http = ScriptedIdsHttp(listOf(IdsHttpResponse(200, emptyMap(), okResponse())))
|
||||
GsaDelegateLoginClient(
|
||||
http,
|
||||
endpoint = GsaDelegateLoginClient.PREFPANE_ENDPOINT,
|
||||
).signIn(request())
|
||||
|
||||
val call = http.calls.single()
|
||||
assertEquals(GsaDelegateLoginClient.PREFPANE_ENDPOINT, call.url)
|
||||
assertNull(call.headers["Authorization"])
|
||||
assertEquals(Base64.getEncoder().encodeToString(byteArrayOf(1, 2, 3)), call.headers["X-Mme-Nas-Qualify"])
|
||||
|
||||
val body = XmlPlist.decode(call.body) as Map<*, *>
|
||||
assertEquals("[email protected]", body["apple-id"])
|
||||
assertEquals("pet-token", body["password"])
|
||||
// the vendored V1 client-id is a plain v4 UUID (no uppercase, RE 2026-10-13)
|
||||
assertEquals("a1b2c3d4-e5f6-4789-abcd-0123456789ab", body["client-id"])
|
||||
assertEquals(
|
||||
setOf("apple-id", "client-id", "delegates", "password"),
|
||||
body.keys,
|
||||
)
|
||||
val delegates = body["delegates"] as Map<*, *>
|
||||
assertEquals(mapOf("protocol-version" to "4"), delegates["com.apple.private.ids"])
|
||||
assertEquals(emptyMap<String, String>(), delegates["com.apple.mobileme"])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `posts the recorded body and headers`() = runBlocking {
|
||||
val http = ScriptedIdsHttp(listOf(IdsHttpResponse(200, emptyMap(), okResponse())))
|
||||
|
||||
Reference in new issue
Block a user