Skip to content

Commit fe37392

Browse files
authored
CognitoIDP: Ensure MFA functions work with non-python SDK's (#8241)
1 parent eea6b16 commit fe37392

6 files changed

Lines changed: 250 additions & 8 deletions

File tree

moto/cognitoidp/models.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2298,6 +2298,20 @@ def _find_backend_by_access_token(self, access_token: str) -> CognitoIdpBackend:
22982298
return backend
22992299
return cognitoidp_backends[self.account_id][self.region_name]
23002300

2301+
def _find_backend_by_access_token_or_session(
2302+
self, access_token: str, session: str
2303+
) -> CognitoIdpBackend:
2304+
for account_specific_backends in cognitoidp_backends.values():
2305+
for region, backend in account_specific_backends.items():
2306+
if region == "global":
2307+
continue
2308+
if session and session in backend.sessions:
2309+
return backend
2310+
for p in backend.user_pools.values():
2311+
if access_token and access_token in p.access_tokens:
2312+
return backend
2313+
return cognitoidp_backends[self.account_id][self.region_name]
2314+
23012315
def _find_backend_for_clientid(self, client_id: str) -> CognitoIdpBackend:
23022316
for account_specific_backends in cognitoidp_backends.values():
23032317
for region, backend in account_specific_backends.items():
@@ -2356,6 +2370,33 @@ def respond_to_auth_challenge(
23562370
session, client_id, challenge_name, challenge_responses
23572371
)
23582372

2373+
def associate_software_token(
2374+
self, access_token: str, session: str
2375+
) -> Dict[str, str]:
2376+
backend = self._find_backend_by_access_token_or_session(access_token, session)
2377+
return backend.associate_software_token(access_token, session)
2378+
2379+
def verify_software_token(self, access_token: str, session: str) -> Dict[str, str]:
2380+
backend = self._find_backend_by_access_token_or_session(access_token, session)
2381+
return backend.verify_software_token(access_token, session)
2382+
2383+
def set_user_mfa_preference(
2384+
self,
2385+
access_token: str,
2386+
software_token_mfa_settings: Dict[str, bool],
2387+
sms_mfa_settings: Dict[str, bool],
2388+
) -> None:
2389+
backend = self._find_backend_by_access_token(access_token)
2390+
return backend.set_user_mfa_preference(
2391+
access_token, software_token_mfa_settings, sms_mfa_settings
2392+
)
2393+
2394+
def update_user_attributes(
2395+
self, access_token: str, attributes: List[Dict[str, str]]
2396+
) -> None:
2397+
backend = self._find_backend_by_access_token(access_token)
2398+
return backend.update_user_attributes(access_token, attributes)
2399+
23592400

23602401
cognitoidp_backends = BackendDict(CognitoIdpBackend, "cognito-idp")
23612402

moto/cognitoidp/responses.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -624,20 +624,24 @@ def initiate_auth(self) -> str:
624624
def associate_software_token(self) -> str:
625625
access_token = self._get_param("AccessToken")
626626
session = self._get_param("Session")
627-
result = self.backend.associate_software_token(access_token, session)
627+
result = self._get_region_agnostic_backend().associate_software_token(
628+
access_token, session
629+
)
628630
return json.dumps(result)
629631

630632
def verify_software_token(self) -> str:
631633
access_token = self._get_param("AccessToken")
632634
session = self._get_param("Session")
633-
result = self.backend.verify_software_token(access_token, session)
635+
result = self._get_region_agnostic_backend().verify_software_token(
636+
access_token, session
637+
)
634638
return json.dumps(result)
635639

636640
def set_user_mfa_preference(self) -> str:
637641
access_token = self._get_param("AccessToken")
638642
software_token_mfa_settings = self._get_param("SoftwareTokenMfaSettings")
639643
sms_mfa_settings = self._get_param("SMSMfaSettings")
640-
self.backend.set_user_mfa_preference(
644+
self._get_region_agnostic_backend().set_user_mfa_preference(
641645
access_token, software_token_mfa_settings, sms_mfa_settings
642646
)
643647
return ""
@@ -671,7 +675,9 @@ def add_custom_attributes(self) -> str:
671675
def update_user_attributes(self) -> str:
672676
access_token = self._get_param("AccessToken")
673677
attributes = self._get_param("UserAttributes")
674-
self.backend.update_user_attributes(access_token, attributes)
678+
self._get_region_agnostic_backend().update_user_attributes(
679+
access_token, attributes
680+
)
675681
return json.dumps({})
676682

677683

other_langs/tests_java/pom.xml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
<dependency>
2323
<groupId>software.amazon.awssdk</groupId>
2424
<artifactId>bom</artifactId>
25-
<version>2.28.13</version>
25+
<version>2.28.26</version>
2626
<type>pom</type>
2727
<scope>import</scope>
2828
</dependency>
@@ -36,6 +36,14 @@
3636
<version>4.13.2</version>
3737
<scope>test</scope>
3838
</dependency>
39+
<dependency>
40+
<groupId>software.amazon.awssdk</groupId>
41+
<artifactId>cognitoidentity</artifactId>
42+
</dependency>
43+
<dependency>
44+
<groupId>software.amazon.awssdk</groupId>
45+
<artifactId>cognitoidentityprovider</artifactId>
46+
</dependency>
3947
<dependency>
4048
<groupId>software.amazon.awssdk</groupId>
4149
<artifactId>dynamodb</artifactId>

other_langs/tests_java/src/main/java/moto/tests/DependencyFactory.java

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import software.amazon.awssdk.http.apache.ApacheHttpClient;
66
import software.amazon.awssdk.http.urlconnection.UrlConnectionHttpClient;
77
import software.amazon.awssdk.regions.Region;
8+
import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient;
89
import software.amazon.awssdk.services.s3.S3Client;
910
import software.amazon.awssdk.services.ses.SesClient;
1011
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
@@ -22,9 +23,14 @@ public class DependencyFactory {
2223

2324
private DependencyFactory() {}
2425

25-
/**
26-
* @return an instance of S3Client
27-
*/
26+
public static CognitoIdentityProviderClient cognitoIdpClient() {
27+
return CognitoIdentityProviderClient.builder()
28+
.region(Region.US_EAST_1)
29+
.httpClientBuilder(ApacheHttpClient.builder())
30+
.endpointOverride(MOTO_URI)
31+
.build();
32+
}
33+
2834
public static S3Client s3Client() {
2935
return S3Client.builder()
3036
.region(Region.US_EAST_1)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
package moto.tests;
2+
3+
import static moto.tests.DependencyFactory.cognitoIdpClient;
4+
import static org.junit.Assert.*;
5+
6+
import org.junit.Test;
7+
import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient;
8+
import software.amazon.awssdk.services.cognitoidentityprovider.model.AssociateSoftwareTokenRequest;
9+
import software.amazon.awssdk.services.cognitoidentityprovider.model.AssociateSoftwareTokenResponse;
10+
import software.amazon.awssdk.services.cognitoidentityprovider.model.AttributeType;
11+
import software.amazon.awssdk.services.cognitoidentityprovider.model.AuthFlowType;
12+
import software.amazon.awssdk.services.cognitoidentityprovider.model.CreateUserPoolRequest;
13+
import software.amazon.awssdk.services.cognitoidentityprovider.model.CreateUserPoolClientRequest;
14+
import software.amazon.awssdk.services.cognitoidentityprovider.model.ConfirmSignUpRequest;
15+
import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthRequest;
16+
import software.amazon.awssdk.services.cognitoidentityprovider.model.InitiateAuthResponse;
17+
import software.amazon.awssdk.services.cognitoidentityprovider.model.SignUpRequest;
18+
import java.util.ArrayList;
19+
import java.util.List;
20+
import java.util.Map;
21+
22+
23+
public class CognitoIDPTest {
24+
25+
@Test
26+
public void testAssociateSoftwareToken() {
27+
CognitoIdentityProviderClient client = cognitoIdpClient();
28+
29+
CreateUserPoolRequest up_request = CreateUserPoolRequest.builder()
30+
.poolName("myfirstuserpool")
31+
.build();
32+
33+
String userPoolId = client.createUserPool(up_request).userPool().id();
34+
System.out.println(userPoolId);
35+
36+
CreateUserPoolClientRequest upc_request = CreateUserPoolClientRequest.builder()
37+
.clientName("myfirstuserpoolclient")
38+
.userPoolId(userPoolId)
39+
.build();
40+
41+
String clientId = client.createUserPoolClient(upc_request).userPoolClient().clientId();
42+
System.out.println(clientId);
43+
44+
// Create User
45+
AttributeType userAttrs = AttributeType.builder()
46+
.name("email")
47+
.value("test@test.com")
48+
.build();
49+
String password = "P@ssw0rdWithTonsOfCharacters";
50+
51+
List<AttributeType> userAttrsList = new ArrayList<>();
52+
userAttrsList.add(userAttrs);
53+
SignUpRequest signUpRequest = SignUpRequest.builder()
54+
.userAttributes(userAttrsList)
55+
.username("myuser")
56+
.clientId(clientId)
57+
.password(password)
58+
.build();
59+
60+
client.signUp(signUpRequest);
61+
62+
ConfirmSignUpRequest confirmSignUpRequest = ConfirmSignUpRequest.builder()
63+
.clientId(clientId)
64+
.confirmationCode("code")
65+
.username("myuser")
66+
.build();
67+
68+
client.confirmSignUp(confirmSignUpRequest);
69+
70+
InitiateAuthResponse initiateAuthResponse = client.initiateAuth(
71+
InitiateAuthRequest.builder()
72+
.authFlow(AuthFlowType.USER_PASSWORD_AUTH)
73+
.authParameters(Map.of(
74+
"USERNAME", "myuser",
75+
"SECRET_HASH", "n/a",
76+
"PASSWORD", password))
77+
.clientId(clientId)
78+
.build());
79+
String accessToken = initiateAuthResponse.authenticationResult().accessToken();
80+
81+
AssociateSoftwareTokenRequest softwareTokenRequest = AssociateSoftwareTokenRequest.builder()
82+
.accessToken(accessToken)
83+
.build();
84+
AssociateSoftwareTokenResponse tokenResponse = client
85+
.associateSoftwareToken(softwareTokenRequest);
86+
String secretCode = tokenResponse.secretCode();
87+
88+
assertNotNull(secretCode);
89+
}
90+
}

tests/test_cognitoidp/test_server.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,94 @@ def test_admin_create_user_without_authentication():
181181
assert "AuthenticationResult" in response
182182
assert "IdToken" in response["AuthenticationResult"]
183183
assert "AccessToken" in response["AuthenticationResult"]
184+
185+
186+
def test_associate_software_token():
187+
backend = server.create_backend_app("cognito-idp")
188+
test_client = backend.test_client()
189+
190+
# Create User Pool
191+
res = test_client.post(
192+
"/",
193+
data='{"PoolName": "test-pool"}',
194+
headers={
195+
"X-Amz-Target": "AWSCognitoIdentityProviderService.CreateUserPool",
196+
"Authorization": "AWS4-HMAC-SHA256 Credential=abcd/20010101/us-east-2/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=...",
197+
},
198+
)
199+
user_pool_id = json.loads(res.data)["UserPool"]["Id"]
200+
201+
# Create User Pool Client
202+
data = {
203+
"UserPoolId": user_pool_id,
204+
"ClientName": "some-client",
205+
"GenerateSecret": False,
206+
"ExplicitAuthFlows": ["ALLOW_USER_PASSWORD_AUTH"],
207+
}
208+
res = test_client.post(
209+
"/",
210+
data=json.dumps(data),
211+
headers={
212+
"X-Amz-Target": "AWSCognitoIdentityProviderService.CreateUserPoolClient",
213+
"Authorization": "AWS4-HMAC-SHA256 Credential=abcd/20010101/us-east-2/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=...",
214+
},
215+
)
216+
client_id = json.loads(res.data)["UserPoolClient"]["ClientId"]
217+
218+
# Sign Up User
219+
data = {
220+
"ClientId": client_id,
221+
"Username": "user_2_mfa",
222+
"Password": "12312sdfasASDFDSF$",
223+
}
224+
res = test_client.post(
225+
"/",
226+
data=json.dumps(data),
227+
headers={"X-Amz-Target": "AWSCognitoIdentityProviderService.SignUp"},
228+
)
229+
assert res.status_code == 200
230+
assert json.loads(res.data)["UserConfirmed"] is False
231+
232+
# Confirm Sign Up User
233+
data = {"ClientId": client_id, "Username": "user_2_mfa", "ConfirmationCode": "sth"}
234+
res = test_client.post(
235+
"/",
236+
data=json.dumps(data),
237+
headers={"X-Amz-Target": "AWSCognitoIdentityProviderService.ConfirmSignUp"},
238+
)
239+
240+
# Initiate Auth
241+
data = {
242+
"AuthFlow": "USER_PASSWORD_AUTH",
243+
"AuthParameters": {
244+
"USERNAME": "user_2_mfa",
245+
"PASSWORD": "12312sdfasASDFDSF$",
246+
"SECRET_HASH": "kIWuIv6ElVe9ahZHJ+gqvZe6CgEkVE/BjQmJcMSgF3E=",
247+
},
248+
"ClientId": client_id,
249+
}
250+
res = test_client.post(
251+
"/",
252+
data=json.dumps(data),
253+
headers={"X-Amz-Target": "AWSCognitoIdentityProviderService.InitiateAuth"},
254+
)
255+
auth_data = json.loads(res.data.decode("utf-8"))["AuthenticationResult"]
256+
257+
# Get User
258+
data = {"AccessToken": auth_data["AccessToken"]}
259+
res = test_client.post(
260+
"/",
261+
data=json.dumps(data),
262+
headers={"X-Amz-Target": "AWSCognitoIdentityProviderService.GetUser"},
263+
)
264+
265+
# Associate Software Token
266+
data = {"AccessToken": auth_data["AccessToken"]}
267+
res = test_client.post(
268+
"/",
269+
data=json.dumps(data),
270+
headers={
271+
"X-Amz-Target": "AWSCognitoIdentityProviderService.AssociateSoftwareToken"
272+
},
273+
)
274+
assert json.loads(res.data.decode("utf-8")) == {"SecretCode": "asdfasdfasdf"}

0 commit comments

Comments
 (0)