diff options
author | samalin <samalin@google.com> | 2021-02-26 02:55:07 +0800 |
---|---|---|
committer | Sama Lin <samalin@google.com> | 2021-05-11 16:53:59 +0000 |
commit | 35ab884e53b2b6f8072be2f286617a313e3c38bd (patch) | |
tree | 59ae754043651a791365914c52328de466ebf0df | |
parent | 0930a0c7630dea225e846219bf746a49c079068a (diff) | |
download | service_entitlement-35ab884e53b2b6f8072be2f286617a313e3c38bd.tar.gz |
Implement for EsimOsdaOprations
Bug: 177544547
Test: unit tests
Change-Id: I0fe91dadc6af7d1a6a72b96d50d0b731586e980d
(cherry picked from commit b48a7e91915dc87faeea10ce3b7e0c4c8008ca11)
4 files changed, 212 insertions, 98 deletions
diff --git a/java/com/android/libraries/entitlement/EsimOdsaOperation.java b/java/com/android/libraries/entitlement/EsimOdsaOperation.java index ce764f2..20f4fa3 100644 --- a/java/com/android/libraries/entitlement/EsimOdsaOperation.java +++ b/java/com/android/libraries/entitlement/EsimOdsaOperation.java @@ -44,7 +44,7 @@ public abstract class EsimOdsaOperation { /** * Indicates that operation_type is not set. */ - static final int OPERATION_TYPE_NOT_SET = -1; + public static final int OPERATION_TYPE_NOT_SET = -1; /** * To activate a subscription, used by {@link #OPERATION_MANAGE_SUBSCRIPTION}. */ @@ -138,13 +138,12 @@ public abstract class EsimOdsaOperation { public abstract String companionTerminalIccid(); /** - * Returns the ICCID of the companion device. Used by HTTP parameter - * "companion_terminal_iccid". + * Returns the EID of the companion device. Used by HTTP parameter "companion_terminal_eid". */ public abstract String companionTerminalEid(); /** - * Returns the ICCID of the primary device eSIM. Used by HTTP parameter "terminal_eid". + * Returns the ICCID of the primary device eSIM. Used by HTTP parameter "terminal_iccid". */ public abstract String terminalIccid(); @@ -175,7 +174,22 @@ public abstract class EsimOdsaOperation { * Returns a new {@link Builder} object. */ public static Builder builder() { - return new AutoValue_EsimOdsaOperation.Builder().setOperationType(OPERATION_TYPE_NOT_SET); + return new AutoValue_EsimOdsaOperation.Builder() + .setOperation("") + .setOperationType(OPERATION_TYPE_NOT_SET) + .setCompanionTerminalId("") + .setCompanionTerminalVendor("") + .setCompanionTerminalModel("") + .setCompanionTerminalSoftwareVersion("") + .setCompanionTerminalFriendlyName("") + .setCompanionTerminalService("") + .setCompanionTerminalIccid("") + .setCompanionTerminalEid("") + .setTerminalIccid("") + .setTerminalEid("") + .setTargetTerminalId("") + .setTargetTerminalIccid("") + .setTargetTerminalEid(""); } /** @@ -283,39 +297,40 @@ public abstract class EsimOdsaOperation { public abstract Builder setCompanionTerminalEid(String value); /** - * Sets the ICCID of the primary device eSIM. Used by HTTP parameter "terminal_eid" if set. + * Sets the ICCID of the primary device eSIM in case of primary SIM not present. Used by + * HTTP parameter "terminal_eid" if set. * * <p>Used by primary device ODSA operation. */ public abstract Builder setTerminalIccid(String value); /** - * Sets the eUICC identifier (EID) of the primary device eSIM. Used by HTTP parameter - * "terminal_eid" if set. + * Sets the eUICC identifier (EID) of the primary device eSIM in case of primary SIM not + * present. Used by HTTP parameter "terminal_eid" if set. * * <p>Used by primary device ODSA operation. */ public abstract Builder setTerminalEid(String value); /** - * Sets the unique identifier of the primary device eSIM, like the IMEI associated with the - * eSIM. Used by HTTP parameter "target_terminal_id" if set. + * Sets the unique identifier of the primary device eSIM in case of multiple SIM, like the + * IMEI associated with the eSIM. Used by HTTP parameter "target_terminal_id" if set. * * <p>Used by primary device ODSA operation. */ public abstract Builder setTargetTerminalId(String value); /** - * Sets the ICCID primary device eSIM. Used by HTTP parameter "target_terminal_iccid" if - * set. + * Sets the ICCID primary device eSIM in case of multiple SIM. Used by HTTP parameter + * "target_terminal_iccid" if set. * * <p>Used by primary device ODSA operation. */ public abstract Builder setTargetTerminalIccid(String value); /** - * Sets the eUICC identifier (EID) of the primary device eSIM. Used by HTTP parameter - * "target_terminal_eid" if set. + * Sets the eUICC identifier (EID) of the primary device eSIM in case of multiple SIM. Used + * by HTTP parameter "target_terminal_eid" if set. * * <p>Used by primary device ODSA operation. */ diff --git a/java/com/android/libraries/entitlement/ServiceEntitlement.java b/java/com/android/libraries/entitlement/ServiceEntitlement.java index 16aeeaa..efb0734 100644 --- a/java/com/android/libraries/entitlement/ServiceEntitlement.java +++ b/java/com/android/libraries/entitlement/ServiceEntitlement.java @@ -152,7 +152,6 @@ public class ServiceEntitlement { public String performEsimOdsa( String appId, ServiceEntitlementRequest request, EsimOdsaOperation operation) throws ServiceEntitlementException { - // TODO(b/177544547): Add implementation - return null; + return eapAkaApi.performEsimOdsaOperation(appId, carrierConfig, request, operation); } } diff --git a/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java b/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java index dd15426..bf53d87 100644 --- a/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java +++ b/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java @@ -29,6 +29,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.libraries.entitlement.CarrierConfig; +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; @@ -48,69 +49,45 @@ public class EapAkaApi { public static final String EAP_CHALLENGE_RESPONSE = "eap-relay-packet"; - /** - * Current version of the entitlement configuration. - */ private static final String VERS = "vers"; - /** - * Version of the entitlement configuration. - */ private static final String ENTITLEMENT_VERSION = "entitlement_version"; - /** - * Unique identifier for the device. Refer to - * {@link android.telephony.TelephonyManager#getImei()}. - */ private static final String TERMINAL_ID = "terminal_id"; - /** - * Device manufacturer. - */ private static final String TERMINAL_VENDOR = "terminal_vendor"; - /** - * Device model. - */ private static final String TERMINAL_MODEL = "terminal_model"; - /** - * Device software version. - */ private static final String TERMIAL_SW_VERSION = "terminal_sw_version"; - /** - * Identifier for the requested entitlement. - */ private static final String APP = "app"; - /** - * NAI needed for EAP-AKA authentication. - */ private static final String EAP_ID = "EAP_ID"; - private static final String IMSI = "IMSI"; private static final String TOKEN = "token"; - /** - * Action for the notification registration token. - */ private static final String NOTIF_ACTION = "notif_action"; - /** - * Attribute name of the notification registration token. - */ private static final String NOTIF_TOKEN = "notif_token"; - /** - * Attribute name of the app version. - */ private static final String APP_VERSION = "app_version"; - /** - * Attribute name of the app name. - */ private static final String APP_NAME = "app_name"; - /** - * Expected content type of the response body. - */ + private static final String ACCEPT_CONTENT_TYPE_JSON_AND_XML = "application/vnd.gsma.eap-relay.v1.0+json, text/vnd.wap.connectivity-xml"; - /** - * Required content type to the response body. - */ private static final String REQUEST_CONTENT_TYPE_JSON = "application/vnd.gsma.eap-relay.v1.0+json"; + private static final String OPERATION = "operation"; + private static final String OPERATION_TYPE = "operation_type"; + 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"; + private static final String COMPANION_TERMINAL_SW_VERSION = "companion_terminal_sw_version"; + private static final String COMPANION_TERMINAL_FRIENDLY_NAME = + "companion_terminal_friendly_name"; + private static final String COMPANION_TERMINAL_SERVICE = "companion_terminal_service"; + private static final String COMPANION_TERMINAL_ICCID = "companion_terminal_iccid"; + private static final String COMPANION_TERMINAL_EID = "companion_terminal_eid"; + + private static final String TERMINAL_ICCID = "terminal_iccid"; + private static final String TERMINAL_EID = "terminal_eid"; + + private static final String TARGET_TERMINAL_ID = "target_terminal_id"; + 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; @@ -138,18 +115,11 @@ public class EapAkaApi { */ @Nullable public String queryEntitlementStatus(ImmutableList<String> appIds, - CarrierConfig carrierConfig, - ServiceEntitlementRequest request) + CarrierConfig carrierConfig, ServiceEntitlementRequest request) throws ServiceEntitlementException { - HttpRequest httpRequest = - HttpRequest.builder() - .setUrl(entitlementStatusUrl(appIds, carrierConfig.serverUrl(), request)) - .setRequestMethod(RequestMethod.GET) - .addRequestProperty(HttpHeaders.ACCEPT, ACCEPT_CONTENT_TYPE_JSON_AND_XML) - .setTimeoutInSec(carrierConfig.timeoutInSec()) - .setNetwork(carrierConfig.network()) - .build(); - HttpResponse response = mHttpClient.request(httpRequest); + Uri.Builder urlBuilder = Uri.parse(carrierConfig.serverUrl()).buildUpon(); + appendParametersForServiceEntitlementRequest(urlBuilder, appIds, request); + HttpResponse response = httpGet(urlBuilder.toString(), carrierConfig); if (!request.authenticationToken().isEmpty()) { // Fast Re-Authentication flow with pre-existing auth token Log.d(TAG, "Fast Re-Authentication"); @@ -210,8 +180,8 @@ public class EapAkaApi { carrierConfig, newChallenge, followSyncFailureCount - 1); } else { throw new ServiceEntitlementException( - ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE, - "Unable to recover from EAP-AKA synchroinization failure"); + ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE, + "Unable to recover from EAP-AKA synchroinization failure"); } } else { // not possible throw new AssertionError("EapAkaResponse invalid."); @@ -243,11 +213,36 @@ public class EapAkaApi { return mHttpClient.request(request); } - String entitlementStatusUrl( - ImmutableList<String> appIds, String serverUrl, ServiceEntitlementRequest request) { + /** + * Retrieves raw doc of performing ODSA operations. For operation type, see {@link + * EsimOdsaOperation}. + * + * <p>Implementation based on GSMA TS.43-v5.0 6.1. + */ + public String performEsimOdsaOperation(String appId, CarrierConfig carrierConfig, + ServiceEntitlementRequest request, EsimOdsaOperation odsaOperation) + throws ServiceEntitlementException { + Uri.Builder urlBuilder = Uri.parse(carrierConfig.serverUrl()).buildUpon(); + appendParametersForServiceEntitlementRequest(urlBuilder, ImmutableList.of(appId), request); + appendParametersForEsimOdsaOperation(urlBuilder, odsaOperation); + + HttpResponse response = httpGet(urlBuilder.toString(), carrierConfig); + if (!request.authenticationToken().isEmpty()) { + // Fast Re-Authentication flow with pre-existing auth token + Log.d(TAG, "Fast Re-Authentication"); + return response.body(); + } + // Full Authentication flow + Log.d(TAG, "Full Authentication"); + return respondToEapAkaChallenge(carrierConfig, response, FOLLOW_SYNC_FAILURE_MAX_COUNT) + .body(); + } + + private void appendParametersForServiceEntitlementRequest( + Uri.Builder urlBuilder, ImmutableList<String> appIds, + ServiceEntitlementRequest request) { TelephonyManager telephonyManager = mContext.getSystemService( TelephonyManager.class).createForSubscriptionId(mSimSubscriptionId); - Uri.Builder urlBuilder = Uri.parse(serverUrl).buildUpon(); if (TextUtils.isEmpty(request.authenticationToken())) { // EAP_ID required for initial AuthN urlBuilder.appendQueryParameter( @@ -276,27 +271,74 @@ public class EapAkaApi { } // Optional query parameters, append them if not empty - if (!TextUtils.isEmpty(request.appVersion())) { - urlBuilder.appendQueryParameter(APP_VERSION, request.appVersion()); - } - - if (!TextUtils.isEmpty(request.appName())) { - urlBuilder.appendQueryParameter(APP_NAME, request.appName()); - } + appendOptionalQueryParameter(urlBuilder, APP_VERSION, request.appVersion()); + appendOptionalQueryParameter(urlBuilder, APP_NAME, request.appName()); for (String appId : appIds) { urlBuilder.appendQueryParameter(APP, appId); } - return urlBuilder + urlBuilder // Identity and Authentication parameters .appendQueryParameter(TERMINAL_VENDOR, request.terminalVendor()) .appendQueryParameter(TERMINAL_MODEL, request.terminalModel()) .appendQueryParameter(TERMIAL_SW_VERSION, request.terminalSoftwareVersion()) // General Service parameters .appendQueryParameter(VERS, Integer.toString(request.configurationVersion())) - .appendQueryParameter(ENTITLEMENT_VERSION, request.entitlementVersion()) - .toString(); + .appendQueryParameter(ENTITLEMENT_VERSION, request.entitlementVersion()); + } + + private void appendParametersForEsimOdsaOperation( + Uri.Builder urlBuilder, EsimOdsaOperation odsaOperation) { + urlBuilder.appendQueryParameter(OPERATION, odsaOperation.operation()); + if (odsaOperation.operationType() != EsimOdsaOperation.OPERATION_TYPE_NOT_SET) { + urlBuilder.appendQueryParameter(OPERATION_TYPE, + Integer.toString(odsaOperation.operationType())); + } + appendOptionalQueryParameter(urlBuilder, COMPANION_TERMINAL_ID, + odsaOperation.companionTerminalId()); + appendOptionalQueryParameter(urlBuilder, COMPANION_TERMINAL_VENDOR, + odsaOperation.companionTerminalVendor()); + appendOptionalQueryParameter(urlBuilder, COMPANION_TERMINAL_MODEL, + odsaOperation.companionTerminalModel()); + appendOptionalQueryParameter(urlBuilder, COMPANION_TERMINAL_SW_VERSION, + odsaOperation.companionTerminalSoftwareVersion()); + appendOptionalQueryParameter(urlBuilder, COMPANION_TERMINAL_FRIENDLY_NAME, + odsaOperation.companionTerminalFriendlyName()); + appendOptionalQueryParameter(urlBuilder, COMPANION_TERMINAL_SERVICE, + odsaOperation.companionTerminalService()); + appendOptionalQueryParameter(urlBuilder, COMPANION_TERMINAL_ICCID, + odsaOperation.companionTerminalIccid()); + appendOptionalQueryParameter(urlBuilder, COMPANION_TERMINAL_EID, + odsaOperation.companionTerminalEid()); + appendOptionalQueryParameter(urlBuilder, TERMINAL_ICCID, + odsaOperation.terminalIccid()); + appendOptionalQueryParameter(urlBuilder, TERMINAL_EID, odsaOperation.terminalEid()); + appendOptionalQueryParameter(urlBuilder, TARGET_TERMINAL_ID, + odsaOperation.targetTerminalId()); + appendOptionalQueryParameter(urlBuilder, TARGET_TERMINAL_ICCID, + odsaOperation.targetTerminalIccid()); + appendOptionalQueryParameter(urlBuilder, TARGET_TERMINAL_EID, + odsaOperation.targetTerminalEid()); + } + + private HttpResponse httpGet(String url, CarrierConfig carrierConfig) + throws ServiceEntitlementException { + HttpRequest httpRequest = + HttpRequest.builder() + .setUrl(url) + .setRequestMethod(RequestMethod.GET) + .addRequestProperty(HttpHeaders.ACCEPT, ACCEPT_CONTENT_TYPE_JSON_AND_XML) + .setTimeoutInSec(carrierConfig.timeoutInSec()) + .setNetwork(carrierConfig.network()) + .build(); + return mHttpClient.request(httpRequest); + } + + private void appendOptionalQueryParameter(Uri.Builder urlBuilder, String key, String value) { + if (!TextUtils.isEmpty(value)) { + urlBuilder.appendQueryParameter(key, value); + } } /** diff --git a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java index cc80ea0..99e8cfa 100644 --- a/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java +++ b/tests/src/com/android/libraries/entitlement/eapaka/EapAkaApiTest.java @@ -38,6 +38,7 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.runner.AndroidJUnit4; import com.android.libraries.entitlement.CarrierConfig; +import com.android.libraries.entitlement.EsimOdsaOperation; import com.android.libraries.entitlement.ServiceEntitlement; import com.android.libraries.entitlement.ServiceEntitlementException; import com.android.libraries.entitlement.ServiceEntitlementRequest; @@ -92,13 +93,19 @@ public class EapAkaApiTest { private static final String ACCEPT_CONTENT_TYPE_JSON_AND_XML = "application/vnd.gsma.eap-relay.v1.0+json, text/vnd.wap.connectivity-xml"; - @Rule public final MockitoRule rule = MockitoJUnit.rule(); + @Rule + public final MockitoRule rule = MockitoJUnit.rule(); - @Mock private HttpClient mMockHttpClient; - @Mock private Network mMockNetwork; - @Mock private TelephonyManager mMockTelephonyManager; - @Mock private TelephonyManager mMockTelephonyManagerForSubId; - @Captor private ArgumentCaptor<HttpRequest> mHttpRequestCaptor; + @Mock + private HttpClient mMockHttpClient; + @Mock + private Network mMockNetwork; + @Mock + private TelephonyManager mMockTelephonyManager; + @Mock + private TelephonyManager mMockTelephonyManagerForSubId; + @Captor + private ArgumentCaptor<HttpRequest> mHttpRequestCaptor; private Context mContext; private EapAkaApi mEapAkaApi; @@ -117,20 +124,20 @@ public class EapAkaApiTest { @Test public void queryEntitlementStatus_hasAuthenticationToken() throws Exception { - HttpResponse response = + HttpResponse httpResponse = HttpResponse.builder().setContentType(ContentType.XML).setBody(RESPONSE_XML) .build(); - when(mMockHttpClient.request(any())).thenReturn(response); + when(mMockHttpClient.request(any())).thenReturn(httpResponse); CarrierConfig carrierConfig = CarrierConfig.builder().setServerUrl(TEST_URL).setNetwork(mMockNetwork).build(); ServiceEntitlementRequest request = ServiceEntitlementRequest.builder().setAuthenticationToken(TOKEN).build(); - String respopnse = + String response = mEapAkaApi.queryEntitlementStatus( ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request); - assertThat(respopnse).isEqualTo(RESPONSE_XML); + assertThat(response).isEqualTo(RESPONSE_XML); verify(mMockHttpClient).request(mHttpRequestCaptor.capture()); assertThat(mHttpRequestCaptor.getValue().timeoutInSec()) .isEqualTo(CarrierConfig.DEFAULT_TIMEOUT_IN_SEC); @@ -208,7 +215,7 @@ public class EapAkaApiTest { ServiceEntitlementException exception = expectThrows( ServiceEntitlementException.class, - () -> mEapAkaApi.queryEntitlementStatus( + () -> mEapAkaApi.queryEntitlementStatus( ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request)); @@ -230,7 +237,7 @@ public class EapAkaApiTest { ServiceEntitlementException exception = expectThrows( ServiceEntitlementException.class, - () -> mEapAkaApi.queryEntitlementStatus( + () -> mEapAkaApi.queryEntitlementStatus( ImmutableList.of(ServiceEntitlement.APP_VOWIFI), carrierConfig, request)); @@ -276,4 +283,55 @@ public class EapAkaApiTest { assertThat(mHttpRequestCaptor.getAllValues().get(2).requestProperties()) .containsEntry(HTTP_HEADER_COOKIE, COOKIE_VALUE); } + + @Test + public void performEsimOdsaOperation_noAuthenticationToken_returnsResult() 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) + .setCookie(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(); + + String response = + mEapAkaApi.performEsimOdsaOperation(ServiceEntitlement.APP_ODSA_COMPANION, + carrierConfig, request, operation); + + assertThat(response).isEqualTo(RESPONSE_XML); + verify(mMockHttpClient, times(2)).request(any()); + } + + @Test + public void performEsimOdsaOperation_manageSubscription_returnsResult() throws Exception { + HttpResponse httpResponse = + HttpResponse.builder().setContentType(ContentType.XML).setBody(RESPONSE_XML) + .build(); + when(mMockHttpClient.request(any())).thenReturn(httpResponse); + CarrierConfig carrierConfig = + CarrierConfig.builder().setServerUrl(TEST_URL).build(); + ServiceEntitlementRequest request = + ServiceEntitlementRequest.builder().setAuthenticationToken(TOKEN).build(); + EsimOdsaOperation operation = EsimOdsaOperation.builder() + .setOperation(EsimOdsaOperation.OPERATION_MANAGE_SUBSCRIPTION) + .setOperationType(EsimOdsaOperation.OPERATION_TYPE_SUBSCRIBE) + .build(); + + String response = + mEapAkaApi.performEsimOdsaOperation(ServiceEntitlement.APP_ODSA_COMPANION, + carrierConfig, request, operation); + + assertThat(response).isEqualTo(RESPONSE_XML); + verify(mMockHttpClient, times(1)).request(any()); + } } |