blob: db9b540e62a7fc4e232c9d2be2a636fe7408eca2 [file] [log] [blame]
/*
* 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.hawq.ranger.authorization;
import org.apache.commons.collections.CollectionUtils;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hawq.ranger.authorization.model.AuthorizationRequest;
import org.apache.hawq.ranger.authorization.model.AuthorizationResponse;
import org.apache.hawq.ranger.authorization.model.HawqPrivilege;
import org.apache.hawq.ranger.authorization.model.HawqResource;
import org.apache.hawq.ranger.authorization.model.ResourceAccess;
import org.apache.ranger.plugin.policyengine.RangerAccessRequest;
import org.apache.ranger.plugin.policyengine.RangerAccessResult;
import org.apache.ranger.plugin.service.RangerBasePlugin;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.mockito.Mock;
import org.mockito.internal.util.collections.Sets;
import org.mockito.runners.MockitoJUnitRunner;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
import java.util.*;
import static junit.framework.TestCase.assertEquals;
import static junit.framework.TestCase.assertNotNull;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@RunWith(PowerMockRunner.class)
@PrepareForTest(UserGroupInformation.class)
public class RangerHawqAuthorizerTest {
private static final Integer TEST_REQUEST_ID = 1;
private static final String TEST_USER = "alex";
private static final String TEST_CLIENT = "1.2.3.4";
private static final String TEST_CONTEXT = "SELECT * FROM sales";
private static final Set<HawqPrivilege> TEST_PRIVILEGES = Sets.newSet(HawqPrivilege.select, HawqPrivilege.update);
private static final String TEST_RESOURCE_REQUEST =
"finance:us:sales>select,update#finance:emea:sales>create";
private static final String TEST_RESOURCE_RESPONSE_ALL_FALSE =
"finance:us:sales>select,update>false#finance:emea:sales>create>false";
private static final String TEST_RESOURCE_RESPONSE_ALL_TRUE =
"finance:us:sales>select,update>true#finance:emea:sales>create>true";
private static final String TEST_RESOURCE_RESPONSE_US_ALLOWED_EMEA_DENIED =
"finance:us:sales>select,update>true#finance:emea:sales>create>false";
private static final String TEST_RESOURCE_RESPONSE_UPDATE_DENIED =
"finance:us:sales>select,update>false#finance:emea:sales>create>true";
private static final String TEST_RESOURCE_REQUEST_CREATE_SCHEMA = "finance>create";
private static final String TEST_RESOURCE_RESPONSE_CREATE_SCHEMA = "finance>create>true";
private static final String TEST_RESOURCE_REQUEST_USAGE_SCHEMA = "finance:us>usage";
private static final String TEST_RESOURCE_RESPONSE_USAGE_SCHEMA = "finance:us>usage>true";
private RangerHawqAuthorizer authorizer;
@Mock
private RangerBasePlugin mockRangerPlugin;
@Mock
private RangerAccessResult mockRangerAccessResult;
@Before
public void setup() throws Exception {
authorizer = RangerHawqAuthorizer.getInstance();
authorizer.setRangerPlugin(mockRangerPlugin);
}
@Test
public void testAuthorize_allAllowed() throws Exception {
when(mockRangerPlugin.isAccessAllowed(any(RangerAccessRequest.class))).thenReturn(mockRangerAccessResult);
when(mockRangerAccessResult.getIsAllowed()).thenReturn(true);
testRequest(TEST_RESOURCE_REQUEST, TEST_RESOURCE_RESPONSE_ALL_TRUE);
}
@Test
public void testAuthorize_allDenied() throws Exception {
when(mockRangerPlugin.isAccessAllowed(any(RangerAccessRequest.class))).thenReturn(mockRangerAccessResult);
when(mockRangerAccessResult.getIsAllowed()).thenReturn(false);
testRequest(TEST_RESOURCE_REQUEST, TEST_RESOURCE_RESPONSE_ALL_FALSE);
}
@Test
public void testAuthorize_usAllowedEmeaDenied() throws Exception {
RangerAccessResult mockRangerAccessResultUS = mock(RangerAccessResult.class);
RangerAccessResult mockRangerAccessResultEMEA = mock(RangerAccessResult.class);
when(mockRangerPlugin.isAccessAllowed(argThat(new SchemaMatcher("us")))).thenReturn(mockRangerAccessResultUS);
when(mockRangerPlugin.isAccessAllowed(argThat(new SchemaMatcher("emea")))).thenReturn(mockRangerAccessResultEMEA);
when(mockRangerAccessResultUS.getIsAllowed()).thenReturn(true);
when(mockRangerAccessResultEMEA.getIsAllowed()).thenReturn(false);
testRequest(TEST_RESOURCE_REQUEST, TEST_RESOURCE_RESPONSE_US_ALLOWED_EMEA_DENIED);
}
@Test
public void testAuthorize_partialPrivilegeUpdateDenied() throws Exception {
RangerAccessResult mockRangerAccessResultCreateSelect = mock(RangerAccessResult.class);
RangerAccessResult mockRangerAccessResultUpdate = mock(RangerAccessResult.class);
when(mockRangerPlugin.isAccessAllowed(argThat(new PrivilegeMatcher("create", "select")))).thenReturn(mockRangerAccessResultCreateSelect);
when(mockRangerPlugin.isAccessAllowed(argThat(new PrivilegeMatcher("update")))).thenReturn(mockRangerAccessResultUpdate);
when(mockRangerAccessResultCreateSelect.getIsAllowed()).thenReturn(true);
when(mockRangerAccessResultUpdate.getIsAllowed()).thenReturn(false);
testRequest(TEST_RESOURCE_REQUEST, TEST_RESOURCE_RESPONSE_UPDATE_DENIED);
}
@Test
public void testAuthorize_createSchemaAllowed() throws Exception {
RangerAccessResult mockRangerAccessResultCreate = mock(RangerAccessResult.class);
when(mockRangerPlugin.isAccessAllowed(argThat(new PrivilegeMatcher("create-schema")))).thenReturn(mockRangerAccessResultCreate);
when(mockRangerAccessResultCreate.getIsAllowed()).thenReturn(true);
testRequest(TEST_RESOURCE_REQUEST_CREATE_SCHEMA, TEST_RESOURCE_RESPONSE_CREATE_SCHEMA);
}
@Test
public void testAuthorize_usageSchemaAllowed() throws Exception {
RangerAccessResult mockRangerAccessResultUsage = mock(RangerAccessResult.class);
when(mockRangerPlugin.isAccessAllowed(argThat(new PrivilegeMatcher("usage-schema")))).thenReturn(mockRangerAccessResultUsage);
when(mockRangerAccessResultUsage.getIsAllowed()).thenReturn(true);
testRequest(TEST_RESOURCE_REQUEST_USAGE_SCHEMA, TEST_RESOURCE_RESPONSE_USAGE_SCHEMA);
}
@Test
public void testAuthorize_allAllowed_group() throws Exception {
UserGroupInformation mockUgi = mock(UserGroupInformation.class);
when(mockUgi.getGroupNames()).thenReturn(new String[]{"foo", "bar"});
PowerMockito.mockStatic(UserGroupInformation.class);
when(UserGroupInformation.createRemoteUser(TEST_USER)).thenReturn(mockUgi);
when(mockRangerPlugin.isAccessAllowed(argThat(new UGIMatcher(TEST_USER, "foo", "bar")))).thenReturn(mockRangerAccessResult);
when(mockRangerAccessResult.getIsAllowed()).thenReturn(true);
testRequest(TEST_RESOURCE_REQUEST, TEST_RESOURCE_RESPONSE_ALL_TRUE);
}
@Test
public void testAuthorize_allAllowed_noGroup() throws Exception {
when(mockRangerPlugin.isAccessAllowed(argThat(new UGIMatcher(TEST_USER, null)))).thenReturn(mockRangerAccessResult);
when(mockRangerAccessResult.getIsAllowed()).thenReturn(true);
testRequest(TEST_RESOURCE_REQUEST, TEST_RESOURCE_RESPONSE_ALL_TRUE);
}
/* ----- VALIDATION TESTS ----- */
@Test(expected=IllegalArgumentException.class)
public void testAuthorize_validationFailure_requestId() {
AuthorizationRequest request = prepareRequest(null, TEST_USER, TEST_CLIENT, TEST_CONTEXT, TEST_RESOURCE_REQUEST);
authorizer.isAccessAllowed(request);
}
@Test(expected=IllegalArgumentException.class)
public void testAuthorize_validationFailure_user() {
AuthorizationRequest request = prepareRequest(TEST_REQUEST_ID, "", TEST_CLIENT, TEST_CONTEXT, TEST_RESOURCE_REQUEST);
authorizer.isAccessAllowed(request);
}
@Test(expected=IllegalArgumentException.class)
public void testAuthorize_validationFailure_client() {
AuthorizationRequest request = prepareRequest(TEST_REQUEST_ID, TEST_USER, "", TEST_CONTEXT, TEST_RESOURCE_REQUEST);
authorizer.isAccessAllowed(request);
}
@Test(expected=IllegalArgumentException.class)
public void testAuthorize_validationFailure_context() {
AuthorizationRequest request = prepareRequest(TEST_REQUEST_ID, TEST_USER, TEST_CLIENT, "", TEST_RESOURCE_REQUEST);
authorizer.isAccessAllowed(request);
}
@Test(expected=IllegalArgumentException.class)
public void testAuthorize_validationFailure_emptyAccessSet() {
AuthorizationRequest request = prepareRequest(TEST_REQUEST_ID, TEST_USER, TEST_CLIENT, TEST_CONTEXT, new HashSet<ResourceAccess>());
authorizer.isAccessAllowed(request);
}
@Test(expected=IllegalArgumentException.class)
public void testAuthorize_validationFailure_emptyResource() {
ResourceAccess resourceAccess = new ResourceAccess();
resourceAccess.setResource(new HashMap<HawqResource, String>());
resourceAccess.setPrivileges(TEST_PRIVILEGES);
AuthorizationRequest request = prepareRequest(TEST_REQUEST_ID, TEST_USER, TEST_CLIENT, TEST_CONTEXT, resourceAccess);
authorizer.isAccessAllowed(request);
}
@Test(expected=IllegalArgumentException.class)
public void testAuthorize_validationFailure_emptyResourceValue() {
ResourceAccess resourceAccess = new ResourceAccess();
HashMap<HawqResource, String> resource = new HashMap<>();
resource.put(HawqResource.database, "");
resourceAccess.setResource(resource);
resourceAccess.setPrivileges(TEST_PRIVILEGES);
AuthorizationRequest request = prepareRequest(TEST_REQUEST_ID, TEST_USER, TEST_CLIENT, TEST_CONTEXT, resourceAccess);
authorizer.isAccessAllowed(request);
}
@Test(expected=IllegalArgumentException.class)
public void testAuthorize_validationFailure_emptyPrivileges() {
ResourceAccess resourceAccess = new ResourceAccess();
HashMap<HawqResource, String> resource = new HashMap<>();
resource.put(HawqResource.database, "abc");
resourceAccess.setResource(resource);
resourceAccess.setPrivileges(new HashSet<HawqPrivilege>());
AuthorizationRequest request = prepareRequest(TEST_REQUEST_ID, TEST_USER, TEST_CLIENT, TEST_CONTEXT, resourceAccess);
authorizer.isAccessAllowed(request);
}
/* ----- HELPER METHODS ----- */
private void testRequest(String request, String expectedResponse) {
AuthorizationRequest authRequest = prepareRequest(TEST_REQUEST_ID, TEST_USER, TEST_CLIENT, TEST_CONTEXT, request);
AuthorizationResponse authResponse = authorizer.isAccessAllowed(authRequest);
validateResponse(authResponse, expectedResponse);
}
private AuthorizationRequest prepareRequest(
Integer requestId, String user, String clientIp, String context, Set<ResourceAccess> access) {
AuthorizationRequest request = new AuthorizationRequest();
request.setRequestId(requestId);
request.setUser(user);
request.setClientIp(clientIp);
request.setContext(context);
request.setAccess(access);
return request;
}
private AuthorizationRequest prepareRequest(
Integer requestId, String user, String clientIp, String context, ResourceAccess resourceAccess) {
Set<ResourceAccess> access = new HashSet<>();
access.add(resourceAccess);
return prepareRequest(requestId, user, clientIp, context, access);
}
private AuthorizationRequest prepareRequest(
Integer requestId, String user, String clientIp, String context, String resources) {
Set<ResourceAccess> access = new HashSet<>();
// resource string is like "db:schema:table>select,update#db:schema:table>create"
for (String resourceStr : resources.split("#")) {
String[] parts = resourceStr.split(">");
String[] resource = parts[0].split(":");
String[] privs = parts[1].split(",");
Map<HawqResource, String> tableResource = new HashMap<>();
tableResource.put(HawqResource.database, resource[0]);
if (resource.length > 1) {
tableResource.put(HawqResource.schema, resource[1]);
}
if (resource.length > 2) {
tableResource.put(HawqResource.table, resource[2]);
}
ResourceAccess tableAccess = new ResourceAccess();
tableAccess.setResource(tableResource);
Set<HawqPrivilege> privSet = new HashSet<>();
for (String priv : privs) {
privSet.add(HawqPrivilege.valueOf(priv));
}
tableAccess.setPrivileges(privSet);
access.add(tableAccess);
}
return prepareRequest(requestId, user, clientIp, context, access);
}
private void validateResponse(AuthorizationResponse response, String resources) {
assertNotNull(response);
Set<ResourceAccess> actual = response.getAccess();
Set<ResourceAccess> expected = new HashSet<>();
// resources string is like "db:schema:table>select,update>true#db:schema:table>create>false"
for (String resourceStr : resources.split("#")) {
String[] parts = resourceStr.split(">");
String[] resource = parts[0].split(":");
String[] privs = parts[1].split(",");
Boolean allowed = Boolean.valueOf(parts[2]);
Map<HawqResource, String> tableResource = new HashMap<>();
tableResource.put(HawqResource.database, resource[0]);
if (resource.length > 1) {
tableResource.put(HawqResource.schema, resource[1]);
}
if (resource.length > 2) {
tableResource.put(HawqResource.table, resource[2]);
}
ResourceAccess tableAccess = new ResourceAccess();
tableAccess.setResource(tableResource);
Set<HawqPrivilege> privSet = new HashSet<>();
for (String priv : privs) {
privSet.add(HawqPrivilege.fromString(priv));
}
tableAccess.setPrivileges(privSet);
tableAccess.setAllowed(allowed);
expected.add(tableAccess);
}
assertEquals(expected.size(), actual.size());
assertEquals(expected, actual);
}
/* ----- Argument Matchers ----- */
private class SchemaMatcher extends ArgumentMatcher<RangerAccessRequest> {
private String schema;
public SchemaMatcher(String schema) {
this.schema = schema;
}
@Override
public boolean matches(Object request) {
return request == null ? false :
schema.equals(((RangerAccessRequest) request).getResource().getAsMap().get("schema"));
}
};
private class PrivilegeMatcher extends ArgumentMatcher<RangerAccessRequest> {
private Set<String> privileges;
public PrivilegeMatcher(String... privileges) {
this.privileges = Sets.newSet(privileges);
}
@Override
public boolean matches(Object request) {
return request == null ? false :
privileges.contains(((RangerAccessRequest) request).getAccessType());
}
};
private class UGIMatcher extends ArgumentMatcher<RangerAccessRequest> {
private String user;
private Set<String> groups;
public UGIMatcher(String user, String... groups) {
this.user = user;
this.groups = groups == null ? Collections.<String>emptySet() : Sets.newSet(groups);
}
@Override
public boolean matches(Object request) {
return request == null ? false :
user.equals(((RangerAccessRequest) request).getUser()) &&
CollectionUtils.isEqualCollection(groups, (((RangerAccessRequest) request).getUserGroups()));
}
};
}