Skip to content

Commit 2df8acd

Browse files
Flatten response times (opensearch-project#2471)
1 parent bba5c4d commit 2df8acd

File tree

2 files changed

+174
-9
lines changed

2 files changed

+174
-9
lines changed

src/main/java/org/opensearch/security/auth/internal/InternalAuthenticationBackend.java

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -75,28 +75,46 @@ public boolean exists(User user) {
7575
if(securityRoles != null) {
7676
user.addSecurityRoles(securityRoles);
7777
}
78-
78+
7979
user.addAttributes(attributeMap);
8080
return true;
8181
}
8282

8383
return false;
8484
}
8585

86+
/**
87+
* A helper function used to verify that both invalid and valid usernames have a hashing check during testing.
88+
* @param hash A string hash of the stored user's password.
89+
* @param array A char array of the provided password
90+
* @return Whether the hash matches the provided password
91+
*/
92+
public boolean passwordMatchesHash(String hash, char[] array) {
93+
return OpenBSDBCrypt.checkPassword(hash, array);
94+
}
95+
8696
@Override
8797
public User authenticate(final AuthCredentials credentials) {
8898

99+
boolean userExists;
100+
89101
if (internalUsersModel == null) {
90102
throw new OpenSearchSecurityException("Internal authentication backend not configured. May be OpenSearch is not initialized.");
91103
}
92104

105+
final byte[] password;
106+
String hash;
93107
if(!internalUsersModel.exists(credentials.getUsername())) {
94-
throw new OpenSearchSecurityException(credentials.getUsername() + " not found");
108+
userExists = false;
109+
password = credentials.getPassword();
110+
hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2"; // Ensure the same cryptographic complexity for users not found and invalid password
111+
} else {
112+
userExists = true;
113+
password = credentials.getPassword();
114+
hash = internalUsersModel.getHash(credentials.getUsername());
95115
}
96116

97-
final byte[] password = credentials.getPassword();
98-
99-
if(password == null || password.length == 0) {
117+
if (password == null || password.length == 0) {
100118
throw new OpenSearchSecurityException("empty passwords not supported");
101119
}
102120

@@ -108,24 +126,26 @@ public User authenticate(final AuthCredentials credentials) {
108126
Arrays.fill(password, (byte)0);
109127

110128
try {
111-
if (OpenBSDBCrypt.checkPassword(internalUsersModel.getHash(credentials.getUsername()), array)) {
129+
if (passwordMatchesHash(hash, array) && userExists) {
112130
final List<String> roles = internalUsersModel.getBackenRoles(credentials.getUsername());
113131
final Map<String, String> customAttributes = internalUsersModel.getAttributes(credentials.getUsername());
114132
if(customAttributes != null) {
115133
for(Entry<String, String> attributeName: customAttributes.entrySet()) {
116134
credentials.addAttribute("attr.internal."+attributeName.getKey(), attributeName.getValue());
117135
}
118136
}
119-
137+
120138
final User user = new User(credentials.getUsername(), roles, credentials);
121-
139+
122140
final List<String> securityRoles = internalUsersModel.getSecurityRoles(credentials.getUsername());
123141
if(securityRoles != null) {
124142
user.addSecurityRoles(securityRoles);
125143
}
126-
127144
return user;
128145
} else {
146+
if (!userExists) {
147+
throw new OpenSearchSecurityException(credentials.getUsername() + " not found");
148+
}
129149
throw new OpenSearchSecurityException("password does not match");
130150
}
131151
} finally {
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
*
4+
* The OpenSearch Contributors require contributions made to
5+
* this file be licensed under the Apache-2.0 license or a
6+
* compatible open source license.
7+
*
8+
* Modifications Copyright OpenSearch Contributors. See
9+
* GitHub history for details.
10+
*/
11+
12+
package org.opensearch.security.auth;
13+
14+
import java.nio.ByteBuffer;
15+
import java.nio.CharBuffer;
16+
import java.nio.charset.StandardCharsets;
17+
import java.util.Arrays;
18+
19+
import org.junit.Assert;
20+
import org.junit.Before;
21+
import org.junit.Test;
22+
import org.mockito.Mockito;
23+
24+
import org.opensearch.OpenSearchSecurityException;
25+
import org.opensearch.security.auth.internal.InternalAuthenticationBackend;
26+
import org.opensearch.security.securityconf.InternalUsersModel;
27+
import org.opensearch.security.user.AuthCredentials;
28+
29+
import static org.mockito.Mockito.doReturn;
30+
import static org.mockito.Mockito.mock;
31+
import static org.mockito.Mockito.spy;
32+
import static org.mockito.Mockito.verify;
33+
import static org.mockito.Mockito.when;
34+
import static org.mockito.internal.verification.VerificationModeFactory.times;
35+
36+
public class InternalAuthBackendTests {
37+
38+
private InternalUsersModel internalUsersModel;
39+
40+
private InternalAuthenticationBackend internalAuthenticationBackend;
41+
42+
@Before
43+
public void internalAuthBackendTestsSetup() {
44+
internalAuthenticationBackend = spy(new InternalAuthenticationBackend());
45+
internalUsersModel = mock(InternalUsersModel.class);
46+
internalAuthenticationBackend.onInternalUsersModelChanged(internalUsersModel);
47+
}
48+
49+
private char[] createArrayFromPasswordBytes(byte[] password) {
50+
ByteBuffer wrap = ByteBuffer.wrap(password);
51+
CharBuffer buf = StandardCharsets.UTF_8.decode(wrap);
52+
char[] array = new char[buf.limit()];
53+
buf.get(array);
54+
Arrays.fill(password, (byte)0);
55+
return array;
56+
}
57+
58+
@Test
59+
public void testHashActionWithValidUserValidPassword() {
60+
61+
// Make authentication info for valid username with valid password
62+
final String validPassword = "admin";
63+
final byte[] validPasswordBytes = validPassword.getBytes();
64+
65+
final AuthCredentials validUsernameAuth = new AuthCredentials("admin", validPasswordBytes);
66+
67+
final String hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2";
68+
69+
char[] array = createArrayFromPasswordBytes(validPasswordBytes);
70+
71+
72+
when(internalUsersModel.getHash(validUsernameAuth.getUsername())).thenReturn(hash);
73+
when(internalUsersModel.exists(validUsernameAuth.getUsername())).thenReturn(true);
74+
doReturn(true).when(internalAuthenticationBackend).passwordMatchesHash(Mockito.any(String.class), Mockito.any(char[].class));
75+
76+
//Act
77+
internalAuthenticationBackend.authenticate(validUsernameAuth);
78+
79+
verify(internalAuthenticationBackend, times(1)).passwordMatchesHash(hash, array);
80+
verify(internalUsersModel, times(1)).getBackenRoles(validUsernameAuth.getUsername());
81+
}
82+
83+
@Test
84+
public void testHashActionWithValidUserInvalidPassword() {
85+
86+
// Make authentication info for valid with bad password
87+
final String gibberishPassword = "ajdhflkasdjfaklsdf";
88+
final byte[] gibberishPasswordBytes = gibberishPassword.getBytes();
89+
final AuthCredentials validUsernameAuth = new AuthCredentials("admin", gibberishPasswordBytes);
90+
91+
final String hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2";
92+
93+
char[] array = createArrayFromPasswordBytes(gibberishPasswordBytes);
94+
95+
when(internalUsersModel.getHash("admin")).thenReturn(hash);
96+
when(internalUsersModel.exists("admin")).thenReturn(true);
97+
98+
OpenSearchSecurityException ex = Assert.assertThrows(OpenSearchSecurityException.class,
99+
() -> internalAuthenticationBackend.authenticate(validUsernameAuth));
100+
assert(ex.getMessage().contains("password does not match"));
101+
verify(internalAuthenticationBackend, times(1)).passwordMatchesHash(hash, array);
102+
}
103+
104+
@Test
105+
public void testHashActionWithInvalidUserValidPassword() {
106+
107+
// Make authentication info for valid and invalid usernames both with bad passwords
108+
final String validPassword = "admin";
109+
final byte[] validPasswordBytes = validPassword.getBytes();
110+
final AuthCredentials invalidUsernameAuth = new AuthCredentials("ertyuiykgjjfguyifdghc", validPasswordBytes);
111+
112+
final String hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2";
113+
114+
char[] array = createArrayFromPasswordBytes(validPasswordBytes);
115+
116+
when(internalUsersModel.exists("ertyuiykgjjfguyifdghc")).thenReturn(false);
117+
when(internalAuthenticationBackend.passwordMatchesHash(hash, array)).thenReturn(true); //Say that the password is correct
118+
119+
OpenSearchSecurityException ex = Assert.assertThrows(OpenSearchSecurityException.class,
120+
() -> internalAuthenticationBackend.authenticate(invalidUsernameAuth));
121+
assert(ex.getMessage().contains("not found"));
122+
verify(internalAuthenticationBackend, times(1)).passwordMatchesHash(hash, array);
123+
}
124+
125+
@Test
126+
public void testHashActionWithInvalidUserInvalidPassword() {
127+
128+
// Make authentication info for valid and invalid usernames both with bad passwords
129+
final String gibberishPassword = "ajdhflkasdjfaklsdf";
130+
final byte[] gibberishPasswordBytes = gibberishPassword.getBytes();
131+
final AuthCredentials invalidUsernameAuth = new AuthCredentials("ertyuiykgjjfguyifdghc", gibberishPasswordBytes);
132+
133+
final String hash = "$2y$12$NmKhjNssNgSIj8iXT7SYxeXvMA1E95a9tCt4cySY9FrQ4fB18xEc2";
134+
135+
char[] array = createArrayFromPasswordBytes(gibberishPasswordBytes);
136+
137+
when(internalUsersModel.exists("ertyuiykgjjfguyifdghc")).thenReturn(false);
138+
139+
140+
OpenSearchSecurityException ex = Assert.assertThrows(OpenSearchSecurityException.class,
141+
() -> internalAuthenticationBackend.authenticate(invalidUsernameAuth));
142+
verify(internalAuthenticationBackend, times(1)).passwordMatchesHash(hash, array);
143+
assert(ex.getMessage().contains("not found"));
144+
}
145+
}

0 commit comments

Comments
 (0)