diff --git a/knox-agent/pom.xml b/knox-agent/pom.xml index b55acb9ba1..396f5e4751 100644 --- a/knox-agent/pom.xml +++ b/knox-agent/pom.xml @@ -317,6 +317,18 @@ ${junit.jupiter.version} test + + org.mockito + mockito-inline + ${mockito.version} + test + + + org.mockito + mockito-junit-jupiter + ${mockito.version} + test + ${basedir}/src/main/java diff --git a/knox-agent/src/test/java/org/apache/ranger/admin/client/TestRangerAdminJersey2RESTClient.java b/knox-agent/src/test/java/org/apache/ranger/admin/client/TestRangerAdminJersey2RESTClient.java new file mode 100644 index 0000000000..d36e5d943a --- /dev/null +++ b/knox-agent/src/test/java/org/apache/ranger/admin/client/TestRangerAdminJersey2RESTClient.java @@ -0,0 +1,780 @@ +/* + * 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.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.security.AccessControlException; +import org.apache.ranger.admin.client.RangerAdminJersey2RESTClient.GsonUnixDateDeserializer; +import org.apache.ranger.plugin.util.RangerRESTUtils; +import org.apache.ranger.plugin.util.RangerRoles; +import org.apache.ranger.plugin.util.RangerServiceNotFoundException; +import org.apache.ranger.plugin.util.RangerUserStore; +import org.apache.ranger.plugin.util.ServicePolicies; +import org.apache.ranger.plugin.util.ServiceTags; +import org.glassfish.jersey.client.ClientProperties; +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.ArgumentMatchers; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.ws.rs.ProcessingException; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.Invocation; +import javax.ws.rs.client.WebTarget; +import javax.ws.rs.core.Cookie; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.NewCookie; +import javax.ws.rs.core.Response; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +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.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** +* @generated by Cursor +* @description +*/ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +public class TestRangerAdminJersey2RESTClient { + private static final String PREFIX = "ranger.plugin.test"; + private static final String SERVICE = "svc"; + private static final String APPID = "app"; + private static final String BASE_URL = "http://localhost:6080"; + private static final String HTTPS_URL = "https://localhost:6182"; + private static final String COOKIE_NAME = "RANGERADMINSESSION"; + + private Configuration buildBaseConfig(String baseUrl) { + Configuration conf = new Configuration(); + conf.set(PREFIX + ".policy.rest.url", baseUrl); + conf.setInt(PREFIX + ".policy.rest.client.connection.timeoutMs", 111); + conf.setInt(PREFIX + ".policy.rest.client.read.timeoutMs", 222); + conf.setInt(PREFIX + ".policy.rest.client.max.retry.attempts", 2); + conf.setInt(PREFIX + ".policy.rest.client.retry.interval.ms", 0); + conf.set(PREFIX + ".access.cluster.name", ""); + conf.setBoolean(PREFIX + ".policy.rest.client.cookie.enabled", true); + conf.set(PREFIX + ".policy.rest.client.session.cookie.name", COOKIE_NAME); + conf.setBoolean(PREFIX + ".forceNonKerberos", true); + return conf; + } + + private void initWithMockClient(RangerAdminJersey2RESTClient clientUnderTest, Client clientMock, + Configuration conf) { + clientUnderTest.client = clientMock; + clientUnderTest.init(SERVICE, APPID, PREFIX, conf); + } + + @Test + public void test01_init_sets_fields_and_timeouts() { + Client clientMock = mock(Client.class); + when(clientMock.property(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenReturn(clientMock); + + Configuration conf = buildBaseConfig(HTTPS_URL); + RangerAdminJersey2RESTClient underTest = new RangerAdminJersey2RESTClient(); + + initWithMockClient(underTest, clientMock, conf); + + assertEquals(true, underTest.isSSL); + assertEquals(111, underTest.restClientConnTimeOutMs); + assertEquals(222, underTest.restClientReadTimeOutMs); + assertEquals(2, underTest.restClientMaxRetryAttempts); + assertEquals(0, underTest.restClientRetryIntervalMs); + assertEquals(SERVICE, underTest.serviceName); + + verify(clientMock, times(1)).property(ClientProperties.CONNECT_TIMEOUT, 111); + verify(clientMock, times(1)).property(ClientProperties.READ_TIMEOUT, 222); + } + + @Test + public void test02_getServicePolicies_credential200_then_cookie304() throws Exception { + Client clientMock = mock(Client.class); + WebTarget targetMock = mock(WebTarget.class); + Invocation.Builder builderMock = mock(Invocation.Builder.class); + Response response200 = mock(Response.class); + Response response304 = mock(Response.class); + + Configuration conf = buildBaseConfig(BASE_URL); + RangerAdminJersey2RESTClient underTest = new RangerAdminJersey2RESTClient(); + initWithMockClient(underTest, clientMock, conf); + + Map cookies = new HashMap<>(); + cookies.put(COOKIE_NAME, new NewCookie(COOKIE_NAME, "v")); + + when(response200.getStatus()).thenReturn(200); + when(response200.readEntity(String.class)).thenReturn("{}"); + when(response200.getCookies()).thenReturn(cookies); + when(response304.getStatus()).thenReturn(304); + when(response304.getCookies()).thenReturn(cookies); + + when(clientMock.target(ArgumentMatchers.anyString())).thenReturn(targetMock); + when(targetMock.queryParam(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenReturn(targetMock); + when(targetMock.request(MediaType.APPLICATION_JSON_TYPE)).thenReturn(builderMock); + when(builderMock.cookie(ArgumentMatchers.nullable(Cookie.class))).thenReturn(builderMock); + when(builderMock.get()).thenReturn(response200, response304); + + ServicePolicies first = underTest.getServicePoliciesIfUpdated(1L, 2L); + assertNotNull(first); + ServicePolicies second = underTest.getServicePoliciesIfUpdated(1L, 2L); + assertNull(second); + + ArgumentCaptor captor = ArgumentCaptor.forClass(Cookie.class); + verify(builderMock, times(2)).cookie(captor.capture()); + Cookie firstCookie = captor.getAllValues().get(0); + Cookie secondCookie = captor.getAllValues().get(1); + assertNull(firstCookie); + assertNotNull(secondCookie); + } + + @Test + public void test03_getServicePolicies_404_and_default_and_null() throws Exception { + Client clientMock = mock(Client.class); + WebTarget targetMock = mock(WebTarget.class); + Invocation.Builder builderMock = mock(Invocation.Builder.class); + Response response404 = mock(Response.class); + Response response500 = mock(Response.class); + + Configuration conf = buildBaseConfig(BASE_URL); + RangerAdminJersey2RESTClient underTest = new RangerAdminJersey2RESTClient(); + initWithMockClient(underTest, clientMock, conf); + + Map cookies = new HashMap<>(); + cookies.put(COOKIE_NAME, new NewCookie(COOKIE_NAME, "v")); + + when(response404.getStatus()).thenReturn(404); + when(response404.hasEntity()).thenReturn(true); + when(response404.readEntity(String.class)).thenReturn(""); + when(response500.getStatus()).thenReturn(500); + when(response500.readEntity(String.class)).thenReturn("ERR"); + + when(clientMock.target(ArgumentMatchers.anyString())).thenReturn(targetMock); + when(targetMock.queryParam(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenReturn(targetMock); + when(targetMock.request(MediaType.APPLICATION_JSON_TYPE)).thenReturn(builderMock); + when(builderMock.cookie(ArgumentMatchers.nullable(Cookie.class))).thenReturn(builderMock); + when(builderMock.get()).thenReturn(response404, response500, null); + + ServicePolicies on404 = underTest.getServicePoliciesIfUpdated(1L, 2L); + assertNull(on404); + ServicePolicies on500 = underTest.getServicePoliciesIfUpdated(1L, 2L); + assertNull(on500); + ServicePolicies onNull = underTest.getServicePoliciesIfUpdated(1L, 2L); + assertNull(onNull); + } + + @Test + public void test04_getServiceTags_flows() throws Exception { + Client clientMock = mock(Client.class); + WebTarget targetMock = mock(WebTarget.class); + Invocation.Builder builderMock = mock(Invocation.Builder.class); + Response response200 = mock(Response.class); + Response response304 = mock(Response.class); + Response response404 = mock(Response.class); + Response response500 = mock(Response.class); + + Configuration conf = buildBaseConfig(BASE_URL); + RangerAdminJersey2RESTClient underTest = new RangerAdminJersey2RESTClient(); + initWithMockClient(underTest, clientMock, conf); + + Map cookies = new HashMap<>(); + cookies.put(COOKIE_NAME, new NewCookie(COOKIE_NAME, "v")); + + when(response200.getStatus()).thenReturn(200); + when(response200.readEntity(String.class)).thenReturn("{}"); + when(response200.getCookies()).thenReturn(cookies); + when(response304.getStatus()).thenReturn(304); + when(response304.getCookies()).thenReturn(cookies); + when(response404.getStatus()).thenReturn(404); + when(response404.hasEntity()).thenReturn(true); + when(response404.readEntity(String.class)).thenReturn(""); + when(response500.getStatus()).thenReturn(500); + when(response500.readEntity(String.class)).thenReturn("ERR"); + + when(clientMock.target(ArgumentMatchers.anyString())).thenReturn(targetMock); + when(targetMock.queryParam(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenReturn(targetMock); + when(targetMock.request(MediaType.APPLICATION_JSON_TYPE)).thenReturn(builderMock); + when(builderMock.cookie(ArgumentMatchers.nullable(Cookie.class))).thenReturn(builderMock); + when(builderMock.get()).thenReturn(response200, response304, response404, response500, null); + + ServiceTags t200 = underTest.getServiceTagsIfUpdated(1L, 2L); + assertNotNull(t200); + ServiceTags t304 = underTest.getServiceTagsIfUpdated(1L, 2L); + assertNull(t304); + ServiceTags t404 = underTest.getServiceTagsIfUpdated(1L, 2L); + assertNull(t404); + ServiceTags t500 = underTest.getServiceTagsIfUpdated(1L, 2L); + assertNull(t500); + ServiceTags tNull = underTest.getServiceTagsIfUpdated(1L, 2L); + assertNull(tNull); + } + + @Test + public void test05_getRoles_flows() throws Exception { + Client clientMock = mock(Client.class); + WebTarget targetMock = mock(WebTarget.class); + Invocation.Builder builderMock = mock(Invocation.Builder.class); + Response response200 = mock(Response.class); + Response response304 = mock(Response.class); + Response response404 = mock(Response.class); + Response response500 = mock(Response.class); + + Configuration conf = buildBaseConfig(BASE_URL); + RangerAdminJersey2RESTClient underTest = new RangerAdminJersey2RESTClient(); + initWithMockClient(underTest, clientMock, conf); + + Map cookies = new HashMap<>(); + cookies.put(COOKIE_NAME, new NewCookie(COOKIE_NAME, "v")); + + when(response200.getStatus()).thenReturn(200); + when(response200.readEntity(String.class)).thenReturn("{}"); + when(response200.getCookies()).thenReturn(cookies); + when(response304.getStatus()).thenReturn(304); + when(response304.getCookies()).thenReturn(cookies); + when(response404.getStatus()).thenReturn(404); + when(response404.hasEntity()).thenReturn(true); + when(response404.readEntity(String.class)).thenReturn(""); + when(response500.getStatus()).thenReturn(500); + when(response500.readEntity(String.class)).thenReturn("ERR"); + + when(clientMock.target(ArgumentMatchers.anyString())).thenReturn(targetMock); + when(targetMock.queryParam(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenReturn(targetMock); + when(targetMock.request(MediaType.APPLICATION_JSON_TYPE)).thenReturn(builderMock); + when(builderMock.cookie(ArgumentMatchers.nullable(Cookie.class))).thenReturn(builderMock); + when(builderMock.get()).thenReturn(response200, response304, response404, response500, null); + + RangerRoles r200 = underTest.getRolesIfUpdated(1L, 2L); + assertNotNull(r200); + RangerRoles r304 = underTest.getRolesIfUpdated(1L, 2L); + assertNull(r304); + RangerRoles r404 = underTest.getRolesIfUpdated(1L, 2L); + assertNull(r404); + RangerRoles r500 = underTest.getRolesIfUpdated(1L, 2L); + assertNull(r500); + RangerRoles rNull = underTest.getRolesIfUpdated(1L, 2L); + assertNull(rNull); + } + + @Test + public void test06_grant_and_revoke_access_paths() throws Exception { + Client clientMock = mock(Client.class); + WebTarget targetMock = mock(WebTarget.class); + Invocation.Builder builderMock = mock(Invocation.Builder.class); + Response response200 = mock(Response.class); + Response response401 = mock(Response.class); + Response response500 = mock(Response.class); + + Configuration conf = buildBaseConfig(BASE_URL); + RangerAdminJersey2RESTClient underTest = new RangerAdminJersey2RESTClient(); + initWithMockClient(underTest, clientMock, conf); + + when(response200.getStatus()).thenReturn(200); + when(response401.getStatus()).thenReturn(401); + when(response500.getStatus()).thenReturn(500); + when(response500.readEntity(String.class)).thenReturn("ERR"); + + when(clientMock.target(ArgumentMatchers.anyString())).thenReturn(targetMock); + when(targetMock.queryParam(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenReturn(targetMock); + when(targetMock.request(MediaType.APPLICATION_JSON_TYPE)).thenReturn(builderMock); + when(builderMock.get()).thenReturn(response200, response401, response500, null); + + underTest.grantAccess(null); + assertThrows(AccessControlException.class, () -> underTest.grantAccess(null)); + assertThrows(Exception.class, () -> underTest.grantAccess(null)); + assertThrows(Exception.class, () -> underTest.grantAccess(null)); + + when(builderMock.get()).thenReturn(response200, response401, response500, null); + underTest.revokeAccess(null); + assertThrows(AccessControlException.class, () -> underTest.revokeAccess(null)); + assertThrows(Exception.class, () -> underTest.revokeAccess(null)); + assertThrows(Exception.class, () -> underTest.revokeAccess(null)); + } + + @Test + public void test07_getUserStore_paths_non_secure() throws Exception { + Client clientMock = mock(Client.class); + WebTarget targetMock = mock(WebTarget.class); + Invocation.Builder builderMock = mock(Invocation.Builder.class); + Response response200 = mock(Response.class); + Response response304 = mock(Response.class); + Response response404 = mock(Response.class); + Response response500 = mock(Response.class); + + Configuration conf = buildBaseConfig(BASE_URL); + conf.setBoolean(PREFIX + ".forceNonKerberos", true); + RangerAdminJersey2RESTClient underTest = new RangerAdminJersey2RESTClient(); + initWithMockClient(underTest, clientMock, conf); + + when(response200.getStatus()).thenReturn(200); + when(response200.readEntity(String.class)).thenReturn("{}"); + when(response304.getStatus()).thenReturn(304); + when(response404.getStatus()).thenReturn(404); + when(response404.hasEntity()).thenReturn(false); + when(response500.getStatus()).thenReturn(500); + when(response500.hasEntity()).thenReturn(true); + when(response500.readEntity(String.class)).thenReturn("ERR"); + + when(clientMock.target(ArgumentMatchers.anyString())).thenReturn(targetMock); + when(targetMock.queryParam(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenReturn(targetMock); + when(targetMock.request(MediaType.APPLICATION_JSON_TYPE)).thenReturn(builderMock); + when(builderMock.get()).thenReturn(response200, response304, response404, response500); + + RangerUserStore u200 = underTest.getUserStoreIfUpdated(1L, 2L); + assertNotNull(u200); + RangerUserStore u304 = underTest.getUserStoreIfUpdated(1L, 2L); + assertNull(u304); + RangerUserStore u404 = underTest.getUserStoreIfUpdated(1L, 2L); + assertNull(u404); + RangerUserStore u500 = underTest.getUserStoreIfUpdated(1L, 2L); + assertNull(u500); + } + + @Test + public void test08_shouldRetry_and_exception_path() { + Client clientMock = mock(Client.class); + Configuration conf = buildBaseConfig(BASE_URL); + RangerAdminJersey2RESTClient underTest = new RangerAdminJersey2RESTClient(); + initWithMockClient(underTest, clientMock, conf); + + boolean first = underTest.shouldRetry(BASE_URL, 0, 0, new ProcessingException("e")); + assertEquals(true, first); + assertThrows(ProcessingException.class, + () -> underTest.shouldRetry(BASE_URL, 0, 2, new ProcessingException("e"))); + } + + @Test + public void test09_getClient_lazy_build_and_singleton() { + RangerAdminJersey2RESTClient underTest = new RangerAdminJersey2RESTClient(); + underTest.isSSL = false; + underTest.client = null; + Client c1 = underTest.getClient(); + Client c2 = underTest.getClient(); + assertNotNull(c1); + assertEquals(c1, c2); + } + + @Test + public void test10_gson_unix_date_deserializer() { + GsonUnixDateDeserializer deser = new GsonUnixDateDeserializer(); + long ts = 123456789L; + JsonElement el = new JsonPrimitive(ts); + Date d = deser.deserialize(el, Date.class, null); + assertEquals(ts, d.getTime()); + } + + @Test + public void test11_getTagTypes_not_implemented() { + Configuration conf = buildBaseConfig(BASE_URL); + RangerAdminJersey2RESTClient underTest = new RangerAdminJersey2RESTClient(); + // use a real client here only to init; we won't make network calls + underTest.client = mock(Client.class); + underTest.init(SERVICE, APPID, PREFIX, conf); + + assertThrows(Exception.class, () -> underTest.getTagTypes("pat")); + } + + @Test + public void test12_policies_secure_mode_uses_secure_url() throws Exception { + Client clientMock = mock(Client.class); + WebTarget targetMock = mock(WebTarget.class); + Invocation.Builder builderMock = mock(Invocation.Builder.class); + Response response200 = mock(Response.class); + + Configuration conf = buildBaseConfig(BASE_URL); + conf.setBoolean(PREFIX + ".forceNonKerberos", false); + RangerAdminJersey2RESTClient underTest = spy(new RangerAdminJersey2RESTClient()); + underTest.client = clientMock; + underTest.init(SERVICE, APPID, PREFIX, conf); + doReturn(true).when(underTest).isKerberosEnabled(ArgumentMatchers.any()); + + Map cookies = new HashMap<>(); + cookies.put(COOKIE_NAME, new NewCookie(COOKIE_NAME, "v")); + + when(response200.getStatus()).thenReturn(200); + when(response200.readEntity(String.class)).thenReturn("{}"); + when(response200.getCookies()).thenReturn(cookies); + + ArgumentCaptor urlCaptor = ArgumentCaptor.forClass(String.class); + + when(clientMock.target(urlCaptor.capture())).thenReturn(targetMock); + when(targetMock.queryParam(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenReturn(targetMock); + when(targetMock.request(MediaType.APPLICATION_JSON_TYPE)).thenReturn(builderMock); + when(builderMock.cookie(ArgumentMatchers.nullable(Cookie.class))).thenReturn(builderMock); + when(builderMock.get()).thenReturn(response200); + + ServicePolicies ret = underTest.getServicePoliciesIfUpdated(1L, 2L); + assertNotNull(ret); + + String calledUrl = urlCaptor.getValue(); + assertNotNull(calledUrl); + String expectedPrefix = RangerRESTUtils.REST_URL_POLICY_GET_FOR_SECURE_SERVICE_IF_UPDATED + SERVICE; + // expect full URL to contain secure relative path + Assertions.assertTrue(calledUrl.contains(expectedPrefix)); + } + + @Test + public void test13_tags_secure_mode_uses_secure_url() throws Exception { + Client clientMock = mock(Client.class); + WebTarget targetMock = mock(WebTarget.class); + Invocation.Builder builderMock = mock(Invocation.Builder.class); + Response response200 = mock(Response.class); + + Configuration conf = buildBaseConfig(BASE_URL); + conf.setBoolean(PREFIX + ".forceNonKerberos", false); + RangerAdminJersey2RESTClient underTest = spy(new RangerAdminJersey2RESTClient()); + underTest.client = clientMock; + underTest.init(SERVICE, APPID, PREFIX, conf); + doReturn(true).when(underTest).isKerberosEnabled(ArgumentMatchers.any()); + + Map cookies = new HashMap<>(); + cookies.put(COOKIE_NAME, new NewCookie(COOKIE_NAME, "v")); + + when(response200.getStatus()).thenReturn(200); + when(response200.readEntity(String.class)).thenReturn("{}"); + when(response200.getCookies()).thenReturn(cookies); + + ArgumentCaptor urlCaptor = ArgumentCaptor.forClass(String.class); + + when(clientMock.target(urlCaptor.capture())).thenReturn(targetMock); + when(targetMock.queryParam(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenReturn(targetMock); + when(targetMock.request(MediaType.APPLICATION_JSON_TYPE)).thenReturn(builderMock); + when(builderMock.cookie(ArgumentMatchers.nullable(Cookie.class))).thenReturn(builderMock); + when(builderMock.get()).thenReturn(response200); + + ServiceTags ret = underTest.getServiceTagsIfUpdated(1L, 2L); + assertNotNull(ret); + + String calledUrl = urlCaptor.getValue(); + assertNotNull(calledUrl); + String expectedPrefix = RangerRESTUtils.REST_URL_GET_SECURE_SERVICE_TAGS_IF_UPDATED + SERVICE; + Assertions.assertTrue(calledUrl.contains(expectedPrefix)); + } + + @Test + public void test14_roles_secure_mode_uses_secure_url() throws Exception { + Client clientMock = mock(Client.class); + WebTarget targetMock = mock(WebTarget.class); + Invocation.Builder builderMock = mock(Invocation.Builder.class); + Response response200 = mock(Response.class); + + Configuration conf = buildBaseConfig(BASE_URL); + conf.setBoolean(PREFIX + ".forceNonKerberos", false); + RangerAdminJersey2RESTClient underTest = spy(new RangerAdminJersey2RESTClient()); + underTest.client = clientMock; + underTest.init(SERVICE, APPID, PREFIX, conf); + doReturn(true).when(underTest).isKerberosEnabled(ArgumentMatchers.any()); + + Map cookies = new HashMap<>(); + cookies.put(COOKIE_NAME, new NewCookie(COOKIE_NAME, "v")); + + when(response200.getStatus()).thenReturn(200); + when(response200.readEntity(String.class)).thenReturn("{}"); + when(response200.getCookies()).thenReturn(cookies); + + ArgumentCaptor urlCaptor = ArgumentCaptor.forClass(String.class); + + when(clientMock.target(urlCaptor.capture())).thenReturn(targetMock); + when(targetMock.queryParam(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenReturn(targetMock); + when(targetMock.request(MediaType.APPLICATION_JSON_TYPE)).thenReturn(builderMock); + when(builderMock.cookie(ArgumentMatchers.nullable(Cookie.class))).thenReturn(builderMock); + when(builderMock.get()).thenReturn(response200); + + RangerRoles ret = underTest.getRolesIfUpdated(1L, 2L); + assertNotNull(ret); + + String calledUrl = urlCaptor.getValue(); + assertNotNull(calledUrl); + String expectedPrefix = RangerRESTUtils.REST_URL_SERVICE_SERCURE_GET_USER_GROUP_ROLES + SERVICE; + Assertions.assertTrue(calledUrl.contains(expectedPrefix)); + } + + @Test + public void test15_userstore_secure_mode_uses_secure_url() throws Exception { + Client clientMock = mock(Client.class); + WebTarget targetMock = mock(WebTarget.class); + Invocation.Builder builderMock = mock(Invocation.Builder.class); + Response response200 = mock(Response.class); + + Configuration conf = buildBaseConfig(BASE_URL); + conf.setBoolean(PREFIX + ".forceNonKerberos", false); + RangerAdminJersey2RESTClient underTest = spy(new RangerAdminJersey2RESTClient()); + underTest.client = clientMock; + underTest.init(SERVICE, APPID, PREFIX, conf); + doReturn(true).when(underTest).isKerberosEnabled(ArgumentMatchers.any()); + + when(response200.getStatus()).thenReturn(200); + when(response200.readEntity(String.class)).thenReturn("{}"); + + ArgumentCaptor urlCaptor = ArgumentCaptor.forClass(String.class); + + when(clientMock.target(urlCaptor.capture())).thenReturn(targetMock); + when(targetMock.queryParam(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenReturn(targetMock); + when(targetMock.request(MediaType.APPLICATION_JSON_TYPE)).thenReturn(builderMock); + when(builderMock.get()).thenReturn(response200); + + RangerUserStore ret = underTest.getUserStoreIfUpdated(1L, 2L); + assertNotNull(ret); + + String calledUrl = urlCaptor.getValue(); + assertNotNull(calledUrl); + String expectedPrefix = RangerRESTUtils.REST_URL_SERVICE_SERCURE_GET_USERSTORE + SERVICE; + Assertions.assertTrue(calledUrl.contains(expectedPrefix)); + } + + @Test + public void test16_buildClient_ssl_path() throws Exception { + RangerAdminJersey2RESTClient underTest = new RangerAdminJersey2RESTClient(); + underTest.isSSL = true; + underTest.sslContext = SSLContext.getInstance("TLS"); + underTest.sslContext.init(null, null, null); + HostnameVerifier verifier = (h, s) -> true; + underTest.hv = verifier; + underTest.client = null; + + Client c = underTest.buildClient(); + assertNotNull(c); + } + + @Test + public void test17_policies_cookie_404_throws_service_not_found() throws Exception { + Client clientMock = mock(Client.class); + WebTarget targetMock = mock(WebTarget.class); + Invocation.Builder builderMock = mock(Invocation.Builder.class); + Response response200 = mock(Response.class); + Response response404 = mock(Response.class); + + Configuration conf = buildBaseConfig(BASE_URL); + RangerAdminJersey2RESTClient underTest = new RangerAdminJersey2RESTClient(); + initWithMockClient(underTest, clientMock, conf); + + Map cookies = new HashMap<>(); + cookies.put(COOKIE_NAME, new NewCookie(COOKIE_NAME, "v")); + + when(response200.getStatus()).thenReturn(200); + when(response200.readEntity(String.class)).thenReturn("{}"); + when(response200.getCookies()).thenReturn(cookies); + + when(response404.getStatus()).thenReturn(404); + when(response404.hasEntity()).thenReturn(true); + when(response404.readEntity(String.class)).thenReturn(RangerServiceNotFoundException.buildExceptionMsg(SERVICE)); + + when(clientMock.target(ArgumentMatchers.anyString())).thenReturn(targetMock); + when(targetMock.queryParam(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenReturn(targetMock); + when(targetMock.request(MediaType.APPLICATION_JSON_TYPE)).thenReturn(builderMock); + when(builderMock.cookie(ArgumentMatchers.nullable(Cookie.class))).thenReturn(builderMock); + when(builderMock.get()).thenReturn(response200, response404); + + ServicePolicies first = underTest.getServicePoliciesIfUpdated(1L, 2L); + assertNotNull(first); + assertThrows(RangerServiceNotFoundException.class, () -> underTest.getServicePoliciesIfUpdated(1L, 2L)); + } + + @Test + public void test18_policies_cookie_default_and_minus1() throws Exception { + Client clientMock = mock(Client.class); + WebTarget targetMock = mock(WebTarget.class); + Invocation.Builder builderMock = mock(Invocation.Builder.class); + Response response200 = mock(Response.class); + Response response500 = mock(Response.class); + + Configuration conf = buildBaseConfig(BASE_URL); + RangerAdminJersey2RESTClient underTest = new RangerAdminJersey2RESTClient(); + initWithMockClient(underTest, clientMock, conf); + + Map cookies = new HashMap<>(); + cookies.put(COOKIE_NAME, new NewCookie(COOKIE_NAME, "v")); + + when(response200.getStatus()).thenReturn(200); + when(response200.readEntity(String.class)).thenReturn("{}"); + when(response200.getCookies()).thenReturn(cookies); + + when(response500.getStatus()).thenReturn(500); + when(response500.readEntity(String.class)).thenReturn("ERR"); + + when(clientMock.target(ArgumentMatchers.anyString())).thenReturn(targetMock); + when(targetMock.queryParam(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenReturn(targetMock); + when(targetMock.request(MediaType.APPLICATION_JSON_TYPE)).thenReturn(builderMock); + when(builderMock.cookie(ArgumentMatchers.nullable(Cookie.class))).thenReturn(builderMock); + when(builderMock.get()).thenReturn(response200, response500, null); + + ServicePolicies first = underTest.getServicePoliciesIfUpdated(1L, 2L); + assertNotNull(first); + ServicePolicies second = underTest.getServicePoliciesIfUpdated(1L, 2L); + assertNull(second); + ServicePolicies third = underTest.getServicePoliciesIfUpdated(1L, 2L); + assertNull(third); + } + + @Test + public void test19_roles_cookie_404_throws_service_not_found() throws Exception { + Client clientMock = mock(Client.class); + WebTarget targetMock = mock(WebTarget.class); + Invocation.Builder builderMock = mock(Invocation.Builder.class); + Response response200 = mock(Response.class); + Response response404 = mock(Response.class); + + Configuration conf = buildBaseConfig(BASE_URL); + RangerAdminJersey2RESTClient underTest = new RangerAdminJersey2RESTClient(); + initWithMockClient(underTest, clientMock, conf); + + Map cookies = new HashMap<>(); + cookies.put(COOKIE_NAME, new NewCookie(COOKIE_NAME, "v")); + + when(response200.getStatus()).thenReturn(200); + when(response200.readEntity(String.class)).thenReturn("{}"); + when(response200.getCookies()).thenReturn(cookies); + + when(response404.getStatus()).thenReturn(404); + when(response404.hasEntity()).thenReturn(true); + when(response404.readEntity(String.class)).thenReturn(RangerServiceNotFoundException.buildExceptionMsg(SERVICE)); + + when(clientMock.target(ArgumentMatchers.anyString())).thenReturn(targetMock); + when(targetMock.queryParam(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenReturn(targetMock); + when(targetMock.request(MediaType.APPLICATION_JSON_TYPE)).thenReturn(builderMock); + when(builderMock.cookie(ArgumentMatchers.nullable(Cookie.class))).thenReturn(builderMock); + when(builderMock.get()).thenReturn(response200, response404); + + RangerRoles first = underTest.getRolesIfUpdated(1L, 2L); + assertNotNull(first); + assertThrows(RangerServiceNotFoundException.class, () -> underTest.getRolesIfUpdated(1L, 2L)); + } + + @Test + public void test20_roles_cookie_default_and_minus1() throws Exception { + Client clientMock = mock(Client.class); + WebTarget targetMock = mock(WebTarget.class); + Invocation.Builder builderMock = mock(Invocation.Builder.class); + Response response200 = mock(Response.class); + Response response500 = mock(Response.class); + + Configuration conf = buildBaseConfig(BASE_URL); + RangerAdminJersey2RESTClient underTest = new RangerAdminJersey2RESTClient(); + initWithMockClient(underTest, clientMock, conf); + + Map cookies = new HashMap<>(); + cookies.put(COOKIE_NAME, new NewCookie(COOKIE_NAME, "v")); + + when(response200.getStatus()).thenReturn(200); + when(response200.readEntity(String.class)).thenReturn("{}"); + when(response200.getCookies()).thenReturn(cookies); + + when(response500.getStatus()).thenReturn(500); + when(response500.readEntity(String.class)).thenReturn("ERR"); + + when(clientMock.target(ArgumentMatchers.anyString())).thenReturn(targetMock); + when(targetMock.queryParam(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenReturn(targetMock); + when(targetMock.request(MediaType.APPLICATION_JSON_TYPE)).thenReturn(builderMock); + when(builderMock.cookie(ArgumentMatchers.nullable(Cookie.class))).thenReturn(builderMock); + when(builderMock.get()).thenReturn(response200, response500, null); + + RangerRoles first = underTest.getRolesIfUpdated(1L, 2L); + assertNotNull(first); + RangerRoles second = underTest.getRolesIfUpdated(1L, 2L); + assertNull(second); + RangerRoles third = underTest.getRolesIfUpdated(1L, 2L); + assertNull(third); + } + + @Test + public void test21_tags_cookie_404_throws_service_not_found() throws Exception { + Client clientMock = mock(Client.class); + WebTarget targetMock = mock(WebTarget.class); + Invocation.Builder builderMock = mock(Invocation.Builder.class); + Response response200 = mock(Response.class); + Response response404 = mock(Response.class); + + Configuration conf = buildBaseConfig(BASE_URL); + RangerAdminJersey2RESTClient underTest = new RangerAdminJersey2RESTClient(); + initWithMockClient(underTest, clientMock, conf); + + Map cookies = new HashMap<>(); + cookies.put(COOKIE_NAME, new NewCookie(COOKIE_NAME, "v")); + + when(response200.getStatus()).thenReturn(200); + when(response200.readEntity(String.class)).thenReturn("{}"); + when(response200.getCookies()).thenReturn(cookies); + + when(response404.getStatus()).thenReturn(404); + when(response404.hasEntity()).thenReturn(true); + when(response404.readEntity(String.class)).thenReturn(RangerServiceNotFoundException.buildExceptionMsg(SERVICE)); + + when(clientMock.target(ArgumentMatchers.anyString())).thenReturn(targetMock); + when(targetMock.queryParam(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenReturn(targetMock); + when(targetMock.request(MediaType.APPLICATION_JSON_TYPE)).thenReturn(builderMock); + when(builderMock.cookie(ArgumentMatchers.nullable(Cookie.class))).thenReturn(builderMock); + when(builderMock.get()).thenReturn(response200, response404); + + ServiceTags first = underTest.getServiceTagsIfUpdated(1L, 2L); + assertNotNull(first); + assertThrows(RangerServiceNotFoundException.class, () -> underTest.getServiceTagsIfUpdated(1L, 2L)); + } + + @Test + public void test22_tags_cookie_default_and_minus1() throws Exception { + Client clientMock = mock(Client.class); + WebTarget targetMock = mock(WebTarget.class); + Invocation.Builder builderMock = mock(Invocation.Builder.class); + Response response200 = mock(Response.class); + Response response500 = mock(Response.class); + + Configuration conf = buildBaseConfig(BASE_URL); + RangerAdminJersey2RESTClient underTest = new RangerAdminJersey2RESTClient(); + initWithMockClient(underTest, clientMock, conf); + + Map cookies = new HashMap<>(); + cookies.put(COOKIE_NAME, new NewCookie(COOKIE_NAME, "v")); + + when(response200.getStatus()).thenReturn(200); + when(response200.readEntity(String.class)).thenReturn("{}"); + when(response200.getCookies()).thenReturn(cookies); + + when(response500.getStatus()).thenReturn(500); + when(response500.readEntity(String.class)).thenReturn("ERR"); + + when(clientMock.target(ArgumentMatchers.anyString())).thenReturn(targetMock); + when(targetMock.queryParam(ArgumentMatchers.anyString(), ArgumentMatchers.any())).thenReturn(targetMock); + when(targetMock.request(MediaType.APPLICATION_JSON_TYPE)).thenReturn(builderMock); + when(builderMock.cookie(ArgumentMatchers.nullable(Cookie.class))).thenReturn(builderMock); + when(builderMock.get()).thenReturn(response200, response500, null); + + ServiceTags first = underTest.getServiceTagsIfUpdated(1L, 2L); + assertNotNull(first); + ServiceTags second = underTest.getServiceTagsIfUpdated(1L, 2L); + assertNull(second); + ServiceTags third = underTest.getServiceTagsIfUpdated(1L, 2L); + assertNull(third); + } +} diff --git a/knox-agent/src/test/java/org/apache/ranger/authorization/knox/TestKnoxRangerPlugin.java b/knox-agent/src/test/java/org/apache/ranger/authorization/knox/TestKnoxRangerPlugin.java new file mode 100644 index 0000000000..704074fc33 --- /dev/null +++ b/knox-agent/src/test/java/org/apache/ranger/authorization/knox/TestKnoxRangerPlugin.java @@ -0,0 +1,99 @@ +/** + * 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.authorization.knox; + +import org.apache.ranger.plugin.policyengine.RangerAccessRequest; +import org.apache.ranger.plugin.policyengine.RangerAccessResource; +import org.apache.ranger.plugin.policyengine.RangerAccessResourceImpl; +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.junit.jupiter.MockitoExtension; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** +* @generated by Cursor +* @description +*/ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +public class TestKnoxRangerPlugin { + @Test + public void test01_init_setsResultProcessorOnce() { + KnoxRangerPlugin plugin = new KnoxRangerPlugin(); + + Assertions.assertNull(plugin.getResultProcessor()); + plugin.init(); + Assertions.assertNotNull(plugin.getResultProcessor()); + + // second init should not reset and should not throw + plugin.init(); + Assertions.assertNotNull(plugin.getResultProcessor()); + } + + @Test + public void test02_requestBuilder_verifyBuildable_throwsOnMissing() { + KnoxRangerPlugin.RequestBuilder b = new KnoxRangerPlugin.RequestBuilder(); + Assertions.assertThrows(IllegalStateException.class, b::verifyBuildable); + + b.service("svc"); + Assertions.assertThrows(IllegalStateException.class, b::verifyBuildable); + + b.topology("top"); + Assertions.assertThrows(IllegalStateException.class, b::verifyBuildable); + + b.user("u"); + // no exception now + b.verifyBuildable(); + } + + @Test + public void test03_requestBuilder_build_setsAllFields() { + Set groups = new HashSet<>(Arrays.asList("g1", "g2")); + List fwd = Arrays.asList("1.1.1.1", "2.2.2.2"); + + KnoxRangerPlugin.RequestBuilder b = new KnoxRangerPlugin.RequestBuilder() + .service("svc") + .topology("top") + .user("user") + .groups(groups) + .clientIp("10.0.0.1") + .remoteIp("10.0.0.1") + .forwardedAddresses(fwd); + + RangerAccessRequest req = b.build(); + Assertions.assertEquals("user", req.getUser()); + Assertions.assertEquals("allow", req.getAction()); + Assertions.assertEquals("allow", req.getAccessType()); + Assertions.assertEquals("10.0.0.1", req.getClientIPAddress()); + Assertions.assertEquals(groups, req.getUserGroups()); + Assertions.assertEquals(fwd, req.getForwardedAddresses()); + + RangerAccessResource res = req.getResource(); + Assertions.assertTrue(res instanceof RangerAccessResourceImpl); + Assertions.assertEquals("svc", ((RangerAccessResourceImpl) res).getValue(KnoxRangerPlugin.KnoxConstants.ResourceName.Service)); + Assertions.assertEquals("top", ((RangerAccessResourceImpl) res).getValue(KnoxRangerPlugin.KnoxConstants.ResourceName.Topology)); + } +} diff --git a/knox-agent/src/test/java/org/apache/ranger/authorization/knox/TestRangerPDPKnoxFilter.java b/knox-agent/src/test/java/org/apache/ranger/authorization/knox/TestRangerPDPKnoxFilter.java new file mode 100644 index 0000000000..12dd3ab88f --- /dev/null +++ b/knox-agent/src/test/java/org/apache/ranger/authorization/knox/TestRangerPDPKnoxFilter.java @@ -0,0 +1,201 @@ +/** + * 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.authorization.knox; + +import org.apache.knox.gateway.filter.AbstractGatewayFilter; +import org.apache.knox.gateway.security.GroupPrincipal; +import org.apache.knox.gateway.security.ImpersonatedPrincipal; +import org.apache.knox.gateway.security.PrimaryPrincipal; +import org.apache.ranger.audit.provider.MiscUtil; +import org.apache.ranger.plugin.policyengine.RangerAccessRequest; +import org.apache.ranger.plugin.policyengine.RangerAccessResult; +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; + +import javax.security.auth.Subject; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.security.PrivilegedExceptionAction; +import java.util.Arrays; +import java.util.List; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** +* @generated by Cursor +* @description +*/ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +public class TestRangerPDPKnoxFilter { + @Test + public void test01_init_initializesPluginOnce() { + RangerPDPKnoxFilter filter = new RangerPDPKnoxFilter(); + FilterConfig cfg = mock(FilterConfig.class); + when(cfg.getInitParameter("resource.role")) + .thenReturn("WEBHDFS"); + + try (MockedStatic mu = Mockito.mockStatic(MiscUtil.class)) { + filter.init(cfg); + // second init call should reuse existing plugin and not throw + filter.init(cfg); + } + } + + @Test + public void test02_doFilter_accessAllowed_callsChain() throws Exception { + RangerPDPKnoxFilter filter = new RangerPDPKnoxFilter(); + + // prepare Subject with principals + Subject subject = new Subject(); + subject.getPrincipals().add(new PrimaryPrincipal("primary")); + subject.getPrincipals().add(new ImpersonatedPrincipal("impersonated")); + subject.getPrincipals().add(new GroupPrincipal("g1")); + subject.getPrincipals().add(new GroupPrincipal("g2")); + + ServletRequest req = mock(HttpServletRequest.class); + ServletResponse res = mock(HttpServletResponse.class); + FilterChain chain = mock(FilterChain.class); + + when(req.getAttribute(AbstractGatewayFilter.SOURCE_REQUEST_CONTEXT_URL_ATTRIBUTE_NAME)) + .thenReturn("/cluster/top1/path"); + when(req.getRemoteAddr()).thenReturn("10.0.0.1"); + + // mock plugin + KnoxRangerPlugin plugin = Mockito.mock(KnoxRangerPlugin.class); + Field f = RangerPDPKnoxFilter.class.getDeclaredField("plugin"); + f.setAccessible(true); + f.set(null, plugin); + + RangerAccessResult allow = Mockito.mock(RangerAccessResult.class); + when(allow.getIsAllowed()).thenReturn(true); + when(plugin.isAccessAllowed(Mockito.any(RangerAccessRequest.class))).thenReturn(allow); + + Subject.doAs(subject, (PrivilegedExceptionAction) () -> { + filter.doFilter(req, res, chain); + return null; + }); + verify(chain, times(1)).doFilter(req, res); + } + + @Test + public void test03_doFilter_accessDenied_sendsForbidden() throws Exception { + RangerPDPKnoxFilter filter = new RangerPDPKnoxFilter(); + Subject subject = new Subject(); + subject.getPrincipals().add(new PrimaryPrincipal("primary")); + ServletRequest req = mock(HttpServletRequest.class); + HttpServletResponse res = mock(HttpServletResponse.class); + FilterChain chain = mock(FilterChain.class); + + when(req.getAttribute(AbstractGatewayFilter.SOURCE_REQUEST_CONTEXT_URL_ATTRIBUTE_NAME)) + .thenReturn("/cluster/top1/path"); + when(req.getRemoteAddr()).thenReturn("10.0.0.1"); + + KnoxRangerPlugin plugin = Mockito.mock(KnoxRangerPlugin.class); + Field f = RangerPDPKnoxFilter.class.getDeclaredField("plugin"); + f.setAccessible(true); + f.set(null, plugin); + + RangerAccessResult deny = Mockito.mock(RangerAccessResult.class); + when(deny.getIsAllowed()).thenReturn(false); + when(plugin.isAccessAllowed(Mockito.any(RangerAccessRequest.class))).thenReturn(deny); + + Subject.doAs(subject, (PrivilegedExceptionAction) () -> { + filter.doFilter(req, res, chain); + return null; + }); + verify(res, times(1)).sendError(403); + } + + @Test + public void test04_getInitParameter_lowercasesName() { + RangerPDPKnoxFilter filter = new RangerPDPKnoxFilter(); + FilterConfig cfg = mock(FilterConfig.class); + when(cfg.getInitParameter("resource.role")).thenReturn("WEBHDFS"); + Assertions.assertEquals("WEBHDFS", invokeGetInitParameter(filter, cfg, "RESOURCE.ROLE")); + } + + @Test + public void test05_getForwardedAddresses_parsesHeader() { + RangerPDPKnoxFilter filter = new RangerPDPKnoxFilter(); + HttpServletRequest req = mock(HttpServletRequest.class); + when(req.getHeader("X-Forwarded-For")).thenReturn("1.1.1.1,2.2.2.2"); + List addrs = invokeGetForwardedAddresses(filter, req); + Assertions.assertEquals(Arrays.asList("1.1.1.1", "2.2.2.2"), addrs); + + when(req.getHeader("X-Forwarded-For")).thenReturn(null); + Assertions.assertNull(invokeGetForwardedAddresses(filter, req)); + } + + @Test + public void test06_getTopologyName_extractsThirdTokenOrNull() { + RangerPDPKnoxFilter filter = new RangerPDPKnoxFilter(); + Assertions.assertNull(invokeGetTopologyName(filter, null)); + Assertions.assertNull(invokeGetTopologyName(filter, "/one")); + Assertions.assertEquals("two", invokeGetTopologyName(filter, "/one/two/three")); + } + + private String invokeGetInitParameter(RangerPDPKnoxFilter filter, FilterConfig cfg, String name) { + try { + Method m = RangerPDPKnoxFilter.class.getDeclaredMethod("getInitParameter", FilterConfig.class, String.class); + m.setAccessible(true); + return (String) m.invoke(filter, cfg, name); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private List invokeGetForwardedAddresses(RangerPDPKnoxFilter filter, ServletRequest req) { + try { + Method m = RangerPDPKnoxFilter.class.getDeclaredMethod("getForwardedAddresses", ServletRequest.class); + m.setAccessible(true); + @SuppressWarnings("unchecked") + List ret = (List) m.invoke(filter, req); + return ret; + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private String invokeGetTopologyName(RangerPDPKnoxFilter filter, String url) { + try { + Method m = RangerPDPKnoxFilter.class.getDeclaredMethod("getTopologyName", String.class); + m.setAccessible(true); + return (String) m.invoke(filter, url); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/knox-agent/src/test/java/org/apache/ranger/services/knox/TestRangerServiceKnox.java b/knox-agent/src/test/java/org/apache/ranger/services/knox/TestRangerServiceKnox.java new file mode 100644 index 0000000000..e80b95a783 --- /dev/null +++ b/knox-agent/src/test/java/org/apache/ranger/services/knox/TestRangerServiceKnox.java @@ -0,0 +1,162 @@ +/** + * 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.services.knox; + +import org.apache.ranger.plugin.client.HadoopException; +import org.apache.ranger.plugin.model.RangerPolicy; +import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyItem; +import org.apache.ranger.plugin.model.RangerPolicy.RangerPolicyItemAccess; +import org.apache.ranger.plugin.model.RangerService; +import org.apache.ranger.plugin.model.RangerServiceDef; +import org.apache.ranger.plugin.service.ResourceLookupContext; +import org.apache.ranger.services.knox.client.KnoxResourceMgr; +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; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @generated by Cursor + * @description + */ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +public class TestRangerServiceKnox { + private RangerServiceKnox createService(String serviceName, Map configs) { + RangerServiceKnox svc = new RangerServiceKnox(); + RangerServiceDef def = new RangerServiceDef(); + RangerService service = new RangerService("knox", serviceName, "desc", null, configs); + svc.init(def, service); + return svc; + } + + @Test + public void test01_validateConfig_success() throws Exception { + Map cfg = new HashMap<>(); + cfg.put("knox.url", "https://knox"); + cfg.put("username", "u"); + cfg.put("password", "p"); + + RangerServiceKnox svc = createService("svc", cfg); + + try (MockedStatic km = Mockito.mockStatic(KnoxResourceMgr.class)) { + Map expected = new HashMap<>(); + expected.put("connectivityStatus", true); + km.when(() -> KnoxResourceMgr.validateConfig("svc", cfg)).thenReturn(expected); + + Map resp = svc.validateConfig(); + Assertions.assertEquals(Boolean.TRUE, resp.get("connectivityStatus")); + } + } + + @Test + public void test02_validateConfig_exceptionPropagates() throws Exception { + Map cfg = new HashMap<>(); + RangerServiceKnox svc = createService("svc", cfg); + try (MockedStatic km = Mockito.mockStatic(KnoxResourceMgr.class)) { + km.when(() -> KnoxResourceMgr.validateConfig("svc", cfg)).thenThrow(new HadoopException("boom")); + Assertions.assertThrows(HadoopException.class, svc::validateConfig); + } + } + + @Test + public void test03_lookupResource_nullContext_returnsEmpty() throws Exception { + RangerServiceKnox svc = createService("svc", new HashMap<>()); + List ret = svc.lookupResource(null); + Assertions.assertNotNull(ret); + Assertions.assertTrue(ret.isEmpty()); + } + + @Test + public void test04_lookupResource_success() throws Exception { + Map cfg = new HashMap<>(); + RangerServiceKnox svc = createService("svc", cfg); + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setResourceName("topology"); + ctx.setUserInput("adm"); + + try (MockedStatic km = Mockito.mockStatic(KnoxResourceMgr.class)) { + List expected = new ArrayList<>(); + expected.add("admin"); + km.when(() -> KnoxResourceMgr.getKnoxResources("svc", cfg, ctx)).thenReturn(expected); + + List ret = svc.lookupResource(ctx); + Assertions.assertEquals(expected, ret); + } + } + + @Test + public void test05_lookupResource_exceptionPropagates() throws Exception { + Map cfg = new HashMap<>(); + RangerServiceKnox svc = createService("svc", cfg); + ResourceLookupContext ctx = new ResourceLookupContext(); + + try (MockedStatic km = Mockito.mockStatic(KnoxResourceMgr.class)) { + km.when(() -> KnoxResourceMgr.getKnoxResources("svc", cfg, ctx)).thenThrow(new HadoopException("boom")); + Assertions.assertThrows(HadoopException.class, () -> svc.lookupResource(ctx)); + } + } + + @Test + public void test06_getDefaultRangerPolicies_addsLookupUserPolicyItem() throws Exception { + Map cfg = new HashMap<>(); + cfg.put("setup.additional.default.policies", "true"); + cfg.put("default-policy.1.name", "all endpoints"); + cfg.put("default-policy.1.resource.path", "/*"); + cfg.put("default-policy.1.policyItem.1.users", "user1"); + cfg.put("default-policy.1.policyItem.1.accessTypes", "read,write"); + + RangerServiceKnox svc = createService("svc", cfg); + + // set lookUpUser via reflection to cover branch in RangerServiceKnox + Field f = org.apache.ranger.plugin.service.RangerBaseService.class.getDeclaredField("lookUpUser"); + f.setAccessible(true); + f.set(svc, "lookupUser"); + + List policies = svc.getDefaultRangerPolicies(); + Assertions.assertNotNull(policies); + Assertions.assertFalse(policies.isEmpty()); + + boolean foundAugmented = false; + for (RangerPolicy p : policies) { + if (p.getName() != null && p.getName().contains("all")) { + for (RangerPolicyItem item : p.getPolicyItems()) { + if (item.getUsers() != null && item.getUsers().contains("lookupUser")) { + List acc = item.getAccesses(); + Assertions.assertEquals(1, acc.size()); + Assertions.assertEquals(RangerServiceKnox.ACCESS_TYPE_ALLOW, acc.get(0).getType()); + foundAugmented = true; + break; + } + } + } + } + Assertions.assertTrue(foundAugmented, "Expected policy item for lookupUser was not added"); + } +} diff --git a/knox-agent/src/test/java/org/apache/ranger/services/knox/client/TestKnoxClient.java b/knox-agent/src/test/java/org/apache/ranger/services/knox/client/TestKnoxClient.java new file mode 100644 index 0000000000..e70f7b6914 --- /dev/null +++ b/knox-agent/src/test/java/org/apache/ranger/services/knox/client/TestKnoxClient.java @@ -0,0 +1,438 @@ +/** + * 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.services.knox.client; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.WebResource.Builder; +import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; +import org.apache.ranger.plugin.client.HadoopException; +import org.apache.ranger.plugin.util.PasswordUtils; +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.MockedConstruction; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.security.Permission; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * @generated by Cursor + * @description + */ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +public class TestKnoxClient { + @Test + public void test01_connectionTest_success() { + Map configs = new HashMap<>(); + configs.put("knox.url", "https://example:8443/gateway/admin/api/v1/topologies"); + configs.put("username", "admin"); + configs.put("password", "pwd"); + + try (MockedStatic knoxStatic = Mockito.mockStatic(KnoxClient.class)) { + KnoxClient dummy = new KnoxClient("u", "u", "p"); + knoxStatic.when(() -> KnoxClient.connectionTest("svc", configs)).thenCallRealMethod(); + knoxStatic.when(() -> KnoxClient.getKnoxClient("svc", configs)).thenReturn(dummy); + knoxStatic.when(() -> KnoxClient.getKnoxResources(Mockito.eq(dummy), anyString(), Mockito.isNull(), + Mockito.isNull(), Mockito.isNull())).thenReturn(Collections.singletonList("top1")); + + Map resp = KnoxClient.connectionTest("svc", configs); + + Assertions.assertNotNull(resp); + Assertions.assertEquals(Boolean.TRUE, resp.get("connectivityStatus")); + Assertions.assertEquals("ConnectionTest Successful", resp.get("message")); + Assertions.assertEquals("ConnectionTest Successful", resp.get("description")); + Assertions.assertNull(resp.get("objectId")); + Assertions.assertNull(resp.get("fieldName")); + } + } + + @Test + public void test02_connectionTest_failure() { + Map configs = new HashMap<>(); + configs.put("knox.url", "https://example:8443/gateway/admin/api/v1/topologies"); + configs.put("username", "admin"); + configs.put("password", "pwd"); + + try (MockedStatic knoxStatic = Mockito.mockStatic(KnoxClient.class)) { + KnoxClient dummy = new KnoxClient("u", "u", "p"); + knoxStatic.when(() -> KnoxClient.connectionTest("svc", configs)).thenCallRealMethod(); + knoxStatic.when(() -> KnoxClient.getKnoxClient("svc", configs)).thenReturn(dummy); + knoxStatic.when(() -> KnoxClient.getKnoxResources(Mockito.eq(dummy), anyString(), Mockito.isNull(), + Mockito.isNull(), Mockito.isNull())).thenReturn(Collections.emptyList()); + + Map resp = KnoxClient.connectionTest("svc", configs); + + Assertions.assertNotNull(resp); + Assertions.assertEquals(Boolean.FALSE, resp.get("connectivityStatus")); + Assertions.assertEquals("Unable to retrieve any topologies/services using given parameters.", + resp.get("message")); + Assertions.assertTrue(String.valueOf(resp.get("description")).contains("You can still save")); + } + } + + @Test + public void test03_getKnoxClient_validConfigs() { + Map configs = new HashMap<>(); + configs.put("knox.url", "https://example"); + configs.put("username", "admin"); + configs.put("password", "pwd"); + + KnoxClient client = KnoxClient.getKnoxClient("svc", configs); + Assertions.assertNotNull(client); + } + + @Test + public void test04_getKnoxClient_emptyConfigs_throws() { + Map configs = new HashMap<>(); + HadoopException ex = Assertions.assertThrows(HadoopException.class, + () -> KnoxClient.getKnoxClient("svc", configs)); + Assertions.assertTrue(ex.getResponseData().containsKey("connectivityStatus")); + Assertions.assertEquals(Boolean.FALSE, ex.getResponseData().get("connectivityStatus")); + Assertions.assertTrue(String.valueOf(ex.getResponseData().get("description")).contains("You can still save")); + } + + @Test + public void test05_getKnoxResources_nullClient_throws() { + HadoopException ex = Assertions.assertThrows(HadoopException.class, + () -> KnoxClient.getKnoxResources(null, "t", null, null, null)); + Assertions.assertEquals(Boolean.FALSE, ex.getResponseData().get("connectivityStatus")); + } + + @Test + public void test06_getKnoxResources_callsTopologyList() throws Exception { + KnoxClient knoxClient = mock(KnoxClient.class); + List expected = Arrays.asList("topA", "topB"); + when(knoxClient.getTopologyList(Mockito.eq("adm"), Mockito.isNull())).thenReturn(expected); + + List result = KnoxClient.getKnoxResources(knoxClient, "adm", null, null, null); + Assertions.assertEquals(expected, result); + } + + @Test + public void test07_getKnoxResources_callsServiceList() throws Exception { + KnoxClient knoxClient = mock(KnoxClient.class); + List expected = Arrays.asList("HIVE", "NAMENODE"); + when(knoxClient.getServiceList(Mockito.anyList(), Mockito.eq("HI"), Mockito.isNull())).thenReturn(expected); + + List topoList = new ArrayList<>(); + List result = KnoxClient.getKnoxResources(knoxClient, null, "HI", topoList, null); + Assertions.assertEquals(expected, result); + } + + @Test + public void test08_getKnoxResources_wrapsException() throws Exception { + KnoxClient knoxClient = mock(KnoxClient.class); + when(knoxClient.getTopologyList(Mockito.eq(""), Mockito.isNull())).thenThrow(new RuntimeException("boom")); + + HadoopException ex = Assertions.assertThrows(HadoopException.class, + () -> KnoxClient.getKnoxResources(knoxClient, null, null, null, null)); + Assertions.assertTrue(String.valueOf(ex.getResponseData().get("description")).contains("You can still save")); + } + + @Test + public void test09_timedTask_success() throws Exception { + String value = KnoxClient.timedTask(new Callable() { + @Override + public String call() { + return "ok"; + } + }, 1L, TimeUnit.SECONDS); + Assertions.assertEquals("ok", value); + } + + @Test + public void test10_timedTask_throws() { + Assertions.assertThrows(Exception.class, () -> KnoxClient.timedTask(new Callable() { + @Override + public String call() throws Exception { + throw new Exception("x"); + } + }, 1L, TimeUnit.SECONDS)); + } + + @Test + public void test11_getTopologyList_status200_and_filters() { + KnoxClient client = new KnoxClient("https://knox/topologies", "admin", "pwd"); + + try (MockedStatic pwStatic = Mockito.mockStatic(PasswordUtils.class); + MockedStatic clientStatic = Mockito.mockStatic(Client.class)) { + pwStatic.when(() -> PasswordUtils.decryptPassword("pwd")).thenReturn("dec"); + + Client jersey = mock(Client.class); + WebResource wr = mock(WebResource.class); + Builder builder = mock(Builder.class); + ClientResponse response = mock(ClientResponse.class); + + clientStatic.when(Client::create).thenReturn(jersey); + Mockito.doNothing().when(jersey).addFilter(Mockito.any(HTTPBasicAuthFilter.class)); + when(jersey.resource("https://knox/topologies")).thenReturn(wr); + when(wr.accept("application/json")).thenReturn(builder); + when(builder.get(ClientResponse.class)).thenReturn(response); + + when(response.getStatus()).thenReturn(200); + String json = "{\"topology\":[{\"name\":\"admin\"},{\"name\":\"gateway\"}]}"; + when(response.getEntity(String.class)).thenReturn(json); + + List listAll = client.getTopologyList("*", null); + Assertions.assertEquals(2, listAll.size()); + + List listFiltered = client.getTopologyList("adm", null); + Assertions.assertEquals(1, listFiltered.size()); + + List skipDueToContains = client.getTopologyList("adm", Collections.singletonList("adm")); + Assertions.assertTrue(skipDueToContains.isEmpty()); + + verify(response, times(3)).close(); + verify(jersey, times(3)).destroy(); + } + } + + @Test + public void test12_getTopologyList_statusNot200_returnsEmpty() { + KnoxClient client = new KnoxClient("https://knox/topologies", "admin", "pwd"); + + try (MockedStatic clientStatic = Mockito.mockStatic(Client.class)) { + Client jersey = mock(Client.class); + WebResource wr = mock(WebResource.class); + Builder builder = mock(Builder.class); + ClientResponse response = mock(ClientResponse.class); + + clientStatic.when(Client::create).thenReturn(jersey); + Mockito.doNothing().when(jersey).addFilter(Mockito.any(HTTPBasicAuthFilter.class)); + when(jersey.resource("https://knox/topologies")).thenReturn(wr); + when(wr.accept("application/json")).thenReturn(builder); + when(builder.get(ClientResponse.class)).thenReturn(response); + + when(response.getStatus()).thenReturn(500); + + List list = client.getTopologyList("*", null); + Assertions.assertTrue(list.isEmpty()); + } + } + + @Test + public void test13_getTopologyList_responseNull_throws() { + KnoxClient client = new KnoxClient("https://knox/topologies", "admin", "pwd"); + + try (MockedStatic clientStatic = Mockito.mockStatic(Client.class)) { + Client jersey = mock(Client.class); + WebResource wr = mock(WebResource.class); + Builder builder = mock(Builder.class); + + clientStatic.when(Client::create).thenReturn(jersey); + Mockito.doNothing().when(jersey).addFilter(Mockito.any(HTTPBasicAuthFilter.class)); + when(jersey.resource("https://knox/topologies")).thenReturn(wr); + when(wr.accept("application/json")).thenReturn(builder); + when(builder.get(ClientResponse.class)).thenReturn(null); + + Assertions.assertThrows(HadoopException.class, () -> client.getTopologyList("*", null)); + } + } + + @Test + public void test14_getTopologyList_throwsWrapped() { + KnoxClient client = new KnoxClient("https://knox/topologies", "admin", "pwd"); + + try (MockedStatic clientStatic = Mockito.mockStatic(Client.class)) { + Client jersey = mock(Client.class); + clientStatic.when(Client::create).thenReturn(jersey); + Mockito.doNothing().when(jersey).addFilter(Mockito.any(HTTPBasicAuthFilter.class)); + when(jersey.resource("https://knox/topologies")).thenThrow(new RuntimeException("boom")); + + Assertions.assertThrows(HadoopException.class, () -> client.getTopologyList("*", null)); + } + } + + @Test + public void test15_getServiceList_happyPath_andFilters() { + KnoxClient client = new KnoxClient("https://knox/topologies", "admin", "pwd"); + + try (MockedStatic pwStatic = Mockito.mockStatic(PasswordUtils.class); + MockedStatic clientStatic = Mockito.mockStatic(Client.class)) { + pwStatic.when(() -> PasswordUtils.decryptPassword("pwd")).thenReturn("dec"); + + Client jersey = mock(Client.class); + WebResource wr = mock(WebResource.class); + Builder builder = mock(Builder.class); + ClientResponse response = mock(ClientResponse.class); + + clientStatic.when(Client::create).thenReturn(jersey); + Mockito.doNothing().when(jersey).addFilter(Mockito.any(HTTPBasicAuthFilter.class)); + when(jersey.resource("https://knox/topologies/top1")).thenReturn(wr); + when(wr.accept("application/json")).thenReturn(builder); + when(builder.get(ClientResponse.class)).thenReturn(response); + + when(response.getStatus()).thenReturn(200); + String json = "{\"topology\":{\"service\":[{\"role\":\"HIVE\"},{\"role\":\"NAMENODE\"}]}}"; + when(response.getEntity(String.class)).thenReturn(json); + + List tops = Collections.singletonList("top1"); + List all = client.getServiceList(tops, "*", null); + Assertions.assertEquals(2, all.size()); + + List filtered = client.getServiceList(tops, "HI", null); + Assertions.assertEquals(1, filtered.size()); + + List skip = client.getServiceList(tops, "HI", Collections.singletonList("HIVE")); + Assertions.assertTrue(skip.isEmpty()); + } + } + + @Test + public void test16_getServiceList_statusNot200_logsAndContinues() { + KnoxClient client = new KnoxClient("https://knox/topologies", "admin", "pwd"); + + try (MockedStatic clientStatic = Mockito.mockStatic(Client.class)) { + Client jersey = mock(Client.class); + WebResource wr = mock(WebResource.class); + Builder builder = mock(Builder.class); + ClientResponse response = mock(ClientResponse.class); + + clientStatic.when(Client::create).thenReturn(jersey); + Mockito.doNothing().when(jersey).addFilter(Mockito.any(HTTPBasicAuthFilter.class)); + when(jersey.resource("https://knox/topologies/top1")).thenReturn(wr); + when(wr.accept("application/json")).thenReturn(builder); + when(builder.get(ClientResponse.class)).thenReturn(response); + + when(response.getStatus()).thenReturn(500); + + List list = client.getServiceList(Collections.singletonList("top1"), "*", null); + Assertions.assertTrue(list.isEmpty()); + } + } + + @Test + public void test17_getServiceList_responseNull_throws() { + KnoxClient client = new KnoxClient("https://knox/topologies", "admin", "pwd"); + + try (MockedStatic clientStatic = Mockito.mockStatic(Client.class)) { + Client jersey = mock(Client.class); + WebResource wr = mock(WebResource.class); + Builder builder = mock(Builder.class); + + clientStatic.when(Client::create).thenReturn(jersey); + Mockito.doNothing().when(jersey).addFilter(Mockito.any(HTTPBasicAuthFilter.class)); + when(jersey.resource("https://knox/topologies/top1")).thenReturn(wr); + when(wr.accept("application/json")).thenReturn(builder); + when(builder.get(ClientResponse.class)).thenReturn(null); + + Assertions.assertThrows(HadoopException.class, + () -> client.getServiceList(Collections.singletonList("top1"), "*", null)); + } + } + + @Test + public void test18_getServiceList_throwsWrapped() { + KnoxClient client = new KnoxClient("https://knox/topologies", "admin", "pwd"); + + try (MockedStatic clientStatic = Mockito.mockStatic(Client.class)) { + Client jersey = mock(Client.class); + clientStatic.when(Client::create).thenReturn(jersey); + Mockito.doNothing().when(jersey).addFilter(Mockito.any(HTTPBasicAuthFilter.class)); + when(jersey.resource("https://knox/topologies/top1")).thenThrow(new RuntimeException("boom")); + + Assertions.assertThrows(HadoopException.class, + () -> client.getServiceList(Collections.singletonList("top1"), "*", null)); + } + } + + @Test + public void test19_main_invalidArgs_exitsWithStatus1() { + SecurityManager originalSm = System.getSecurityManager(); + class NoExitSecurityManager extends SecurityManager { + private Integer status; + + @Override + public void checkPermission(Permission perm) { + // allow + } + + @Override + public void checkPermission(Permission perm, Object context) { + // allow + } + + @Override + public void checkExit(int status) { + this.status = status; + throw new SecurityException("exit"); + } + } + + NoExitSecurityManager sm = new NoExitSecurityManager(); + PrintStream origErr = System.err; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + System.setErr(new PrintStream(baos)); + System.setSecurityManager(sm); + try { + Assertions.assertThrows(SecurityException.class, () -> KnoxClient.main(new String[] {"only", "two"})); + Assertions.assertEquals(Integer.valueOf(1), sm.status); + String err = baos.toString(); + Assertions.assertTrue(err.contains("USAGE: java ")); + Assertions.assertTrue(err.contains("KnoxClient")); + } finally { + System.setErr(origErr); + System.setSecurityManager(originalSm); + } + } + + @Test + public void test20_main_validArgs_happyPath_printsServices() { + String[] args = new String[] {"https://knox", "admin", "pwd"}; + PrintStream origOut = System.out; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + System.setOut(new PrintStream(baos)); + + try (MockedConstruction construction = Mockito.mockConstruction(KnoxClient.class, + (mock, context) -> { + when(mock.getTopologyList("", null)).thenReturn(Collections.singletonList("top1")); + when(mock.getServiceList(Collections.singletonList("top1"), "*", null)) + .thenReturn(Collections.singletonList("HIVE")); + })) { + KnoxClient.main(args); + String out = baos.toString(); + Assertions.assertTrue(out.contains("Found service for topology:")); + } finally { + System.setOut(origOut); + } + } +} diff --git a/knox-agent/src/test/java/org/apache/ranger/services/knox/client/TestKnoxConnectionMgr.java b/knox-agent/src/test/java/org/apache/ranger/services/knox/client/TestKnoxConnectionMgr.java new file mode 100644 index 0000000000..2c8df80dc9 --- /dev/null +++ b/knox-agent/src/test/java/org/apache/ranger/services/knox/client/TestKnoxConnectionMgr.java @@ -0,0 +1,103 @@ +/** + * 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.services.knox.client; + +import org.apache.ranger.plugin.model.RangerService; +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.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.util.HashMap; +import java.util.Map; + +/** + * @generated by Cursor + * @description + */ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +public class TestKnoxConnectionMgr { + @Test + public void test01_getKnoxClientbyService_nullService_returnsNull() { + KnoxConnectionMgr mgr = new KnoxConnectionMgr(); + Assertions.assertNull(mgr.getKnoxClientbyService(null)); + } + + @Test + public void test02_getKnoxClientbyService_validService_returnsClient() { + RangerService svc = Mockito.mock(RangerService.class); + Map cfg = new HashMap<>(); + cfg.put("knox.url", "https://knox"); + cfg.put("username", "u"); + cfg.put("password", "p"); + Mockito.when(svc.getConfigs()).thenReturn(cfg); + + KnoxConnectionMgr mgr = new KnoxConnectionMgr(); + KnoxClient client = mgr.getKnoxClientbyService(svc); + Assertions.assertNotNull(client); + } + + @Test + public void test03_getKnoxClientByConfig_null_returnsNull() { + KnoxConnectionMgr mgr = new KnoxConnectionMgr(); + Assertions.assertNull(mgr.getKnoxClientByConfig(null)); + } + + @Test + public void test04_getKnoxClientByConfig_valid_returnsClient() { + KnoxConnectionMgr mgr = new KnoxConnectionMgr(); + Map cfg = new HashMap<>(); + cfg.put("knox.url", "https://knox"); + cfg.put("username", "u"); + cfg.put("password", "p"); + Assertions.assertNotNull(mgr.getKnoxClientByConfig(cfg)); + } + + @Test + public void test05_getKnoxClient_nullConfig_returnsNull() { + KnoxConnectionMgr mgr = new KnoxConnectionMgr(); + Assertions.assertNull(mgr.getKnoxClient("svc", null)); + } + + @Test + public void test06_getKnoxClient_validConfig_returnsClient() { + KnoxConnectionMgr mgr = new KnoxConnectionMgr(); + Map cfg = new HashMap<>(); + cfg.put("knox.url", "https://knox"); + cfg.put("username", "u"); + cfg.put("password", "p"); + Assertions.assertNotNull(mgr.getKnoxClient("svc", cfg)); + } + + @Test + public void test07_getKnoxClient_byValues_validatesInputs() { + KnoxConnectionMgr mgr = new KnoxConnectionMgr(); + Assertions.assertNull(mgr.getKnoxClient(null, "u", "p")); + Assertions.assertNull(mgr.getKnoxClient("", "u", "p")); + Assertions.assertNull(mgr.getKnoxClient("https://knox", null, "p")); + Assertions.assertNull(mgr.getKnoxClient("https://knox", "", "p")); + Assertions.assertNull(mgr.getKnoxClient("https://knox", "u", null)); + Assertions.assertNull(mgr.getKnoxClient("https://knox", "u", "")); + Assertions.assertNotNull(mgr.getKnoxClient("https://knox", "u", "p")); + } +} diff --git a/knox-agent/src/test/java/org/apache/ranger/services/knox/client/TestKnoxResourceMgr.java b/knox-agent/src/test/java/org/apache/ranger/services/knox/client/TestKnoxResourceMgr.java new file mode 100644 index 0000000000..3beeb1aaa3 --- /dev/null +++ b/knox-agent/src/test/java/org/apache/ranger/services/knox/client/TestKnoxResourceMgr.java @@ -0,0 +1,157 @@ +/** + * 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.services.knox.client; + +import org.apache.ranger.plugin.client.HadoopException; +import org.apache.ranger.plugin.service.ResourceLookupContext; +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; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @generated by Cursor + * @description + */ +@ExtendWith(MockitoExtension.class) +@TestMethodOrder(MethodOrderer.MethodName.class) +public class TestKnoxResourceMgr { + @Test + public void test01_validateConfig_success() { + Map cfg = new HashMap<>(); + cfg.put("knox.url", "https://knox"); + cfg.put("username", "u"); + cfg.put("password", "p"); + + try (MockedStatic kc = Mockito.mockStatic(KnoxClient.class)) { + kc.when(() -> KnoxClient.connectionTest("svc", cfg)).thenReturn(new HashMap() { + { + put("connectivityStatus", true); + put("message", "ok"); + put("description", "ok"); + } + }); + + Map resp = KnoxResourceMgr.validateConfig("svc", cfg); + Assertions.assertEquals(Boolean.TRUE, resp.get("connectivityStatus")); + } + } + + @Test + public void test02_validateConfig_exceptionPropagates() { + Map cfg = new HashMap<>(); + try (MockedStatic kc = Mockito.mockStatic(KnoxClient.class)) { + kc.when(() -> KnoxClient.connectionTest("svc", cfg)).thenThrow(new HadoopException("boom")); + Assertions.assertThrows(HadoopException.class, () -> KnoxResourceMgr.validateConfig("svc", cfg)); + } + } + + @Test + public void test03_getKnoxResources_missingConfig_returnsNull() { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("x"); + ctx.setResourceName("topology"); + + Map cfg = new HashMap<>(); + // missing all + Assertions.assertNull(KnoxResourceMgr.getKnoxResources("svc", cfg, ctx)); + + cfg.put("knox.url", ""); + Assertions.assertNull(KnoxResourceMgr.getKnoxResources("svc", cfg, ctx)); + + cfg.put("knox.url", "https://knox"); + Assertions.assertNull(KnoxResourceMgr.getKnoxResources("svc", cfg, ctx)); + + cfg.put("username", "u"); + Assertions.assertNull(KnoxResourceMgr.getKnoxResources("svc", cfg, ctx)); + } + + @Test + public void test04_getKnoxResources_topologyFlow() { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("adm"); + ctx.setResourceName("topology"); + + Map> resMap = new HashMap<>(); + resMap.put("topology", new ArrayList<>()); + ctx.setResources(resMap); + + Map cfg = new HashMap<>(); + cfg.put("knox.url", "https://knox"); + cfg.put("username", "u"); + cfg.put("password", "p"); + + try (MockedStatic kc = Mockito.mockStatic(KnoxClient.class)) { + kc.when(() -> KnoxClient.getKnoxResources(Mockito.any(KnoxClient.class), Mockito.eq("adm"), + Mockito.isNull(), Mockito.anyList(), Mockito.isNull())).thenReturn(new ArrayList() { + { + add("admin"); + } + }); + + // Use real call for connection mgr within KnoxResourceMgr + List result = KnoxResourceMgr.getKnoxResources("svc", cfg, ctx); + Assertions.assertNotNull(result); + Assertions.assertEquals(1, result.size()); + } + } + + @Test + public void test05_getKnoxResources_serviceFlow() { + ResourceLookupContext ctx = new ResourceLookupContext(); + ctx.setUserInput("HI"); + ctx.setResourceName("service"); + + Map> resMap = new HashMap<>(); + resMap.put("service", new ArrayList<>()); + resMap.put("topology", new ArrayList() { + { + add("top1"); + } + }); + ctx.setResources(resMap); + + Map cfg = new HashMap<>(); + cfg.put("knox.url", "https://knox"); + cfg.put("username", "u"); + cfg.put("password", "p"); + + try (MockedStatic kc = Mockito.mockStatic(KnoxClient.class)) { + kc.when(() -> KnoxClient.getKnoxResources(Mockito.any(KnoxClient.class), Mockito.isNull(), Mockito.eq("HI"), + Mockito.anyList(), Mockito.anyList())).thenReturn(new ArrayList() { + { + add("HIVE"); + } + }); + + List result = KnoxResourceMgr.getKnoxResources("svc", cfg, ctx); + Assertions.assertNotNull(result); + Assertions.assertEquals(1, result.size()); + } + } +}