diff --git a/agents-common/pom.xml b/agents-common/pom.xml
index 591deda888..335d0ec2fc 100644
--- a/agents-common/pom.xml
+++ b/agents-common/pom.xml
@@ -219,6 +219,18 @@
${junit.jupiter.version}
test
+
+ org.mockito
+ mockito-inline
+ ${mockito.version}
+ test
+
+
+ org.mockito
+ mockito-junit-jupiter
+ ${mockito.version}
+ test
+
org.slf4j
log4j-over-slf4j
diff --git a/agents-common/src/test/java/org/apache/hadoop/security/TestKrbPasswordSaverLoginModule.java b/agents-common/src/test/java/org/apache/hadoop/security/TestKrbPasswordSaverLoginModule.java
new file mode 100644
index 0000000000..6d9709dab1
--- /dev/null
+++ b/agents-common/src/test/java/org/apache/hadoop/security/TestKrbPasswordSaverLoginModule.java
@@ -0,0 +1,74 @@
+/*
+ * 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.hadoop.security;
+
+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.Subject;
+import javax.security.auth.callback.CallbackHandler;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+* @generated by Cursor
+* @description
+*/
+@ExtendWith(MockitoExtension.class)
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class TestKrbPasswordSaverLoginModule {
+ @Test
+ public void test01_initialize_storesCredentialsInSharedState() throws Exception {
+ KrbPasswordSaverLoginModule module = new KrbPasswordSaverLoginModule();
+ Map shared = new HashMap<>();
+ Map options = new HashMap<>();
+ options.put(KrbPasswordSaverLoginModule.USERNAME_PARAM, "user1");
+ options.put(KrbPasswordSaverLoginModule.PASSWORD_PARAM, "secret");
+
+ module.initialize(new Subject(), (CallbackHandler) null, shared, options);
+
+ assertEquals("user1", shared.get(KrbPasswordSaverLoginModule.USERNAME_PARAM));
+ assertArrayEquals("secret".toCharArray(), (char[]) shared.get(KrbPasswordSaverLoginModule.PASSWORD_PARAM));
+ }
+
+ @Test
+ public void test02_login_commit_abort_logout_returnTrue() throws Exception {
+ KrbPasswordSaverLoginModule module = new KrbPasswordSaverLoginModule();
+ assertTrue(module.login());
+ assertTrue(module.commit());
+ assertTrue(module.abort());
+ assertTrue(module.logout());
+ }
+
+ @Test
+ public void test03_initialize_withNullOptions_doesNotPopulateShared() {
+ KrbPasswordSaverLoginModule module = new KrbPasswordSaverLoginModule();
+ Map shared = new HashMap<>();
+ module.initialize(new Subject(), (CallbackHandler) null, shared, null);
+ assertTrue(shared.isEmpty());
+ }
+}
diff --git a/agents-common/src/test/java/org/apache/hadoop/security/TestSecureClientLogin.java b/agents-common/src/test/java/org/apache/hadoop/security/TestSecureClientLogin.java
new file mode 100644
index 0000000000..22ba288439
--- /dev/null
+++ b/agents-common/src/test/java/org/apache/hadoop/security/TestSecureClientLogin.java
@@ -0,0 +1,222 @@
+/*
+ * 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.hadoop.security;
+
+import org.apache.hadoop.security.authentication.util.KerberosName;
+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.Subject;
+import javax.security.auth.login.AppConfigurationEntry;
+import javax.security.auth.login.AppConfigurationEntry.LoginModuleControlFlag;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * @generated by Cursor
+ * @description
+ */
+@ExtendWith(MockitoExtension.class)
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class TestSecureClientLogin {
+ @Test
+ public void test01_isKerberosCredentialExists_nullKeytab_returnsFalse() {
+ boolean valid = SecureClientLogin.isKerberosCredentialExists("user@EXAMPLE.COM", null);
+ assertEquals(false, valid);
+ }
+
+ @Test
+ public void test02_isKerberosCredentialExists_missingFile_returnsFalse() {
+ File f = new File("/tmp/does-not-exist-" + System.nanoTime());
+ boolean valid = SecureClientLogin.isKerberosCredentialExists("user@EXAMPLE.COM", f.getAbsolutePath());
+ assertEquals(false, valid);
+ }
+
+ @Test
+ public void test03_isKerberosCredentialExists_existingFileAndPrincipal_returnsTrue() throws Exception {
+ File f = File.createTempFile("keytab", ".kt");
+ f.deleteOnExit();
+ try (FileWriter w = new FileWriter(f)) {
+ w.write("dummy");
+ }
+ boolean valid = SecureClientLogin.isKerberosCredentialExists("user@EXAMPLE.COM", f.getAbsolutePath());
+ assertEquals(true, valid);
+ }
+
+ @Test
+ public void test04_getPrincipal_noPattern_returnsSame() throws Exception {
+ String in = "user/host@EXAMPLE.COM";
+ String out = SecureClientLogin.getPrincipal(in, "host");
+ assertEquals(in, out);
+ }
+
+ @Test
+ public void test05_getPrincipal_withPatternAndHost_replacesWithLowercase() throws Exception {
+ String out = SecureClientLogin.getPrincipal("user/_HOST@EXAMPLE.COM", "FooBar.Example.COM");
+ assertEquals("user/foobar.example.com@EXAMPLE.COM", out);
+ }
+
+ @Test
+ public void test05b_getPrincipal_withPatternAndWildcardHost_usesLocalHost() throws Exception {
+ String out = SecureClientLogin.getPrincipal("user/_HOST@EXAMPLE.COM", "0.0.0.0");
+ assertTrue(out.startsWith("user/"));
+ assertTrue(out.endsWith("@EXAMPLE.COM"));
+ }
+
+ @Test
+ public void test06_getPrincipal_withNullHost_throwsIOException() {
+ assertThrows(IOException.class, () -> SecureClientLogin.getPrincipal("user/_HOST@EXAMPLE.COM", null));
+ }
+
+ @Test
+ public void test07_login_returnsSubjectWithPrincipal() throws Exception {
+ Subject s = SecureClientLogin.login("alice");
+ assertNotNull(s);
+ Set principals = SecureClientLogin.getUserPrincipals(s);
+ assertNotNull(principals);
+ assertTrue(principals.stream().anyMatch(p -> "alice".equals(p.getName())));
+ }
+
+ @Test
+ public void test08_getUserPrincipals_nullSubject_returnsNull() {
+ assertNull(SecureClientLogin.getUserPrincipals(null));
+ }
+
+ @Test
+ public void test09_loginUserFromKeytab_invalid_throwsException() {
+ assertThrows(Exception.class,
+ () -> SecureClientLogin.loginUserFromKeytab("user@EXAMPLE.COM", "/path/not/found"));
+ }
+
+ @Test
+ public void test10_loginUserWithPassword_invalid_throwsException() {
+ assertThrows(Exception.class, () -> SecureClientLogin.loginUserWithPassword("user@EXAMPLE.COM", "badpass"));
+ }
+
+ @Test
+ public void test11_loginUserFromKeytab_withNameRules_invalid_throwsException() {
+ assertThrows(Exception.class,
+ () -> SecureClientLogin.loginUserFromKeytab("user@EXAMPLE.COM", "/path/not/found", "DEFAULT"));
+ }
+
+ @Test
+ public void test12_createUserPrincipal_returnsPrincipalWithName() {
+ Principal p = SecureClientLogin.createUserPrincipal("bob");
+ assertNotNull(p);
+ assertEquals("bob", p.getName());
+ }
+
+ @Test
+ public void test13_secureClientLoginConfiguration_withKeytab_returnsSingleKerberosEntry() {
+ SecureClientLogin.SecureClientLoginConfiguration conf = new SecureClientLogin.SecureClientLoginConfiguration(
+ true, "user@EXAMPLE.COM", "/tmp/fake.keytab");
+ AppConfigurationEntry[] entries = conf.getAppConfigurationEntry("hadoop-keytab-kerberos");
+ assertNotNull(entries);
+ assertEquals(1, entries.length);
+ assertEquals(LoginModuleControlFlag.REQUIRED, entries[0].getControlFlag());
+ assertEquals("user@EXAMPLE.COM", entries[0].getOptions().get("principal"));
+ assertEquals("true", entries[0].getOptions().get("useKeyTab"));
+ assertEquals("/tmp/fake.keytab", entries[0].getOptions().get("keyTab"));
+ }
+
+ @Test
+ public void test14_secureClientLoginConfiguration_withPassword_returnsPwdSaverThenKerberos() {
+ SecureClientLogin.SecureClientLoginConfiguration conf = new SecureClientLogin.SecureClientLoginConfiguration(
+ false, "user@EXAMPLE.COM", "pwd");
+ AppConfigurationEntry[] entries = conf.getAppConfigurationEntry("hadoop-keytab-kerberos");
+ assertNotNull(entries);
+ assertEquals(2, entries.length);
+ assertEquals(KrbPasswordSaverLoginModule.class.getName(), entries[0].getLoginModuleName());
+ assertEquals("user@EXAMPLE.COM", entries[0].getOptions().get(KrbPasswordSaverLoginModule.USERNAME_PARAM));
+ assertEquals("pwd", entries[0].getOptions().get(KrbPasswordSaverLoginModule.PASSWORD_PARAM));
+ assertTrue(entries[1].getLoginModuleName().contains("Krb5LoginModule"));
+ }
+
+ @Test
+ public void test15_isKerberosCredentialExists_unreadableFile_returnsFalse() throws Exception {
+ File f = File.createTempFile("keytab", ".kt");
+ f.deleteOnExit();
+ try (FileWriter w = new FileWriter(f)) {
+ w.write("dummy");
+ }
+ // make unreadable if possible
+ boolean permChanged = f.setReadable(false, false);
+ // If permission cannot be changed in this environment, skip assert to avoid
+ // flakiness
+ if (permChanged) {
+ boolean valid = SecureClientLogin.isKerberosCredentialExists("user@EXAMPLE.COM", f.getAbsolutePath());
+ assertEquals(false, valid);
+ }
+ }
+
+ @Test
+ public void test16_isKerberosCredentialExists_emptyPrincipal_returnsFalse() throws Exception {
+ File f = File.createTempFile("keytab", ".kt");
+ f.deleteOnExit();
+ try (FileWriter w = new FileWriter(f)) {
+ w.write("dummy");
+ }
+ boolean valid = SecureClientLogin.isKerberosCredentialExists("", f.getAbsolutePath());
+ assertEquals(false, valid);
+ }
+
+ @Test
+ public void test17_loginUserFromKeytab_withInvalidNameRules_throwsIllegalArgumentException() {
+ assertThrows(IllegalArgumentException.class,
+ () -> SecureClientLogin.loginUserFromKeytab("user@EXAMPLE.COM", "/path/not/found", "INVALID_RULES_FORMAT"));
+ }
+
+ @Test
+ public void test18_loginUserFromKeytab_twoArg_catchesLoginException() {
+ String originalRules = "DEFAULT";
+ String allowExampleRule = "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\nDEFAULT";
+ try {
+ KerberosName.setRules(allowExampleRule);
+ assertThrows(IOException.class,
+ () -> SecureClientLogin.loginUserFromKeytab("user@EXAMPLE.COM", "/path/not/found"));
+ } finally {
+ KerberosName.setRules(originalRules);
+ }
+ }
+
+ @Test
+ public void test19_loginUserFromKeytab_threeArg_catchesLoginException() {
+ String originalRules = "DEFAULT";
+ String allowExampleRule = "RULE:[1:$1@$0](.*@EXAMPLE.COM)s/@.*//\nDEFAULT";
+ try {
+ assertThrows(IOException.class, () -> SecureClientLogin.loginUserFromKeytab("user@EXAMPLE.COM",
+ "/path/not/found", allowExampleRule));
+ } finally {
+ KerberosName.setRules(originalRules);
+ }
+ }
+}
diff --git a/agents-common/src/test/java/org/apache/ranger/admin/client/TestAbstractRangerAdminClient.java b/agents-common/src/test/java/org/apache/ranger/admin/client/TestAbstractRangerAdminClient.java
new file mode 100644
index 0000000000..550983ee34
--- /dev/null
+++ b/agents-common/src/test/java/org/apache/ranger/admin/client/TestAbstractRangerAdminClient.java
@@ -0,0 +1,98 @@
+/*
+ * 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.admin.client;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.junit.jupiter.api.Assertions;
+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.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+/**
+* @generated by Cursor
+* @description
+*/
+@ExtendWith(MockitoExtension.class)
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class TestAbstractRangerAdminClient {
+ public static class DummyClient extends AbstractRangerAdminClient {
+ @Override
+ public void init(String serviceName, String appId, String configPropertyPrefix, Configuration config) {
+ super.init(serviceName, appId, configPropertyPrefix, config);
+ }
+ }
+
+ @Test
+ public void test01_initSetsGsonAndForceNonKerberos() {
+ DummyClient c = new DummyClient();
+ Configuration cfg = new Configuration(false);
+ cfg.setBoolean("test.forceNonKerberos", true);
+ c.init("svc", "app", "test", cfg);
+ // isKerberosEnabled should return false when forceNonKerberos is true
+ try (MockedStatic ignored = Mockito.mockStatic(UserGroupInformation.class)) {
+ Assertions.assertFalse(c.isKerberosEnabled(null));
+ }
+ }
+
+ @Test
+ public void test02_isKerberosEnabled_respectsUGI() {
+ DummyClient c = new DummyClient();
+ Configuration cfg = new Configuration(false);
+ cfg.setBoolean("p.forceNonKerberos", false);
+ c.init("svc", "app", "p", cfg);
+
+ UserGroupInformation ugi = Mockito.mock(UserGroupInformation.class);
+ try (MockedStatic mocked = Mockito.mockStatic(UserGroupInformation.class)) {
+ mocked.when(UserGroupInformation::isSecurityEnabled).thenReturn(true);
+ Mockito.when(ugi.hasKerberosCredentials()).thenReturn(true);
+ Assertions.assertTrue(c.isKerberosEnabled(ugi));
+
+ Mockito.when(ugi.hasKerberosCredentials()).thenReturn(false);
+ Assertions.assertFalse(c.isKerberosEnabled(ugi));
+ }
+ }
+
+ @Test
+ public void test03_defaultNoOpMethodsReturnNullOrNoThrow() throws Exception {
+ DummyClient c = new DummyClient();
+ Configuration cfg = new Configuration(false);
+ c.init("svc", "app", "p", cfg);
+ Assertions.assertNull(c.getServicePoliciesIfUpdated(1L, 2L));
+ Assertions.assertNull(c.getRolesIfUpdated(1L, 2L));
+ Assertions.assertNull(c.createRole(null));
+ c.dropRole("u", "r");
+ Assertions.assertNull(c.getAllRoles("u"));
+ Assertions.assertNull(c.getUserRoles("u"));
+ Assertions.assertNull(c.getRole("u", "r"));
+ c.grantRole(null);
+ c.revokeRole(null);
+ c.grantAccess(null);
+ c.revokeAccess(null);
+ Assertions.assertNull(c.getServiceTagsIfUpdated(1L, 2L));
+ Assertions.assertNull(c.getTagTypes("x"));
+ Assertions.assertNull(c.getUserStoreIfUpdated(1L, 2L));
+ Assertions.assertNull(c.getGdsInfoIfUpdated(1L, 2L));
+ }
+}
diff --git a/agents-common/src/test/java/org/apache/ranger/admin/client/TestRangerAdminRESTClient.java b/agents-common/src/test/java/org/apache/ranger/admin/client/TestRangerAdminRESTClient.java
new file mode 100644
index 0000000000..ee4d4aff24
--- /dev/null
+++ b/agents-common/src/test/java/org/apache/ranger/admin/client/TestRangerAdminRESTClient.java
@@ -0,0 +1,1108 @@
+/*
+ * 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.admin.client;
+
+import com.sun.jersey.api.client.ClientResponse;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.security.AccessControlException;
+import org.apache.hadoop.security.UserGroupInformation;
+import org.apache.ranger.admin.client.datatype.RESTResponse;
+import org.apache.ranger.audit.provider.MiscUtil;
+import org.apache.ranger.plugin.model.RangerRole;
+import org.apache.ranger.plugin.util.GrantRevokeRequest;
+import org.apache.ranger.plugin.util.GrantRevokeRoleRequest;
+import org.apache.ranger.plugin.util.RangerRESTClient;
+import org.apache.ranger.plugin.util.RangerRoles;
+import org.apache.ranger.plugin.util.RangerServiceNotFoundException;
+import org.apache.ranger.plugin.util.ServiceGdsInfo;
+import org.apache.ranger.plugin.util.ServicePolicies;
+import org.apache.ranger.plugin.util.ServiceTags;
+import org.junit.jupiter.api.Assertions;
+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.ArgumentCaptor;
+import org.mockito.MockedStatic;
+import org.mockito.Mockito;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import javax.ws.rs.core.Cookie;
+import javax.ws.rs.core.NewCookie;
+
+import java.lang.reflect.Field;
+import java.security.PrivilegedExceptionAction;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @generated by Cursor
+ * @description
+ */
+@ExtendWith(MockitoExtension.class)
+@TestMethodOrder(MethodOrderer.MethodName.class)
+public class TestRangerAdminRESTClient {
+ private static void setPrivateField(Object target, String name, Object value) throws Exception {
+ Field f = target.getClass().getDeclaredField(name);
+ f.setAccessible(true);
+ f.set(target, value);
+ }
+
+ private static Object getPrivateField(Object target, String name) throws Exception {
+ Field f = target.getClass().getDeclaredField(name);
+ f.setAccessible(true);
+ return f.get(target);
+ }
+
+ @Test
+ public void test01_getTagTypes_okAndCookieSet() throws Exception {
+ RangerAdminRESTClient client = new RangerAdminRESTClient();
+ Configuration cfg = new Configuration(false);
+ cfg.set("p.policy.rest.url", "http://localhost:6080");
+ cfg.setBoolean("p.policy.rest.client.cookie.enabled", true);
+ cfg.set("p.policy.rest.client.session.cookie.name", "RANGERSESSION");
+ client.init("svc", "app", "p", cfg);
+
+ RangerRESTClient rest = Mockito.mock(RangerRESTClient.class);
+ setPrivateField(client, "restClient", rest);
+
+ ClientResponse resp = Mockito.mock(ClientResponse.class);
+ Mockito.when(resp.getStatus()).thenReturn(200);
+ Mockito.when(resp.getEntity(String.class)).thenReturn("[\"t1\",\"t2\"]");
+ Mockito.when(resp.getCookies()).thenReturn(Arrays.asList(new NewCookie("RANGERSESSION", "abc")));
+
+ ArgumentCaptor url = ArgumentCaptor.forClass(String.class);
+ ArgumentCaptor