Skip to content
This repository was archived by the owner on Aug 10, 2021. It is now read-only.

Commit 33f98d1

Browse files
JorgenHeinsoopriitr
authored andcommitted
AUT-374 - Validate country against allowed country codes list and val… (#121)
* AUT-374 - Validate country against allowed country codes list and validate configured country codes against regex during application startup * AUT-374 - update translation
1 parent c2b5028 commit 33f98d1

File tree

8 files changed

+118
-13
lines changed

8 files changed

+118
-13
lines changed

src/main/java/ee/ria/sso/config/eidas/EidasConfigurationProvider.java

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ee.ria.sso.config.eidas;
22

33
import ee.ria.sso.config.TaraResourceBundleMessageSource;
4+
import ee.ria.sso.utils.CountryCodeUtil;
45
import lombok.Getter;
56
import lombok.Setter;
67
import lombok.extern.slf4j.Slf4j;
@@ -17,7 +18,11 @@
1718

1819
import javax.annotation.PostConstruct;
1920
import java.security.KeyStore;
20-
import java.util.*;
21+
import java.util.Arrays;
22+
import java.util.Collections;
23+
import java.util.List;
24+
import java.util.Locale;
25+
import java.util.Objects;
2126
import java.util.stream.Collectors;
2227

2328
@Component
@@ -57,12 +62,22 @@ public void init() {
5762
Assert.notNull(clientCertificateKeystorePass, "No client certificate keystore password provided");
5863
}
5964

60-
listOfCountries = parsePropertiesList(availableCountries);
65+
listOfCountries = parseAvailableCountries(availableCountries);
6166
}
6267

63-
private static List<String> parsePropertiesList(String input) {
64-
if (StringUtils.isEmpty(input)) return Collections.emptyList();
65-
return Arrays.asList(input.split(","));
68+
private static List<String> parseAvailableCountries(String input) {
69+
if (StringUtils.isBlank(input)) {
70+
return Collections.emptyList();
71+
}
72+
List<String> countryCodes = Arrays.asList(input.split(","));
73+
validateCountryCodes(countryCodes);
74+
return countryCodes;
75+
}
76+
77+
private static void validateCountryCodes(List<String> countryCodes) {
78+
for (String countryCode : countryCodes) {
79+
Assert.isTrue(CountryCodeUtil.isValidCountryCode(countryCode), "Invalid ISO 3166-1 alpha-2 country code '" + countryCode + "'");
80+
}
6681
}
6782

6883
public List<String> getListOfCountries(String locale) {

src/main/java/ee/ria/sso/service/eidas/EidasAuthenticationService.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import ee.ria.sso.authentication.AuthenticationType;
66
import ee.ria.sso.authentication.LevelOfAssurance;
77
import ee.ria.sso.authentication.credential.PreAuthenticationCredential;
8+
import ee.ria.sso.config.eidas.EidasConfigurationProvider;
89
import ee.ria.sso.security.CspDirective;
910
import ee.ria.sso.security.CspHeaderUtil;
1011
import ee.ria.sso.service.AbstractService;
@@ -13,6 +14,7 @@
1314
import ee.ria.sso.statistics.StatisticsHandler;
1415
import ee.ria.sso.statistics.StatisticsOperation;
1516
import ee.ria.sso.statistics.StatisticsRecord;
17+
import ee.ria.sso.utils.CountryCodeUtil;
1618
import lombok.extern.slf4j.Slf4j;
1719
import org.apache.commons.lang3.StringUtils;
1820
import org.apereo.cas.web.flow.CasWebflowConstants;
@@ -41,16 +43,18 @@
4143
public class EidasAuthenticationService extends AbstractService {
4244

4345
public static final Pattern VALID_PERSON_IDENTIFIER_PATTERN = Pattern.compile("^([A-Z]{2,2})\\/([A-Z]{2,2})\\/(.*)$");
44-
public static final Pattern VALID_COUNTRY_PATTERN = Pattern.compile("^[A-Z]{2,2}$");
4546
public static final String SESSION_ATTRIBUTE_COUNTRY = "country";
4647
public static final String SESSION_ATTRIBUTE_RELAY_STATE = "relayState";
4748

4849
private final EidasAuthenticator eidasAuthenticator;
50+
private final EidasConfigurationProvider eidasConfigurationProvider;
4951

5052
public EidasAuthenticationService(StatisticsHandler statistics,
51-
EidasAuthenticator eidasAuthenticator) {
53+
EidasAuthenticator eidasAuthenticator,
54+
EidasConfigurationProvider eidasConfigurationProvider) {
5255
super(statistics);
5356
this.eidasAuthenticator = eidasAuthenticator;
57+
this.eidasConfigurationProvider = eidasConfigurationProvider;
5458
}
5559

5660
@Audit(
@@ -180,10 +184,18 @@ private String getCountry(RequestContext context) {
180184
}
181185

182186
private void validateAndStoreCountry(PreAuthenticationCredential credential, RequestContext context) {
183-
if (StringUtils.isBlank(credential.getCountry()) || !VALID_COUNTRY_PATTERN.matcher(credential.getCountry()).matches()) {
184-
throw new UserAuthenticationFailedException("message.eidas.invalidcountry", String.format("User provided invalid country code: <%s>", credential.getCountry()));
187+
String countryCode = credential.getCountry();
188+
if (StringUtils.isBlank(countryCode) || !CountryCodeUtil.isValidCountryCode(countryCode)) {
189+
throw new UserAuthenticationFailedException("message.eidas.invalidcountry", String.format("User provided invalid country code: <%s>", countryCode));
185190
}
186-
context.getExternalContext().getSessionMap().put(SESSION_ATTRIBUTE_COUNTRY, credential.getCountry().toUpperCase());
191+
if (!isAllowedCountryCode(countryCode)) {
192+
throw new UserAuthenticationFailedException("message.eidas.notAllowedCountry", String.format("User provided not allowed country code: <%s>", countryCode));
193+
}
194+
context.getExternalContext().getSessionMap().put(SESSION_ATTRIBUTE_COUNTRY, countryCode.toUpperCase());
195+
}
196+
197+
private boolean isAllowedCountryCode(String countryCode) {
198+
return eidasConfigurationProvider.getAvailableCountries().contains(countryCode);
187199
}
188200

189201
private void logEvent(RequestContext context, StatisticsOperation operation) {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package ee.ria.sso.utils;
2+
3+
import org.apache.commons.lang3.StringUtils;
4+
5+
import java.util.regex.Pattern;
6+
7+
public class CountryCodeUtil {
8+
9+
private static final Pattern COUNTRY_CODE_PATTERN = Pattern.compile("^[A-Z]{2,2}$");
10+
11+
public static boolean isValidCountryCode(String countryCode) {
12+
if (StringUtils.isBlank(countryCode)) {
13+
return false;
14+
}
15+
return COUNTRY_CODE_PATTERN.matcher(countryCode).matches();
16+
}
17+
}

src/main/webapp/WEB-INF/classes/messages.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ message.smartId.error.sessionNotFound=User authentication failed in Smart-ID sys
113113
message.smartId.error.general=Authentication failed due to internal error. Please try again in a while.
114114

115115
message.eidas.invalidcountry=Country code is in an incorrect format.
116+
message.eidas.notAllowedCountry=Country code is not allowed.
116117
message.eidas.authfailed=Authentication failed
117118
message.eidas.error=Authentication failed due to internal error. Please try again later.
118119

src/main/webapp/WEB-INF/classes/messages_et.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ message.smartId.error.sessionNotFound = Kasutaja autentimine Smart-ID süsteemis
113113
message.smartId.error.general = Autentimine ebaõnnestus sisemise vea tõttu. Palun proovige mõne aja pärast uuesti.
114114

115115
message.eidas.invalidcountry=Riigikood on ebakorrektses formaadis.
116+
message.eidas.notAllowedCountry=Riigikood ei ole lubatud.
116117
message.eidas.authfailed=Autentimine ebaõnnestus
117118
message.eidas.error=Autentimine ebaõnnestus sisemise vea tõttu. Palun proovige mõne aja pärast uuesti.
118119

src/main/webapp/WEB-INF/classes/messages_ru.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ message.smartId.error.sessionNotFound = Ошибка аутентификаци
113113
message.smartId.error.general = Ошибка аутентификации из-за внутренней ошибки. Повторите попытку через некоторое время.
114114

115115
message.eidas.invalidcountry=Код страны указан в неверном формате.
116+
message.eidas.notAllowedCountry=Код страны не допускается.
116117
message.eidas.authfailed=Аутентификация не удалась
117118
message.eidas.error=Ошибка аутентификации из-за внутренней ошибки. Пожалуйста, попробуйте позже.
118119

src/test/java/ee/ria/sso/service/eidas/EidasAuthenticationServiceTest.java

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
import ee.ria.sso.CommonConstants;
44
import ee.ria.sso.Constants;
55
import ee.ria.sso.authentication.AuthenticationType;
6-
import ee.ria.sso.service.ExternalServiceHasFailedException;
76
import ee.ria.sso.authentication.LevelOfAssurance;
87
import ee.ria.sso.authentication.credential.PreAuthenticationCredential;
98
import ee.ria.sso.config.eidas.EidasConfigurationProvider;
109
import ee.ria.sso.config.eidas.TestEidasConfiguration;
1110
import ee.ria.sso.service.AbstractAuthenticationServiceTest;
11+
import ee.ria.sso.service.ExternalServiceHasFailedException;
1212
import ee.ria.sso.service.UserAuthenticationFailedException;
1313
import ee.ria.sso.statistics.StatisticsHandler;
1414
import ee.ria.sso.statistics.StatisticsOperation;
@@ -63,6 +63,9 @@ public class EidasAuthenticationServiceTest extends AbstractAuthenticationServic
6363
@Autowired
6464
private StatisticsHandler statistics;
6565

66+
@Autowired
67+
private EidasConfigurationProvider eidasConfigurationProvider;
68+
6669
@Mock
6770
private EidasAuthenticator authenticatorMock;
6871

@@ -71,7 +74,7 @@ public class EidasAuthenticationServiceTest extends AbstractAuthenticationServic
7174
@Before
7275
public void setUp() {
7376
Mockito.reset(authenticatorMock);
74-
authenticationService = new EidasAuthenticationService(statistics, authenticatorMock);
77+
authenticationService = new EidasAuthenticationService(statistics, authenticatorMock, eidasConfigurationProvider);
7578
}
7679

7780
@After
@@ -81,7 +84,7 @@ public void cleanUp() {
8184

8285
@Test
8386
public void startLoginByEidasWithoutLoaShouldSucceedAndWriteAuthenticatorResponse() throws Exception {
84-
String country = "SE";
87+
String country = "FI";
8588
PreAuthenticationCredential credential = new PreAuthenticationCredential();
8689
credential.setCountry(country);
8790

@@ -134,6 +137,25 @@ public void startLoginByEidasShouldFailWhenInvalidCountryCodeFormat() throws Exc
134137
Assert.fail("Should not reach this!");
135138
}
136139

140+
@Test
141+
public void startLoginByEidasShouldFailWhenCountryCodeFormatValidButNotAllowed() {
142+
PreAuthenticationCredential credential = new PreAuthenticationCredential();
143+
credential.setCountry("RU");
144+
145+
MockRequestContext requestContext = this.getMockRequestContext(null, credential);
146+
147+
expectedEx.expect(UserAuthenticationFailedException.class);
148+
expectedEx.expectMessage("User provided not allowed country code: <RU>");
149+
150+
try {
151+
this.authenticationService.startLoginByEidas(requestContext);
152+
Assert.fail("Expected to throw exception!");
153+
} catch (Exception e) {
154+
Assert.assertTrue("Should not log to statistics when input is invalid", SimpleTestAppender.events.isEmpty());
155+
throw e;
156+
}
157+
}
158+
137159
@Test
138160
public void startLoginByEidasShouldFailWhenUnexpectedException() throws Exception {
139161
PreAuthenticationCredential credential = new PreAuthenticationCredential();
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package ee.ria.sso.utils;
2+
3+
import org.junit.Test;
4+
5+
import static org.junit.Assert.assertFalse;
6+
import static org.junit.Assert.assertTrue;
7+
8+
public class CountryCodeUtilTest {
9+
10+
@Test
11+
public void validCountryCodesFormat() {
12+
assertTrue(CountryCodeUtil.isValidCountryCode("EE"));
13+
assertTrue(CountryCodeUtil.isValidCountryCode("EN"));
14+
assertTrue(CountryCodeUtil.isValidCountryCode("LT"));
15+
assertTrue(CountryCodeUtil.isValidCountryCode("RU"));
16+
assertTrue(CountryCodeUtil.isValidCountryCode("TT"));
17+
assertTrue(CountryCodeUtil.isValidCountryCode("XX"));
18+
}
19+
20+
@Test
21+
public void invalidCountryCodeFormat() {
22+
assertFalse(CountryCodeUtil.isValidCountryCode("E"));
23+
assertFalse(CountryCodeUtil.isValidCountryCode("EEE"));
24+
assertFalse(CountryCodeUtil.isValidCountryCode("ee"));
25+
assertFalse(CountryCodeUtil.isValidCountryCode("Ee"));
26+
assertFalse(CountryCodeUtil.isValidCountryCode("E E"));
27+
assertFalse(CountryCodeUtil.isValidCountryCode(" EE"));
28+
assertFalse(CountryCodeUtil.isValidCountryCode("EE "));
29+
assertFalse(CountryCodeUtil.isValidCountryCode("--"));
30+
assertFalse(CountryCodeUtil.isValidCountryCode("##"));
31+
assertFalse(CountryCodeUtil.isValidCountryCode("E2"));
32+
assertFalse(CountryCodeUtil.isValidCountryCode(""));
33+
assertFalse(CountryCodeUtil.isValidCountryCode(" "));
34+
assertFalse(CountryCodeUtil.isValidCountryCode(null));
35+
}
36+
}

0 commit comments

Comments
 (0)