From da4e83507e45052f5e84a8881db26807b93db252 Mon Sep 17 00:00:00 2001 From: Karthikeyan Vasuki Balasubramaniam Date: Thu, 24 Oct 2019 14:16:42 -0700 Subject: [PATCH] [AWSMobileClient] Add non-null check in accessing fields of SignUpResult --- .idea/codeStyles/Project.xml | 116 ++++++++ .idea/vcs.xml | 6 + CHANGELOG.md | 8 + ...SMobileClientSignUpNoVerificationTest.java | 265 ++++++++++++++++++ .../mobile/client/AWSMobileClient.java | 48 +++- .../mobile/client/results/SignUpResult.java | 2 +- 6 files changed, 429 insertions(+), 16 deletions(-) create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/vcs.xml create mode 100644 aws-android-sdk-mobile-client/src/androidTest/java/com/amazonaws/mobile/client/AWSMobileClientSignUpNoVerificationTest.java diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000000..681f41ae2a --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,116 @@ + + + + + + + +
+ + + + xmlns:android + + ^$ + + + +
+
+ + + + xmlns:.* + + ^$ + + + BY_NAME + +
+
+ + + + .*:id + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + .*:name + + http://schemas.android.com/apk/res/android + + + +
+
+ + + + name + + ^$ + + + +
+
+ + + + style + + ^$ + + + +
+
+ + + + .* + + ^$ + + + BY_NAME + +
+
+ + + + .* + + http://schemas.android.com/apk/res/android + + + ANDROID_ATTRIBUTE_ORDER + +
+
+ + + + .* + + .* + + + BY_NAME + +
+
+
+
+
+
\ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000..94a25f7f4c --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 171b32153e..0e0a5cb163 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log - AWS SDK for Android +## [Release 2.16.3](https://github.com/aws/aws-sdk-android/releases/tag/release_v2.16.3) + +### Bug Fixes + +- **AWS Mobile Client** + - During `signUp`, when the user is confirmed, Cognito does not send `CognitoUserCodeDeliveryDetails` +and it appears to be null when the SignUpResult is unmarshalled. Add a check before accessing the `CognitoUserCodeDeliveryDetails` only when the user is not confirmed. See [issue #1264](https://github.com/aws-amplify/aws-sdk-android/issues/1264) for more details. + ## [Release 2.16.2](https://github.com/aws/aws-sdk-android/releases/tag/release_v2.16.2) ### New Features diff --git a/aws-android-sdk-mobile-client/src/androidTest/java/com/amazonaws/mobile/client/AWSMobileClientSignUpNoVerificationTest.java b/aws-android-sdk-mobile-client/src/androidTest/java/com/amazonaws/mobile/client/AWSMobileClientSignUpNoVerificationTest.java new file mode 100644 index 0000000000..39267d326e --- /dev/null +++ b/aws-android-sdk-mobile-client/src/androidTest/java/com/amazonaws/mobile/client/AWSMobileClientSignUpNoVerificationTest.java @@ -0,0 +1,265 @@ +/* + * Copyright 2019 Amazon.com, Inc. or its affiliates. + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.amazonaws.mobile.client; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.util.Log; + +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.mobile.client.results.SignInResult; +import com.amazonaws.mobile.client.results.SignInState; +import com.amazonaws.mobile.client.results.SignUpResult; +import com.amazonaws.mobile.config.AWSConfiguration; +import com.amazonaws.regions.Region; +import com.amazonaws.regions.Regions; +import com.amazonaws.services.cognitoidentityprovider.AmazonCognitoIdentityProvider; +import com.amazonaws.services.cognitoidentityprovider.AmazonCognitoIdentityProviderClient; +import com.amazonaws.services.cognitoidentityprovider.model.AdminConfirmSignUpRequest; +import com.amazonaws.services.cognitoidentityprovider.model.AdminDeleteUserRequest; +import com.amazonaws.services.cognitoidentityprovider.model.ListUsersRequest; +import com.amazonaws.services.cognitoidentityprovider.model.ListUsersResult; +import com.amazonaws.services.cognitoidentityprovider.model.UserNotConfirmedException; +import com.amazonaws.services.cognitoidentityprovider.model.UserType; + +import org.json.JSONObject; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +/** + * This class tests {@link AWSMobileClient#signUp(String, String, Map, Map)} + * with a {@link com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserPool} that has + * No MFA and No other verification mechanisms + * which will send a {@link com.amazonaws.mobile.client.results.SignUpResult} with no + * {@link com.amazonaws.mobile.client.results.UserCodeDeliveryDetails} + */ +public class AWSMobileClientSignUpNoVerificationTest extends AWSMobileClientTestBase { + private static final String TAG = AWSMobileClientTest.class.getSimpleName(); + + private static final String EMAIL = "somebody@email.com"; + private static final String USERNAME = "somebody"; + private static final String PASSWORD = "1234Password!"; + private static final long THREAD_WAIT_DURATION = 60; + + private static BasicAWSCredentials adminCredentials; + private static AmazonCognitoIdentityProvider userPoolLowLevelClient; + + static { + try { + adminCredentials = new BasicAWSCredentials( + getPackageConfigure().getString("create_cognito_user_access_key"), + getPackageConfigure().getString("create_cognito_user_secret_key")); + } catch (Exception e) { + e.printStackTrace(); + } + } + + // Populated from awsconfiguration.json + private static Regions clientRegion = Regions.US_WEST_2; + private static String userPoolId; + + private Context appContext; + private AWSMobileClient auth; + private String username; + + private static synchronized AmazonCognitoIdentityProvider getUserPoolLowLevelClient() { + if (userPoolLowLevelClient == null) { + userPoolLowLevelClient = new AmazonCognitoIdentityProviderClient(adminCredentials); + userPoolLowLevelClient.setRegion(Region.getRegion(clientRegion)); + } + return userPoolLowLevelClient; + } + + private static void createUser(final AWSMobileClient auth, + final String userPoolId, + final String username, + final String password, + final String email) throws Exception { + HashMap userAttributes = new HashMap(); + userAttributes.put("email", email); + auth.signUp(username, password, userAttributes,null); + + AdminConfirmSignUpRequest adminConfirmSignUpRequest = new AdminConfirmSignUpRequest(); + adminConfirmSignUpRequest.withUsername(username).withUserPoolId(userPoolId); + getUserPoolLowLevelClient().adminConfirmSignUp(adminConfirmSignUpRequest); + } + + private static void deleteAllUsers(final String userPoolId) { + ListUsersResult listUsersResult; + do { + ListUsersRequest listUsersRequest = new ListUsersRequest() + .withUserPoolId(userPoolId) + .withLimit(60); + listUsersResult = getUserPoolLowLevelClient().listUsers(listUsersRequest); + for (UserType user : listUsersResult.getUsers()) { + if (USERNAME.equals(user.getUsername()) + || "bimin".equals(user.getUsername()) || "customAuthTestUser".equals(user.getUsername())) { + // This user is saved to test the identity id permanence + continue; + } + boolean retryConfirmSignUp = false; + do { + try { + Log.d(TAG, "deleteAllUsers: " + user.getUsername()); + getUserPoolLowLevelClient().adminDeleteUser( + new AdminDeleteUserRequest() + .withUsername(user.getUsername()) + .withUserPoolId(userPoolId)); + } catch (UserNotConfirmedException e) { + if (!retryConfirmSignUp) { + AdminConfirmSignUpRequest adminConfirmSignUpRequest = new AdminConfirmSignUpRequest(); + adminConfirmSignUpRequest + .withUsername(user.getUsername()) + .withUserPoolId(userPoolId); + getUserPoolLowLevelClient().adminConfirmSignUp(adminConfirmSignUpRequest); + retryConfirmSignUp = true; + try { + Thread.sleep(10); + } catch (InterruptedException e1) { + e1.printStackTrace(); + } + } else { + retryConfirmSignUp = false; + } + } catch (Exception e) { + Log.e(TAG, "deleteAllUsers: Some error trying to delete user", e); + } + } while (retryConfirmSignUp); + } + } while (listUsersResult.getPaginationToken() != null); + } + + @BeforeClass + public static void beforeClass() throws Exception { + Context appContext = InstrumentationRegistry.getTargetContext(); + + final CountDownLatch waitUntilInitialized = new CountDownLatch(1); + final AWSConfiguration awsConfiguration = new AWSConfiguration(appContext); + awsConfiguration.setConfiguration("AWSMobileClientIntegrationTest-NoSignUpVerification"); + AWSMobileClient.getInstance().initialize(appContext, + awsConfiguration, + new Callback() { + @Override + public void onResult(UserStateDetails result) { + waitUntilInitialized.countDown(); + } + + @Override + public void onError(Exception e) { + waitUntilInitialized.countDown(); + } + }); + waitUntilInitialized.await(); + + JSONObject userPoolConfig = awsConfiguration.optJsonObject("CognitoUserPool"); + assertNotNull(userPoolConfig); + clientRegion = Regions.fromName(userPoolConfig.getString("Region")); + userPoolId = userPoolConfig.getString("PoolId"); + + JSONObject identityPoolConfig = + awsConfiguration.optJsonObject("CredentialsProvider").getJSONObject( + "CognitoIdentity").getJSONObject("Default"); + assertNotNull(identityPoolConfig); + + deleteAllUsers(userPoolId); + } + + @Before + public void before() throws Exception { + appContext = InstrumentationRegistry.getTargetContext(); + auth = AWSMobileClient.getInstance(); + auth.signOut(); + + username = "testUser" + System.currentTimeMillis() + new Random().nextInt(); + createUser(auth, userPoolId, username, PASSWORD, EMAIL); + } + + @After + public void after() { + auth.signOut(); + + appContext.getSharedPreferences(AWSMobileClient.SHARED_PREFERENCES_KEY, + Context.MODE_PRIVATE) + .edit() + .clear() + .apply(); + deleteAllEncryptionKeys(); + } + + @Test(expected = com.amazonaws.services.cognitoidentityprovider.model.UserNotConfirmedException.class) + public void testSignUpSyncNoVerification() throws Exception { + final String username = "testUser" + System.currentTimeMillis() + new Random().nextInt(); + assertNotEquals("generated usernames are the same", this.username, username); + + final HashMap userAttributes = new HashMap(); + userAttributes.put("email", EMAIL); + + final SignUpResult signUpResult = auth.signUp(username, PASSWORD, userAttributes, null); + assertNotNull(signUpResult.getUserSub()); + assertFalse(signUpResult.getConfirmationState()); + assertNull(signUpResult.getUserCodeDeliveryDetails()); + assertNotNull(signUpResult.getUserSub()); + + final SignInResult signInResult = auth.signIn(username, PASSWORD, null); + assertEquals("Cannot support MFA in tests", SignInState.DONE, signInResult.getSignInState()); + } + + @Test(expected = com.amazonaws.services.cognitoidentityprovider.model.UserNotConfirmedException.class) + public void testSignUpAsyncNoVerification() throws Exception { + final String username = "testUser" + System.currentTimeMillis() + new Random().nextInt(); + assertNotEquals("generated usernames are the same", this.username, username); + + final HashMap userAttributes = new HashMap(); + userAttributes.put("email", EMAIL); + + final CountDownLatch waitForSignUpResult = new CountDownLatch(1); + auth.signUp(username, PASSWORD, userAttributes, null, new Callback() { + @Override + public void onResult(SignUpResult signUpResult) { + assertNotNull(signUpResult.getUserSub()); + assertFalse(signUpResult.getConfirmationState()); + assertNull(signUpResult.getUserCodeDeliveryDetails()); + assertNotNull(signUpResult.getUserSub()); + waitForSignUpResult.countDown(); + } + + @Override + public void onError(Exception e) { + waitForSignUpResult.countDown(); + } + }); + waitForSignUpResult.await(THREAD_WAIT_DURATION, TimeUnit.SECONDS); + + final SignInResult signInResult = auth.signIn(username, PASSWORD, null); + assertEquals("Cannot support MFA in tests", SignInState.DONE, signInResult.getSignInState()); + } +} diff --git a/aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/AWSMobileClient.java b/aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/AWSMobileClient.java index ed1fb3a1c8..c317704ede 100644 --- a/aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/AWSMobileClient.java +++ b/aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/AWSMobileClient.java @@ -1790,13 +1790,15 @@ public void onFailure(Exception e) { /** * Sign-up users. The {@link SignUpResult} will contain next steps if necessary. - * Call confirmSignUp with the necessary next step code obtained from user. + * Call {@link #confirmSignUp(String, String, Callback)} with the necessary next + * step code obtained from user. * * @param username username/email address/handle * @param password user's password * @param userAttributes attributes associated with user * @param validationData optional, set of data to validate the sign-up request - * @param callback callback + * @param callback callback will be invoked to notify the success or failure of the + * SignUp operation */ @AnyThread public void signUp(final String username, @@ -1811,14 +1813,14 @@ public void signUp(final String username, /** * Sign-up users. The {@link SignUpResult} will contain next steps if necessary. - * Call confirmSignUp with the necessary next step code obtained from user. + * Call {@link #confirmSignUp(String, String)} with the necessary next step code obtained from user. * * @param username username/email address/handle * @param password user's password * @param userAttributes attributes associated with user * @param validationData optional, set of data to validate the sign-up request * @return result of the operation, potentially with next steps - * @throws Exception + * @throws Exception if there is any error generated by the client */ @WorkerThread public SignUpResult signUp(final String username, @@ -1850,12 +1852,27 @@ public void onSuccess(final CognitoUser user, final com.amazonaws.services.cognitoidentityprovider.model.SignUpResult signUpResult) { signUpUser = user; - UserCodeDeliveryDetails userCodeDeliveryDetails = new UserCodeDeliveryDetails( - signUpResult.getCodeDeliveryDetails().getDestination(), - signUpResult.getCodeDeliveryDetails().getDeliveryMedium(), - signUpResult.getCodeDeliveryDetails().getAttributeName() - ); - callback.onResult(new SignUpResult(signUpResult.getUserConfirmed(), userCodeDeliveryDetails, signUpResult.getUserSub())); + if (signUpResult == null) { + callback.onError(new Exception("SignUpResult received is null")); + return; + } + + // When the user is confirmed, Cognito does not send CognitoUserCodeDeliveryDetails + // and it appears to be null when the SignUpResult is unmarshalled. Extract the + // CognitoUserCodeDeliveryDetails only when the user is not confirmed. + if (signUpResult.getCodeDeliveryDetails() == null) { + callback.onResult(new SignUpResult(signUpResult.getUserConfirmed(), + null, + signUpResult.getUserSub())); + } else { + UserCodeDeliveryDetails userCodeDeliveryDetails = new UserCodeDeliveryDetails( + signUpResult.getCodeDeliveryDetails().getDestination(), + signUpResult.getCodeDeliveryDetails().getDeliveryMedium(), + signUpResult.getCodeDeliveryDetails().getAttributeName()); + callback.onResult(new SignUpResult(signUpResult.getUserConfirmed(), + userCodeDeliveryDetails, + signUpResult.getUserSub())); + } } @Override @@ -1870,9 +1887,10 @@ public void onFailure(Exception exception) { /** * Confirm the sign-up request with follow-up information * - * @param username - * @param signUpChallengeResponse - * @param callback + * @param username username/email address/handle of the user who is signing up + * @param signUpChallengeResponse response to the signUp challenge posted + * @param callback the callback will be invoked to notify the success or + * failure of the confirmSignUp operation */ @AnyThread public void confirmSignUp(final String username, @@ -1886,8 +1904,8 @@ public void confirmSignUp(final String username, /** * Confirm the sign-up request with follow-up information * - * @param username - * @param signUpChallengeResponse + * @param username username of the user who is signing up + * @param signUpChallengeResponse response to the signUp challenge posted */ @WorkerThread public SignUpResult confirmSignUp(final String username, diff --git a/aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/results/SignUpResult.java b/aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/results/SignUpResult.java index 82922b40eb..ff2ca02796 100644 --- a/aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/results/SignUpResult.java +++ b/aws-android-sdk-mobile-client/src/main/java/com/amazonaws/mobile/client/results/SignUpResult.java @@ -20,7 +20,7 @@ /** * The result of a sign up action. Check the confirmation state and delivery details to proceed. */ -public class SignUpResult { +public final class SignUpResult { private final boolean signUpConfirmationState; private final UserCodeDeliveryDetails cognitoUserCodeDeliveryDetails;