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
|
* The delegate login of spec §1.5: POST the configured endpoint (the recorded
|
||||||
* Basic `username:PET` auth and the recorded header set; the response's `com.apple.private.ids`
|
* `/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`.
|
* delegate serviceData yields the IDS `authToken`/`profileId`.
|
||||||
*/
|
*/
|
||||||
class GsaDelegateLoginClient(
|
class GsaDelegateLoginClient(
|
||||||
@@ -144,29 +146,45 @@ class GsaDelegateLoginClient(
|
|||||||
logEvent("gsa: iCloud sign-in")
|
logEvent("gsa: iCloud sign-in")
|
||||||
val basic = Base64.getEncoder()
|
val basic = Base64.getEncoder()
|
||||||
.encodeToString("${request.username}:${request.pet}".toByteArray(Charsets.UTF_8))
|
.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 {
|
val headers = buildMap {
|
||||||
putAll(request.anisette)
|
putAll(request.anisette)
|
||||||
put("Authorization", "Basic $basic")
|
if (!prefpane) put("Authorization", "Basic $basic")
|
||||||
put(HEADER_NAS_QUALIFY, Base64.getEncoder().encodeToString(request.validationData))
|
put(HEADER_NAS_QUALIFY, Base64.getEncoder().encodeToString(request.validationData))
|
||||||
put(HEADER_ADSID, request.adsid)
|
put(HEADER_ADSID, request.adsid)
|
||||||
put(HEADER_CLIENT_INFO, request.clientInfo)
|
put(HEADER_CLIENT_INFO, request.clientInfo)
|
||||||
put("User-Agent", SIGNIN_USER_AGENT)
|
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 encoded = XmlPlist.encode(body)
|
||||||
val response = http.post(endpoint, headers, encoded, CONTENT_TYPE)
|
val response = http.post(endpoint, headers, encoded, CONTENT_TYPE)
|
||||||
if (response.status != 200) {
|
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
|
@Test
|
||||||
fun `posts the recorded body and headers`() = runBlocking {
|
fun `posts the recorded body and headers`() = runBlocking {
|
||||||
val http = ScriptedIdsHttp(listOf(IdsHttpResponse(200, emptyMap(), okResponse())))
|
val http = ScriptedIdsHttp(listOf(IdsHttpResponse(200, emptyMap(), okResponse())))
|
||||||
|
|||||||
Reference in new issue
Block a user