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

Commit ebd4557

Browse files
authored
3072_url_based_authentication (SORMAS-Foundation#3090)
* SORMAS-Foundation#3072 centralize authentication provider config and make username case sensitivity dependent on it * SORMAS-Foundation#3072 fix unit tests and update mockito version * SORMAS-Foundation#3072 fix unnecessary stubbing exception for new Mockito version * SORMAS-Foundation#3072 fixed unit tests
1 parent 6ab3853 commit ebd4557

7 files changed

Lines changed: 110 additions & 33 deletions

File tree

sormas-api/src/main/java/de/symeda/sormas/api/AuthProvider.java

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,66 @@
1919
package de.symeda.sormas.api;
2020

2121
/**
22-
* Authentication provider which can be obtained trough the {@link ConfigFacade#getAuthenticationProvider()} property.
22+
* Authentication provider which can be configured trough the {@link ConfigFacade#getAuthenticationProvider()} property.
23+
* Once initialized it provides Auth Provider specific authentication configs like:
24+
* <ul>
25+
* <li>is user name case sensitive</li>
26+
* <li>is email required</li>
27+
* </ul>
2328
*
2429
* @author Alex Vidrean
2530
* @since 13-Aug-20
2631
*/
2732
public class AuthProvider {
2833

29-
public static final String KEYCLOAK = "KEYCLOAK";
34+
public static final String KEYCLOAK = "KEYCLOAK";
3035

31-
public static final String SORMAS = "SORMAS";
36+
public static final String SORMAS = "SORMAS";
3237

38+
private static AuthProvider provider;
39+
40+
private final boolean isUsernameCaseSensitive;
41+
42+
private final boolean isEmailRequired;
43+
44+
private final boolean isDefaultProvider;
45+
46+
private AuthProvider() {
47+
String configuredProvider = FacadeProvider.getConfigFacade().getAuthenticationProvider();
48+
isUsernameCaseSensitive = SORMAS.equalsIgnoreCase(configuredProvider);
49+
isEmailRequired = KEYCLOAK.equalsIgnoreCase(configuredProvider);
50+
isDefaultProvider = SORMAS.equalsIgnoreCase(configuredProvider);
51+
}
52+
53+
public static AuthProvider getProvider() {
54+
if (provider == null) {
55+
synchronized (AuthProvider.class) {
56+
if (provider == null) {
57+
provider = new AuthProvider();
58+
}
59+
}
60+
}
61+
return provider;
62+
}
63+
64+
/**
65+
* Authentication Provider requires usernames to be case sensitive or insensitive
66+
*/
67+
public boolean isUsernameCaseSensitive() {
68+
return isUsernameCaseSensitive;
69+
}
70+
71+
/**
72+
* Authentication Provider requires emails to be required or optional.
73+
*/
74+
public boolean isEmailRequired() {
75+
return isEmailRequired;
76+
}
77+
78+
/**
79+
* Current Authentication Provider is the SORMAS default one.
80+
*/
81+
public boolean isDefaultProvider() {
82+
return isDefaultProvider;
83+
}
3384
}

sormas-backend/src/main/java/de/symeda/sormas/backend/user/UserService.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@
3131
import javax.persistence.TypedQuery;
3232
import javax.persistence.criteria.CriteriaBuilder;
3333
import javax.persistence.criteria.CriteriaQuery;
34+
import javax.persistence.criteria.Expression;
3435
import javax.persistence.criteria.From;
3536
import javax.persistence.criteria.Join;
3637
import javax.persistence.criteria.JoinType;
3738
import javax.persistence.criteria.ParameterExpression;
3839
import javax.persistence.criteria.Predicate;
3940
import javax.persistence.criteria.Root;
4041

42+
import de.symeda.sormas.api.AuthProvider;
4143
import de.symeda.sormas.api.facility.FacilityType;
4244
import de.symeda.sormas.api.user.JurisdictionLevel;
4345
import de.symeda.sormas.api.user.UserCriteria;
@@ -84,9 +86,17 @@ public User getByUserName(String userName) {
8486
ParameterExpression<String> userNameParam = cb.parameter(String.class, User.USER_NAME);
8587
CriteriaQuery<User> cq = cb.createQuery(getElementClass());
8688
Root<User> from = cq.from(getElementClass());
87-
cq.where(cb.equal(cb.lower(from.get(User.USER_NAME)), userNameParam));
8889

89-
TypedQuery<User> q = em.createQuery(cq).setParameter(userNameParam, userName.toLowerCase());
90+
Expression<String> userNameExpression = from.get(User.USER_NAME);
91+
String userNameParamValue = userName;
92+
if (!AuthProvider.getProvider().isUsernameCaseSensitive()) {
93+
userNameExpression = cb.lower(userNameExpression);
94+
userNameParamValue = userName.toLowerCase();
95+
}
96+
97+
cq.where(cb.equal(userNameExpression, userNameParam));
98+
99+
TypedQuery<User> q = em.createQuery(cq).setParameter(userNameParam, userNameParamValue);
90100

91101
User entity = q.getResultList().stream().findFirst().orElse(null);
92102
return entity;
@@ -259,13 +269,22 @@ public boolean isLoginUnique(String uuid, String userName) {
259269
ParameterExpression<String> userNameParam = cb.parameter(String.class, User.USER_NAME);
260270
CriteriaQuery<User> cq = cb.createQuery(getElementClass());
261271
Root<User> from = cq.from(getElementClass());
262-
cq.where(cb.equal(from.get(User.USER_NAME), userNameParam));
263272

264-
TypedQuery<User> q = em.createQuery(cq).setParameter(userNameParam, userName);
273+
274+
Expression<String> userNameExpression = from.get(User.USER_NAME);
275+
String userNameParamValue = userName;
276+
if (!AuthProvider.getProvider().isUsernameCaseSensitive()) {
277+
userNameExpression = cb.lower(userNameExpression);
278+
userNameParamValue = userName.toLowerCase();
279+
}
280+
281+
cq.where(cb.equal(userNameExpression, userNameParam));
282+
283+
TypedQuery<User> q = em.createQuery(cq).setParameter(userNameParam, userNameParamValue);
265284

266285
User entity = q.getResultList().stream().findFirst().orElse(null);
267286

268-
return entity == null || (entity != null && entity.getUuid().equals(uuid));
287+
return entity == null || entity.getUuid().equals(uuid);
269288
}
270289

271290
public String resetPassword(String userUuid) {

sormas-backend/src/test/java/de/symeda/sormas/backend/sormastosormas/SormasToSormasFacadeEjbTest.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -331,16 +331,16 @@ public void testShareCase() throws SormasToSormasException, JsonProcessingExcept
331331

332332
Mockito.when(MockProducer.getSormasToSormasClient().post(Matchers.anyString(), Matchers.anyString(), Matchers.anyString(), Matchers.any()))
333333
.thenAnswer(invocation -> {
334-
assertThat(invocation.getArgumentAt(0, String.class), is(SECOND_SERVER_REST_URL));
335-
assertThat(invocation.getArgumentAt(1, String.class), is("/sormasToSormas/case"));
334+
assertThat(invocation.getArgument(0, String.class), is(SECOND_SERVER_REST_URL));
335+
assertThat(invocation.getArgument(1, String.class), is("/sormasToSormas/case"));
336336

337-
String authToken = invocation.getArgumentAt(2, String.class);
337+
String authToken = invocation.getArgument(2, String.class);
338338
assertThat(authToken, startsWith("Basic "));
339339
String credentials = new String(Base64.getDecoder().decode(authToken.replace("Basic ", "")), StandardCharsets.UTF_8);
340340
// uses password from server-list.csv from `serveraccessdefault` package
341341
assertThat(credentials, is(StartupShutdownService.SORMAS_TO_SORMAS_USER_NAME + ":" + SECOND_SERVER_REST_PASSWORD));
342342

343-
SormasToSormasEncryptedDataDto encryptedData = invocation.getArgumentAt(3, SormasToSormasEncryptedDataDto.class);
343+
SormasToSormasEncryptedDataDto encryptedData = invocation.getArgument(3, SormasToSormasEncryptedDataDto.class);
344344
assertThat(encryptedData.getOrganizationId(), is(DEFAULT_SERVER_ACCESS_CN));
345345

346346
SormasToSormasCaseDto sharedCase = decryptSharesData(encryptedData.getData(), SormasToSormasCaseDto.class);
@@ -397,7 +397,7 @@ public void testShareCaseWithContacts()
397397

398398
Mockito.when(MockProducer.getSormasToSormasClient().post(Matchers.anyString(), Matchers.anyString(), Matchers.anyString(), Matchers.any()))
399399
.thenAnswer(invocation -> {
400-
SormasToSormasEncryptedDataDto encryptedData = invocation.getArgumentAt(3, SormasToSormasEncryptedDataDto.class);
400+
SormasToSormasEncryptedDataDto encryptedData = invocation.getArgument(3, SormasToSormasEncryptedDataDto.class);
401401
SormasToSormasCaseDto sharedCase = decryptSharesData(encryptedData.getData(), SormasToSormasCaseDto.class);
402402

403403
assertThat(sharedCase.getAssociatedContacts().size(), is(1));
@@ -435,16 +435,16 @@ public void testShareContact() throws SormasToSormasException, JsonProcessingExc
435435

436436
Mockito.when(MockProducer.getSormasToSormasClient().post(Matchers.anyString(), Matchers.anyString(), Matchers.anyString(), Matchers.any()))
437437
.thenAnswer(invocation -> {
438-
assertThat(invocation.getArgumentAt(0, String.class), is(SECOND_SERVER_REST_URL));
439-
assertThat(invocation.getArgumentAt(1, String.class), is("/sormasToSormas/contact"));
438+
assertThat(invocation.getArgument(0, String.class), is(SECOND_SERVER_REST_URL));
439+
assertThat(invocation.getArgument(1, String.class), is("/sormasToSormas/contact"));
440440

441-
String authToken = invocation.getArgumentAt(2, String.class);
441+
String authToken = invocation.getArgument(2, String.class);
442442
assertThat(authToken, startsWith("Basic "));
443443
String credentials = new String(Base64.getDecoder().decode(authToken.replace("Basic ", "")), StandardCharsets.UTF_8);
444444
// uses password from server-list.csv from `serveraccessdefault` package
445445
assertThat(credentials, is(StartupShutdownService.SORMAS_TO_SORMAS_USER_NAME + ":" + SECOND_SERVER_REST_PASSWORD));
446446

447-
SormasToSormasEncryptedDataDto encryptedData = invocation.getArgumentAt(3, SormasToSormasEncryptedDataDto.class);
447+
SormasToSormasEncryptedDataDto encryptedData = invocation.getArgument(3, SormasToSormasEncryptedDataDto.class);
448448
SormasToSormasContactDto sharedContact = decryptSharesData(encryptedData.getData(), SormasToSormasContactDto.class);
449449

450450
assertThat(sharedContact.getPerson().getFirstName(), is(person.getFirstName()));
@@ -496,7 +496,7 @@ public void testShareCaseWithPseudonymizePersonalData()
496496

497497
Mockito.when(MockProducer.getSormasToSormasClient().post(Matchers.anyString(), Matchers.anyString(), Matchers.anyString(), Matchers.any()))
498498
.thenAnswer(invocation -> {
499-
SormasToSormasEncryptedDataDto encryptedData = invocation.getArgumentAt(3, SormasToSormasEncryptedDataDto.class);
499+
SormasToSormasEncryptedDataDto encryptedData = invocation.getArgument(3, SormasToSormasEncryptedDataDto.class);
500500
SormasToSormasCaseDto sharedCase = decryptSharesData(encryptedData.getData(), SormasToSormasCaseDto.class);
501501

502502
assertThat(sharedCase.getPerson().getFirstName(), is("Confidential"));
@@ -532,7 +532,7 @@ public void testShareCaseWithPseudonymizeSensitiveData()
532532

533533
Mockito.when(MockProducer.getSormasToSormasClient().post(Matchers.anyString(), Matchers.anyString(), Matchers.anyString(), Matchers.any()))
534534
.thenAnswer(invocation -> {
535-
SormasToSormasEncryptedDataDto encryptedData = invocation.getArgumentAt(3, SormasToSormasEncryptedDataDto.class);
535+
SormasToSormasEncryptedDataDto encryptedData = invocation.getArgument(3, SormasToSormasEncryptedDataDto.class);
536536
SormasToSormasCaseDto sharedCase = decryptSharesData(encryptedData.getData(), SormasToSormasCaseDto.class);
537537

538538
assertThat(sharedCase.getPerson().getFirstName(), is("Confidential"));

sormas-backend/src/test/java/de/symeda/sormas/backend/user/UserFacadeEjbTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
import static org.hamcrest.Matchers.hasSize;
66
import static org.hamcrest.Matchers.nullValue;
77
import static org.junit.Assert.assertThat;
8+
import static org.mockito.Mockito.*;
89

910
import java.util.Collections;
1011
import java.util.List;
1112
import java.util.Set;
1213
import java.util.stream.Collectors;
1314

15+
import de.symeda.sormas.api.AuthProvider;
16+
import de.symeda.sormas.api.FacadeProvider;
1417
import org.junit.Test;
1518

1619
import de.symeda.sormas.api.user.UserCriteria;
@@ -22,6 +25,8 @@
2225
import de.symeda.sormas.backend.TestDataCreator.RDCFEntities;
2326
import de.symeda.sormas.backend.region.Region;
2427
import de.symeda.sormas.backend.region.RegionService;
28+
import org.mockito.MockedStatic;
29+
import org.mockito.Mockito;
2530

2631
public class UserFacadeEjbTest extends AbstractBeanTest {
2732

@@ -77,6 +82,12 @@ public void testGetIndexListFilteredAndOrderedByAddress() {
7782
@Test
7883
public void testGetValidLoginRoles() {
7984

85+
AuthProvider authProvider = mock(AuthProvider.class);
86+
87+
MockedStatic<AuthProvider> mockAuthProvider = mockStatic(AuthProvider.class);
88+
mockAuthProvider.when(AuthProvider::getProvider).thenReturn(authProvider);
89+
when(authProvider.isUsernameCaseSensitive()).thenReturn(true);
90+
8091
RDCF rdcf = creator.createRDCF();
8192
UserDto user = creator.createUser(rdcf, UserRole.SURVEILLANCE_SUPERVISOR);
8293
String password = getUserFacade().resetPassword(user.getUuid());

sormas-base/pom.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@
6060

6161
<dependency>
6262
<groupId>org.mockito</groupId>
63-
<artifactId>mockito-core</artifactId>
64-
<version>1.10.19</version>
63+
<artifactId>mockito-inline</artifactId>
64+
<version>3.5.13</version>
6565
<scope>test</scope>
6666
</dependency>
6767

@@ -411,7 +411,7 @@
411411

412412
<dependency>
413413
<groupId>org.mockito</groupId>
414-
<artifactId>mockito-core</artifactId>
414+
<artifactId>mockito-inline</artifactId>
415415
</dependency>
416416

417417
</dependencies>

sormas-ui/src/main/java/de/symeda/sormas/ui/user/UserController.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -53,12 +53,6 @@
5353

5454
public class UserController {
5555

56-
public final boolean isSormasAuthentication;
57-
58-
public UserController() {
59-
isSormasAuthentication = FacadeProvider.getConfigFacade().getAuthenticationProvider().equalsIgnoreCase(AuthProvider.SORMAS);
60-
}
61-
6256
public void create() {
6357
CommitDiscardWrapperComponent<UserEditForm> userCreateComponent = getUserCreateComponent();
6458
Window window = VaadinUiUtil.showModalPopupWindow(userCreateComponent, I18nProperties.getString(Strings.headingCreateNewUser));
@@ -180,7 +174,7 @@ public boolean isLoginUnique(String uuid, String userName) {
180174
}
181175

182176
public void makeInitialPassword(String userUuid) {
183-
if (isSormasAuthentication) {
177+
if (AuthProvider.getProvider().isDefaultProvider()) {
184178
String newPassword = FacadeProvider.getUserFacade().resetPassword(userUuid);
185179
showPasswordResetInternalSuccessPopup(newPassword);
186180
} else {
@@ -191,7 +185,7 @@ public void makeInitialPassword(String userUuid) {
191185
public void makeNewPassword(String userUuid) {
192186
String newPassword = FacadeProvider.getUserFacade().resetPassword(userUuid);
193187

194-
if (isSormasAuthentication) {
188+
if (AuthProvider.getProvider().isDefaultProvider()) {
195189
showPasswordResetInternalSuccessPopup(newPassword);
196190
} else {
197191
showPasswordResetExternalSuccessPopup();
@@ -312,6 +306,6 @@ public void setFlagIcons(ComboBox cbLanguage) {
312306
}
313307

314308
public boolean isEmailRequired() {
315-
return !isSormasAuthentication;
309+
return AuthProvider.getProvider().isEmailRequired();
316310
}
317311
}

sormas-ui/src/test/java/de/symeda/sormas/ui/importer/InfrastructureImporterTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
import com.opencsv.exceptions.CsvValidationException;
1212
import org.junit.Test;
1313
import org.junit.runner.RunWith;
14-
import org.mockito.runners.MockitoJUnitRunner;
1514

1615
import de.symeda.sormas.api.facility.FacilityCriteria;
1716
import de.symeda.sormas.api.importexport.InvalidColumnException;
@@ -29,8 +28,11 @@
2928
import de.symeda.sormas.ui.AbstractBeanTest;
3029
import de.symeda.sormas.ui.TestDataCreator;
3130
import de.symeda.sormas.ui.TestDataCreator.RDCF;
31+
import org.mockito.junit.MockitoJUnitRunner;
3232

33-
@RunWith(MockitoJUnitRunner.class)
33+
//Using Silent Runner to ignore unnecessary stubbing exception
34+
//which is a side effect of extending AbstractBeanTest
35+
@RunWith(MockitoJUnitRunner.Silent.class)
3436
public class InfrastructureImporterTest extends AbstractBeanTest {
3537

3638
@Test

0 commit comments

Comments
 (0)