| // 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.impala.authorization; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.Sets; |
| import com.sun.jersey.api.client.ClientResponse; |
| import org.apache.impala.analysis.AnalysisContext; |
| import org.apache.impala.authorization.ranger.RangerAuthorizationChecker; |
| import org.apache.impala.authorization.ranger.RangerAuthorizationConfig; |
| import org.apache.impala.authorization.ranger.RangerAuthorizationFactory; |
| import org.apache.impala.authorization.ranger.RangerCatalogdAuthorizationManager; |
| import org.apache.impala.authorization.ranger.RangerImpalaPlugin; |
| import org.apache.impala.authorization.ranger.RangerImpalaResourceBuilder; |
| import org.apache.impala.authorization.sentry.SentryAuthorizationConfig; |
| import org.apache.impala.authorization.sentry.SentryAuthorizationFactory; |
| import org.apache.impala.authorization.sentry.SentryPolicyService; |
| import org.apache.impala.catalog.Role; |
| import org.apache.impala.catalog.ScalarFunction; |
| import org.apache.impala.catalog.Type; |
| import org.apache.impala.common.FrontendTestBase; |
| import org.apache.impala.common.ImpalaException; |
| import org.apache.impala.service.Frontend; |
| import org.apache.impala.testutil.ImpaladTestCatalog; |
| import org.apache.impala.thrift.TColumnValue; |
| import org.apache.impala.thrift.TDescribeOutputStyle; |
| import org.apache.impala.thrift.TDescribeResult; |
| import org.apache.impala.thrift.TFunctionBinaryType; |
| import org.apache.impala.thrift.TPrincipalType; |
| import org.apache.impala.thrift.TPrivilege; |
| import org.apache.impala.thrift.TPrivilegeLevel; |
| import org.apache.impala.thrift.TPrivilegeScope; |
| import org.apache.impala.thrift.TResultRow; |
| import org.apache.impala.thrift.TTableName; |
| import org.apache.ranger.plugin.util.GrantRevokeRequest; |
| import org.apache.ranger.plugin.util.RangerRESTClient; |
| import org.apache.ranger.plugin.util.RangerRESTUtils; |
| import org.json.simple.JSONArray; |
| import org.json.simple.JSONObject; |
| import org.json.simple.parser.JSONParser; |
| |
| import javax.ws.rs.core.Response; |
| import javax.ws.rs.core.Response.Status.Family; |
| import java.io.InputStreamReader; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| /** |
| * Base class for authorization tests. |
| */ |
| public abstract class AuthorizationTestBase extends FrontendTestBase { |
| protected static final String RANGER_ADMIN_URL = "http://localhost:6080"; |
| protected static final String RANGER_USER = "admin"; |
| protected static final String RANGER_PASSWORD = "admin"; |
| protected static final String SERVER_NAME = "server1"; |
| |
| // For the Ranger tests, 'OWNER_USER' is used to denote a requesting user that is |
| // the owner of the resource. |
| protected static final User OWNER_USER = new User(System.getProperty("user.name")); |
| // For the Ranger tests, 'GROUPS' is used to denote the name of the group where a |
| // non-owner is associated with. |
| protected static final List<String> GROUPS = Collections.singletonList("non_owner"); |
| // For the Ranger tests, 'OWNER_GROUPS' is used to denote the name of the group where |
| // an owner is associated with. |
| protected static final List<String> OWNER_GROUPS = |
| Collections.singletonList(System.getProperty("user.name")); |
| |
| protected static final String RANGER_SERVICE_TYPE = "hive"; |
| protected static final String RANGER_SERVICE_NAME = "test_impala"; |
| protected static final String RANGER_APP_ID = "impala"; |
| protected static final User RANGER_ADMIN = new User("admin"); |
| |
| // For the Ranger tests, 'user_' is used to denote a requesting user that is not the |
| // owner of the resource. Note that we defer the setup of 'user_' to the constructor |
| // and assign a different name for each authorization provider to avoid the need for |
| // providing a customized group mapping service when instantiating a Sentry |
| // ResourceAuthorizationProvider as we do in TestShortUsernameWithAuthToLocal() of |
| // AuthorizationTest.java. |
| protected static User user_ = null; |
| // For the Ranger tests, 'as_owner_' is used to indicate whether or not the requesting |
| // user in a test query is the owner of the resource. |
| protected static boolean as_owner_ = false; |
| |
| protected final AuthorizationConfig authzConfig_; |
| protected final AuthorizationFactory authzFactory_; |
| protected final AuthorizationProvider authzProvider_; |
| protected final AnalysisContext authzCtx_; |
| protected final SentryPolicyService sentryService_; |
| protected final ImpaladTestCatalog authzCatalog_; |
| protected final Frontend authzFrontend_; |
| protected final RangerImpalaPlugin rangerImpalaPlugin_; |
| protected final RangerRESTClient rangerRestClient_; |
| |
| public AuthorizationTestBase(AuthorizationProvider authzProvider) |
| throws ImpalaException { |
| authzProvider_ = authzProvider; |
| switch (authzProvider) { |
| case SENTRY: |
| user_ = new User(System.getProperty("user.name")); |
| authzConfig_ = SentryAuthorizationConfig.createHadoopGroupAuthConfig( |
| "server1", |
| System.getenv("IMPALA_HOME") + "/fe/src/test/resources/sentry-site.xml"); |
| authzFactory_ = createAuthorizationFactory(authzProvider); |
| authzCtx_ = createAnalysisCtx(authzFactory_, user_.getName()); |
| authzCatalog_ = new ImpaladTestCatalog(authzFactory_); |
| authzFrontend_ = new Frontend(authzFactory_, authzCatalog_); |
| sentryService_ = new SentryPolicyService( |
| ((SentryAuthorizationConfig) authzConfig_).getSentryConfig()); |
| rangerImpalaPlugin_ = null; |
| rangerRestClient_ = null; |
| break; |
| case RANGER: |
| user_ = new User("non_owner"); |
| authzConfig_ = new RangerAuthorizationConfig(RANGER_SERVICE_TYPE, RANGER_APP_ID, |
| SERVER_NAME); |
| authzFactory_ = createAuthorizationFactory(authzProvider); |
| authzCtx_ = createAnalysisCtx(authzFactory_, user_.getName()); |
| authzCatalog_ = new ImpaladTestCatalog(authzFactory_); |
| authzFrontend_ = new Frontend(authzFactory_, authzCatalog_); |
| rangerImpalaPlugin_ = |
| ((RangerAuthorizationChecker) authzFrontend_.getAuthzChecker()) |
| .getRangerImpalaPlugin(); |
| assertEquals("test-cluster", rangerImpalaPlugin_.getClusterName()); |
| sentryService_ = null; |
| rangerRestClient_ = new RangerRESTClient(RANGER_ADMIN_URL, null); |
| rangerRestClient_.setBasicAuthInfo(RANGER_USER, RANGER_PASSWORD); |
| break; |
| default: |
| throw new IllegalArgumentException(String.format( |
| "Unsupported authorization provider: %s", authzProvider)); |
| } |
| } |
| |
| protected AuthorizationFactory createAuthorizationFactory( |
| AuthorizationProvider authzProvider) { |
| return authzProvider == AuthorizationProvider.SENTRY ? |
| new SentryAuthorizationFactory(authzConfig_) : |
| new RangerAuthorizationFactory(authzConfig_); |
| } |
| |
| protected interface WithPrincipal { |
| void init(TPrivilege[]... privileges) throws ImpalaException; |
| void cleanUp() throws ImpalaException; |
| String getName(); |
| } |
| |
| protected abstract class WithSentryPrincipal implements WithPrincipal { |
| protected final String role_ = "authz_test_role"; |
| protected final String sentry_user_ = user_.getName(); |
| |
| protected void createRole(TPrivilege[]... privileges) throws ImpalaException { |
| Role role = authzCatalog_.addRole(role_); |
| authzCatalog_.addRoleGrantGroup(role_, sentry_user_); |
| for (TPrivilege[] privs: privileges) { |
| for (TPrivilege privilege: privs) { |
| privilege.setPrincipal_id(role.getId()); |
| privilege.setPrincipal_type(TPrincipalType.ROLE); |
| authzCatalog_.addRolePrivilege(role_, privilege); |
| } |
| } |
| } |
| |
| protected void createUser(TPrivilege[]... privileges) throws ImpalaException { |
| org.apache.impala.catalog.User user = authzCatalog_.addUser(sentry_user_); |
| for (TPrivilege[] privs: privileges) { |
| for (TPrivilege privilege: privs) { |
| privilege.setPrincipal_id(user.getId()); |
| privilege.setPrincipal_type(TPrincipalType.USER); |
| authzCatalog_.addUserPrivilege(sentry_user_, privilege); |
| } |
| } |
| } |
| |
| protected void dropRole() throws ImpalaException { |
| authzCatalog_.removeRole(role_); |
| } |
| |
| protected void dropUser() throws ImpalaException { |
| authzCatalog_.removeUser(sentry_user_); |
| } |
| } |
| |
| public class WithSentryUser extends WithSentryPrincipal { |
| @Override |
| public void init(TPrivilege[]... privileges) throws ImpalaException { |
| createUser(privileges); |
| } |
| |
| @Override |
| public void cleanUp() throws ImpalaException { dropUser(); } |
| |
| @Override |
| public String getName() { return sentry_user_; } |
| } |
| |
| public class WithSentryRole extends WithSentryPrincipal { |
| @Override |
| public void init(TPrivilege[]... privileges) throws ImpalaException { |
| createRole(privileges); |
| } |
| |
| @Override |
| public void cleanUp() throws ImpalaException { dropRole(); } |
| |
| @Override |
| public String getName() { return role_; } |
| } |
| |
| protected abstract class WithRanger implements WithPrincipal { |
| private final List<GrantRevokeRequest> grants = new ArrayList<>(); |
| private final RangerCatalogdAuthorizationManager authzManager = |
| new RangerCatalogdAuthorizationManager(() -> rangerImpalaPlugin_, null); |
| |
| @Override |
| public void init(TPrivilege[]... privileges) throws ImpalaException { |
| for (TPrivilege[] privilege : privileges) { |
| grants.addAll(buildRequest(Arrays.asList(privilege)) |
| .stream() |
| .peek(r -> { |
| r.setResource(updateUri(r.getResource())); |
| if (r.getAccessTypes().contains("owner")) { |
| r.getAccessTypes().remove("owner"); |
| r.getAccessTypes().add("all"); |
| } |
| }).collect(Collectors.toList())); |
| } |
| |
| authzManager.grantPrivilege(grants, "", "127.0.0.1"); |
| rangerImpalaPlugin_.refreshPoliciesAndTags(); |
| } |
| |
| /** |
| * Create the {@link GrantRevokeRequest}s used for granting and revoking privileges. |
| */ |
| protected abstract List<GrantRevokeRequest> buildRequest(List<TPrivilege> privileges); |
| |
| @Override |
| public void cleanUp() throws ImpalaException { |
| authzManager.revokePrivilege(grants, "", "127.0.0.1"); |
| } |
| |
| /** |
| * Depending on whether or not the principal is the owner of the resource, we return |
| * either 'OWNER_USER' or 'user_'. This function is used in authzOk() and |
| * anthzError(). |
| */ |
| @Override |
| public String getName() { |
| return (as_owner_ ? OWNER_USER.getName() : user_.getName()); |
| } |
| } |
| |
| public class WithRangerUser extends WithRanger { |
| @Override |
| protected List<GrantRevokeRequest> buildRequest(List<TPrivilege> privileges) { |
| return RangerCatalogdAuthorizationManager.createGrantRevokeRequests( |
| RANGER_ADMIN.getName(), true, |
| // We provide the name of the grantee, which is a user in this case, according |
| // to whether or not we test the query with the requesting user that is the |
| // owner of the resource. |
| getName(), |
| Collections.emptyList(), |
| rangerImpalaPlugin_.getClusterName(), "127.0.0.1", privileges); |
| } |
| } |
| |
| public class WithRangerGroup extends WithRanger { |
| @Override |
| protected List<GrantRevokeRequest> buildRequest(List<TPrivilege> privileges) { |
| return RangerCatalogdAuthorizationManager.createGrantRevokeRequests( |
| RANGER_ADMIN.getName(), true, null, |
| // We provide the name of the grantee, which is a group in this case, according |
| // to whether or not we test the query with the requesting user that is the |
| // owner of the resource. |
| (as_owner_ ? OWNER_GROUPS : GROUPS), |
| // groups, |
| rangerImpalaPlugin_.getClusterName(), "127.0.0.1", privileges); |
| } |
| } |
| |
| private static Map<String, String> updateUri(Map<String, String> resources) { |
| String uri = resources.get(RangerImpalaResourceBuilder.URL); |
| if (uri != null && uri.startsWith("/")) { |
| uri = "hdfs://localhost:20500" + uri; |
| } |
| resources.put(RangerImpalaResourceBuilder.URL, uri); |
| |
| return resources; |
| } |
| |
| protected class DescribeOutput { |
| private String[] excludedStrings_ = new String[0]; |
| private String[] includedStrings_ = new String[0]; |
| private final TDescribeOutputStyle outputStyle_; |
| |
| public DescribeOutput(TDescribeOutputStyle style) { |
| outputStyle_ = style; |
| } |
| |
| /** |
| * Indicates which strings must not appear in the output of the describe statement. |
| * During validation, if one of these strings exists, an assertion is thrown. |
| * |
| * @param excluded - Array of strings that must not exist in the output. |
| * @return DescribeOutput instance. |
| */ |
| public DescribeOutput excludeStrings(String[] excluded) { |
| excludedStrings_ = excluded; |
| return this; |
| } |
| |
| /** |
| * Indicates which strings are required to appear in the output of the describe |
| * statement. During validation, if any one of these strings does not exist, an |
| * assertion is thrown. |
| * |
| * @param included - Array of strings that must exist in the output. |
| * @return DescribeOutput instance. |
| */ |
| public DescribeOutput includeStrings(String[] included) { |
| includedStrings_ = included; |
| return this; |
| } |
| |
| public void validate(TTableName table) throws ImpalaException { |
| Preconditions.checkArgument(includedStrings_.length != 0 || |
| excludedStrings_.length != 0, |
| "One or both of included or excluded strings must be defined."); |
| List<String> result = resultToStringList(authzFrontend_.describeTable(table, |
| outputStyle_, user_)); |
| for (String str: includedStrings_) { |
| assertTrue(String.format("\"%s\" is not in the describe output.\n" + |
| "Expected : %s\n Actual : %s", str, Arrays.toString(includedStrings_), |
| result), result.contains(str)); |
| } |
| for (String str: excludedStrings_) { |
| assertTrue(String.format("\"%s\" should not be in the describe output.", str), |
| !result.contains(str)); |
| } |
| } |
| } |
| |
| protected DescribeOutput describeOutput(TDescribeOutputStyle style) { |
| return new DescribeOutput(style); |
| } |
| |
| protected List<WithPrincipal> buildWithPrincipals() { |
| List<WithPrincipal> withPrincipals = new ArrayList<>(); |
| switch (authzProvider_) { |
| case SENTRY: |
| withPrincipals.add(new WithSentryRole()); |
| withPrincipals.add(new WithSentryUser()); |
| break; |
| case RANGER: |
| withPrincipals.add(new WithRangerUser()); |
| withPrincipals.add(new WithRangerGroup()); |
| break; |
| default: |
| throw new IllegalArgumentException(String.format( |
| "Unsupported authorization provider: %s", authzProvider_)); |
| } |
| return withPrincipals; |
| } |
| |
| protected class AuthzTest { |
| private final AnalysisContext context_; |
| private final String stmt_; |
| |
| public AuthzTest(String stmt) { |
| this(null, stmt); |
| } |
| |
| public AuthzTest(AnalysisContext context, String stmt) { |
| Preconditions.checkNotNull(stmt); |
| context_ = context; |
| stmt_ = stmt; |
| } |
| |
| /** |
| * This method runs with the specified privileges. |
| */ |
| public AuthzTest ok(TPrivilege[]... privileges) |
| throws ImpalaException { |
| for (WithPrincipal withPrincipal: buildWithPrincipals()) { |
| try { |
| withPrincipal.init(privileges); |
| if (context_ != null) { |
| authzOk(context_, stmt_, withPrincipal); |
| } else { |
| authzOk(stmt_, withPrincipal); |
| } |
| } finally { |
| withPrincipal.cleanUp(); |
| } |
| } |
| return this; |
| } |
| |
| /** |
| * This method runs with the specified privileges. |
| */ |
| public AuthzTest okDescribe(TTableName table, DescribeOutput output, |
| TPrivilege[]... privileges) throws ImpalaException { |
| for (WithPrincipal withPrincipal: buildWithPrincipals()) { |
| try { |
| withPrincipal.init(privileges); |
| if (context_ != null) { |
| authzOk(context_, stmt_, withPrincipal); |
| } else { |
| authzOk(stmt_, withPrincipal); |
| } |
| output.validate(table); |
| } finally { |
| withPrincipal.cleanUp(); |
| } |
| } |
| return this; |
| } |
| |
| /** |
| * This method runs with the specified privileges. |
| */ |
| public AuthzTest error(String expectedError, TPrivilege[]... privileges) |
| throws ImpalaException { |
| for (WithPrincipal withPrincipal: buildWithPrincipals()) { |
| try { |
| withPrincipal.init(privileges); |
| if (context_ != null) { |
| authzError(context_, stmt_, expectedError, withPrincipal); |
| } else { |
| authzError(stmt_, expectedError, withPrincipal); |
| } |
| } finally { |
| withPrincipal.cleanUp(); |
| } |
| } |
| return this; |
| } |
| } |
| |
| protected AuthzTest authorize(String stmt) { |
| return new AuthzTest(stmt); |
| } |
| |
| protected AuthzTest authorize(AnalysisContext ctx, String stmt) { |
| return new AuthzTest(ctx, stmt); |
| } |
| |
| protected TPrivilege[] onServer(TPrivilegeLevel... levels) { |
| return onServer(false, levels); |
| } |
| |
| protected TPrivilege[] onServer(boolean grantOption, TPrivilegeLevel... levels) { |
| TPrivilege[] privileges = new TPrivilege[levels.length]; |
| for (int i = 0; i < levels.length; i++) { |
| privileges[i] = new TPrivilege(levels[i], TPrivilegeScope.SERVER, false); |
| privileges[i].setServer_name(SERVER_NAME); |
| privileges[i].setHas_grant_opt(grantOption); |
| } |
| return privileges; |
| } |
| |
| protected TPrivilege[] onDatabase(String db, TPrivilegeLevel... levels) { |
| return onDatabase(false, db, levels); |
| } |
| |
| protected TPrivilege[] onDatabase(boolean grantOption, String db, |
| TPrivilegeLevel... levels) { |
| TPrivilege[] privileges = new TPrivilege[levels.length]; |
| for (int i = 0; i < levels.length; i++) { |
| privileges[i] = new TPrivilege(levels[i], TPrivilegeScope.DATABASE, false); |
| privileges[i].setServer_name(SERVER_NAME); |
| privileges[i].setDb_name(db); |
| privileges[i].setHas_grant_opt(grantOption); |
| } |
| return privileges; |
| } |
| |
| protected TPrivilege[] onTable(String db, String table, TPrivilegeLevel... levels) { |
| return onTable(false, db, table, levels); |
| } |
| |
| protected TPrivilege[] onTable(boolean grantOption, String db, String table, |
| TPrivilegeLevel... levels) { |
| TPrivilege[] privileges = new TPrivilege[levels.length]; |
| for (int i = 0; i < levels.length; i++) { |
| privileges[i] = new TPrivilege(levels[i], TPrivilegeScope.TABLE, false); |
| privileges[i].setServer_name(SERVER_NAME); |
| privileges[i].setDb_name(db); |
| privileges[i].setTable_name(table); |
| privileges[i].setHas_grant_opt(grantOption); |
| } |
| return privileges; |
| } |
| |
| protected TPrivilege[] onColumn(String db, String table, String column, |
| TPrivilegeLevel... levels) { |
| return onColumn(db, table, new String[]{column}, levels); |
| } |
| |
| protected TPrivilege[] onColumn(boolean grantOption, String db, String table, |
| String column, TPrivilegeLevel... levels) { |
| return onColumn(grantOption, db, table, new String[]{column}, levels); |
| } |
| |
| protected TPrivilege[] onColumn(String db, String table, String[] columns, |
| TPrivilegeLevel... levels) { |
| return onColumn(false, db, table, columns, levels); |
| } |
| |
| protected TPrivilege[] onColumn(boolean grantOption, String db, String table, |
| String[] columns, TPrivilegeLevel... levels) { |
| int size = columns.length * levels.length; |
| TPrivilege[] privileges = new TPrivilege[size]; |
| int idx = 0; |
| for (int i = 0; i < levels.length; i++) { |
| for (String column: columns) { |
| privileges[idx] = new TPrivilege(levels[i], TPrivilegeScope.COLUMN, false); |
| privileges[idx].setServer_name(SERVER_NAME); |
| privileges[idx].setDb_name(db); |
| privileges[idx].setTable_name(table); |
| privileges[idx].setColumn_name(column); |
| privileges[idx].setHas_grant_opt(grantOption); |
| idx++; |
| } |
| } |
| return privileges; |
| } |
| |
| protected TPrivilege[] onUri(String uri, TPrivilegeLevel... levels) { |
| return onUri(false, uri, levels); |
| } |
| |
| protected TPrivilege[] onUri(boolean grantOption, String uri, |
| TPrivilegeLevel... levels) { |
| TPrivilege[] privileges = new TPrivilege[levels.length]; |
| for (int i = 0; i < levels.length; i++) { |
| privileges[i] = new TPrivilege(levels[i], TPrivilegeScope.URI, false); |
| privileges[i].setServer_name(SERVER_NAME); |
| privileges[i].setUri(uri); |
| privileges[i].setHas_grant_opt(grantOption); |
| } |
| return privileges; |
| } |
| |
| private void authzOk(String stmt, WithPrincipal withPrincipal) throws ImpalaException { |
| authzOk(authzCtx_, stmt, withPrincipal); |
| } |
| |
| private void authzOk(AnalysisContext context, String stmt, WithPrincipal withPrincipal) |
| throws ImpalaException { |
| try { |
| parseAndAnalyze(stmt, context, authzFrontend_); |
| } catch (AuthorizationException e) { |
| // Because the same test can be called from multiple statements |
| // it is useful to know which statement caused the exception. |
| throw new AuthorizationException(String.format( |
| "\nPrincipal: %s\nStatement: %s\nError: %s", withPrincipal.getName(), |
| stmt, e.getMessage(), e)); |
| } |
| } |
| |
| /** |
| * Verifies that a given statement fails authorization and the expected error |
| * string matches. |
| */ |
| private void authzError(String stmt, String expectedError, |
| WithPrincipal withPrincipal) throws ImpalaException { |
| authzError(authzCtx_, stmt, expectedError, startsWith(), withPrincipal); |
| } |
| |
| private void authzError(AnalysisContext context, String stmt, String expectedError, |
| WithPrincipal withPrincipal) throws ImpalaException { |
| authzError(context, stmt, expectedError, startsWith(), withPrincipal); |
| } |
| |
| @FunctionalInterface |
| private interface Matcher { |
| boolean match(String actual, String expected); |
| } |
| |
| private static Matcher exact() { |
| return (actual, expected) -> actual.equals(expected); |
| } |
| |
| private static Matcher startsWith() { |
| return (actual, expected) -> actual.startsWith(expected); |
| } |
| |
| private void authzError(AnalysisContext ctx, String stmt, |
| String expectedErrorString, Matcher matcher, WithPrincipal withPrincipal) |
| throws ImpalaException { |
| Preconditions.checkNotNull(expectedErrorString); |
| try { |
| parseAndAnalyze(stmt, ctx, authzFrontend_); |
| } catch (AuthorizationException e) { |
| // Insert the username into the error. |
| expectedErrorString = String.format(expectedErrorString, ctx.getUser()); |
| String errorString = e.getMessage(); |
| assertTrue( |
| "got error:\n" + errorString + "\nexpected:\n" + expectedErrorString, |
| matcher.match(errorString, expectedErrorString)); |
| return; |
| } |
| fail(String.format("Statement did not result in authorization error.\n" + |
| "Principal: %s\nStatement: %s", withPrincipal.getName(), stmt)); |
| } |
| |
| protected void createRangerPolicy(String policyName, String json) { |
| ClientResponse response = rangerRestClient_ |
| .getResource("/service/public/v2/api/policy") |
| .accept(RangerRESTUtils.REST_MIME_TYPE_JSON) |
| .type(RangerRESTUtils.REST_MIME_TYPE_JSON) |
| .post(ClientResponse.class, json); |
| if (response.getStatusInfo().getFamily() != Family.SUCCESSFUL) { |
| throw new RuntimeException(String.format( |
| "Unable to create a Ranger policy: %s Response: %s", |
| policyName, response.getEntity(String.class))); |
| } |
| } |
| |
| protected void deleteRangerPolicy(String policyName) { |
| ClientResponse response = rangerRestClient_ |
| .getResource("/service/public/v2/api/policy") |
| .queryParam("servicename", RANGER_SERVICE_NAME) |
| .queryParam("policyname", policyName) |
| .delete(ClientResponse.class); |
| if (response.getStatusInfo().getFamily() != Family.SUCCESSFUL) { |
| throw new RuntimeException( |
| String.format("Unable to delete Ranger policy: %s.", policyName)); |
| } |
| } |
| |
| protected void clearRangerRowFilterPolicies(String dbName, String tableName) { |
| ClientResponse response = rangerRestClient_ |
| .getResource("/service/public/v2/api/policy") |
| .queryParam("servicename", RANGER_SERVICE_NAME) |
| .queryParam("policyType", "2") // Row filter policy type: "2" |
| .queryParam("databases", dbName) |
| .queryParam("tables", tableName) |
| .get(ClientResponse.class); |
| if (response.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) { |
| throw new RuntimeException(String.format( |
| "Unable to search Ranger policies on %s.%s", dbName, tableName)); |
| } |
| JSONParser parser = new JSONParser(); |
| JSONArray policies; |
| try { |
| policies = (JSONArray) parser.parse( |
| new InputStreamReader(response.getEntityInputStream())); |
| } catch (Exception e) { |
| throw new RuntimeException("Error parsing ranger response", e); |
| } |
| for (Object obj: policies) { |
| JSONObject policy = (JSONObject) obj; |
| deleteRangerPolicy(policy.get("name").toString()); |
| } |
| } |
| |
| // Convert TDescribeResult to list of strings. |
| private static List<String> resultToStringList(TDescribeResult result) { |
| List<String> list = new ArrayList<>(); |
| for (TResultRow row: result.getResults()) { |
| for (TColumnValue col: row.getColVals()) { |
| list.add(col.getString_val() == null ? "NULL": col.getString_val().trim()); |
| } |
| } |
| return list; |
| } |
| |
| protected static String selectError(String object) { |
| return "User '%s' does not have privileges to execute 'SELECT' on: " + object; |
| } |
| |
| protected static String insertError(String object) { |
| return "User '%s' does not have privileges to execute 'INSERT' on: " + object; |
| } |
| |
| protected static String createError(String object) { |
| return "User '%s' does not have privileges to execute 'CREATE' on: " + object; |
| } |
| |
| protected static String alterError(String object) { |
| return "User '%s' does not have privileges to execute 'ALTER' on: " + object; |
| } |
| |
| protected static String dropError(String object) { |
| return "User '%s' does not have privileges to execute 'DROP' on: " + object; |
| } |
| |
| protected static String accessError(String object) { |
| return accessError(false, object); |
| } |
| |
| protected static String accessError(boolean grantOption, String object) { |
| return "User '%s' does not have privileges" + |
| (grantOption ? " with 'GRANT OPTION'" : "") + " to access: " + object; |
| } |
| |
| protected static String refreshError(String object) { |
| return "User '%s' does not have privileges to execute " + |
| "'INVALIDATE METADATA/REFRESH' on: " + object; |
| } |
| |
| protected static String systemDbError() { |
| return "Cannot modify system database."; |
| } |
| |
| protected static String viewDefError(String object) { |
| return "User '%s' does not have privileges to see the definition of view '" + |
| object + "'."; |
| } |
| |
| protected static String createFunctionError(String object) { |
| return "User '%s' does not have privileges to CREATE functions in: " + object; |
| } |
| |
| protected static String dropFunctionError(String object) { |
| return "User '%s' does not have privileges to DROP functions in: " + object; |
| } |
| |
| protected static String accessFunctionError(String object) { |
| return "User '%s' does not have privileges to ANY functions in: " + object; |
| } |
| |
| protected static String columnMaskError(String object) { |
| return "Impala does not support column masking yet. Column masking is enabled on " + |
| "column: " + object; |
| } |
| |
| protected static String rowFilterError(String object) { |
| return "Impala does not support row filtering yet. Row filtering is enabled on " + |
| "table: " + object; |
| } |
| |
| protected ScalarFunction addFunction(String db, String fnName, List<Type> argTypes, |
| Type retType, String uriPath, String symbolName) { |
| ScalarFunction fn = ScalarFunction.createForTesting(db, fnName, argTypes, retType, |
| uriPath, symbolName, null, null, TFunctionBinaryType.NATIVE); |
| authzCatalog_.addFunction(fn); |
| return fn; |
| } |
| |
| protected ScalarFunction addFunction(String db, String fnName) { |
| return addFunction(db, fnName, new ArrayList<>(), Type.INT, "/dummy", |
| "dummy.class"); |
| } |
| |
| protected void removeFunction(ScalarFunction fn) { |
| authzCatalog_.removeFunction(fn); |
| } |
| |
| protected TPrivilegeLevel[] join(TPrivilegeLevel[] level1, TPrivilegeLevel... level2) { |
| TPrivilegeLevel[] levels = new TPrivilegeLevel[level1.length + level2.length]; |
| int index = 0; |
| for (TPrivilegeLevel level: level1) { |
| levels[index++] = level; |
| } |
| for (TPrivilegeLevel level: level2) { |
| levels[index++] = level; |
| } |
| return levels; |
| } |
| |
| protected TPrivilegeLevel[] viewMetadataPrivileges() { |
| return new TPrivilegeLevel[]{TPrivilegeLevel.ALL, TPrivilegeLevel.OWNER, |
| TPrivilegeLevel.SELECT, TPrivilegeLevel.INSERT, TPrivilegeLevel.REFRESH}; |
| } |
| |
| protected static TPrivilegeLevel[] allExcept(TPrivilegeLevel... excludedPrivLevels) { |
| Set<TPrivilegeLevel> excludedSet = Sets.newHashSet(excludedPrivLevels); |
| List<TPrivilegeLevel> privLevels = new ArrayList<>(); |
| for (TPrivilegeLevel level: TPrivilegeLevel.values()) { |
| if (!excludedSet.contains(level)) { |
| privLevels.add(level); |
| } |
| } |
| return privLevels.toArray(new TPrivilegeLevel[0]); |
| } |
| } |