aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-07-07 04:47:41 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-07-07 04:47:41 +0000
commitf1bcc626cdc722dc09bb63a193a7d57b8d300d83 (patch)
treed53c9c8b58b0e475e5774751728d52fa88f8259b
parent7d220dc5d69769c860d8eea475fb941ad7f1b398 (diff)
parent1a392f75763d5679f669b26d97269f275491e79e (diff)
downloadservice_entitlement-android14-mainline-adservices-release.tar.gz
Change-Id: Ie7d93493642e83a60219a0ea5886250cd1cc0287
-rw-r--r--Android.bp3
-rw-r--r--OWNERS4
-rw-r--r--TEST_MAPPING5
-rw-r--r--java/com/android/libraries/entitlement/EsimOdsaOperation.java49
-rw-r--r--java/com/android/libraries/entitlement/ServiceEntitlement.java78
-rw-r--r--java/com/android/libraries/entitlement/ServiceEntitlementException.java5
-rw-r--r--java/com/android/libraries/entitlement/ServiceEntitlementRequest.java29
-rw-r--r--java/com/android/libraries/entitlement/eapaka/EapAkaApi.java199
-rw-r--r--java/com/android/libraries/entitlement/http/HttpClient.java35
-rw-r--r--java/com/android/libraries/entitlement/http/HttpResponse.java7
-rw-r--r--tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java354
-rw-r--r--tests/src/com/android/libraries/entitlement/http/HttpClientTest.java39
12 files changed, 746 insertions, 61 deletions
diff --git a/Android.bp b/Android.bp
index b989425..dbedea3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -96,6 +96,9 @@ java_library {
"java/com/android/libraries/entitlement/ServiceEntitlementException.java",
"java/com/android/libraries/entitlement/ServiceEntitlementRequest.java",
],
+ static_libs: [
+ "guava",
+ ],
apex_available: [
"//apex_available:platform",
"com.android.wifi",
diff --git a/OWNERS b/OWNERS
index a167e8d..34b7bde 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,3 +1,3 @@
mewan@google.com
-samalin@google.com
-danielwbhuang@google.com
+kiwonp@google.com
+akaustubh@google.com
diff --git a/TEST_MAPPING b/TEST_MAPPING
index ec047b7..2f98cca 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -3,10 +3,5 @@
{
"name": "service-entitlement-tests"
}
- ],
- "postsubmit": [
- {
- "name": "service-entitlement-tests"
- }
]
}
diff --git a/java/com/android/libraries/entitlement/EsimOdsaOperation.java b/java/com/android/libraries/entitlement/EsimOdsaOperation.java
index 20f4fa3..9a3eae6 100644
--- a/java/com/android/libraries/entitlement/EsimOdsaOperation.java
+++ b/java/com/android/libraries/entitlement/EsimOdsaOperation.java
@@ -17,6 +17,7 @@
package com.android.libraries.entitlement;
import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
/**
* HTTP request parameters specific to on device service actiavation (ODSA). See GSMA spec TS.43
@@ -40,6 +41,10 @@ public abstract class EsimOdsaOperation {
* OSDA operation: AcquireConfiguration.
*/
public static final String OPERATION_ACQUIRE_CONFIGURATION = "AcquireConfiguration";
+ /**
+ * OSDA operation: AcquireTemporaryToken.
+ */
+ public static final String OPERATION_ACQUIRE_TEMPORARY_TOKEN = "AcquireTemporaryToken";
/**
* Indicates that operation_type is not set.
@@ -97,6 +102,12 @@ public abstract class EsimOdsaOperation {
public abstract int operationType();
/**
+ * Returns the comma separated list of operation targets used with temporary token from
+ * AcquireTemporaryToken operation. Used by HTTP parameter "operation_targets".
+ */
+ public abstract ImmutableList<String> operationTargets();
+
+ /**
* Returns the unique identifier of the companion device, like IMEI. Used by HTTP parameter
* "companion_terminal_id".
*/
@@ -170,6 +181,18 @@ public abstract class EsimOdsaOperation {
*/
public abstract String targetTerminalEid();
+
+ /**
+ * Returns the unique identifier of the old device eSIM, like the IMEI associated with the
+ * eSIM. Used by HTTP parameter "old_terminal_id".
+ */
+ public abstract String oldTerminalId();
+
+ /**
+ * Returns the ICCID of old device eSIM. Used by HTTP parameter "old_terminal_iccid".
+ */
+ public abstract String oldTerminalIccid();
+
/**
* Returns a new {@link Builder} object.
*/
@@ -177,6 +200,7 @@ public abstract class EsimOdsaOperation {
return new AutoValue_EsimOdsaOperation.Builder()
.setOperation("")
.setOperationType(OPERATION_TYPE_NOT_SET)
+ .setOperationTargets(ImmutableList.of())
.setCompanionTerminalId("")
.setCompanionTerminalVendor("")
.setCompanionTerminalModel("")
@@ -189,7 +213,9 @@ public abstract class EsimOdsaOperation {
.setTerminalEid("")
.setTargetTerminalId("")
.setTargetTerminalIccid("")
- .setTargetTerminalEid("");
+ .setTargetTerminalEid("")
+ .setOldTerminalId("")
+ .setOldTerminalIccid("");
}
/**
@@ -230,6 +256,12 @@ public abstract class EsimOdsaOperation {
public abstract Builder setOperationType(int value);
/**
+ * Sets the operation targets to be used with temporary token from AcquireTemporaryToken
+ * operation. Used by HTTP parameter "operation_targets" if set.
+ */
+ public abstract Builder setOperationTargets(ImmutableList<String> value);
+
+ /**
* Sets the unique identifier of the companion device, like IMEI. Used by HTTP parameter
* "companion_terminal_id" if set.
*
@@ -336,6 +368,21 @@ public abstract class EsimOdsaOperation {
*/
public abstract Builder setTargetTerminalEid(String value);
+ /**
+ * Sets the unique identifier of the old device eSIM, like the IMEI associated with the
+ * eSIM. Used by HTTP parameter "old_terminal_id" if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ */
+ public abstract Builder setOldTerminalId(String value);
+
+ /**
+ * Sets the ICCID old device eSIM. Used by HTTP parameter "old_terminal_iccid" if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ */
+ public abstract Builder setOldTerminalIccid(String value);
+
public abstract EsimOdsaOperation build();
}
}
diff --git a/java/com/android/libraries/entitlement/ServiceEntitlement.java b/java/com/android/libraries/entitlement/ServiceEntitlement.java
index d723e4c..c0d6d55 100644
--- a/java/com/android/libraries/entitlement/ServiceEntitlement.java
+++ b/java/com/android/libraries/entitlement/ServiceEntitlement.java
@@ -25,6 +25,8 @@ import com.android.libraries.entitlement.eapaka.EapAkaApi;
import com.google.common.collect.ImmutableList;
+import java.util.List;
+
/**
* Implemnets protocol for carrier service entitlement configuration query and operation, based on
* GSMA TS.43 spec.
@@ -50,6 +52,10 @@ public class ServiceEntitlement {
* App ID for on device service activation (OSDA) for primary device.
*/
public static final String APP_ODSA_PRIMARY = "ap2009";
+ /**
+ * App ID for data plan information entitlement.
+ */
+ public static final String APP_DATA_PLAN_BOOST = "ap2010";
private final CarrierConfig carrierConfig;
private final EapAkaApi eapAkaApi;
@@ -67,8 +73,63 @@ public class ServiceEntitlement {
* for how to get the subscroption ID.
*/
public ServiceEntitlement(Context context, CarrierConfig carrierConfig, int simSubscriptionId) {
+ this(
+ context,
+ carrierConfig,
+ simSubscriptionId,
+ /* saveHttpHistory= */ false,
+ /* bypassEapAkaResponse= */ "");
+ }
+
+ /**
+ * Creates an instance for service entitlement configuration query and operation for the
+ * carrier.
+ *
+ * @param context context of application
+ * @param carrierConfig carrier specific configs used in the queries and operations.
+ * @param simSubscriptionId the subscroption ID of the carrier's SIM on device. This indicates
+ * which SIM to retrieve IMEI/IMSI from and perform EAP-AKA authentication with. See {@link
+ * android.telephony.SubscriptionManager} for how to get the subscroption ID.
+ * @param saveHttpHistory set to {@code true} to save the history of request and response which
+ * can later be retrieved by {@code getHistory()}. Intended for debugging.
+ */
+ public ServiceEntitlement(
+ Context context,
+ CarrierConfig carrierConfig,
+ int simSubscriptionId,
+ boolean saveHttpHistory) {
+ this(
+ context,
+ carrierConfig,
+ simSubscriptionId,
+ saveHttpHistory,
+ /* bypassEapAkaResponse= */ "");
+ }
+
+ /**
+ * Creates an instance for service entitlement configuration query and operation for the
+ * carrier.
+ *
+ * @param context context of application
+ * @param carrierConfig carrier specific configs used in the queries and operations.
+ * @param simSubscriptionId the subscroption ID of the carrier's SIM on device. This indicates
+ * which SIM to retrieve IMEI/IMSI from and perform EAP-AKA authentication with. See {@link
+ * android.telephony.SubscriptionManager} for how to get the subscroption ID.
+ * @param saveHttpHistory set to {@code true} to save the history of request and response which
+ * can later be retrieved by {@code getHistory()}. Intended for debugging.
+ * @param bypassEapAkaResponse set to non empty string to bypass EAP-AKA authentication.
+ * The client will accept any challenge from the server and return this string as a
+ * response. Must not be {@code null}. Intended for testing.
+ */
+ public ServiceEntitlement(
+ Context context,
+ CarrierConfig carrierConfig,
+ int simSubscriptionId,
+ boolean saveHttpHistory,
+ String bypassEapAkaResponse) {
this.carrierConfig = carrierConfig;
- this.eapAkaApi = new EapAkaApi(context, simSubscriptionId);
+ this.eapAkaApi =
+ new EapAkaApi(context, simSubscriptionId, saveHttpHistory, bypassEapAkaResponse);
}
@VisibleForTesting
@@ -151,4 +212,19 @@ public class ServiceEntitlement {
throws ServiceEntitlementException {
return eapAkaApi.performEsimOdsaOperation(appId, carrierConfig, request, operation);
}
+
+ /**
+ * Retrieves the history of past HTTP request and responses if {@code saveHttpHistory} was set
+ * in constructor.
+ */
+ public List<String> getHistory() {
+ return eapAkaApi.getHistory();
+ }
+
+ /**
+ * Clears the history of past HTTP request and responses.
+ */
+ public void clearHistory() {
+ eapAkaApi.clearHistory();
+ }
}
diff --git a/java/com/android/libraries/entitlement/ServiceEntitlementException.java b/java/com/android/libraries/entitlement/ServiceEntitlementException.java
index 45b1b9b..b1cb50f 100644
--- a/java/com/android/libraries/entitlement/ServiceEntitlementException.java
+++ b/java/com/android/libraries/entitlement/ServiceEntitlementException.java
@@ -43,6 +43,11 @@ public class ServiceEntitlementException extends Exception {
* synchronization" procedure as defined in RFC 4187.
*/
public static final int ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE = 21;
+ /**
+ * EAP-AKA failure that happens when the client fails to authenticate within the maximum number
+ * of attempts
+ */
+ public static final int ERROR_EAP_AKA_FAILURE = 21;
// HTTP related failures
/**
diff --git a/java/com/android/libraries/entitlement/ServiceEntitlementRequest.java b/java/com/android/libraries/entitlement/ServiceEntitlementRequest.java
index c7b0ad3..e0ecbf7 100644
--- a/java/com/android/libraries/entitlement/ServiceEntitlementRequest.java
+++ b/java/com/android/libraries/entitlement/ServiceEntitlementRequest.java
@@ -62,6 +62,11 @@ public abstract class ServiceEntitlementRequest {
public abstract String authenticationToken();
/**
+ * Returns the temporary token. Used by HTTP parameter "temporary_token".
+ */
+ public abstract String temporaryToken();
+
+ /**
* Returns the unique identifier of the device like IMEI. Used by HTTP parameter "terminal_id".
*/
public abstract String terminalId();
@@ -118,6 +123,11 @@ public abstract class ServiceEntitlementRequest {
public abstract String acceptContentType();
/**
+ * Returns the boost type for premium network. Used for premium network slice entitlement.
+ */
+ public abstract String boostType();
+
+ /**
* Returns a new {@link Builder} object.
*/
public static Builder builder() {
@@ -125,6 +135,7 @@ public abstract class ServiceEntitlementRequest {
.setConfigurationVersion(DEFAULT_CONFIGURATION_VERSION)
.setEntitlementVersion(DEFAULT_ENTITLEMENT_VERSION)
.setAuthenticationToken("")
+ .setTemporaryToken("")
.setTerminalId("")
.setTerminalVendor(Build.MANUFACTURER)
.setTerminalModel(Build.MODEL)
@@ -133,7 +144,8 @@ public abstract class ServiceEntitlementRequest {
.setAppVersion("")
.setNotificationToken("")
.setNotificationAction(NOTICATION_ACTION_ENABLE_FCM)
- .setAcceptContentType(ACCEPT_CONTENT_TYPE_JSON_AND_XML);
+ .setAcceptContentType(ACCEPT_CONTENT_TYPE_JSON_AND_XML)
+ .setBoostType("");
}
/**
@@ -167,6 +179,13 @@ public abstract class ServiceEntitlementRequest {
public abstract Builder setAuthenticationToken(String value);
/**
+ * Sets the temporary token. Used by HTTP parameter "temporary_token".
+ *
+ * <p>Optional.
+ */
+ public abstract Builder setTemporaryToken(String value);
+
+ /**
* Sets the unique identifier of the device like IMEI. Used by HTTP parameter
* "terminal_id".
*
@@ -243,6 +262,14 @@ public abstract class ServiceEntitlementRequest {
*/
public abstract Builder setAcceptContentType(String contentType);
+ /**
+ * Sets the boost type for premium network. Used by HTTP parameter
+ * "boost_type" in case of premium network slice entitlement.
+ *
+ * <p>Optional.
+ */
+ public abstract Builder setBoostType(String value);
+
public abstract ServiceEntitlementRequest build();
}
}
diff --git a/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java b/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java
index 4482bf7..be41ca7 100644
--- a/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java
+++ b/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java
@@ -16,6 +16,7 @@
package com.android.libraries.entitlement.eapaka;
+import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_EAP_AKA_FAILURE;
import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE;
import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE;
@@ -33,6 +34,7 @@ import com.android.libraries.entitlement.EsimOdsaOperation;
import com.android.libraries.entitlement.ServiceEntitlementException;
import com.android.libraries.entitlement.ServiceEntitlementRequest;
import com.android.libraries.entitlement.http.HttpClient;
+import com.android.libraries.entitlement.http.HttpConstants.ContentType;
import com.android.libraries.entitlement.http.HttpConstants.RequestMethod;
import com.android.libraries.entitlement.http.HttpRequest;
import com.android.libraries.entitlement.http.HttpResponse;
@@ -43,6 +45,8 @@ import com.google.common.net.HttpHeaders;
import org.json.JSONException;
import org.json.JSONObject;
+import java.util.List;
+
public class EapAkaApi {
private static final String TAG = "ServiceEntitlement";
@@ -58,6 +62,7 @@ public class EapAkaApi {
private static final String EAP_ID = "EAP_ID";
private static final String IMSI = "IMSI";
private static final String TOKEN = "token";
+ private static final String TEMPORARY_TOKEN = "temporary_token";
private static final String NOTIF_ACTION = "notif_action";
private static final String NOTIF_TOKEN = "notif_token";
private static final String APP_VERSION = "app_version";
@@ -65,6 +70,7 @@ public class EapAkaApi {
private static final String OPERATION = "operation";
private static final String OPERATION_TYPE = "operation_type";
+ private static final String OPERATION_TARGETS = "operation_targets";
private static final String COMPANION_TERMINAL_ID = "companion_terminal_id";
private static final String COMPANION_TERMINAL_VENDOR = "companion_terminal_vendor";
private static final String COMPANION_TERMINAL_MODEL = "companion_terminal_model";
@@ -82,22 +88,38 @@ public class EapAkaApi {
private static final String TARGET_TERMINAL_ICCID = "target_terminal_iccid";
private static final String TARGET_TERMINAL_EID = "target_terminal_eid";
- // In case of EAP-AKA synchronization failure, we try to recover for at most two times.
- private static final int FOLLOW_SYNC_FAILURE_MAX_COUNT = 2;
+ private static final String OLD_TERMINAL_ID = "old_terminal_id";
+ private static final String OLD_TERMINAL_ICCID = "old_terminal_iccid";
+
+ private static final String BOOST_TYPE = "boost_type";
+
+ // In case of EAP-AKA synchronization failure or another challenge, we try to authenticate for
+ // at most three times.
+ private static final int MAX_EAP_AKA_ATTEMPTS = 3;
private final Context mContext;
private final int mSimSubscriptionId;
private final HttpClient mHttpClient;
-
- public EapAkaApi(Context context, int simSubscriptionId) {
- this(context, simSubscriptionId, new HttpClient());
+ private final String mBypassEapAkaResponse;
+
+ public EapAkaApi(
+ Context context,
+ int simSubscriptionId,
+ boolean saveHistory,
+ String bypassEapAkaResponse) {
+ this(context, simSubscriptionId, new HttpClient(saveHistory), bypassEapAkaResponse);
}
@VisibleForTesting
- EapAkaApi(Context context, int simSubscriptionId, HttpClient httpClient) {
+ EapAkaApi(
+ Context context,
+ int simSubscriptionId,
+ HttpClient httpClient,
+ String bypassEapAkaResponse) {
this.mContext = context;
this.mSimSubscriptionId = simSubscriptionId;
this.mHttpClient = httpClient;
+ this.mBypassEapAkaResponse = bypassEapAkaResponse;
}
/**
@@ -126,10 +148,17 @@ public class EapAkaApi {
urlBuilder.toString(),
carrierConfig,
ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON);
+ String eapAkaChallenge = getEapAkaChallenge(challengeResponse);
+ if (eapAkaChallenge == null) {
+ throw new ServiceEntitlementException(
+ ERROR_MALFORMED_HTTP_RESPONSE,
+ "Failed to parse EAP-AKA challenge: " + challengeResponse.body());
+ }
return respondToEapAkaChallenge(
carrierConfig,
- challengeResponse,
- FOLLOW_SYNC_FAILURE_MAX_COUNT,
+ eapAkaChallenge,
+ challengeResponse.cookies(),
+ MAX_EAP_AKA_ATTEMPTS,
request.acceptContentType())
.body();
}
@@ -139,55 +168,79 @@ public class EapAkaApi {
* Sends a follow-up HTTP request to the HTTP {@code response} using the same cookie, and
* returns the follow-up HTTP response.
*
- * <p>The {@code response} should contain a EAP-AKA challenge from server, and the
- * follow-up request could contain:
+ * <p>The {@code eapAkaChallenge} should be the EAP-AKA challenge from server, and the follow-up
+ * request could contain:
*
* <ul>
- * <li>The EAP-AKA response message, and the follow-up response should contain the
- * service entitlement configuration; or,
- * <li>The EAP-AKA synchronization failure message, and the follow-up response should
- * contain the new EAP-AKA challenge. Then this method calls itself to follow-up
- * the new challenge and return a new response, if {@code followSyncFailureCount}
- * is greater than zero. When this method call itself {@code followSyncFailureCount} is
- * reduced by one to prevent infinite loop (unlikely in practice, but just in case).
+ * <li>The EAP-AKA response message, and the follow-up response should contain the service
+ * entitlement configuration, or another EAP-AKA challenge in which case the method calls
+ * if {@code remainingAttempts} is greater than zero (If {@code remainingAttempts} reaches
+ * 0, the method will throw ServiceEntitlementException) ; or
+ * <li>The EAP-AKA synchronization failure message, and the follow-up response should contain
+ * the new EAP-AKA challenge. Then this method calls itself to follow-up the new challenge
+ * and return a new response, as long as {@code remainingAttempts} is greater than zero.
* </ul>
*
* @param response Challenge response from server which its content type is JSON
*/
private HttpResponse respondToEapAkaChallenge(
CarrierConfig carrierConfig,
- HttpResponse response,
- int followSyncFailureCount,
+ String eapAkaChallenge,
+ ImmutableList<String> cookies,
+ int remainingAttempts,
String contentType)
throws ServiceEntitlementException {
- String eapAkaChallenge;
- try {
- eapAkaChallenge = new JSONObject(response.body()).getString(EAP_CHALLENGE_RESPONSE);
- } catch (JSONException jsonException) {
- throw new ServiceEntitlementException(
- ERROR_MALFORMED_HTTP_RESPONSE, "Failed to parse json object", jsonException);
+ if (!mBypassEapAkaResponse.isEmpty()) {
+ return challengeResponse(mBypassEapAkaResponse, carrierConfig, cookies, contentType);
}
+
EapAkaChallenge challenge = EapAkaChallenge.parseEapAkaChallenge(eapAkaChallenge);
EapAkaResponse eapAkaResponse =
EapAkaResponse.respondToEapAkaChallenge(mContext, mSimSubscriptionId, challenge);
- // This could be a successful authentication, or synchronization failure.
- if (eapAkaResponse.response() != null) { // successful authentication
- return challengeResponse(
- eapAkaResponse.response(),
- carrierConfig,
- response.cookies(),
- contentType);
+ // This could be a successful authentication, another challenge, or synchronization failure.
+ if (eapAkaResponse.response() != null) {
+ HttpResponse response =
+ challengeResponse(
+ eapAkaResponse.response(), carrierConfig, cookies, contentType);
+ String nextEapAkaChallenge = getEapAkaChallenge(response);
+ // successful authentication
+ if (nextEapAkaChallenge == null) {
+ return response;
+ }
+ // another challenge
+ Log.d(TAG, "Received another challenge");
+ if (remainingAttempts > 0) {
+ return respondToEapAkaChallenge(
+ carrierConfig,
+ nextEapAkaChallenge,
+ cookies,
+ remainingAttempts - 1,
+ contentType);
+ } else {
+ throw new ServiceEntitlementException(
+ ERROR_EAP_AKA_FAILURE, "Unable to EAP-AKA authenticate");
+ }
} else if (eapAkaResponse.synchronizationFailureResponse() != null) {
Log.d(TAG, "synchronization failure");
HttpResponse newChallenge =
challengeResponse(
eapAkaResponse.synchronizationFailureResponse(),
carrierConfig,
- response.cookies(),
+ cookies,
ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON);
- if (followSyncFailureCount > 0) {
+ String nextEapAkaChallenge = getEapAkaChallenge(newChallenge);
+ if (nextEapAkaChallenge == null) {
+ throw new ServiceEntitlementException(
+ ERROR_MALFORMED_HTTP_RESPONSE,
+ "Failed to parse EAP-AKA challenge: " + newChallenge.body());
+ }
+ if (remainingAttempts > 0) {
return respondToEapAkaChallenge(
- carrierConfig, newChallenge, followSyncFailureCount - 1, contentType);
+ carrierConfig,
+ nextEapAkaChallenge,
+ cookies,
+ remainingAttempts - 1,
+ contentType);
} else {
throw new ServiceEntitlementException(
ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE,
@@ -241,7 +294,8 @@ public class EapAkaApi {
appendParametersForServiceEntitlementRequest(urlBuilder, ImmutableList.of(appId), request);
appendParametersForEsimOdsaOperation(urlBuilder, odsaOperation);
- if (!TextUtils.isEmpty(request.authenticationToken())) {
+ if (!TextUtils.isEmpty(request.authenticationToken())
+ || !TextUtils.isEmpty(request.temporaryToken())) {
// Fast Re-Authentication flow with pre-existing auth token
Log.d(TAG, "Fast Re-Authentication");
return httpGet(
@@ -254,10 +308,17 @@ public class EapAkaApi {
urlBuilder.toString(),
carrierConfig,
ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON);
+ String eapAkaChallenge = getEapAkaChallenge(challengeResponse);
+ if (eapAkaChallenge == null) {
+ throw new ServiceEntitlementException(
+ ERROR_MALFORMED_HTTP_RESPONSE,
+ "Failed to parse EAP-AKA challenge: " + challengeResponse.body());
+ }
return respondToEapAkaChallenge(
carrierConfig,
- challengeResponse,
- FOLLOW_SYNC_FAILURE_MAX_COUNT,
+ eapAkaChallenge,
+ challengeResponse.cookies(),
+ MAX_EAP_AKA_ATTEMPTS,
request.acceptContentType())
.body();
}
@@ -268,17 +329,20 @@ public class EapAkaApi {
ServiceEntitlementRequest request) {
TelephonyManager telephonyManager = mContext.getSystemService(
TelephonyManager.class).createForSubscriptionId(mSimSubscriptionId);
- if (TextUtils.isEmpty(request.authenticationToken())) {
+ if (!TextUtils.isEmpty(request.authenticationToken())) {
+ // IMSI and token required for fast AuthN.
+ urlBuilder
+ .appendQueryParameter(IMSI, telephonyManager.getSubscriberId())
+ .appendQueryParameter(TOKEN, request.authenticationToken());
+ } else if (!TextUtils.isEmpty(request.temporaryToken())) {
+ // temporary_token required for fast AuthN.
+ urlBuilder.appendQueryParameter(TEMPORARY_TOKEN, request.temporaryToken());
+ } else {
// EAP_ID required for initial AuthN
urlBuilder.appendQueryParameter(
EAP_ID,
getImsiEap(telephonyManager.getSimOperator(),
telephonyManager.getSubscriberId()));
- } else {
- // IMSI and token required for fast AuthN.
- urlBuilder
- .appendQueryParameter(IMSI, telephonyManager.getSubscriberId())
- .appendQueryParameter(TOKEN, request.authenticationToken());
}
if (!TextUtils.isEmpty(request.notificationToken())) {
@@ -298,6 +362,7 @@ public class EapAkaApi {
// Optional query parameters, append them if not empty
appendOptionalQueryParameter(urlBuilder, APP_VERSION, request.appVersion());
appendOptionalQueryParameter(urlBuilder, APP_NAME, request.appName());
+ appendOptionalQueryParameter(urlBuilder, BOOST_TYPE, request.boostType());
for (String appId : appIds) {
urlBuilder.appendQueryParameter(APP, appId);
@@ -320,6 +385,10 @@ public class EapAkaApi {
urlBuilder.appendQueryParameter(OPERATION_TYPE,
Integer.toString(odsaOperation.operationType()));
}
+ appendOptionalQueryParameter(
+ urlBuilder,
+ OPERATION_TARGETS,
+ TextUtils.join(",", odsaOperation.operationTargets()));
appendOptionalQueryParameter(urlBuilder, COMPANION_TERMINAL_ID,
odsaOperation.companionTerminalId());
appendOptionalQueryParameter(urlBuilder, COMPANION_TERMINAL_VENDOR,
@@ -345,6 +414,10 @@ public class EapAkaApi {
odsaOperation.targetTerminalIccid());
appendOptionalQueryParameter(urlBuilder, TARGET_TERMINAL_EID,
odsaOperation.targetTerminalEid());
+ appendOptionalQueryParameter(urlBuilder, OLD_TERMINAL_ICCID,
+ odsaOperation.oldTerminalIccid());
+ appendOptionalQueryParameter(urlBuilder, OLD_TERMINAL_ID,
+ odsaOperation.oldTerminalId());
}
private HttpResponse httpGet(String url, CarrierConfig carrierConfig, String contentType)
@@ -366,6 +439,30 @@ public class EapAkaApi {
}
}
+ @Nullable
+ private String getEapAkaChallenge(HttpResponse response) throws ServiceEntitlementException {
+ String eapAkaChallenge = null;
+ String responseBody = response.body();
+ if (response.contentType() == ContentType.JSON) {
+ try {
+ eapAkaChallenge =
+ new JSONObject(responseBody).optString(EAP_CHALLENGE_RESPONSE, null);
+ } catch (JSONException jsonException) {
+ throw new ServiceEntitlementException(
+ ERROR_MALFORMED_HTTP_RESPONSE,
+ "Failed to parse json object",
+ jsonException);
+ }
+ } else if (response.contentType() == ContentType.XML) {
+ // TODO: possibly support parsing eap-relay-packet in XML format
+ return null;
+ } else {
+ throw new ServiceEntitlementException(
+ ERROR_MALFORMED_HTTP_RESPONSE, "Unknown HTTP content type");
+ }
+ return eapAkaChallenge;
+ }
+
/**
* Returns the IMSI EAP value. The resulting realm part of the Root NAI in 3GPP TS 23.003 clause
* 19.3.2 will be in the form:
@@ -385,4 +482,18 @@ public class EapAkaApi {
}
return "0" + imsi + "@nai.epc.mnc" + mnc + ".mcc" + mcc + ".3gppnetwork.org";
}
+
+ /**
+ * Retrieves the history of past HTTP request and responses.
+ */
+ public List<String> getHistory() {
+ return mHttpClient.getHistory();
+ }
+
+ /**
+ * Clears the history of past HTTP request and responses.
+ */
+ public void clearHistory() {
+ mHttpClient.clearHistory();
+ }
}
diff --git a/java/com/android/libraries/entitlement/http/HttpClient.java b/java/com/android/libraries/entitlement/http/HttpClient.java
index 9ccb5ee..f2b394d 100644
--- a/java/com/android/libraries/entitlement/http/HttpClient.java
+++ b/java/com/android/libraries/entitlement/http/HttpClient.java
@@ -47,6 +47,7 @@ import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -55,9 +56,19 @@ public class HttpClient {
private static final String TAG = "ServiceEntitlement";
private HttpURLConnection mConnection;
+ private boolean mSaveHistory;
+ private ArrayList<String> mHistory;
+
+ public HttpClient(boolean saveHistory) {
+ mSaveHistory = saveHistory;
+ mHistory = new ArrayList<>();
+ }
@WorkerThread
public HttpResponse request(HttpRequest request) throws ServiceEntitlementException {
+ if (mSaveHistory) {
+ mHistory.add(request.toString());
+ }
logPii("HttpClient.request url: " + request.url());
createConnection(request);
logPii("HttpClient.request headers (partial): " + mConnection.getRequestProperties());
@@ -73,18 +84,38 @@ public class HttpClient {
}
mConnection.connect(); // This is to trigger SocketTimeoutException early
HttpResponse response = getHttpResponse(mConnection);
- Log.d(TAG, "HttpClient.response : " + response);
+ Log.d(TAG, "HttpClient.response : " + response.toShortDebugString());
+ if (mSaveHistory) {
+ mHistory.add(response.toString());
+ }
return response;
} catch (IOException ioe) {
throw new ServiceEntitlementException(
ERROR_HTTP_STATUS_NOT_SUCCESS,
- StreamUtils.inputStreamToStringSafe(mConnection.getErrorStream()),
+ "Connection error stream: "
+ + StreamUtils.inputStreamToStringSafe(mConnection.getErrorStream())
+ + " IOException: "
+ + ioe.toString(),
ioe);
} finally {
closeConnection();
}
}
+ /**
+ * Retrieves the history of past HTTP request and responses.
+ */
+ public List<String> getHistory() {
+ return mHistory;
+ }
+
+ /**
+ * Clears the history of past HTTP request and responses.
+ */
+ public void clearHistory() {
+ mHistory.clear();
+ }
+
private void createConnection(HttpRequest request) throws ServiceEntitlementException {
try {
URL url = new URL(request.url());
diff --git a/java/com/android/libraries/entitlement/http/HttpResponse.java b/java/com/android/libraries/entitlement/http/HttpResponse.java
index f495578..142639e 100644
--- a/java/com/android/libraries/entitlement/http/HttpResponse.java
+++ b/java/com/android/libraries/entitlement/http/HttpResponse.java
@@ -74,8 +74,11 @@ public abstract class HttpResponse {
.setCookies(ImmutableList.of());
}
- @Override
- public final String toString() {
+ /**
+ * Returns a short string representation for debugging purposes. Doesn't include the cookie or
+ * full body to prevent leaking sensitive data.
+ */
+ public String toShortDebugString() {
return new StringBuilder("HttpResponse{")
.append("contentType=")
.append(contentType())
diff --git a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java
index b837695..aff8f4b 100644
--- a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java
+++ b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java
@@ -24,6 +24,7 @@ import static com.android.libraries.entitlement.eapaka.EapAkaResponseTest.EAP_AK
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -66,6 +67,8 @@ public class EapAkaApiTest {
private static final String TEST_URL = "https://test.url/test-path";
private static final String EAP_AKA_CHALLENGE =
"{\"eap-relay-packet\":\"" + EAP_AKA_CHALLENGE_REQUEST + "\"}";
+ private static final String INVALID_EAP_AKA_CHALLENGE =
+ "{\"invalid-eap-relay-packet\":\"" + EAP_AKA_CHALLENGE_REQUEST + "\"}";
// com.google.common.net.HttpHeaders.COOKIE
private static final String HTTP_HEADER_COOKIE = "Cookie";
private static final String COOKIE_VALUE = "COOKIE=abcdefg";
@@ -94,6 +97,7 @@ public class EapAkaApiTest {
private static final int SUB_ID = 1;
private static final String ACCEPT_CONTENT_TYPE_JSON_AND_XML =
"application/vnd.gsma.eap-relay.v1.0+json, text/vnd.wap.connectivity-xml";
+ private static final String BYPASS_EAP_AKA_RESPONSE = "abc";
@Rule public final MockitoRule rule = MockitoJUnit.rule();
@@ -105,11 +109,14 @@ public class EapAkaApiTest {
private Context mContext;
private EapAkaApi mEapAkaApi;
+ private EapAkaApi mEapAkaApiBypassAuthentication;
@Before
public void setUp() {
mContext = spy(ApplicationProvider.getApplicationContext());
- mEapAkaApi = new EapAkaApi(mContext, SUB_ID, mMockHttpClient);
+ mEapAkaApi = new EapAkaApi(mContext, SUB_ID, mMockHttpClient, "");
+ mEapAkaApiBypassAuthentication =
+ new EapAkaApi(mContext, SUB_ID, mMockHttpClient, BYPASS_EAP_AKA_RESPONSE);
when(mContext.getSystemService(TelephonyManager.class))
.thenReturn(mMockTelephonyManager);
when(mMockTelephonyManager.createForSubscriptionId(SUB_ID))
@@ -164,8 +171,8 @@ public class EapAkaApiTest {
ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
assertThat(respopnse).isEqualTo(RESPONSE_XML);
- // Verify that the 2nd request has cookies set by the 1st response
verify(mMockHttpClient, times(2)).request(mHttpRequestCaptor.capture());
+ // Verify that the 2nd request has cookies set by the 1st response
assertThat(mHttpRequestCaptor.getAllValues().get(1).requestProperties())
.containsAtLeast(HTTP_HEADER_COOKIE, COOKIE_VALUE,
HTTP_HEADER_COOKIE, COOKIE_VALUE_1);
@@ -178,6 +185,180 @@ public class EapAkaApiTest {
}
@Test
+ public void queryEntitlementStatus_noAuthenticationToken_invalidChallenge() throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
+ HttpResponse eapChallengeResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(INVALID_EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE, COOKIE_VALUE_1))
+ .build();
+ HttpResponse xmlResponse =
+ HttpResponse.builder().setContentType(ContentType.XML).setBody(RESPONSE_XML)
+ .build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse).thenReturn(xmlResponse);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+
+ ServiceEntitlementException exception =
+ expectThrows(
+ ServiceEntitlementException.class,
+ () ->
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI),
+ carrierConfig,
+ request));
+
+ assertThat(exception.getErrorCode())
+ .isEqualTo(ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE);
+ assertThat(exception.getMessage())
+ .isEqualTo("Failed to parse EAP-AKA challenge: " + INVALID_EAP_AKA_CHALLENGE);
+ assertThat(exception.getCause()).isNull();
+ assertThat(exception.getHttpStatus()).isEqualTo(0);
+ assertThat(exception.getRetryAfter()).isEmpty();
+ }
+
+ @Test
+ public void queryEntitlementStatus_noAuthenticationToken_secondChallenge() throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
+ HttpResponse eapChallengeResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE, COOKIE_VALUE_1))
+ .build();
+ HttpResponse xmlResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
+ .build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(xmlResponse);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+
+ String respopnse =
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
+
+ assertThat(respopnse).isEqualTo(RESPONSE_XML);
+ // Verify that the subsequent requests have cookies set by the 1st response
+ verify(mMockHttpClient, times(3)).request(mHttpRequestCaptor.capture());
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).requestProperties())
+ .containsAtLeast(HTTP_HEADER_COOKIE, COOKIE_VALUE,
+ HTTP_HEADER_COOKIE, COOKIE_VALUE_1);
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).network()).isNull();
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).network()).isNull();
+ assertThat(mHttpRequestCaptor.getAllValues().get(2).timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getAllValues().get(2).network()).isNull();
+ }
+
+ @Test
+ public void queryEntitlementStatus_noAuthenticationToken_thirdChallenge() throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
+ HttpResponse eapChallengeResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE, COOKIE_VALUE_1))
+ .build();
+ HttpResponse xmlResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
+ .build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(xmlResponse);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+
+ String respopnse =
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
+
+ assertThat(respopnse).isEqualTo(RESPONSE_XML);
+ // Verify that the subsequent requests have cookies set by the 1st response
+ verify(mMockHttpClient, times(4)).request(mHttpRequestCaptor.capture());
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).requestProperties())
+ .containsAtLeast(HTTP_HEADER_COOKIE, COOKIE_VALUE,
+ HTTP_HEADER_COOKIE, COOKIE_VALUE_1);
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).network()).isNull();
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).network()).isNull();
+ assertThat(mHttpRequestCaptor.getAllValues().get(2).timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getAllValues().get(2).network()).isNull();
+ assertThat(mHttpRequestCaptor.getAllValues().get(3).timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getAllValues().get(3).network()).isNull();
+ }
+
+ @Test
+ public void queryEntitlementStatus_noAuthenticationToken_fourthChallenge_throwException()
+ throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
+ HttpResponse eapChallengeResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE, COOKIE_VALUE_1))
+ .build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+
+ ServiceEntitlementException exception =
+ expectThrows(
+ ServiceEntitlementException.class,
+ () ->
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI),
+ carrierConfig,
+ request));
+
+ assertThat(exception.getErrorCode())
+ .isEqualTo(ServiceEntitlementException.ERROR_EAP_AKA_FAILURE);
+ assertThat(exception.getMessage()).isEqualTo("Unable to EAP-AKA authenticate");
+ assertThat(exception.getCause()).isNull();
+ assertThat(exception.getHttpStatus()).isEqualTo(0);
+ assertThat(exception.getRetryAfter()).isEmpty();
+ }
+
+ @Test
public void queryEntitlementStatus_hasAuthenticationToken_multipleAppIds() throws Exception {
HttpResponse response =
HttpResponse.builder().setContentType(ContentType.XML).setBody(RESPONSE_XML)
@@ -261,6 +442,131 @@ public class EapAkaApiTest {
}
@Test
+ public void queryEntitlementStatus_noAuthenticationToken_eapAkaSyncFailure_invalidChallenge()
+ throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SYNC_FAILURE);
+ HttpResponse eapChallengeResponse =
+ HttpResponse
+ .builder().setContentType(ContentType.JSON).setBody(EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE)).build();
+ HttpResponse invalidEapChallengeResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(INVALID_EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE))
+ .build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(invalidEapChallengeResponse);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+
+ ServiceEntitlementException exception =
+ expectThrows(
+ ServiceEntitlementException.class,
+ () ->
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI),
+ carrierConfig,
+ request));
+
+ assertThat(exception.getErrorCode())
+ .isEqualTo(ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE);
+ assertThat(exception.getMessage())
+ .isEqualTo("Failed to parse EAP-AKA challenge: " + INVALID_EAP_AKA_CHALLENGE);
+ assertThat(exception.getCause()).isNull();
+ assertThat(exception.getHttpStatus()).isEqualTo(0);
+ assertThat(exception.getRetryAfter()).isEmpty();
+ }
+
+ @Test
+ public void queryEntitlementStatus_noAuthenticationToken_fourthEapAkaSyncFailure()
+ throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SYNC_FAILURE)
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SYNC_FAILURE)
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SYNC_FAILURE)
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SYNC_FAILURE);
+ HttpResponse eapChallengeResponse =
+ HttpResponse
+ .builder().setContentType(ContentType.JSON).setBody(EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE)).build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(eapChallengeResponse);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+
+ ServiceEntitlementException exception =
+ expectThrows(
+ ServiceEntitlementException.class,
+ () ->
+ mEapAkaApi.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI),
+ carrierConfig,
+ request));
+
+ assertThat(exception.getErrorCode())
+ .isEqualTo(ServiceEntitlementException.ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE);
+ assertThat(exception.getMessage())
+ .isEqualTo("Unable to recover from EAP-AKA synchroinization failure");
+ assertThat(exception.getCause()).isNull();
+ assertThat(exception.getHttpStatus()).isEqualTo(0);
+ assertThat(exception.getRetryAfter()).isEmpty();
+ }
+
+ @Test
+ public void queryEntitlementStatus_hasNoAuthenticationToken_bypassAuthentication()
+ throws Exception {
+ HttpResponse eapChallengeResponse =
+ HttpResponse
+ .builder().setContentType(ContentType.JSON).setBody(EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE, COOKIE_VALUE_1)).build();
+ HttpResponse xmlResponse =
+ HttpResponse.builder().setContentType(ContentType.XML).setBody(RESPONSE_XML)
+ .build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse).thenReturn(xmlResponse);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+
+ String respopnse =
+ mEapAkaApiBypassAuthentication.queryEntitlementStatus(
+ ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request);
+
+ assertThat(respopnse).isEqualTo(RESPONSE_XML);
+ // Verify that the 2nd request has cookies set by the 1st response
+ verify(mMockHttpClient, times(2)).request(mHttpRequestCaptor.capture());
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).requestProperties())
+ .containsAtLeast(HTTP_HEADER_COOKIE, COOKIE_VALUE,
+ HTTP_HEADER_COOKIE, COOKIE_VALUE_1);
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getAllValues().get(0).network()).isNull();
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).timeoutInSec())
+ .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC);
+ assertThat(mHttpRequestCaptor.getAllValues().get(1).network()).isNull();
+ verify(mMockTelephonyManagerForSubId, times(0))
+ .getIccAuthentication(anyInt(), anyInt(), any());
+ assertThat(
+ mHttpRequestCaptor
+ .getAllValues()
+ .get(1)
+ .postData()
+ .get(EapAkaApi.EAP_CHALLENGE_RESPONSE))
+ .isEqualTo(BYPASS_EAP_AKA_RESPONSE);
+ }
+
+ @Test
public void queryEntitlementStatus_acceptContentTypeSpecified_verfityAcceptContentType()
throws Exception {
HttpResponse response = HttpResponse.builder().setBody(RESPONSE_XML).build();
@@ -351,4 +657,48 @@ public class EapAkaApiTest {
assertThat(response).isEqualTo(RESPONSE_XML);
verify(mMockHttpClient, times(1)).request(any());
}
+
+ @Test
+ public void performEsimOdsaOperation_noAuthenticationToken_invalidChallenge() throws Exception {
+ when(mMockTelephonyManagerForSubId.getIccAuthentication(
+ TelephonyManager.APPTYPE_USIM,
+ TelephonyManager.AUTHTYPE_EAP_AKA,
+ EAP_AKA_SECURITY_CONTEXT_REQUEST_EXPECTED))
+ .thenReturn(EAP_AKA_SECURITY_CONTEXT_RESPONSE_SUCCESS);
+ HttpResponse eapChallengeResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.JSON)
+ .setBody(INVALID_EAP_AKA_CHALLENGE)
+ .setCookies(ImmutableList.of(COOKIE_VALUE))
+ .build();
+ HttpResponse xmlResponse =
+ HttpResponse.builder()
+ .setContentType(ContentType.XML)
+ .setBody(RESPONSE_XML)
+ .build();
+ when(mMockHttpClient.request(any()))
+ .thenReturn(eapChallengeResponse)
+ .thenReturn(xmlResponse);
+ CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).build();
+ ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().build();
+ EsimOdsaOperation operation = EsimOdsaOperation.builder().build();
+
+ ServiceEntitlementException exception =
+ expectThrows(
+ ServiceEntitlementException.class,
+ () ->
+ mEapAkaApi.performEsimOdsaOperation(
+ ServiceEntitlement.APP_ODSA_COMPANION,
+ carrierConfig,
+ request,
+ operation));
+
+ assertThat(exception.getErrorCode())
+ .isEqualTo(ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE);
+ assertThat(exception.getMessage())
+ .isEqualTo("Failed to parse EAP-AKA challenge: " + INVALID_EAP_AKA_CHALLENGE);
+ assertThat(exception.getCause()).isNull();
+ assertThat(exception.getHttpStatus()).isEqualTo(0);
+ assertThat(exception.getRetryAfter()).isEmpty();
+ }
}
diff --git a/tests/src/com/android/libraries/entitlement/http/HttpClientTest.java b/tests/src/com/android/libraries/entitlement/http/HttpClientTest.java
index 505e8b5..9f05828 100644
--- a/tests/src/com/android/libraries/entitlement/http/HttpClientTest.java
+++ b/tests/src/com/android/libraries/entitlement/http/HttpClientTest.java
@@ -46,6 +46,7 @@ import org.junit.runner.RunWith;
import java.net.HttpURLConnection;
import java.net.URL;
+import java.util.List;
import java.util.Map;
@RunWith(AndroidJUnit4.class)
@@ -70,7 +71,7 @@ public class HttpClientTest {
// Reset sFakeURLStreamHandler
sFakeURLStreamHandler.stubResponse(ImmutableMap.of());
- mHttpClient = new HttpClient();
+ mHttpClient = new HttpClient(true);
}
@Test
@@ -244,4 +245,40 @@ public class HttpClientTest {
assertThat(exception.getHttpStatus()).isEqualTo(0);
assertThat(exception.getRetryAfter()).isEmpty();
}
+
+ @Test
+ public void history() throws Exception {
+ FakeResponse responseContent =
+ FakeResponse.builder()
+ .setResponseCode(HttpURLConnection.HTTP_OK)
+ .setResponseLocation(null)
+ .setResponseBody(TEST_RESPONSE_BODY.getBytes(UTF_8))
+ .setContentType(CONTENT_TYPE_STRING_JSON)
+ .build();
+ Map<String, FakeResponse> response = ImmutableMap.of(TEST_URL, responseContent);
+ sFakeURLStreamHandler.stubResponse(response);
+ HttpRequest request =
+ HttpRequest.builder()
+ .setUrl(TEST_URL)
+ .setRequestMethod(RequestMethod.GET)
+ .setTimeoutInSec(70)
+ .build();
+
+ HttpResponse httpResponse0 = mHttpClient.request(request);
+ HttpResponse httpResponse1 = mHttpClient.request(request);
+ List<String> history = mHttpClient.getHistory();
+
+ assertThat(history)
+ .containsExactly(
+ request.toString(),
+ httpResponse0.toString(),
+ request.toString(),
+ httpResponse1.toString())
+ .inOrder();
+
+ mHttpClient.clearHistory();
+ history = mHttpClient.getHistory();
+
+ assertThat(history).isEmpty();
+ }
}