This is an automated email from the ASF dual-hosted git repository.
dhavalshah9131 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ranger.git
The following commit(s) were added to refs/heads/master by this push:
new 0aef900f7 RANGER-5331: Test Cases for unixauthclient & unixauthservice
Module (#675)
0aef900f7 is described below
commit 0aef900f720dcb8e461f652c7f8df434aa404492
Author: Bhaavesh Amol Amre <[email protected]>
AuthorDate: Mon Sep 29 12:45:49 2025 +0530
RANGER-5331: Test Cases for unixauthclient & unixauthservice Module (#675)
---
unixauthclient/pom.xml | 12 +
.../jaas/TestConsolePromptCallbackHandler.java | 69 ++++
.../unix/jaas/TestPamLoginModule.java | 281 ++++++++++++++
.../authentication/unix/jaas/TestPamPrincipal.java | 66 ++++
.../unix/jaas/TestRemoteUnixLoginModule.java | 405 +++++++++++++++++++++
.../unix/jaas/TestUnixGroupPrincipal.java | 42 +++
.../unix/jaas/TestUnixUserPrincipal.java | 42 +++
.../jaas/TestUsernamePasswordCallbackHandler.java | 90 +++++
unixauthservice/pom.xml | 12 +
.../authentication/TestPasswordValidator.java | 183 ++++++++++
.../TestUnixAuthenticationService.java | 385 ++++++++++++++++++++
11 files changed, 1587 insertions(+)
diff --git a/unixauthclient/pom.xml b/unixauthclient/pom.xml
index c59321b2b..3e8c653ef 100644
--- a/unixauthclient/pom.xml
+++ b/unixauthclient/pom.xml
@@ -102,6 +102,18 @@
<artifactId>libpam4j</artifactId>
<version>${libpam4j.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-inline</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-junit-jupiter</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
diff --git
a/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestConsolePromptCallbackHandler.java
b/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestConsolePromptCallbackHandler.java
new file mode 100644
index 000000000..9b9b48fcb
--- /dev/null
+++
b/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestConsolePromptCallbackHandler.java
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ranger.authentication.unix.jaas;
+
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @generated by Cursor
+ * @description : Unit Test cases for ConsolePromptCallbackHandler
+ */
+
+@ExtendWith(MockitoExtension.class)
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class TestConsolePromptCallbackHandler {
+ @Test
+ public void test01_handle_setsNameAndPassword() throws IOException,
UnsupportedCallbackException {
+ String input = "alice\nsecret\n";
+ System.setIn(new
ByteArrayInputStream(input.getBytes(StandardCharsets.UTF_8)));
+
+ ConsolePromptCallbackHandler handler = new
ConsolePromptCallbackHandler();
+ NameCallback nameCallback = new
NameCallback("User: ");
+ PasswordCallback passwordCallback = new
PasswordCallback("Pass: ", false);
+
+ handler.handle(new Callback[] {nameCallback, passwordCallback});
+
+ assertEquals("alice", nameCallback.getName());
+ assertArrayEquals("secret".toCharArray(),
passwordCallback.getPassword());
+ }
+
+ @Test
+ public void test02_handle_unknownCallback_doesNotThrow() throws
IOException, UnsupportedCallbackException {
+ System.setIn(new
ByteArrayInputStream("\n".getBytes(StandardCharsets.UTF_8)));
+ ConsolePromptCallbackHandler handler = new
ConsolePromptCallbackHandler();
+ Callback unknown = new Callback() {};
+ handler.handle(new Callback[] {unknown});
+ }
+}
diff --git
a/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestPamLoginModule.java
b/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestPamLoginModule.java
new file mode 100644
index 000000000..22cc637ef
--- /dev/null
+++
b/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestPamLoginModule.java
@@ -0,0 +1,281 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ranger.authentication.unix.jaas;
+
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.jvnet.libpam.PAM;
+import org.jvnet.libpam.UnixUser;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.login.FailedLoginException;
+import javax.security.auth.login.LoginException;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.Principal;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * @generated by Cursor
+ * @description : Unit Test cases for PamLoginModule
+ */
+
+@ExtendWith(MockitoExtension.class)
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class TestPamLoginModule {
+ @Test
+ public void test01_initialize_withoutService_throwsOnLogin() throws
Exception {
+ PamLoginModule m = new PamLoginModule();
+ Subject subject = new Subject();
+ Map<String, Object> opts = new HashMap<>();
+ m.initialize(subject, creds("alice", "secret"), new HashMap<>(), opts);
+ setField(m, "options", new HashMap<>());
+ assertThrows(LoginException.class, m::login);
+ }
+
+ @Test
+ public void
test02_commit_addsPrincipal_and_logout_clears_without_realPam() throws
Exception {
+ PamLoginModule m = new PamLoginModule();
+ Subject subject = new Subject();
+ UnixUser user = Mockito.mock(UnixUser.class);
+ Mockito.when(user.getUserName()).thenReturn("alice");
+ Mockito.when(user.getGecos()).thenReturn("");
+ Mockito.when(user.getDir()).thenReturn("");
+ Mockito.when(user.getShell()).thenReturn("");
+ Mockito.when(user.getUID()).thenReturn(0);
+ Mockito.when(user.getGID()).thenReturn(0);
+ Mockito.when(user.getGroups()).thenReturn(Collections.emptySet());
+
+ setField(m, "subject", subject);
+ setField(m, "authSucceeded", true);
+ setField(m, "principal", new PamPrincipal(user));
+ setField(m, "pam", Mockito.mock(PAM.class));
+ setField(m, "passwordChar", new char[] {'x'});
+
+ assertTrue(m.commit());
+ Set<Principal> principals = subject.getPrincipals();
+ assertTrue(principals.stream().anyMatch(p -> p instanceof PamPrincipal
&& p.getName().equals("alice")));
+
+ assertTrue(m.logout());
+ assertTrue(subject.getPrincipals().isEmpty());
+ }
+
+ @Test
+ public void test03_login_throwsLoginException_withRealPam() throws
Exception {
+ PamLoginModule m = new PamLoginModule();
+ Subject subject = new Subject();
+ Map<String, Object> opts = new HashMap<>();
+ opts.put(PamLoginModule.SERVICE_KEY, "sshd");
+ CallbackHandler cb = creds("alice", "bad");
+ m.initialize(subject, cb, new HashMap<>(), opts);
+ setField(m, "options", opts);
+ setField(m, "callbackHandler", cb);
+ assertThrows(LoginException.class, m::login);
+ }
+
+ @Test
+ public void test04_abort_withoutAuth_returnsFalse() throws Exception {
+ PamLoginModule m = new PamLoginModule();
+ Subject subject = new Subject();
+ Map<String, Object> opts = new HashMap<>();
+ opts.put(PamLoginModule.SERVICE_KEY, "sshd");
+ CallbackHandler cb = creds("u", "p");
+ m.initialize(subject, cb, new HashMap<>(), opts);
+ setField(m, "options", opts);
+ setField(m, "subject", subject);
+ setField(m, "callbackHandler", cb);
+ assertFalse(m.abort());
+ }
+
+ @Test
+ public void test05_abort_afterSuccess_cleansUpAndReturnsTrue() throws
Exception {
+ PamLoginModule m = new PamLoginModule();
+ setField(m, "authSucceeded", true);
+ PAM pam = Mockito.mock(PAM.class);
+ setField(m, "pam", pam);
+ setField(m, "passwordChar", new char[] {'x'});
+ assertTrue(m.abort());
+ }
+
+ @Test
+ public void test06_commit_returnsFalse_whenAuthNotSucceeded() throws
Exception {
+ PamLoginModule m = new PamLoginModule();
+ setField(m, "authSucceeded", false);
+ setField(m, "subject", new Subject());
+ assertFalse(m.commit());
+ }
+
+ @Test
+ public void test07_commit_readOnlySubject_throws_and_cleansUp() throws
Exception {
+ PamLoginModule m = new PamLoginModule();
+ Subject subject = new Subject();
+ subject.setReadOnly();
+ setField(m, "subject", subject);
+ setField(m, "authSucceeded", true);
+ setField(m, "principal", new
PamPrincipal(Mockito.mock(UnixUser.class)));
+ PAM pam = Mockito.mock(PAM.class);
+ setField(m, "pam", pam);
+ setField(m, "passwordChar", new char[] {'p'});
+ assertThrows(LoginException.class, m::commit);
+ Mockito.verify(pam).dispose();
+ }
+
+ @Test
+ public void test08_obtainUserAndPassword_noCallbackHandler_throws() throws
Exception {
+ PamLoginModule m = new PamLoginModule();
+ setField(m, "callbackHandler", null);
+ Method method =
PamLoginModule.class.getDeclaredMethod("obtainUserAndPassword");
+ method.setAccessible(true);
+ assertThrows(LoginException.class, () ->
invokeAndRethrowLoginException(m, method));
+ }
+
+ @Test
+ public void test09_obtainUserAndPassword_errorInCallbacks_throws() throws
Exception {
+ PamLoginModule m = new PamLoginModule();
+ CallbackHandler cb = new CallbackHandler() {
+ @Override
+ public void handle(Callback[] callbacks) throws
java.io.IOException {
+ throw new java.io.IOException("boom");
+ }
+ };
+ setField(m, "callbackHandler", cb);
+ Method method =
PamLoginModule.class.getDeclaredMethod("obtainUserAndPassword");
+ method.setAccessible(true);
+ assertThrows(LoginException.class, () ->
invokeAndRethrowLoginException(m, method));
+ }
+
+ @Test
+ public void test10_performLogin_withNullPassword_throwsFailedLogin()
throws Exception {
+ PamLoginModule m = new PamLoginModule();
+ setField(m, "username", "alice");
+ setField(m, "pam", Mockito.mock(PAM.class));
+ Method method = PamLoginModule.class.getDeclaredMethod("performLogin");
+ method.setAccessible(true);
+ assertThrows(FailedLoginException.class, () ->
invokeAndRethrowLoginException(m, method));
+ }
+
+ @Test
+ public void test11_performLogin_success_setsPrincipalAndAuthSucceeded()
throws Exception {
+ PamLoginModule m = new PamLoginModule();
+ setField(m, "username", "alice");
+ setField(m, "passwordChar", new char[] {'s'});
+ PAM pam = Mockito.mock(PAM.class);
+ UnixUser user = Mockito.mock(UnixUser.class);
+ Mockito.when(user.getUserName()).thenReturn("alice");
+ Mockito.when(pam.authenticate("alice", "s")).thenReturn(user);
+ setField(m, "pam", pam);
+
+ Method method = PamLoginModule.class.getDeclaredMethod("performLogin");
+ method.setAccessible(true);
+ assertTrue(invokeAndReturnBoolean(m, method));
+ Object principal = getField(m, "principal");
+ assertNotNull(principal);
+ assertTrue(principal instanceof PamPrincipal);
+ assertEquals("alice", ((PamPrincipal) principal).getName());
+ assertTrue((Boolean) getField(m, "authSucceeded"));
+ }
+
+ @Test
+ public void test12_logout_readOnlySubject_throws_and_cleansUp() throws
Exception {
+ PamLoginModule m = new PamLoginModule();
+ Subject subject = new Subject();
+ subject.setReadOnly();
+ setField(m, "subject", subject);
+ setField(m, "principal", new
PamPrincipal(Mockito.mock(UnixUser.class)));
+ PAM pam = Mockito.mock(PAM.class);
+ setField(m, "pam", pam);
+ setField(m, "passwordChar", new char[] {'p'});
+ assertThrows(LoginException.class, m::logout);
+ Mockito.verify(pam).dispose();
+ }
+
+ private static void setField(Object target, String name, Object value)
throws Exception {
+ Field f = target.getClass().getDeclaredField(name);
+ f.setAccessible(true);
+ f.set(target, value);
+ }
+
+ private static Object getField(Object target, String name) throws
Exception {
+ Field f = target.getClass().getDeclaredField(name);
+ f.setAccessible(true);
+ return f.get(target);
+ }
+
+ private static void invokeAndRethrowLoginException(Object target, Method
method) throws LoginException {
+ try {
+ method.invoke(target);
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof LoginException) {
+ throw (LoginException) cause;
+ }
+ throw new RuntimeException(cause);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static boolean invokeAndReturnBoolean(Object target, Method
method) throws Exception {
+ try {
+ Object ret = method.invoke(target);
+ return (Boolean) ret;
+ } catch (InvocationTargetException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof Exception) {
+ throw (Exception) cause;
+ }
+ throw new RuntimeException(cause);
+ }
+ }
+
+ private static CallbackHandler creds(String user, String pass) {
+ return new CallbackHandler() {
+ @Override
+ public void handle(Callback[] callbacks) {
+ for (Callback c : callbacks) {
+ if (c instanceof NameCallback) {
+ ((NameCallback) c).setName(user);
+ }
+ if (c instanceof PasswordCallback) {
+ ((PasswordCallback) c).setPassword(pass != null ?
pass.toCharArray() : null);
+ }
+ }
+ }
+ };
+ }
+}
diff --git
a/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestPamPrincipal.java
b/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestPamPrincipal.java
new file mode 100644
index 000000000..541d4e00f
--- /dev/null
+++
b/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestPamPrincipal.java
@@ -0,0 +1,66 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ranger.authentication.unix.jaas;
+
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.jvnet.libpam.UnixUser;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * @generated by Cursor
+ * @description : Unit Test cases for PamPrincipal
+ */
+
+@ExtendWith(MockitoExtension.class)
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class TestPamPrincipal {
+ @Test
+ public void test01_getters_returnValuesFromUnixUser() {
+ Set<String> groups = new HashSet<>(Arrays.asList("g1", "g2"));
+ UnixUser user = Mockito.mock(UnixUser.class);
+ Mockito.when(user.getUserName()).thenReturn("alice");
+ Mockito.when(user.getGecos()).thenReturn("gecos");
+ Mockito.when(user.getDir()).thenReturn("/home/alice");
+ Mockito.when(user.getShell()).thenReturn("/bin/bash");
+ Mockito.when(user.getUID()).thenReturn(1000);
+ Mockito.when(user.getGID()).thenReturn(100);
+ Mockito.when(user.getGroups()).thenReturn(groups);
+
+ PamPrincipal p = new PamPrincipal(user);
+ assertEquals("alice", p.getName());
+ assertEquals("gecos", p.getGecos());
+ assertEquals("/home/alice", p.getHomeDir());
+ assertEquals("/bin/bash", p.getShell());
+ assertEquals(1000, p.getUid());
+ assertEquals(100, p.getGid());
+ assertEquals(groups, p.getGroups());
+ assertThrows(UnsupportedOperationException.class, () ->
p.getGroups().add("x"));
+ }
+}
diff --git
a/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestRemoteUnixLoginModule.java
b/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestRemoteUnixLoginModule.java
new file mode 100644
index 000000000..7d7dae165
--- /dev/null
+++
b/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestRemoteUnixLoginModule.java
@@ -0,0 +1,405 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ranger.authentication.unix.jaas;
+
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+import sun.security.x509.AlgorithmId;
+import sun.security.x509.CertificateAlgorithmId;
+import sun.security.x509.CertificateSerialNumber;
+import sun.security.x509.CertificateValidity;
+import sun.security.x509.CertificateVersion;
+import sun.security.x509.CertificateX509Key;
+import sun.security.x509.X500Name;
+import sun.security.x509.X509CertImpl;
+import sun.security.x509.X509CertInfo;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+import javax.security.auth.login.LoginException;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.math.BigInteger;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.Principal;
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * @generated by Cursor
+ * @description : Unit Test cases for RemoteUnixLoginModule
+ */
+
+@ExtendWith(MockitoExtension.class)
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class TestRemoteUnixLoginModule {
+ @Test
+ public void test01_login_success_commit_addsPrincipals_and_logout_clears()
throws Exception {
+ try (OneShotAuthServer server = new OneShotAuthServer("OK: g1, g2")) {
+ RemoteUnixLoginModule m = new RemoteUnixLoginModule();
+ Subject subject = new Subject();
+ Map<String, Object> opts = baseOptions("127.0.0.1",
server.getPort());
+ m.initialize(subject, fixedCredentials("alice", "secret"), new
HashMap<>(), opts);
+
+ assertTrue(m.login());
+ assertTrue(m.commit());
+
+ Set<Principal> principals = subject.getPrincipals();
+ assertTrue(principals.stream().anyMatch(p -> p instanceof
UnixUserPrincipal && p.getName().equals("alice")));
+ assertTrue(principals.stream().anyMatch(p -> p instanceof
UnixGroupPrincipal && p.getName().equals("g1")));
+ assertTrue(principals.stream().anyMatch(p -> p instanceof
UnixGroupPrincipal && p.getName().equals("g2")));
+
+ assertTrue(m.logout());
+ assertTrue(subject.getPrincipals().isEmpty());
+ }
+ }
+
+ @Test
+ public void test02_login_failed_throws_LoginException() throws Exception {
+ try (OneShotAuthServer server = new OneShotAuthServer("FAILED: Invalid
Password")) {
+ RemoteUnixLoginModule m = new RemoteUnixLoginModule();
+ Subject subject = new Subject();
+ Map<String, Object> opts = baseOptions("127.0.0.1",
server.getPort());
+ m.initialize(subject, fixedCredentials("alice", "secret"), new
HashMap<>(), opts);
+ assertThrows(LoginException.class, m::login);
+ assertFalse(m.commit());
+ }
+ }
+
+ @Test
+ public void test03_login_nullReply_throws_LoginException() throws
Exception {
+ try (OneShotAuthServer server = new OneShotAuthServer(null)) {
+ RemoteUnixLoginModule m = new RemoteUnixLoginModule();
+ Subject subject = new Subject();
+ Map<String, Object> opts = baseOptions("127.0.0.1",
server.getPort());
+ m.initialize(subject, fixedCredentials("bob", "x"), new
HashMap<>(), opts);
+ assertThrows(LoginException.class, m::login);
+ }
+ }
+
+ @Test
+ public void
test04_login_disabled_returnsFalse_and_commitClearsPrincipals() throws
Exception {
+ RemoteUnixLoginModule m = new RemoteUnixLoginModule();
+ Subject subject = new Subject();
+ Map<String, Object> opts = baseOptions("localhost", 65535);
+ opts.put("ranger.unixauth.remote.login.enabled", "false");
+ m.initialize(subject, fixedCredentials("tom", "y"), new HashMap<>(),
opts);
+
+ assertFalse(m.login());
+ assertFalse(m.commit());
+ assertTrue(subject.getPrincipals().isEmpty());
+ }
+
+ @Test
+ public void
test05_ssl_disabledValidation_connectFailure_throwsLoginException() {
+ RemoteUnixLoginModule m = new RemoteUnixLoginModule();
+ Subject subject = new Subject();
+ Map<String, Object> opts = baseOptions("127.0.0.1", 65000);
+ opts.put("ranger.unixauth.ssl.enabled", "true");
+ opts.put("ranger.unixauth.server.cert.validation", "false");
+ m.initialize(subject, fixedCredentials("alice", "secret"), new
HashMap<>(), opts);
+ assertThrows(LoginException.class, m::login);
+ }
+
+ @Test
+ public void test06_ssl_withTruststore_loadFails_throwsLoginException() {
+ RemoteUnixLoginModule m = new RemoteUnixLoginModule();
+ Subject subject = new Subject();
+ Map<String, Object> opts = baseOptions("127.0.0.1", 65001);
+ opts.put("ranger.unixauth.ssl.enabled", "true");
+ opts.put("ranger.unixauth.server.cert.validation", "true");
+ opts.put("ranger.unixauth.truststore", "nonexistent.jks");
+ opts.put("ranger.unixauth.truststore.password", "");
+ m.initialize(subject, fixedCredentials("bob", "pw"), new HashMap<>(),
opts);
+ assertThrows(LoginException.class, m::login);
+ }
+
+ @Test
+ public void test07_ssl_withKeystore_loadFails_throwsLoginException() {
+ RemoteUnixLoginModule m = new RemoteUnixLoginModule();
+ Subject subject = new Subject();
+ Map<String, Object> opts = baseOptions("127.0.0.1", 65002);
+ opts.put("ranger.unixauth.ssl.enabled", "true");
+ opts.put("ranger.unixauth.keystore", "nonexistent-keystore.jks");
+ opts.put("ranger.unixauth.keystore.password", "");
+ // disable server cert validation to avoid needing truststore
+ opts.put("ranger.unixauth.server.cert.validation", "false");
+ m.initialize(subject, fixedCredentials("carol", "pw"), new
HashMap<>(), opts);
+ assertThrows(LoginException.class, m::login);
+ }
+
+ @Test
+ public void test08_abort_resetsLoginState_and_commitReturnsFalse() throws
Exception {
+ try (OneShotAuthServer server = new OneShotAuthServer("OK:")) {
+ RemoteUnixLoginModule m = new RemoteUnixLoginModule();
+ Subject subject = new Subject();
+ Map<String, Object> opts = baseOptions("127.0.0.1",
server.getPort());
+ m.initialize(subject, fixedCredentials("alice", "secret"), new
HashMap<>(), opts);
+
+ assertTrue(m.login());
+ assertTrue(m.abort());
+
+ // After abort(), loginSuccessful should be false; commit should
return false and clear principals
+ subject.getPrincipals().add(new UnixUserPrincipal("temp"));
+ assertFalse(m.commit());
+ assertTrue(subject.getPrincipals().isEmpty());
+ }
+ }
+
+ @Test
+ public void
test09_ssl_ignoreValidation_successfulHandshake_executesTrustManager_andLogin()
throws Exception {
+ try (SslOneShotAuthServer server = new SslOneShotAuthServer("OK: gA,
gB")) {
+ RemoteUnixLoginModule m = new RemoteUnixLoginModule();
+ Subject subject = new Subject();
+ Map<String, Object> opts = baseOptions("127.0.0.1",
server.getPort());
+ opts.put("ranger.unixauth.ssl.enabled", "true");
+ opts.put("ranger.unixauth.server.cert.validation", "false");
+ m.initialize(subject, fixedCredentials("eve", "pw"), new
HashMap<>(), opts);
+
+ assertTrue(m.login());
+ assertTrue(m.commit());
+ assertTrue(subject.getPrincipals().stream().anyMatch(p -> p
instanceof UnixUserPrincipal && p.getName().equals("eve")));
+ assertTrue(subject.getPrincipals().stream().anyMatch(p -> p
instanceof UnixGroupPrincipal && p.getName().equals("gA")));
+ assertTrue(m.logout());
+ }
+ }
+
+ @Test
+ public void
test10_login_callbackHandlerThrowsIOException_isWrappedAsLoginException() {
+ RemoteUnixLoginModule m = new RemoteUnixLoginModule();
+ Subject subject = new Subject();
+ Map<String, Object> opts = baseOptions("127.0.0.1", 65010);
+ CallbackHandler cb = new CallbackHandler() {
+ @Override
+ public void handle(Callback[] callbacks) throws IOException {
+ throw new IOException("boom");
+ }
+ };
+ m.initialize(subject, cb, new HashMap<>(), opts);
+ assertThrows(LoginException.class, m::login);
+ }
+
+ @Test
+ public void
test11_login_callbackHandlerThrowsUnsupported_isWrappedAsLoginException() {
+ RemoteUnixLoginModule m = new RemoteUnixLoginModule();
+ Subject subject = new Subject();
+ Map<String, Object> opts = baseOptions("127.0.0.1", 65011);
+ CallbackHandler cb = new CallbackHandler() {
+ @Override
+ public void handle(Callback[] callbacks) throws
UnsupportedCallbackException {
+ throw new UnsupportedCallbackException(new
NameCallback("user"));
+ }
+ };
+ m.initialize(subject, cb, new HashMap<>(), opts);
+ assertThrows(LoginException.class, m::login);
+ }
+
+ private static Map<String, Object> baseOptions(String host, int port) {
+ Map<String, Object> opts = new HashMap<>();
+ opts.put("ranger.unixauth.remote.login.enabled", "true");
+ opts.put("ranger.unixauth.service.hostname", host);
+ opts.put("ranger.unixauth.service.port", String.valueOf(port));
+ opts.put("ranger.unixauth.ssl.enabled", "false");
+ opts.put("ranger.unixauth.debug", "false");
+ return opts;
+ }
+
+ private static CallbackHandler fixedCredentials(String user, String pass) {
+ return new CallbackHandler() {
+ @Override
+ public void handle(Callback[] callbacks) throws IOException {
+ for (Callback cb : callbacks) {
+ if (cb instanceof NameCallback) {
+ ((NameCallback) cb).setName(user);
+ } else if (cb instanceof PasswordCallback) {
+ ((PasswordCallback)
cb).setPassword(pass.toCharArray());
+ }
+ }
+ }
+ };
+ }
+
+ private static class OneShotAuthServer implements AutoCloseable {
+ private final ServerSocket server;
+ private final Thread thread;
+ private final CountDownLatch ready = new CountDownLatch(1);
+ private final String reply;
+
+ OneShotAuthServer(String reply) throws IOException {
+ this.server = new ServerSocket(0);
+ this.reply = reply;
+ this.thread = new Thread(() -> {
+ ready.countDown();
+ try (Socket s = server.accept()) {
+ BufferedReader in = new BufferedReader(new
InputStreamReader(s.getInputStream()));
+ in.readLine();
+ try (OutputStreamWriter out = new
OutputStreamWriter(s.getOutputStream())) {
+ if (reply != null) {
+ out.write(reply + "\n");
+ out.flush();
+ } else {
+ // close without sending
+ }
+ }
+ } catch (IOException ignored) {
+ } finally {
+ try {
+ server.close();
+ } catch (IOException ignored) {
+ }
+ }
+ });
+ this.thread.setDaemon(true);
+ this.thread.start();
+ try {
+ ready.await(5, TimeUnit.SECONDS);
+ } catch (InterruptedException ignored) {
+ }
+ }
+
+ @Override
+ public void close() {
+ try {
+ server.close();
+ } catch (IOException ignored) {
+ }
+ }
+
+ int getPort() {
+ return server.getLocalPort();
+ }
+ }
+
+ private static class SslOneShotAuthServer implements AutoCloseable {
+ private final SSLServerSocket server;
+ private final Thread thread;
+ private final CountDownLatch ready = new CountDownLatch(1);
+ private final String reply;
+
+ SslOneShotAuthServer(String reply) throws Exception {
+ this.reply = reply;
+
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
+ kpg.initialize(2048);
+ KeyPair kp = kpg.generateKeyPair();
+
+ X509Certificate cert = generateSelfSigned("CN=localhost", kp);
+
+ KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());
+ ks.load(null, null);
+ char[] pass = "changeit".toCharArray();
+ ks.setKeyEntry("server", kp.getPrivate(), pass, new Certificate[]
{cert});
+
+ KeyManagerFactory kmf =
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ kmf.init(ks, pass);
+ KeyManager[] kms = kmf.getKeyManagers();
+
+ SSLContext ctx = SSLContext.getInstance("TLSv1.2");
+ ctx.init(kms, null, new SecureRandom());
+ SSLServerSocketFactory ssf = ctx.getServerSocketFactory();
+ this.server = (SSLServerSocket) ssf.createServerSocket(0);
+
+ this.thread = new Thread(() -> {
+ ready.countDown();
+ try (Socket s = server.accept()) {
+ BufferedReader in = new BufferedReader(new
InputStreamReader(s.getInputStream()));
+ in.readLine();
+ try (OutputStreamWriter out = new
OutputStreamWriter(s.getOutputStream())) {
+ out.write(reply + "\n");
+ out.flush();
+ }
+ } catch (IOException ignored) {
+ } finally {
+ try {
+ server.close();
+ } catch (IOException ignored) {
+ }
+ }
+ });
+ this.thread.setDaemon(true);
+ this.thread.start();
+ try {
+ ready.await(5, TimeUnit.SECONDS);
+ } catch (InterruptedException ignored) {
+ }
+ }
+
+ @Override
+ public void close() {
+ try {
+ server.close();
+ } catch (IOException ignored) {
+ }
+ }
+
+ int getPort() {
+ return server.getLocalPort();
+ }
+
+ private static X509Certificate generateSelfSigned(String dn, KeyPair
keyPair) throws Exception {
+ long now = System.currentTimeMillis();
+ Date from = new Date(now - 60000);
+ Date to = new Date(now + 86400000L);
+
+ X509CertInfo info = new X509CertInfo();
+ info.set(X509CertInfo.VERSION, new
CertificateVersion(CertificateVersion.V3));
+ info.set(X509CertInfo.SERIAL_NUMBER, new
CertificateSerialNumber(new BigInteger(64, new SecureRandom())));
+ X500Name owner = new X500Name(dn);
+ info.set(X509CertInfo.SUBJECT, owner);
+ info.set(X509CertInfo.ISSUER, owner);
+ info.set(X509CertInfo.VALIDITY, new CertificateValidity(from, to));
+ info.set(X509CertInfo.KEY, new
CertificateX509Key(keyPair.getPublic()));
+ info.set(X509CertInfo.ALGORITHM_ID, new
CertificateAlgorithmId(AlgorithmId.get("SHA256withRSA")));
+
+ X509CertImpl cert = new X509CertImpl(info);
+ cert.sign(keyPair.getPrivate(), "SHA256withRSA");
+ return cert;
+ }
+ }
+}
diff --git
a/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUnixGroupPrincipal.java
b/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUnixGroupPrincipal.java
new file mode 100644
index 000000000..407c43895
--- /dev/null
+++
b/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUnixGroupPrincipal.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ranger.authentication.unix.jaas;
+
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @generated by Cursor
+ * @description : Unit Test cases for UnixGroupPrincipal
+ */
+
+@ExtendWith(MockitoExtension.class)
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class TestUnixGroupPrincipal {
+ @Test
+ public void test01_getName_returnsGroupName() {
+ UnixGroupPrincipal principal = new UnixGroupPrincipal("dev");
+ assertEquals("dev", principal.getName());
+ }
+}
diff --git
a/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUnixUserPrincipal.java
b/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUnixUserPrincipal.java
new file mode 100644
index 000000000..e53d0b678
--- /dev/null
+++
b/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUnixUserPrincipal.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ranger.authentication.unix.jaas;
+
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * @generated by Cursor
+ * @description : Unit Test cases for UnixUserPrincipal
+ */
+
+@ExtendWith(MockitoExtension.class)
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class TestUnixUserPrincipal {
+ @Test
+ public void test01_getName_returnsUserName() {
+ UnixUserPrincipal principal = new UnixUserPrincipal("alice");
+ assertEquals("alice", principal.getName());
+ }
+}
diff --git
a/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUsernamePasswordCallbackHandler.java
b/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUsernamePasswordCallbackHandler.java
new file mode 100644
index 000000000..782700dc9
--- /dev/null
+++
b/unixauthclient/src/test/java/org/apache/ranger/authentication/unix/jaas/TestUsernamePasswordCallbackHandler.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.ranger.authentication.unix.jaas;
+
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+/**
+ * @generated by Cursor
+ * @description : Unit Test cases for UsernamePasswordCallbackHandler
+ */
+
+@ExtendWith(MockitoExtension.class)
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class TestUsernamePasswordCallbackHandler {
+ @Test
+ public void test01_handle_name_only_constructorBug_setsNullName() throws
IOException, UnsupportedCallbackException {
+ UsernamePasswordCallbackHandler handler = new
UsernamePasswordCallbackHandler("alice", "secret");
+ NameCallback nameCallback = new
NameCallback("User:");
+ handler.handle(new Callback[] {nameCallback});
+ assertNull(nameCallback.getName());
+ }
+
+ @Test
+ public void test02_handle_password_throwsNPE_whenPasswordNull() {
+ UsernamePasswordCallbackHandler handler = new
UsernamePasswordCallbackHandler(null, null);
+ PasswordCallback passwordCallback = new
PasswordCallback("Pass:", false);
+ assertThrows(NullPointerException.class, () -> handler.handle(new
Callback[] {passwordCallback}));
+ }
+
+ @Test
+ public void test03_handle_setsNameAndPassword_whenFieldsInjected() throws
Exception {
+ UsernamePasswordCallbackHandler handler = new
UsernamePasswordCallbackHandler(null, null);
+
+ Field userField =
UsernamePasswordCallbackHandler.class.getDeclaredField("user");
+ userField.setAccessible(true);
+ userField.set(handler, "bob");
+
+ Field pwdField =
UsernamePasswordCallbackHandler.class.getDeclaredField("password");
+ pwdField.setAccessible(true);
+ pwdField.set(handler, "p@ss");
+
+ NameCallback nameCallback = new NameCallback("User:");
+ PasswordCallback passwordCallback = new PasswordCallback("Pass:",
false);
+
+ handler.handle(new Callback[] {nameCallback, passwordCallback});
+
+ assertEquals("bob", nameCallback.getName());
+ assertArrayEquals("p@ss".toCharArray(),
passwordCallback.getPassword());
+ }
+
+ @Test
+ public void test04_handle_unsupportedCallback_throws() {
+ UsernamePasswordCallbackHandler handler = new
UsernamePasswordCallbackHandler(null, null);
+ Callback unsupported = new Callback() {};
+ assertThrows(UnsupportedCallbackException.class, () ->
handler.handle(new Callback[] {unsupported}));
+ }
+}
diff --git a/unixauthservice/pom.xml b/unixauthservice/pom.xml
index 07f7ab760..2821307f5 100644
--- a/unixauthservice/pom.xml
+++ b/unixauthservice/pom.xml
@@ -101,6 +101,18 @@
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-inline</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-junit-jupiter</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
diff --git
a/unixauthservice/src/test/java/org/apache/ranger/authentication/TestPasswordValidator.java
b/unixauthservice/src/test/java/org/apache/ranger/authentication/TestPasswordValidator.java
new file mode 100644
index 000000000..69860af0b
--- /dev/null
+++
b/unixauthservice/src/test/java/org/apache/ranger/authentication/TestPasswordValidator.java
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.ranger.authentication;
+
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * @generated by Cursor
+ * @description : Unit Test cases for PasswordValidator
+ */
+
+@ExtendWith(MockitoExtension.class)
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class TestPasswordValidator {
+ @Test
+ public void test00_staticGettersAndSetters() {
+ PasswordValidator.setValidatorProgram("/tmp/prog");
+ PasswordValidator.setAdminUserList(Collections.singletonList("root"));
+ PasswordValidator.setAdminRoleNames("ROLE_SUPER");
+
+ assertEquals("/tmp/prog", PasswordValidator.getValidatorProgram());
+ assertEquals(Collections.singletonList("root"),
PasswordValidator.getAdminUserList());
+ assertEquals("ROLE_SUPER", PasswordValidator.getAdminRoleNames());
+ }
+
+ @Test
+ public void test01_nullProgramWritesFailedAndClosesSocket() throws
Exception {
+ PasswordValidator.setValidatorProgram(null);
+ PasswordValidator.setAdminUserList(null);
+ PasswordValidator.setAdminRoleNames(null);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ Socket socket = buildSocket("LOGIN: alice secret", out);
+
+ new PasswordValidator(socket).run();
+
+ String response = out.toString(StandardCharsets.UTF_8.name()).trim();
+ assertEquals("FAILED: Unable to validate credentials.", response);
+ verify(socket, times(1)).close();
+ }
+
+ @Test
+ public void test02_okResponseAppendsAdminRoleAndDestroysProcess() throws
Exception {
+ PasswordValidator.setValidatorProgram("/bin/validator");
+ PasswordValidator.setAdminUserList(Arrays.asList("alice", "bob"));
+ PasswordValidator.setAdminRoleNames("ROLE_ADMIN");
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ Socket socket = buildSocket("LOGIN: alice secret", out);
+
+ Process process = mock(Process.class);
+ ByteArrayInputStream processStdout = new
ByteArrayInputStream("OK".getBytes(StandardCharsets.UTF_8));
+ when(process.getInputStream()).thenReturn(processStdout);
+ when(process.getOutputStream()).thenReturn(new
ByteArrayOutputStream());
+
+ try (MockedStatic<Runtime> runtimeStatic = mockStatic(Runtime.class)) {
+ Runtime rt = mock(Runtime.class);
+ runtimeStatic.when(Runtime::getRuntime).thenReturn(rt);
+ when(rt.exec("/bin/validator")).thenReturn(process);
+
+ new PasswordValidator(socket).run();
+ }
+
+ String response = out.toString(StandardCharsets.UTF_8.name()).trim();
+ assertEquals("OK ROLE_ADMIN", response);
+ verify(process, times(1)).destroy();
+ verify(socket, times(1)).close();
+ }
+
+ @Test
+ public void test03_okResponseWithoutAdminDoesNotAppendRole() throws
Exception {
+ PasswordValidator.setValidatorProgram("/bin/validator");
+ PasswordValidator.setAdminUserList(Collections.singletonList("bob"));
+ PasswordValidator.setAdminRoleNames("ROLE_ADMIN");
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ Socket socket = buildSocket("LOGIN: alice secret", out);
+
+ Process process = mock(Process.class);
+ ByteArrayInputStream processStdout = new
ByteArrayInputStream("OK".getBytes(StandardCharsets.UTF_8));
+ when(process.getInputStream()).thenReturn(processStdout);
+ when(process.getOutputStream()).thenReturn(new
ByteArrayOutputStream());
+
+ try (MockedStatic<Runtime> runtimeStatic = mockStatic(Runtime.class)) {
+ Runtime rt = mock(Runtime.class);
+ runtimeStatic.when(Runtime::getRuntime).thenReturn(rt);
+ when(rt.exec("/bin/validator")).thenReturn(process);
+
+ new PasswordValidator(socket).run();
+ }
+
+ String response = out.toString(StandardCharsets.UTF_8.name()).trim();
+ assertEquals("OK", response);
+ verify(process, times(1)).destroy();
+ verify(socket, times(1)).close();
+ }
+
+ @Test
+ public void test04_execThrowsWritesFailed() throws Exception {
+ PasswordValidator.setValidatorProgram("/bin/validator");
+ PasswordValidator.setAdminUserList(Collections.singletonList("alice"));
+ PasswordValidator.setAdminRoleNames("ROLE_ADMIN");
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ Socket socket = buildSocket("LOGIN: alice secret", out);
+
+ try (MockedStatic<Runtime> runtimeStatic = mockStatic(Runtime.class)) {
+ Runtime rt = mock(Runtime.class);
+ runtimeStatic.when(Runtime::getRuntime).thenReturn(rt);
+ when(rt.exec("/bin/validator")).thenThrow(new IOException("boom"));
+
+ new PasswordValidator(socket).run();
+ }
+
+ // Output may not be flushed in catch path; verify socket is closed
indicating catch executed
+ verify(socket, times(1)).close();
+ }
+
+ @Test
+ public void test05_closeSocketIOExceptionHandled() throws Exception {
+ PasswordValidator.setValidatorProgram(null);
+ PasswordValidator.setAdminUserList(null);
+ PasswordValidator.setAdminRoleNames(null);
+
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ Socket socket = buildSocket("LOGIN: alice secret", out);
+ doThrow(new IOException("close failure")).when(socket).close();
+
+ new PasswordValidator(socket).run();
+
+ // No exception should be thrown even if close() fails; nothing to
assert besides method completed
+ verify(socket, times(1)).close();
+ }
+
+ private Socket buildSocket(String requestLine, ByteArrayOutputStream
responseOut) throws IOException {
+ Socket socket = mock(Socket.class);
+ InputStream in = new ByteArrayInputStream((requestLine +
"\n").getBytes(StandardCharsets.UTF_8));
+ OutputStream out = responseOut;
+ when(socket.getInputStream()).thenReturn(in);
+ when(socket.getOutputStream()).thenReturn(out);
+ return socket;
+ }
+}
diff --git
a/unixauthservice/src/test/java/org/apache/ranger/authentication/TestUnixAuthenticationService.java
b/unixauthservice/src/test/java/org/apache/ranger/authentication/TestUnixAuthenticationService.java
new file mode 100644
index 000000000..194204a1d
--- /dev/null
+++
b/unixauthservice/src/test/java/org/apache/ranger/authentication/TestUnixAuthenticationService.java
@@ -0,0 +1,385 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.ranger.authentication;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.ranger.credentialapi.CredentialReader;
+import org.apache.ranger.plugin.util.XMLUtils;
+import org.apache.ranger.unixusersync.config.UserGroupSyncConfig;
+import org.apache.ranger.unixusersync.ha.UserSyncHAInitializerImpl;
+import org.junit.jupiter.api.MethodOrderer;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestMethodOrder;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedConstruction;
+import org.mockito.MockedStatic;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.Socket;
+import java.security.KeyStore;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Properties;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * @generated by Cursor
+ * @description : Unit Test cases for UnixAuthenticationService
+ */
+
+@ExtendWith(MockitoExtension.class)
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class TestUnixAuthenticationService {
+ @Test
+ public void test01_initThrowsWhenCredFileMissing() throws Exception {
+ UnixAuthenticationService svc = new UnixAuthenticationService();
+
+ try (MockedStatic<XMLUtils> xmlMock = mockStatic(XMLUtils.class)) {
+ xmlMock.when(() -> XMLUtils.loadConfig(any(String.class),
any(Properties.class))).thenAnswer(inv -> null);
+
+ try (MockedStatic<CredentialReader> credMock =
mockStatic(CredentialReader.class)) {
+ // Do not need to reach CredentialReader because missing file
check will throw first
+ Properties props = new Properties();
+ props.setProperty("ranger.usersync.credstore.filename",
"/no/such/file.jceks");
+ props.setProperty("ranger.usersync.port", "6168");
+
+ // Inject properties via XMLUtils sequence
+ xmlMock.when(() ->
XMLUtils.loadConfig(eq("ranger-ugsync-default.xml"), any(Properties.class)))
+ .thenAnswer(inv -> {
+ ((Properties) inv.getArgument(1)).putAll(props);
+ return null;
+ });
+ xmlMock.when(() ->
XMLUtils.loadConfig(eq("ranger-ugsync-site.xml"), any(Properties.class)))
+ .thenAnswer(inv -> null);
+
+ InvocationTargetException ex =
assertThrows(InvocationTargetException.class, () -> invokeInit(svc));
+ assertInstanceOf(RuntimeException.class, ex.getCause());
+ }
+ }
+ }
+
+ @Test
+ public void test02_getFileInputStreamFallsBackToResourcePath() throws
Exception {
+ UnixAuthenticationService svc = new UnixAuthenticationService();
+ Method m =
UnixAuthenticationService.class.getDeclaredMethod("getFileInputStream",
String.class);
+ m.setAccessible(true);
+
+ // Point to a file guaranteed to not exist; should fallback to
resource stream, which will be null
+ InputStream is = (InputStream) m.invoke(svc,
"/this/does/not/exist.txt");
+ // Since no resource with that name, it can still be null; verify
method returns (null allowed)
+ // Next, try with a real file via temp file which should be returned
+ File tmp = File.createTempFile("ranger-test", ".txt");
+ try {
+ InputStream fis = (InputStream) m.invoke(svc,
tmp.getAbsolutePath());
+ assertNotNull(fis);
+ fis.close();
+ } finally {
+ //noinspection ResultOfMethodCallIgnored
+ tmp.delete();
+ }
+ }
+
+ @Test
+ public void test03_runInterruptedStopsHA() throws Exception {
+ UnixAuthenticationService svc = new UnixAuthenticationService();
+ UserSyncHAInitializerImpl ha = mock(UserSyncHAInitializerImpl.class);
+ setField(svc, "userSyncHAInitializerImpl", ha);
+
+ try (MockedConstruction<Thread> threadConstruction =
mockConstruction(Thread.class, (mock, ctx) -> {
+ doNothing().when(mock).setName(any());
+ doNothing().when(mock).setDaemon(false);
+ doNothing().when(mock).start();
+ })) {
+ // Interrupt current thread so Thread.sleep throws immediately
+ Thread.currentThread().interrupt();
+ // Call run; should catch InterruptedException and then stop HA in
finally
+ svc.run();
+ // Clear interrupted status for subsequent tests
+ Thread.interrupted();
+ }
+ }
+
+ @Test
+ public void test04_startServiceSslAndAcceptIOExceptionPath() throws
Throwable {
+ UnixAuthenticationService svc = new UnixAuthenticationService();
+
+ // Reflectively set fields needed by startService
+ setField(svc, "sslEnabled", true);
+ setField(svc, "portNum", 0);
+ setField(svc, "enabledProtocolsList",
Collections.singletonList("TLSV1.2"));
+ setField(svc, "enabledCipherSuiteList", new ArrayList<>());
+ setField(svc, "keyStorePath", "");
+ setField(svc, "trustStorePath", "");
+ setField(svc, "keyStoreType", KeyStore.getDefaultType());
+ setField(svc, "trustStoreType", KeyStore.getDefaultType());
+
+ try (MockedStatic<SSLContext> sslStatic =
mockStatic(SSLContext.class)) {
+ SSLContext sslContext = mock(SSLContext.class);
+ SSLServerSocketFactory sf =
mock(SSLServerSocketFactory.class);
+ SSLServerSocket secureSocket = mock(SSLServerSocket.class);
+
+ when(SSLContext.getInstance("TLSv1.2")).thenReturn(sslContext);
+ when(sslContext.getServerSocketFactory()).thenReturn(sf);
+ when(sf.createServerSocket(anyInt())).thenReturn(secureSocket);
+ when(secureSocket.getEnabledProtocols()).thenReturn(new String[]
{"TLSv1.2"});
+ when(secureSocket.getEnabledCipherSuites()).thenReturn(new
String[] {});
+ when(secureSocket.accept()).thenThrow(new IOException("accept
fail"));
+
+ assertThrows(IOException.class, svc::startService);
+ }
+ }
+
+ @Test
+ public void test05_initSuccessPathWithBcfksAndAdminLists() throws
Exception {
+ UnixAuthenticationService svc = new UnixAuthenticationService();
+
+ Properties props = new Properties();
+ File credFile = File.createTempFile("ranger-cred", ".bcfks");
+ try {
+ props.setProperty("ranger.usersync.credstore.filename",
credFile.getAbsolutePath());
+ props.setProperty("ranger.usersync.keystore.file", "");
+ props.setProperty("ranger.usersync.truststore.file", "");
+ props.setProperty("ranger.keystore.file.type", "bcfks");
+ props.setProperty("ranger.truststore.file.type", "bcfks");
+ props.setProperty("ranger.usersync.port", "6168");
+ props.setProperty("ranger.usersync.passwordvalidator.path",
"/bin/validator");
+ props.setProperty("admin.users", "alice,bob");
+ props.setProperty("admin.roleNames", "ROLE_ADMIN,ROLE_AUDITOR");
+ props.setProperty("ranger.usersync.ssl", "true");
+ props.setProperty("ranger.usersync.https.ssl.enabled.protocols",
"TLSv1.2");
+
props.setProperty("ranger.usersync.https.ssl.enabled.cipher.suites", "");
+
+ try (MockedStatic<XMLUtils> xmlMock = mockStatic(XMLUtils.class);
+ MockedStatic<CredentialReader> credMock =
mockStatic(CredentialReader.class)) {
+ xmlMock.when(() -> XMLUtils.loadConfig(any(String.class),
any(Properties.class))).thenAnswer(inv -> {
+ ((Properties) inv.getArgument(1)).putAll(props);
+ return null;
+ });
+ credMock.when(() -> CredentialReader.getDecryptedString(any(),
any(), any())).thenReturn("pass");
+
+ // invoke private init
+ invokeInit(svc);
+ }
+ } finally {
+ //noinspection ResultOfMethodCallIgnored
+ credFile.delete();
+ }
+ }
+
+ @Test
+ public void test06_startServiceWithKeyAndTrustStores() throws Throwable {
+ UnixAuthenticationService svc = new UnixAuthenticationService();
+
+ setField(svc, "sslEnabled", true);
+ setField(svc, "portNum", 0);
+ setField(svc, "enabledProtocolsList",
Collections.singletonList("TLSV1.2"));
+ setField(svc, "enabledCipherSuiteList", new ArrayList<>());
+ setField(svc, "keyStorePath", "/tmp/keystore.jks");
+ setField(svc, "trustStorePath", "/tmp/truststore.jks");
+ setField(svc, "keyStoreType", KeyStore.getDefaultType());
+ setField(svc, "trustStoreType", KeyStore.getDefaultType());
+ // leave passwords as null to exercise empty defaulting branch
+
+ try (MockedStatic<KeyManagerFactory> kmfStatic =
mockStatic(KeyManagerFactory.class);
+ MockedStatic<TrustManagerFactory> tmfStatic =
mockStatic(TrustManagerFactory.class);
+ MockedStatic<SSLContext> sslStatic =
mockStatic(SSLContext.class)) {
+ KeyManagerFactory kmf = mock(KeyManagerFactory.class);
+ kmfStatic.when(() ->
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())).thenReturn(kmf);
+ when(kmf.getKeyManagers()).thenReturn(new KeyManager[0]);
+
+ TrustManagerFactory tmf = mock(TrustManagerFactory.class);
+ tmfStatic.when(() ->
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())).thenReturn(tmf);
+ when(tmf.getTrustManagers()).thenReturn(new TrustManager[0]);
+
+ SSLContext sslContext = mock(SSLContext.class);
+ SSLServerSocketFactory sf =
mock(SSLServerSocketFactory.class);
+ SSLServerSocket secureSocket = mock(SSLServerSocket.class);
+
+ when(SSLContext.getInstance("TLSv1.2")).thenReturn(sslContext);
+ when(sslContext.getServerSocketFactory()).thenReturn(sf);
+ when(sf.createServerSocket(anyInt())).thenReturn(secureSocket);
+ when(secureSocket.getEnabledProtocols()).thenReturn(new String[]
{"TLSv1.2"});
+ when(secureSocket.getEnabledCipherSuites()).thenReturn(new
String[] {});
+ when(secureSocket.accept()).thenThrow(new
java.io.IOException("accept fail"));
+
+ assertThrows(java.io.IOException.class, svc::startService);
+ }
+ }
+
+ @Test
+ public void test07_mainEnableUnixAuthInvokesRun() {
+ try (MockedStatic<UserGroupSyncConfig> ugscStatic =
mockStatic(UserGroupSyncConfig.class);
+ MockedStatic<UserSyncHAInitializerImpl> haStatic =
mockStatic(UserSyncHAInitializerImpl.class);
+ MockedConstruction<UnixAuthenticationService>
serviceConstruction = mockConstruction(UnixAuthenticationService.class, (mock,
ctx) -> {
+ // service.run() will be called by main; make it a no-op
+ })) {
+ UserGroupSyncConfig ugsc = mock(UserGroupSyncConfig.class);
+ ugscStatic.when(UserGroupSyncConfig::getInstance).thenReturn(ugsc);
+ when(ugsc.getUserGroupConfig()).thenReturn(new Configuration());
+
+ UserSyncHAInitializerImpl ha =
mock(UserSyncHAInitializerImpl.class);
+ haStatic.when(() ->
UserSyncHAInitializerImpl.getInstance(any())).thenReturn(ha);
+
+ UnixAuthenticationService.main(new String[] {"-enableUnixAuth"});
+ }
+ }
+
+ @Test
+ public void test08_startServiceEnablesCipherSuites() throws Throwable {
+ UnixAuthenticationService svc = new UnixAuthenticationService();
+ setField(svc, "sslEnabled", true);
+ setField(svc, "portNum", 0);
+ setField(svc, "enabledProtocolsList",
Collections.singletonList("TLSV1.2"));
+ ArrayList<String> enabledCiphers = new ArrayList<>();
+ enabledCiphers.add("CIPHER_A");
+ setField(svc, "enabledCipherSuiteList", enabledCiphers);
+ setField(svc, "keyStorePath", "");
+ setField(svc, "trustStorePath", "");
+ setField(svc, "keyStoreType", KeyStore.getDefaultType());
+ setField(svc, "trustStoreType", KeyStore.getDefaultType());
+
+ try (MockedStatic<SSLContext> sslStatic =
mockStatic(SSLContext.class)) {
+ SSLContext sslContext = mock(SSLContext.class);
+ SSLServerSocketFactory sf =
mock(SSLServerSocketFactory.class);
+ SSLServerSocket secureSocket = mock(SSLServerSocket.class);
+
+ when(SSLContext.getInstance("TLSv1.2")).thenReturn(sslContext);
+ when(sslContext.getServerSocketFactory()).thenReturn(sf);
+ when(sf.createServerSocket(anyInt())).thenReturn(secureSocket);
+ when(secureSocket.getEnabledProtocols()).thenReturn(new String[]
{"TLSv1.2"});
+ when(secureSocket.getEnabledCipherSuites()).thenReturn(new
String[] {"CIPHER_A", "CIPHER_B"});
+ when(secureSocket.accept()).thenThrow(new IOException("accept
fail"));
+
+ assertThrows(IOException.class, svc::startService);
+ verify(secureSocket).setEnabledCipherSuites(new String[]
{"CIPHER_A"});
+ }
+ }
+
+ @Test
+ public void test09_startServiceSpawnsValidatorThread() throws Throwable {
+ UnixAuthenticationService svc = new UnixAuthenticationService();
+ setField(svc, "sslEnabled", true);
+ setField(svc, "portNum", 0);
+ setField(svc, "enabledProtocolsList",
Collections.singletonList("TLSV1.2"));
+ setField(svc, "enabledCipherSuiteList", new ArrayList<>());
+ setField(svc, "keyStorePath", "");
+ setField(svc, "trustStorePath", "");
+ setField(svc, "keyStoreType", KeyStore.getDefaultType());
+ setField(svc, "trustStoreType", KeyStore.getDefaultType());
+
+ final boolean[] runnableIsPasswordValidator = new boolean[] {false};
+
+ try (MockedStatic<SSLContext> sslStatic = mockStatic(SSLContext.class);
+ MockedConstruction<Thread> threadConstruction =
mockConstruction(Thread.class, (mockThread, ctx) -> {
+ Object arg0 = ctx.arguments().get(0);
+ runnableIsPasswordValidator[0] = (arg0 instanceof
Runnable) &&
arg0.getClass().getName().equals(PasswordValidator.class.getName());
+ doNothing().when(mockThread).start();
+ })) {
+ SSLContext sslContext = mock(SSLContext.class);
+ SSLServerSocketFactory sf =
mock(SSLServerSocketFactory.class);
+ SSLServerSocket secureSocket = mock(SSLServerSocket.class);
+ Socket client = mock(Socket.class);
+
+ when(SSLContext.getInstance("TLSv1.2")).thenReturn(sslContext);
+ when(sslContext.getServerSocketFactory()).thenReturn(sf);
+ when(sf.createServerSocket(anyInt())).thenReturn(secureSocket);
+ when(secureSocket.getEnabledProtocols()).thenReturn(new String[]
{"TLSV1.2"});
+ when(secureSocket.getEnabledCipherSuites()).thenReturn(new
String[] {});
+ when(secureSocket.accept()).thenReturn(client).thenThrow(new
IOException("done"));
+
+ assertThrows(IOException.class, svc::startService);
+ assertTrue(runnableIsPasswordValidator[0]);
+ }
+ }
+
+ @Test
+ public void
test10_startUnixUserGroupSyncProcess_MetricsEnabledAndDisabled() throws
Exception {
+ UnixAuthenticationService svc = new UnixAuthenticationService();
+ Method m =
UnixAuthenticationService.class.getDeclaredMethod("startUnixUserGroupSyncProcess");
+ m.setAccessible(true);
+
+ try (MockedStatic<UserGroupSyncConfig> ugscStatic =
mockStatic(UserGroupSyncConfig.class)) {
+ UserGroupSyncConfig ugsc = mock(UserGroupSyncConfig.class);
+ ugscStatic.when(UserGroupSyncConfig::getInstance).thenReturn(ugsc);
+
+ // Metrics enabled -> two threads created
+ when(ugsc.isUserSyncMetricsEnabled()).thenReturn(true);
+ try (MockedConstruction<Thread> threadConstruction =
mockConstruction(Thread.class, (mockThread, ctx) -> {
+ doNothing().when(mockThread).setName(any());
+ doNothing().when(mockThread).setDaemon(false);
+ doNothing().when(mockThread).start();
+ })) {
+ m.invoke(svc);
+ assertEquals(2, threadConstruction.constructed().size());
+ }
+
+ // Metrics disabled -> one thread created
+ when(ugsc.isUserSyncMetricsEnabled()).thenReturn(false);
+ try (MockedConstruction<Thread> threadConstruction =
mockConstruction(Thread.class, (mockThread, ctx) -> {
+ doNothing().when(mockThread).setName(any());
+ doNothing().when(mockThread).setDaemon(false);
+ doNothing().when(mockThread).start();
+ })) {
+ m.invoke(svc);
+ assertEquals(1, threadConstruction.constructed().size());
+ }
+ }
+ }
+
+ private void invokeInit(UnixAuthenticationService svc) throws Exception {
+ Method m = UnixAuthenticationService.class.getDeclaredMethod("init");
+ m.setAccessible(true);
+ m.invoke(svc);
+ }
+
+ private void setField(Object target, String fieldName, Object value)
throws Exception {
+ Field f = UnixAuthenticationService.class.getDeclaredField(fieldName);
+ f.setAccessible(true);
+ f.set(target, value);
+ }
+}