Skip to content

Commit 3633585

Browse files
minbiKarthikeyan
authored andcommitted
[MobileClient] Expose network failures during API calls (#922)
* [Infra] Update project metadata * [MobileClient] Delay showSignIn dependency usage until called * [MobileClient] Fix initialization NPE; reduce logging of unfound dependencies Logging is reduced by moving optional dependency calls into separate methods that are conditionally called * [MobileClient] Update logic to expose network related exceptions during API calls The network related exceptions were not surfaced previously in API calls like getUserAttributes. Only the exceptions that conclusively indicate signed-out are used to trigger SIGNED_OUT_TOKENS_INVALID variants. * Update changelog with mobile client changes for 2.13.4
1 parent 29ea62b commit 3633585

File tree

10 files changed

+1135
-138
lines changed

10 files changed

+1135
-138
lines changed

.idea/codeStyles/Project.xml

Lines changed: 0 additions & 29 deletions
This file was deleted.

.idea/vcs.xml

Lines changed: 0 additions & 6 deletions
This file was deleted.

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# Change Log - AWS SDK for Android
22

3+
## [Release 2.13.4](https://github.com/aws/aws-sdk-android/releases/tag/release_v2.13.4)
4+
5+
### Bug Fixes
6+
7+
* **AWS Mobile Client**
8+
* Fix initialization NPE for Hosted UI feature. See [issue #888](https://github.com/aws-amplify/aws-sdk-android/issues/888)
9+
10+
### Enhancements
11+
12+
* **AWS Mobile Client**
13+
* Update logic to expose network related exceptions during API calls. The network related exceptions were not surfaced previously in API calls like getUserAttributes. Only the exceptions that conclusively indicate signed-out are used to trigger SIGNED_OUT_TOKENS_INVALID variants. See [issue #679](https://github.com/aws-amplify/aws-sdk-android/issues/679)
14+
* Reduced logging of unfound dependencies for the Hosted UI feature when not used.
15+
* Delay usage of drop-in UI dependencies until `showSignIn()` is called.
16+
317
## [Release 2.13.3](https://github.com/aws/aws-sdk-android/releases/tag/release_v2.13.3)
418

519
### New Features
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
package com.amazonaws.mobile.client;
2+
3+
import android.content.Context;
4+
import android.support.test.InstrumentationRegistry;
5+
import android.support.test.runner.AndroidJUnit4;
6+
7+
import com.amazonaws.AmazonClientException;
8+
import com.amazonaws.AmazonServiceException;
9+
import com.amazonaws.auth.AWSAbstractCognitoIdentityProvider;
10+
import com.amazonaws.auth.CognitoCredentialsProvider;
11+
import com.amazonaws.internal.keyvaluestore.AWSKeyValueStore;
12+
import com.amazonaws.mobile.config.AWSConfiguration;
13+
import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserPool;
14+
import com.amazonaws.regions.Regions;
15+
import com.amazonaws.services.cognitoidentity.AmazonCognitoIdentity;
16+
import com.amazonaws.services.cognitoidentity.model.GetCredentialsForIdentityRequest;
17+
import com.amazonaws.services.cognitoidentity.model.GetCredentialsForIdentityResult;
18+
import com.amazonaws.services.cognitoidentity.model.GetIdRequest;
19+
import com.amazonaws.services.cognitoidentity.model.GetIdResult;
20+
import com.amazonaws.services.cognitoidentityprovider.AmazonCognitoIdentityProvider;
21+
import com.amazonaws.services.cognitoidentityprovider.model.InitiateAuthRequest;
22+
import com.amazonaws.services.cognitoidentityprovider.model.InitiateAuthResult;
23+
24+
import org.json.JSONException;
25+
import org.json.JSONObject;
26+
import org.junit.After;
27+
import org.junit.Before;
28+
import org.junit.BeforeClass;
29+
import org.junit.Test;
30+
import org.junit.runner.RunWith;
31+
32+
import java.lang.reflect.Field;
33+
import java.net.UnknownHostException;
34+
import java.util.Random;
35+
import java.util.concurrent.CountDownLatch;
36+
37+
import static org.junit.Assert.assertEquals;
38+
import static org.junit.Assert.assertNotNull;
39+
import static org.junit.Assert.assertTrue;
40+
import static org.junit.Assert.fail;
41+
42+
/**
43+
* Userpool and identity pool were create with Amplify CLI 0.1.23 Default configuration
44+
*/
45+
@RunWith(AndroidJUnit4.class)
46+
public class AWSMobileClientNetworkIssueTest extends AWSMobileClientTestBase {
47+
private static final String TAG = AWSMobileClientNetworkIssueTest.class.getSimpleName();
48+
public static final String USERNAME = "somebody";
49+
50+
// Populated from awsconfiguration.json
51+
static Regions clientRegion = Regions.US_WEST_2;
52+
static String userPoolId;
53+
static String identityPoolId;
54+
55+
Context appContext;
56+
AWSMobileClient auth;
57+
UserStateListener listener;
58+
String username;
59+
60+
@BeforeClass
61+
public static void beforeClass() throws Exception {
62+
Context appContext = InstrumentationRegistry.getTargetContext();
63+
64+
// Initialize
65+
final CountDownLatch latch = new CountDownLatch(1);
66+
AWSMobileClient.getInstance().initialize(appContext, new Callback<UserStateDetails>() {
67+
@Override
68+
public void onResult(UserStateDetails result) {
69+
latch.countDown();
70+
}
71+
72+
@Override
73+
public void onError(Exception e) {
74+
latch.countDown();
75+
}
76+
});
77+
latch.await();
78+
79+
final AWSConfiguration awsConfiguration = new AWSConfiguration(appContext);
80+
81+
JSONObject userPoolConfig = awsConfiguration.optJsonObject("CognitoUserPool");
82+
assertNotNull(userPoolConfig);
83+
clientRegion = Regions.fromName(userPoolConfig.getString("Region"));
84+
userPoolId = userPoolConfig.getString("PoolId");
85+
86+
JSONObject identityPoolConfig =
87+
awsConfiguration.optJsonObject("CredentialsProvider").getJSONObject(
88+
"CognitoIdentity").getJSONObject("Default");
89+
assertNotNull(identityPoolConfig);
90+
identityPoolId = identityPoolConfig.getString("PoolId");
91+
}
92+
93+
@Before
94+
public void before() throws InterruptedException {
95+
appContext = InstrumentationRegistry.getTargetContext();
96+
auth = AWSMobileClient.getInstance();
97+
auth.signOut();
98+
99+
username = "testUser" + System.currentTimeMillis() + new Random().nextInt();
100+
}
101+
102+
@After
103+
public void after() {
104+
auth.removeUserStateListener(listener);
105+
auth.listeners.clear();
106+
auth.signOut();
107+
}
108+
109+
/**
110+
* This set of tests mocks internal components, so it is necessary to re-initialize
111+
* the client to create working internal components.
112+
* @throws InterruptedException
113+
*/
114+
public void reinitialize() throws InterruptedException {
115+
// Reset initialization of client
116+
AWSMobileClient.getInstance().awsConfiguration = null;
117+
118+
// Initialize
119+
final CountDownLatch latch = new CountDownLatch(1);
120+
AWSMobileClient.getInstance().initialize(appContext, new Callback<UserStateDetails>() {
121+
@Override
122+
public void onResult(UserStateDetails result) {
123+
latch.countDown();
124+
}
125+
126+
@Override
127+
public void onError(Exception e) {
128+
latch.countDown();
129+
}
130+
});
131+
latch.await();
132+
}
133+
134+
@Test
135+
public void useAppContext() throws Exception {
136+
// Context of the app under test.
137+
Context appContext = InstrumentationRegistry.getTargetContext();
138+
139+
final AWSConfiguration awsConfiguration = new AWSConfiguration(appContext);
140+
141+
assertNotNull(awsConfiguration.optJsonObject("CognitoUserPool"));
142+
assertEquals("us-west-2", awsConfiguration.optJsonObject("CognitoUserPool").getString("Region"));
143+
144+
assertEquals("com.amazonaws.mobile.client.test", appContext.getPackageName());
145+
}
146+
147+
@Test
148+
public void testGetConfiguration() {
149+
final AWSConfiguration awsConfiguration = AWSMobileClient.getInstance().getConfiguration();
150+
151+
assertNotNull(awsConfiguration.optJsonObject("CognitoUserPool"));
152+
try {
153+
assertEquals("us-west-2", awsConfiguration.optJsonObject("CognitoUserPool").getString("Region"));
154+
} catch (JSONException e) {
155+
e.printStackTrace();
156+
fail(e.getMessage());
157+
}
158+
}
159+
160+
@Test
161+
public void testNetworkExceptionPropagation_getUserAttributes() throws Exception {
162+
reinitialize();
163+
final AWSKeyValueStore awsKeyValueStore = new AWSKeyValueStore(appContext,
164+
AWSMobileClient.SHARED_PREFERENCES_KEY,
165+
true);
166+
awsKeyValueStore.put(AWSMobileClient.PROVIDER_KEY, AWSMobileClient.getInstance().getLoginKey());
167+
awsKeyValueStore.put(AWSMobileClient.TOKEN_KEY, getValidJWT(-3600L));
168+
awsKeyValueStore.put(AWSMobileClient.IDENTITY_ID_KEY, "");
169+
writeUserpoolsTokens(appContext, auth.getConfiguration().optJsonObject("CognitoUserPool").getString("AppClientId"), username, -3600L);
170+
setField(auth.userpool, CognitoUserPool.class, "client", mockLowLevel);
171+
try {
172+
auth.getUserAttributes();
173+
fail("Should throw exception for network issue");
174+
} catch (Exception e) {
175+
assertTrue("Deep cause should be network exception", e.getCause().getCause() instanceof UnknownHostException);
176+
}
177+
}
178+
179+
@Test
180+
public void testNetworkExceptionPropagation_getTokens() throws Exception {
181+
reinitialize();
182+
final AWSKeyValueStore awsKeyValueStore = new AWSKeyValueStore(appContext,
183+
AWSMobileClient.SHARED_PREFERENCES_KEY,
184+
true);
185+
awsKeyValueStore.put(AWSMobileClient.PROVIDER_KEY, AWSMobileClient.getInstance().getLoginKey());
186+
awsKeyValueStore.put(AWSMobileClient.TOKEN_KEY, getValidJWT(-3600L));
187+
awsKeyValueStore.put(AWSMobileClient.IDENTITY_ID_KEY, "");
188+
writeUserpoolsTokens(appContext, auth.getConfiguration().optJsonObject("CognitoUserPool").getString("AppClientId"), username, -3600L);
189+
Field f1 = CognitoUserPool.class.getDeclaredField("client");
190+
f1.setAccessible(true);
191+
f1.set(auth.userpool, mockLowLevel);
192+
try {
193+
auth.getTokens();
194+
fail("Should throw exception for network issue");
195+
} catch (Exception e) {
196+
assertTrue("Deep cause should be network exception", e.getCause().getCause().getCause() instanceof UnknownHostException);
197+
}
198+
}
199+
200+
@Test
201+
public void testNetworkExceptionPropagation_getTokens_federationStep1() throws Exception {
202+
reinitialize();
203+
auth.signIn(USERNAME, "1234Password!", null);
204+
// setField(auth.cognitoIdentity, CognitoCredentialsProvider.class, "cib", mockIdentityLowLevelGetId);
205+
setField(auth.provider, AWSAbstractCognitoIdentityProvider.class, "cib", mockIdentityLowLevelGetId);
206+
setField(auth.provider, AWSAbstractCognitoIdentityProvider.class, "identityId", null);
207+
auth.mFederatedLoginsMap.clear();
208+
try {
209+
auth.getCredentials();
210+
fail("Should throw exception for network issue");
211+
} catch (Exception e) {
212+
assertTrue("Deep cause should be network exception", e.getCause().getCause() instanceof UnknownHostException);
213+
}
214+
}
215+
216+
@Test
217+
public void testNetworkExceptionPropagation_getTokens_federationStep2() throws Exception {
218+
reinitialize();
219+
auth.signIn(USERNAME, "1234Password!", null);
220+
setField(auth.cognitoIdentity, CognitoCredentialsProvider.class, "cib", mockIdentityLowLevelGetIdAndGetCredentials);
221+
auth.mFederatedLoginsMap.clear();
222+
try {
223+
auth.getCredentials();
224+
fail("Should throw exception for network issue");
225+
} catch (Exception e) {
226+
assertTrue("Deep cause should be network exception", e.getCause().getCause() instanceof UnknownHostException);
227+
}
228+
}
229+
230+
public static void setField(Object obj, Class clazz, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
231+
Field identityProviderCibField = clazz.getDeclaredField(fieldName);
232+
identityProviderCibField.setAccessible(true);
233+
identityProviderCibField.set(obj, value);
234+
}
235+
236+
AmazonCognitoIdentityProvider mockLowLevel = new AbstractAmazonCognitoIdentityProvider() {
237+
@Override
238+
public InitiateAuthResult initiateAuth(InitiateAuthRequest initiateAuthRequest) throws AmazonClientException, AmazonServiceException {
239+
throw new AmazonClientException("Unable to execute HTTP request: Unable to resolve " +
240+
"host \"cognito-idp.us-west-2.amazonaws.com\": No address associated with " +
241+
"hostname", new UnknownHostException("Unable to resolve host \"cognito-idp" +
242+
".us-west-2.amazonaws.com\": No address associated with hostname"));
243+
}
244+
};
245+
246+
AmazonCognitoIdentity mockIdentityLowLevelGetId = new AbstractAmazonCognitoIdentity() {
247+
@Override
248+
public GetIdResult getId(GetIdRequest getIdRequest) throws AmazonClientException, AmazonServiceException {
249+
throw new AmazonClientException("Unable to execute HTTP request: Unable to resolve " +
250+
"host \"cognito-idp.us-west-2.amazonaws.com\": No address associated with " +
251+
"hostname", new UnknownHostException("Unable to resolve host \"cognito-idp" +
252+
".us-west-2.amazonaws.com\": No address associated with hostname"));
253+
}
254+
};
255+
256+
AmazonCognitoIdentity mockIdentityLowLevelGetIdAndGetCredentials = new AbstractAmazonCognitoIdentity() {
257+
@Override
258+
public GetCredentialsForIdentityResult getCredentialsForIdentity(GetCredentialsForIdentityRequest getCredentialsForIdentityRequest) throws AmazonClientException, AmazonServiceException {
259+
throw new AmazonClientException("Unable to execute HTTP request: Unable to resolve " +
260+
"host \"cognito-idp.us-west-2.amazonaws.com\": No address associated with " +
261+
"hostname", new UnknownHostException("Unable to resolve host \"cognito-idp" +
262+
".us-west-2.amazonaws.com\": No address associated with hostname")); // TODO secondary
263+
}
264+
265+
@Override
266+
public GetIdResult getId(GetIdRequest getIdRequest) throws AmazonClientException, AmazonServiceException {
267+
return new GetIdResult().withIdentityId("mocked-id");
268+
}
269+
};
270+
271+
}

aws-android-sdk-mobile-client/src/androidTest/java/com/amazonaws/mobile/client/AWSMobileClientTestBase.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ public static void writeUserpoolsTokens(final Context appContext, final String c
4444
"CognitoIdentityProviderCache",
4545
true);
4646
String storeFieldPrefix = "CognitoIdentityProvider." + clientId + "." + username + ".";
47+
awsKeyValueStore.put("CognitoIdentityProvider." + clientId + ".LastAuthUser", username);
4748
awsKeyValueStore.put(storeFieldPrefix + "idToken", getValidJWT(expiryFromNow));
4849
awsKeyValueStore.put(storeFieldPrefix + "accessToken", getValidJWT(expiryFromNow));
4950
awsKeyValueStore.put(storeFieldPrefix + "refreshToken", "DummyRefresh");

aws-android-sdk-mobile-client/src/androidTest/java/com/amazonaws/mobile/client/AWSMobileClientUITest.java

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import android.content.Context;
44
import android.support.test.InstrumentationRegistry;
55
import android.support.test.rule.ActivityTestRule;
6+
import android.support.test.uiautomator.By;
67
import android.support.test.uiautomator.BySelector;
78
import android.support.test.uiautomator.UiDevice;
89
import android.support.test.uiautomator.UiObject2;
@@ -143,8 +144,8 @@ public void onError(Exception e) {
143144
/// The UI automation code is not finalized, do the steps manually, the code will wait for
144145
/// success indicators
145146

146-
// final UiDevice uiDevice =
147-
// UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
147+
final UiDevice uiDevice =
148+
UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
148149
// final UiObject2 acceptButton = waitForObject(By.text("Accept & continue"), false);
149150
// if (acceptButton != null) {
150151
// acceptButton.click();
@@ -153,13 +154,13 @@ public void onError(Exception e) {
153154
// if (noThanksButton != null) {
154155
// noThanksButton.click();
155156
// }
156-
// try {
157-
// uiDevice.findObjects(By.clazz("android.widget.EditText")).get(0).setText("somebody");
158-
// uiDevice.findObjects(By.clazz("android.widget.EditText")).get(1).setText("1234Password!");
159-
// uiDevice.findObject(By.text("Sign in")).click();
160-
// } catch (Exception e) {
161-
// Log.e(TAG, "testShowSignInHostedUIWithUserpools: the user may already be signed-in in the browser", e);
162-
// }
157+
try {
158+
uiDevice.findObjects(By.clazz("android.widget.EditText")).get(0).setText("somebody");
159+
uiDevice.findObjects(By.clazz("android.widget.EditText")).get(1).setText("1234Password!");
160+
uiDevice.findObject(By.text("Sign in")).click();
161+
} catch (Exception e) {
162+
Log.e(TAG, "testShowSignInHostedUIWithUserpools: the user may already be signed-in in the browser", e);
163+
}
163164
while (AWSMobileClientUITestActivity.intents.size() == 0) {
164165
Log.e(TAG, "testShowSignInAuth0: Waiting on intent");
165166
Thread.sleep(50);

0 commit comments

Comments
 (0)