Skip to content

Commit a15960a

Browse files
authored
Merge pull request #108 from auth0/fix-pwd-policy
Support password policy error response
2 parents 5d83ed8 + be74cae commit a15960a

5 files changed

Lines changed: 301 additions & 1 deletion

File tree

src/main/java/com/auth0/exception/APIException.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,13 @@ private static String obtainExceptionMessage(Map<String, Object> values) {
7474
return (String) values.get("error_description");
7575
}
7676
if (values.containsKey("description")) {
77-
return (String) values.get("description");
77+
Object description = values.get("description");
78+
if(description instanceof String) {
79+
return (String) description;
80+
} else{
81+
PasswordStrengthErrorParser policy = new PasswordStrengthErrorParser((Map<String, Object>) description);
82+
return policy.getDescription();
83+
}
7884
}
7985
if (values.containsKey("message")) {
8086
return (String) values.get("message");
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package com.auth0.exception;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.Map;
6+
7+
@SuppressWarnings("unchecked")
8+
class PasswordStrengthErrorParser {
9+
private static final String RULE_TYPE_LENGTH_AT_LEAST = "lengthAtLeast";
10+
private static final String RULE_TYPE_CONTAINS_AT_LEAST = "containsAtLeast";
11+
private static final String RULE_TYPE_SHOULD_CONTAIN = "shouldContain";
12+
private static final String RULE_TYPE_IDENTICAL_CHARS = "identicalChars";
13+
14+
private static final String KEY_RULES = "rules";
15+
private static final String KEY_CODE = "code";
16+
private static final String KEY_VERIFIED = "verified";
17+
private static final String KEY_FORMAT = "format";
18+
private static final String KEY_ITEMS = "items";
19+
private static final String KEY_MESSAGE = "message";
20+
21+
private String description;
22+
23+
PasswordStrengthErrorParser(Map<String, Object> descriptionMap) {
24+
List<Map<String, Object>> rules = (List<Map<String, Object>>) descriptionMap.get(KEY_RULES);
25+
parseRules(rules);
26+
}
27+
28+
public String getDescription() {
29+
return description;
30+
}
31+
32+
private void parseRules(List<Map<String, Object>> rules) {
33+
List<String> items = new ArrayList<>();
34+
for (Map<String, Object> rule : rules) {
35+
boolean isVerified = (boolean) rule.get(KEY_VERIFIED);
36+
if (isVerified) {
37+
continue;
38+
}
39+
String code = (String) rule.get(KEY_CODE);
40+
switch (code) {
41+
case RULE_TYPE_LENGTH_AT_LEAST:
42+
items.add(asLengthAtLeast(rule));
43+
break;
44+
case RULE_TYPE_IDENTICAL_CHARS:
45+
items.add(asIdenticalChars(rule));
46+
break;
47+
case RULE_TYPE_CONTAINS_AT_LEAST:
48+
case RULE_TYPE_SHOULD_CONTAIN:
49+
items.add(asContainsCharset(rule));
50+
break;
51+
}
52+
}
53+
54+
this.description = joinStrings("; ", items);
55+
}
56+
57+
private String asLengthAtLeast(Map<String, Object> rule) {
58+
List<Number> length = (List<Number>) rule.get(KEY_FORMAT);
59+
String message = (String) rule.get(KEY_MESSAGE);
60+
return String.format(message, length.get(0).intValue());
61+
}
62+
63+
private String asContainsCharset(Map<String, Object> rule) {
64+
List<Map<String, Object>> itemsList = (List<Map<String, Object>>) rule.get(KEY_ITEMS);
65+
List<String> items = new ArrayList<>();
66+
for (Map<String, Object> i : itemsList) {
67+
items.add((String) i.get(KEY_MESSAGE));
68+
}
69+
String requiredItems = joinStrings(", ", items);
70+
String message = (String) rule.get(KEY_MESSAGE);
71+
72+
if (rule.containsKey(KEY_FORMAT)) {
73+
List<Number> quantity = (List<Number>) rule.get(KEY_FORMAT);
74+
message = String.format(message, quantity.get(0).intValue(), quantity.get(1).intValue());
75+
}
76+
77+
return String.format("%s %s", message, requiredItems);
78+
}
79+
80+
private String asIdenticalChars(Map<String, Object> rule) {
81+
List<Object> example = (List<Object>) rule.get(KEY_FORMAT);
82+
Number count = (Number) example.get(0);
83+
String message = (String) rule.get(KEY_MESSAGE);
84+
return String.format(message, count.intValue(), example.get(1));
85+
}
86+
87+
private String joinStrings(String delimiter, List<String> items) {
88+
StringBuilder sb = new StringBuilder();
89+
int i;
90+
for (i = 0; i < items.size() - 1; i++) {
91+
sb.append(items.get(i)).append(delimiter);
92+
}
93+
sb.append(items.get(i));
94+
return sb.toString();
95+
}
96+
}

src/test/java/com/auth0/client/auth/AuthAPITest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
package com.auth0.client.auth;
22

33
import com.auth0.client.MockServer;
4+
import com.auth0.exception.APIException;
45
import com.auth0.json.auth.CreatedUser;
56
import com.auth0.json.auth.TokenHolder;
67
import com.auth0.json.auth.UserInfo;
78
import com.auth0.net.AuthRequest;
89
import com.auth0.net.Request;
910
import com.auth0.net.SignUpRequest;
1011
import com.auth0.net.TelemetryInterceptor;
12+
import com.fasterxml.jackson.core.type.TypeReference;
13+
import com.fasterxml.jackson.databind.ObjectMapper;
1114
import okhttp3.Interceptor;
1215
import okhttp3.logging.HttpLoggingInterceptor;
1316
import okhttp3.logging.HttpLoggingInterceptor.Level;
@@ -18,6 +21,7 @@
1821
import org.junit.Test;
1922
import org.junit.rules.ExpectedException;
2023

24+
import java.io.FileReader;
2125
import java.util.HashMap;
2226
import java.util.List;
2327
import java.util.Map;
@@ -37,6 +41,8 @@ public class AuthAPITest {
3741
private static final String DOMAIN = "domain.auth0.com";
3842
private static final String CLIENT_ID = "clientId";
3943
private static final String CLIENT_SECRET = "clientSecret";
44+
private static final String PASSWORD_STRENGTH_ERROR_RESPONSE_NONE = "src/test/resources/auth/password_strength_error_none.json";
45+
private static final String PASSWORD_STRENGTH_ERROR_RESPONSE_SOME = "src/test/resources/auth/password_strength_error_some.json";
4046

4147
private MockServer server;
4248
private AuthAPI api;
@@ -379,6 +385,28 @@ public void shouldThrowOnUsernameSignUpWithNullConnection() throws Exception {
379385
api.signUp("me@auth0.com", "me", "p455w0rd", null);
380386
}
381387

388+
@Test
389+
public void shouldHaveNotStrongPasswordWithDetailedDescription() throws Exception {
390+
ObjectMapper mapper = new ObjectMapper();
391+
FileReader fr = new FileReader(PASSWORD_STRENGTH_ERROR_RESPONSE_NONE);
392+
Map<String, Object> mapPayload = mapper.readValue(fr, new TypeReference<Map<String, Object>>() {});
393+
APIException ex = new APIException(mapPayload, 400);
394+
assertThat(ex.getError(), is("invalid_password"));
395+
String expectedDescription = "At least 10 characters in length; Contain at least 3 of the following 4 types of characters: lower case letters (a-z), upper case letters (A-Z), numbers (i.e. 0-9), special characters (e.g. !@#$%^&*); Should contain: lower case letters (a-z), upper case letters (A-Z), numbers (i.e. 0-9), special characters (e.g. !@#$%^&*); No more than 2 identical characters in a row (e.g., \"aaa\" not allowed)";
396+
assertThat(ex.getDescription(), is(expectedDescription));
397+
}
398+
399+
@Test
400+
public void shouldHaveNotStrongPasswordWithShortDetailedDescription() throws Exception {
401+
ObjectMapper mapper = new ObjectMapper();
402+
FileReader fr = new FileReader(PASSWORD_STRENGTH_ERROR_RESPONSE_SOME);
403+
Map<String, Object> mapPayload = mapper.readValue(fr, new TypeReference<Map<String, Object>>() {});
404+
APIException ex = new APIException(mapPayload, 400);
405+
assertThat(ex.getError(), is("invalid_password"));
406+
String expectedDescription = "Should contain: lower case letters (a-z), upper case letters (A-Z), numbers (i.e. 0-9), special characters (e.g. !@#$%^&*)";
407+
assertThat(ex.getDescription(), is(expectedDescription));
408+
}
409+
382410
@Test
383411
public void shouldCreateSignUpRequestWithUsername() throws Exception {
384412
SignUpRequest request = api.signUp("me@auth0.com", "me", "p455w0rd", "db-connection");
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
{
2+
"code": "invalid_password",
3+
"description": {
4+
"rules": [
5+
{
6+
"code": "lengthAtLeast",
7+
"format": [
8+
10
9+
],
10+
"message": "At least %d characters in length",
11+
"verified": false
12+
},
13+
{
14+
"code": "containsAtLeast",
15+
"format": [
16+
3,
17+
4
18+
],
19+
"items": [
20+
{
21+
"code": "lowerCase",
22+
"message": "lower case letters (a-z)",
23+
"verified": false
24+
},
25+
{
26+
"code": "upperCase",
27+
"message": "upper case letters (A-Z)",
28+
"verified": false
29+
},
30+
{
31+
"code": "numbers",
32+
"message": "numbers (i.e. 0-9)",
33+
"verified": false
34+
},
35+
{
36+
"code": "specialCharacters",
37+
"message": "special characters (e.g. !@#$%^&*)",
38+
"verified": false
39+
}
40+
],
41+
"message": "Contain at least %d of the following %d types of characters:",
42+
"verified": false
43+
},
44+
{
45+
"code": "shouldContain",
46+
"items": [
47+
{
48+
"code": "lowerCase",
49+
"message": "lower case letters (a-z)",
50+
"verified": false
51+
},
52+
{
53+
"code": "upperCase",
54+
"message": "upper case letters (A-Z)",
55+
"verified": false
56+
},
57+
{
58+
"code": "numbers",
59+
"message": "numbers (i.e. 0-9)",
60+
"verified": false
61+
},
62+
{
63+
"code": "specialCharacters",
64+
"message": "special characters (e.g. !@#$%^&*)",
65+
"verified": false
66+
}
67+
],
68+
"message": "Should contain:",
69+
"verified": false
70+
},
71+
{
72+
"code": "identicalChars",
73+
"format": [
74+
2,
75+
"aaa"
76+
],
77+
"message": "No more than %d identical characters in a row (e.g., \"%s\" not allowed)",
78+
"verified": false
79+
}
80+
],
81+
"verified": false
82+
},
83+
"name": "PasswordStrengthError",
84+
"statusCode": 400
85+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
{
2+
"code": "invalid_password",
3+
"description": {
4+
"rules": [
5+
{
6+
"code": "lengthAtLeast",
7+
"format": [
8+
10
9+
],
10+
"message": "At least %d characters in length",
11+
"verified": true
12+
},
13+
{
14+
"code": "containsAtLeast",
15+
"format": [
16+
3,
17+
4
18+
],
19+
"items": [
20+
{
21+
"code": "lowerCase",
22+
"message": "lower case letters (a-z)",
23+
"verified": true
24+
},
25+
{
26+
"code": "upperCase",
27+
"message": "upper case letters (A-Z)",
28+
"verified": true
29+
},
30+
{
31+
"code": "numbers",
32+
"message": "numbers (i.e. 0-9)",
33+
"verified": true
34+
},
35+
{
36+
"code": "specialCharacters",
37+
"message": "special characters (e.g. !@#$%^&*)",
38+
"verified": false
39+
}
40+
],
41+
"message": "Contain at least %d of the following %d types of characters:",
42+
"verified": true
43+
},
44+
{
45+
"code": "shouldContain",
46+
"items": [
47+
{
48+
"code": "lowerCase",
49+
"message": "lower case letters (a-z)",
50+
"verified": false
51+
},
52+
{
53+
"code": "upperCase",
54+
"message": "upper case letters (A-Z)",
55+
"verified": false
56+
},
57+
{
58+
"code": "numbers",
59+
"message": "numbers (i.e. 0-9)",
60+
"verified": false
61+
},
62+
{
63+
"code": "specialCharacters",
64+
"message": "special characters (e.g. !@#$%^&*)",
65+
"verified": false
66+
}
67+
],
68+
"message": "Should contain:",
69+
"verified": false
70+
},
71+
{
72+
"code": "identicalChars",
73+
"format": [
74+
2,
75+
"aaa"
76+
],
77+
"message": "No more than %d identical characters in a row (e.g., \"%s\" not allowed)",
78+
"verified": true
79+
}
80+
],
81+
"verified": false
82+
},
83+
"name": "PasswordStrengthError",
84+
"statusCode": 400
85+
}

0 commit comments

Comments
 (0)