Skip to content

Commit f41523a

Browse files
rjulianodiv5yesh
andauthored
fix(cognitoidentityprovider):save lastAuthUser when using cached tokens (#2628)
* fix(cognitoidentityprovider):save lastAuthUser when using cached tokens * added unit test Co-authored-by: Divyesh Chitroda <[email protected]>
1 parent 3737e3e commit f41523a

File tree

4 files changed

+191
-2
lines changed

4 files changed

+191
-2
lines changed

aws-android-sdk-cognitoidentityprovider/build.gradle

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,17 @@ android {
88
minSdkVersion 9
99
targetSdkVersion 29
1010
}
11-
11+
compileOptions {
12+
sourceCompatibility 1.8
13+
targetCompatibility 1.8
14+
}
1215
testOptions.unitTests.includeAndroidResources = true
1316
}
1417

1518
dependencies {
1619
api project(':aws-android-sdk-core')
1720
implementation 'com.amazonaws:aws-android-sdk-cognitoidentityprovider-asf:1.0.0'
21+
implementation 'androidx.annotation:annotation:1.1.0'
1822

1923
testImplementation 'junit:junit:4.13.1'
2024
testImplementation 'org.mockito:mockito-core:1.10.19'

aws-android-sdk-cognitoidentityprovider/src/main/java/com/amazonaws/mobileconnectors/cognitoidentityprovider/CognitoUser.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1284,6 +1284,7 @@ protected CognitoUserSession getCachedSession() {
12841284

12851285
if (cipSession != null) {
12861286
if (cipSession.isValidForThreshold()) {
1287+
cacheLastAuthUser();
12871288
return cipSession;
12881289
}
12891290
}
@@ -1292,6 +1293,7 @@ protected CognitoUserSession getCachedSession() {
12921293

12931294
if (cognitoUserSessionFromStore.isValidForThreshold()) {
12941295
cipSession = cognitoUserSessionFromStore;
1296+
cacheLastAuthUser();
12951297
return cipSession;
12961298
}
12971299

@@ -2802,6 +2804,16 @@ void cacheTokens(CognitoUserSession session) {
28022804
}
28032805
}
28042806

2807+
void cacheLastAuthUser() {
2808+
try {
2809+
final String csiLastUserKey = "CognitoIdentityProvider." + clientId + ".LastAuthUser";
2810+
pool.awsKeyValueStore.put(csiLastUserKey, userId);
2811+
} catch (final Exception e) {
2812+
// Logging exception, this is not a fatal error
2813+
LOGGER.error("Error while writing to SharedPreferences.", e);
2814+
}
2815+
}
2816+
28052817
/**
28062818
* Creates a user session with the tokens from authentication.
28072819
*

aws-android-sdk-cognitoidentityprovider/src/main/java/com/amazonaws/mobileconnectors/cognitoidentityprovider/CognitoUserPool.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import android.content.Context;
2121
import android.os.Handler;
2222

23+
import androidx.annotation.VisibleForTesting;
24+
2325
import com.amazonaws.ClientConfiguration;
2426
import com.amazonaws.auth.AnonymousAWSCredentials;
2527
import com.amazonaws.internal.keyvaluestore.AWSKeyValueStore;
@@ -97,7 +99,7 @@ public class CognitoUserPool {
9799
/**
98100
* CIP low-level client.
99101
*/
100-
private final AmazonCognitoIdentityProvider client;
102+
private AmazonCognitoIdentityProvider client;
101103

102104
/**
103105
* Calculated with {@code userId}, {@code clientId} and {@code clientSecret}
@@ -332,6 +334,11 @@ public CognitoUserPool(Context context, String userPoolId, String clientId, Stri
332334
}
333335
}
334336

337+
@VisibleForTesting
338+
void setIdentityProvider(AmazonCognitoIdentityProvider userpool) {
339+
this.client = userpool;
340+
}
341+
335342

336343
private void initialize(final Context context) {
337344
this.awsKeyValueStore = new AWSKeyValueStore(context,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
package com.amazonaws.mobileconnectors.cognitoidentityprovider;
16+
17+
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
18+
import static org.junit.Assert.assertEquals;
19+
import static org.mockito.Matchers.any;
20+
import static org.mockito.Mockito.mock;
21+
import static org.mockito.Mockito.when;
22+
23+
import android.util.Base64;
24+
import android.util.Log;
25+
26+
import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.AuthenticationContinuation;
27+
import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.AuthenticationDetails;
28+
import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.ChallengeContinuation;
29+
import com.amazonaws.mobileconnectors.cognitoidentityprovider.continuations.MultiFactorAuthenticationContinuation;
30+
import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.AuthenticationHandler;
31+
import com.amazonaws.mobileconnectors.cognitoidentityprovider.util.CognitoServiceConstants;
32+
import com.amazonaws.regions.Regions;
33+
import com.amazonaws.services.cognitoidentityprovider.AmazonCognitoIdentityProvider;
34+
import com.amazonaws.services.cognitoidentityprovider.model.AuthenticationResultType;
35+
import com.amazonaws.services.cognitoidentityprovider.model.InitiateAuthRequest;
36+
import com.amazonaws.services.cognitoidentityprovider.model.InitiateAuthResult;
37+
import com.amazonaws.services.cognitoidentityprovider.model.RespondToAuthChallengeRequest;
38+
import com.amazonaws.services.cognitoidentityprovider.model.RespondToAuthChallengeResult;
39+
import com.amazonaws.util.StringUtils;
40+
41+
import org.junit.Before;
42+
import org.junit.Test;
43+
import org.junit.runner.RunWith;
44+
import org.robolectric.RobolectricTestRunner;
45+
import org.robolectric.annotation.Config;
46+
import org.robolectric.shadows.ShadowLog;
47+
48+
import java.util.Collections;
49+
import java.util.HashMap;
50+
import java.util.concurrent.CountDownLatch;
51+
import java.util.concurrent.TimeUnit;
52+
53+
@RunWith(RobolectricTestRunner.class)
54+
@Config(manifest= Config.NONE, sdk = 27)
55+
public class CognitoUserPoolTest {
56+
private static final String USER1 = "user1";
57+
private static final String USER2 = "user2";
58+
private CognitoUserPool cognitoUserPool;
59+
private AmazonCognitoIdentityProvider mockProvider;
60+
@Before
61+
public void setup() {
62+
ShadowLog.stream = System.out;
63+
mockProvider = mock(AmazonCognitoIdentityProvider.class);
64+
cognitoUserPool = new CognitoUserPool(getApplicationContext(), "us_east_1_xxxxx", "dummyclientid", "dummysecret", Regions.US_EAST_1);
65+
cognitoUserPool.setAdvancedSecurityDataCollectionFlag(false);
66+
cognitoUserPool.setIdentityProvider(mockProvider);
67+
}
68+
69+
/**
70+
* Verify that the last auth user is saved when a previous session is read from cache.
71+
*
72+
* @throws InterruptedException
73+
*/
74+
@Test
75+
public void verifyLastAuthUserWithMultipleLogins() throws InterruptedException {
76+
// First login of user1 should result in the session getting cached
77+
setupMock(USER1);
78+
CountDownLatch loginLatch = new CountDownLatch(1);
79+
cognitoUserPool.getUser(USER1).getSession(new LatchedAuthenticationHandler(loginLatch));
80+
loginLatch.await(5, TimeUnit.SECONDS);
81+
CognitoUser currentUser1 = cognitoUserPool.getCurrentUser();
82+
assertEquals(USER1, currentUser1.getUserId());
83+
84+
// First login of user2 should result in the session getting cached
85+
setupMock(USER2);
86+
loginLatch = new CountDownLatch(1);
87+
cognitoUserPool.getUser(USER2).getSession(new LatchedAuthenticationHandler(loginLatch));
88+
loginLatch.await(5, TimeUnit.SECONDS);
89+
CognitoUser currentUser2 = cognitoUserPool.getCurrentUser();
90+
assertEquals(USER2, currentUser2.getUserId());
91+
92+
// Second login of user1 should read cached session and save the last auth user.
93+
loginLatch = new CountDownLatch(1);
94+
cognitoUserPool.getUser(USER1).getSession(new LatchedAuthenticationHandler(loginLatch));
95+
loginLatch.await(5, TimeUnit.SECONDS);
96+
currentUser1 = cognitoUserPool.getCurrentUser();
97+
assertEquals(USER1, currentUser1.getUserId());
98+
}
99+
100+
static final class LatchedAuthenticationHandler implements AuthenticationHandler {
101+
private final CountDownLatch loginLatch;
102+
103+
public LatchedAuthenticationHandler(CountDownLatch loginLatch) {
104+
this.loginLatch = loginLatch;
105+
}
106+
@Override
107+
public void onSuccess(CognitoUserSession userSession, CognitoDevice newDevice) {
108+
loginLatch.countDown();
109+
}
110+
111+
@Override
112+
public void getAuthenticationDetails(AuthenticationContinuation authenticationContinuation, String userId) {
113+
authenticationContinuation.setAuthenticationDetails(new AuthenticationDetails(userId, "test", Collections.emptyMap()));
114+
authenticationContinuation.continueTask();
115+
}
116+
117+
@Override
118+
public void getMFACode(MultiFactorAuthenticationContinuation continuation) {
119+
120+
}
121+
122+
@Override
123+
public void authenticationChallenge(ChallengeContinuation continuation) {
124+
125+
}
126+
127+
@Override
128+
public void onFailure(Exception exception) {
129+
Log.e("Test", "Login failed.", exception);
130+
}
131+
}
132+
133+
private void setupMock(String userName) {
134+
HashMap<String, String> parameters = new HashMap<>();
135+
parameters.put(CognitoServiceConstants.CHLG_PARAM_USERNAME, userName);
136+
parameters.put(CognitoServiceConstants.CHLG_PARAM_SRP_B, "1");
137+
parameters.put(CognitoServiceConstants.CHLG_PARAM_SALT, "1234");
138+
parameters.put(CognitoServiceConstants.CHLG_PARAM_USER_ID_FOR_SRP, userName);
139+
when(mockProvider.initiateAuth(any(InitiateAuthRequest.class))).thenAnswer(answer -> {
140+
Log.i("Test", "Inside mock initiateAuth");
141+
return new InitiateAuthResult()
142+
.withChallengeName(CognitoServiceConstants.CHLG_TYPE_USER_PASSWORD_VERIFIER)
143+
.withChallengeParameters(parameters);
144+
});
145+
when(mockProvider.respondToAuthChallenge(any(RespondToAuthChallengeRequest.class))).thenAnswer(answer -> {
146+
String validJWT = getValidJWT(360);
147+
return new RespondToAuthChallengeResult()
148+
.withAuthenticationResult(new AuthenticationResultType()
149+
.withAccessToken(validJWT)
150+
.withIdToken(validJWT));
151+
});
152+
}
153+
154+
private String getValidJWT(long expiryInSecs){
155+
long epoch = System.currentTimeMillis()/1000L;
156+
epoch = epoch + expiryInSecs;
157+
String accessToken_p1_Base64 = "eyJ0eXAiOiAiSldUIiwgImFsZyI6IlJTMjU2In0=";
158+
String accessToken_p3_Base64 = "e0VuY3J5cHRlZF9LZXl9";
159+
String accessToken_p2_Str = "{\"iss\": \"userPoolId\",\"sub\": \"[email protected]\",\"aud\": \"https:aws.cognito.com\",\"exp\": \"" + String.valueOf(epoch).toString() + "\"}";
160+
byte[] accessToken_p2_UTF8 = accessToken_p2_Str.getBytes(StringUtils.UTF8);
161+
//String accessToken_p2_Base64 = Base64.encodeToString(accessToken_p2_UTF8, Base64.DEFAULT);
162+
String accessToken_p2_Base64 = new String(Base64.encode(accessToken_p2_UTF8, Base64.DEFAULT));
163+
String validAccessToken = accessToken_p1_Base64+"."+accessToken_p2_Base64+"."+accessToken_p3_Base64;
164+
return validAccessToken;
165+
}
166+
}

0 commit comments

Comments
 (0)