You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sentry.apache.org by sp...@apache.org on 2018/05/31 03:32:40 UTC
[62/86] sentry git commit: SENTRY-2208: Refactor out Sentry service
into own module from sentry-provider-db (Anthony Young-Garner,
reviewed by Sergio Pena, Steve Moist, Na Li)
http://git-wip-us.apache.org/repos/asf/sentry/blob/7db84b2f/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/DelegateSentryStore.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/DelegateSentryStore.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/DelegateSentryStore.java
new file mode 100644
index 0000000..3026a62
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/DelegateSentryStore.java
@@ -0,0 +1,422 @@
+/**
+ * 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.sentry.provider.db.generic.service.persistent;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.sentry.core.common.Authorizable;
+import org.apache.sentry.core.common.exception.SentryAccessDeniedException;
+import org.apache.sentry.core.common.exception.SentryGrantDeniedException;
+import org.apache.sentry.core.common.exception.SentryInvalidInputException;
+import org.apache.sentry.core.common.exception.SentryNoSuchObjectException;
+import org.apache.sentry.core.common.exception.SentryUserException;
+import org.apache.sentry.provider.db.service.model.MSentryGMPrivilege;
+import org.apache.sentry.provider.db.service.model.MSentryGroup;
+import org.apache.sentry.provider.db.service.model.MSentryRole;
+import org.apache.sentry.provider.db.service.persistent.SentryStore;
+import org.apache.sentry.api.service.thrift.SentryPolicyStoreProcessor;
+import org.apache.sentry.api.service.thrift.TSentryGroup;
+import org.apache.sentry.api.service.thrift.TSentryRole;
+import org.apache.sentry.service.common.ServiceConstants.ServerConfig;
+
+import javax.jdo.PersistenceManager;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * The DelegateSentryStore will supports the generic authorizable model. It stores the authorizables
+ * into separated column. Take the authorizables:[DATABASE=db1,TABLE=tb1,COLUMN=cl1] for example,
+ * The DATABASE,db1,TABLE,tb1,COLUMN and cl1 will be stored into the six columns(resourceName0=db1,resourceType0=DATABASE,
+ * resourceName1=tb1,resourceType1=TABLE,
+ * resourceName2=cl1,resourceType2=COLUMN ) of generic privilege table
+ */
+public class DelegateSentryStore implements SentryStoreLayer {
+ private SentryStore delegate;
+ private Configuration conf;
+ private Set<String> adminGroups;
+ private PrivilegeOperatePersistence privilegeOperator;
+
+ public DelegateSentryStore(Configuration conf) throws Exception {
+ this.privilegeOperator = new PrivilegeOperatePersistence(conf);
+ this.conf = conf;
+ //delegated old sentryStore
+ this.delegate = new SentryStore(conf);
+ adminGroups = ImmutableSet.copyOf(toTrimmed(Sets.newHashSet(conf.getStrings(
+ ServerConfig.ADMIN_GROUPS, new String[]{}))));
+ }
+
+ private MSentryRole getRole(String roleName, PersistenceManager pm) {
+ return delegate.getRole(pm, roleName);
+ }
+
+ @Override
+ public Object createRole(String component, String role,
+ String requestor) throws Exception {
+ delegate.createSentryRole(role);
+ return null;
+ }
+
+ /**
+ * The role is global in the generic model, such as the role may be has more than one component
+ * privileges, so delete role will remove all privileges related to it.
+ */
+ @Override
+ public Object dropRole(final String component, final String role, final String requestor)
+ throws Exception {
+ delegate.dropSentryRole(toTrimmedLower(role));
+ return null;
+ }
+
+ @Override
+ public Set<String> getAllRoleNames() throws Exception {
+ return delegate.getAllRoleNames();
+ }
+
+ @Override
+ public Object alterRoleAddGroups(String component, String role,
+ Set<String> groups, String requestor) throws Exception {
+ delegate.alterSentryRoleAddGroups(requestor, role, toTSentryGroups(groups));
+ return null;
+ }
+
+ @Override
+ public Object alterRoleDeleteGroups(String component, String role,
+ Set<String> groups, String requestor) throws Exception {
+ delegate.alterSentryRoleDeleteGroups(role, toTSentryGroups(groups));
+ return null;
+ }
+
+ @Override
+ public Object alterRoleGrantPrivilege(final String component, final String role,
+ final PrivilegeObject privilege, final String grantorPrincipal)
+ throws Exception {
+ delegate.getTransactionManager().executeTransactionWithRetry(
+ pm -> {
+ pm.setDetachAllOnCommit(false); // No need to detach objects
+ String trimmedRole = toTrimmedLower(role);
+ MSentryRole mRole = getRole(trimmedRole, pm);
+ if (mRole == null) {
+ throw new SentryNoSuchObjectException("Role: " + trimmedRole);
+ }
+
+ // check with grant option
+ grantOptionCheck(privilege, grantorPrincipal, pm);
+
+ privilegeOperator.grantPrivilege(privilege, mRole, pm);
+ return null;
+ });
+ return null;
+ }
+
+ @Override
+ public Object alterRoleRevokePrivilege(final String component,
+ final String role, final PrivilegeObject privilege, final String grantorPrincipal)
+ throws Exception {
+ delegate.getTransactionManager().executeTransactionWithRetry(
+ pm -> {
+ pm.setDetachAllOnCommit(false); // No need to detach objects
+ String trimmedRole = toTrimmedLower(role);
+ MSentryRole mRole = getRole(trimmedRole, pm);
+ if (mRole == null) {
+ throw new SentryNoSuchObjectException("Role: " + trimmedRole);
+ }
+
+ // check with grant option
+ grantOptionCheck(privilege, grantorPrincipal, pm);
+
+ privilegeOperator.revokePrivilege(privilege, mRole, pm);
+ return null;
+ });
+ return null;
+ }
+
+ @Override
+ public Object renamePrivilege(final String component, final String service,
+ final List<? extends Authorizable> oldAuthorizables,
+ final List<? extends Authorizable> newAuthorizables, final String requestor)
+ throws Exception {
+ Preconditions.checkNotNull(component);
+ Preconditions.checkNotNull(service);
+ Preconditions.checkNotNull(oldAuthorizables);
+ Preconditions.checkNotNull(newAuthorizables);
+
+ if (oldAuthorizables.size() != newAuthorizables.size()) {
+ throw new SentryAccessDeniedException(
+ "rename privilege denied: the size of oldAuthorizables must equals the newAuthorizables "
+ + "oldAuthorizables:" + Arrays.toString(oldAuthorizables.toArray()) + " "
+ + "newAuthorizables:" + Arrays.toString(newAuthorizables.toArray()));
+ }
+
+ delegate.getTransactionManager().executeTransactionWithRetry(
+ pm -> {
+ pm.setDetachAllOnCommit(false); // No need to detach objects
+ privilegeOperator.renamePrivilege(toTrimmedLower(component), toTrimmedLower(service),
+ oldAuthorizables, newAuthorizables, requestor, pm);
+ return null;
+ });
+ return null;
+ }
+
+ @Override
+ public Object dropPrivilege(final String component,
+ final PrivilegeObject privilege, final String requestor) throws Exception {
+ Preconditions.checkNotNull(requestor);
+
+ delegate.getTransactionManager().executeTransactionWithRetry(
+ pm -> {
+ pm.setDetachAllOnCommit(false); // No need to detach objects
+ privilegeOperator.dropPrivilege(privilege, pm);
+ return null;
+ });
+ return null;
+ }
+
+ /**
+ * Grant option check
+ * @throws SentryUserException
+ */
+ private void grantOptionCheck(PrivilegeObject requestPrivilege,
+ String grantorPrincipal,PersistenceManager pm)
+ throws SentryUserException {
+
+ if (Strings.isNullOrEmpty(grantorPrincipal)) {
+ throw new SentryInvalidInputException("grantorPrincipal should not be null or empty");
+ }
+
+ Set<String> groups = getRequestorGroups(grantorPrincipal);
+ if (groups == null || groups.isEmpty()) {
+ throw new SentryGrantDeniedException(grantorPrincipal
+ + " has no grant!");
+ }
+ //admin group check
+ if (!Sets.intersection(adminGroups, toTrimmed(groups)).isEmpty()) {
+ return;
+ }
+ //privilege grant option check
+ Set<MSentryRole> mRoles = delegate.getRolesForGroups(pm, groups);
+ if (!privilegeOperator.checkPrivilegeOption(mRoles, requestPrivilege, pm)) {
+ throw new SentryGrantDeniedException(grantorPrincipal
+ + " has no grant!");
+ }
+ }
+
+ @Override
+ public Set<String> getRolesByGroups(String component, Set<String> groups)
+ throws Exception {
+ if (groups == null || groups.isEmpty()) {
+ return Collections.emptySet();
+ }
+
+ Set<String> roles = Sets.newHashSet();
+ for (TSentryRole tSentryRole : delegate.getTSentryRolesByGroupName(groups,
+ true)) {
+ roles.add(tSentryRole.getRoleName());
+ }
+ return roles;
+ }
+
+ @Override
+ public Set<String> getGroupsByRoles(final String component, final Set<String> roles)
+ throws Exception {
+ // In all calls roles contain exactly one group
+ if (roles.isEmpty()) {
+ return Collections.emptySet();
+ }
+
+ // Collect resulting group names in a set
+ Set<String> groupNames = new HashSet<>();
+ for (String role : roles) {
+ MSentryRole sentryRole = null;
+ try {
+ sentryRole = delegate.getMSentryRoleByName(role);
+ }
+ catch (SentryNoSuchObjectException e) {
+ // Role disappeared - not a big deal, just ognore it
+ continue;
+ }
+ // Collect all group names for this role.
+ // Since we use a set, a group can appear multiple times and will only
+ // show up once in a set
+ for (MSentryGroup group : sentryRole.getGroups()) {
+ groupNames.add(group.getGroupName());
+ }
+ }
+
+ return groupNames;
+ }
+
+ @Override
+ public Set<PrivilegeObject> getPrivilegesByRole(final String component,
+ final Set<String> roles) throws Exception {
+ Preconditions.checkNotNull(roles);
+ if (roles.isEmpty()) {
+ return Collections.emptySet();
+ }
+ return delegate.getTransactionManager().executeTransaction(
+ pm -> {
+ pm.setDetachAllOnCommit(false); // No need to detach objects
+ Set<MSentryRole> mRoles = new HashSet<>();
+ for (String role : roles) {
+ MSentryRole mRole = getRole(toTrimmedLower(role), pm);
+ if (mRole != null) {
+ mRoles.add(mRole);
+ }
+ }
+ return new HashSet<>(privilegeOperator.getPrivilegesByRole(mRoles, pm));
+ });
+ }
+
+ @Override
+ public Set<PrivilegeObject> getPrivilegesByProvider(final String component,
+ final String service, final Set<String> roles, final Set<String> groups,
+ final List<? extends Authorizable> authorizables) throws Exception {
+ Preconditions.checkNotNull(component);
+ Preconditions.checkNotNull(service);
+
+ return delegate.getTransactionManager().executeTransaction(
+ pm -> {
+ pm.setDetachAllOnCommit(false); // No need to detach objects
+ String trimmedComponent = toTrimmedLower(component);
+ String trimmedService = toTrimmedLower(service);
+
+ //CaseInsensitive roleNames
+ Set<String> trimmedRoles = SentryStore.toTrimedLower(roles);
+
+ if (groups != null) {
+ trimmedRoles.addAll(delegate.getRoleNamesForGroups(groups));
+ }
+
+ if (trimmedRoles.isEmpty()) {
+ return Collections.emptySet();
+ }
+
+ Set<MSentryRole> mRoles = new HashSet<>(trimmedRoles.size());
+ for (String role : trimmedRoles) {
+ MSentryRole mRole = getRole(role, pm);
+ if (mRole != null) {
+ mRoles.add(mRole);
+ }
+ }
+ //get the privileges
+ Set<PrivilegeObject> privileges = new HashSet<>();
+ privileges.addAll(privilegeOperator.
+ getPrivilegesByProvider(trimmedComponent,
+ trimmedService, mRoles, authorizables, pm));
+ return privileges;
+ });
+ }
+
+ @Override
+ public Set<MSentryGMPrivilege> getPrivilegesByAuthorizable(final String component,
+ final String service, final Set<String> validActiveRoles,
+ final List<? extends Authorizable> authorizables) throws Exception {
+ if (validActiveRoles == null || validActiveRoles.isEmpty()) {
+ return Collections.emptySet();
+ }
+
+ Preconditions.checkNotNull(component);
+ Preconditions.checkNotNull(service);
+
+ return delegate.getTransactionManager().executeTransaction(
+ pm -> {
+ String lComponent = toTrimmedLower(component);
+ String lService = toTrimmedLower(service);
+ Set<MSentryRole> mRoles = new HashSet<>(validActiveRoles.size());
+ for (String role : validActiveRoles) {
+ MSentryRole mRole = getRole(role, pm);
+ if (mRole != null) {
+ mRoles.add(mRole);
+ }
+ }
+
+ //get the privileges
+ Set<MSentryGMPrivilege> mSentryGMPrivileges =
+ privilegeOperator.getPrivilegesByAuthorizable(lComponent, lService,
+ mRoles, authorizables, pm);
+
+ final Set<MSentryGMPrivilege> privileges =
+ new HashSet<>(mSentryGMPrivileges.size());
+ for (MSentryGMPrivilege mSentryGMPrivilege : mSentryGMPrivileges) {
+ /*
+ * force to load all roles related this privilege
+ * avoid the lazy-loading
+ */
+ pm.retrieve(mSentryGMPrivilege);
+ privileges.add(mSentryGMPrivilege);
+ }
+ return privileges;
+ });
+ }
+
+ @Override
+ public void close() {
+ delegate.stop();
+ }
+
+ private Set<TSentryGroup> toTSentryGroups(Set<String> groups) {
+ if (groups.isEmpty()) {
+ return Collections.emptySet();
+ }
+ Set<TSentryGroup> tSentryGroups = new HashSet<>(groups.size());
+ for (String group : groups) {
+ tSentryGroups.add(new TSentryGroup(group));
+ }
+ return tSentryGroups;
+ }
+
+ private static Set<String> toTrimmed(Set<String> s) {
+ if (s.isEmpty()) {
+ return Collections.emptySet();
+ }
+ Set<String> result = new HashSet<>(s.size());
+ for (String v : s) {
+ result.add(v.trim());
+ }
+ return result;
+ }
+
+ private static String toTrimmedLower(String s) {
+ if (s == null) {
+ return "";
+ }
+ return s.trim().toLowerCase();
+ }
+
+ private Set<String> getRequestorGroups(String userName)
+ throws SentryUserException {
+ return SentryPolicyStoreProcessor.getGroupsFromUserName(this.conf, userName);
+ }
+
+ @VisibleForTesting
+ void clearAllTables() throws Exception {
+ delegate.getTransactionManager().executeTransaction(
+ pm -> {
+ pm.newQuery(MSentryRole.class).deletePersistentAll();
+ pm.newQuery(MSentryGroup.class).deletePersistentAll();
+ pm.newQuery(MSentryGMPrivilege.class).deletePersistentAll();
+ return null;
+ });
+ }
+}
http://git-wip-us.apache.org/repos/asf/sentry/blob/7db84b2f/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/PrivilegeObject.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/PrivilegeObject.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/PrivilegeObject.java
new file mode 100644
index 0000000..feab1e9
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/PrivilegeObject.java
@@ -0,0 +1,231 @@
+/**
+ * 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.sentry.provider.db.generic.service.persistent;
+
+import static org.apache.sentry.core.common.utils.SentryConstants.KV_JOINER;
+import static org.apache.sentry.core.common.utils.SentryConstants.AUTHORIZABLE_JOINER;
+
+import java.util.List;
+import org.apache.sentry.core.common.Authorizable;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Lists;
+
+public final class PrivilegeObject {
+ private final String component;
+ private final String service;
+ private final String action;
+ private final Boolean grantOption;
+ private List<? extends Authorizable> authorizables;
+
+ private PrivilegeObject(String component, String service, String action,
+ Boolean grantOption,
+ List<? extends Authorizable> authorizables) {
+ this.component = component;
+ this.service = service;
+ this.action = action;
+ this.grantOption = grantOption;
+ this.authorizables = authorizables;
+ }
+
+ public List<? extends Authorizable> getAuthorizables() {
+ return authorizables;
+ }
+
+ public String getAction() {
+ return action;
+ }
+
+ public String getComponent() {
+ return component;
+ }
+
+ public String getService() {
+ return service;
+ }
+
+ public Boolean getGrantOption() {
+ return grantOption;
+ }
+
+ @Override
+ public String toString() {
+ List<String> authorizable = Lists.newArrayList();
+ for (Authorizable az : authorizables) {
+ authorizable.add(KV_JOINER.join(az.getTypeName(),az.getName()));
+ }
+ return "PrivilegeObject [" + ", service=" + service + ", component="
+ + component + ", authorizables=" + AUTHORIZABLE_JOINER.join(authorizable)
+ + ", action=" + action + ", grantOption=" + grantOption + "]";
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((action == null) ? 0 : action.hashCode());
+ result = prime * result + ((component == null) ? 0 : component.hashCode());
+ result = prime * result + ((service == null) ? 0 : service.hashCode());
+ result = prime * result + ((grantOption == null) ? 0 : grantOption.hashCode());
+ for (Authorizable authorizable : authorizables) {
+ result = prime * result + authorizable.getTypeName().hashCode();
+ result = prime * result + authorizable.getName().hashCode();
+ }
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ PrivilegeObject other = (PrivilegeObject) obj;
+ if (action == null) {
+ if (other.action != null) {
+ return false;
+ }
+ } else if (!action.equals(other.action)) {
+ return false;
+ }
+ if (service == null) {
+ if (other.service != null) {
+ return false;
+ }
+ } else if (!service.equals(other.service)) {
+ return false;
+ }
+ if (component == null) {
+ if (other.component != null) {
+ return false;
+ }
+ } else if (!component.equals(other.component)) {
+ return false;
+ }
+ if (grantOption == null) {
+ if (other.grantOption != null) {
+ return false;
+ }
+ } else if (!grantOption.equals(other.grantOption)) {
+ return false;
+ }
+
+ if (authorizables.size() != other.authorizables.size()) {
+ return false;
+ }
+ for (int i = 0; i < authorizables.size(); i++) {
+ String o1 = KV_JOINER.join(authorizables.get(i).getTypeName(),
+ authorizables.get(i).getName());
+ String o2 = KV_JOINER.join(other.authorizables.get(i).getTypeName(),
+ other.authorizables.get(i).getName());
+ if (!o1.equalsIgnoreCase(o2)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static class Builder {
+ private String component;
+ private String service;
+ private String action;
+ private Boolean grantOption;
+ private List<? extends Authorizable> authorizables;
+
+ public Builder() {
+
+ }
+
+ public Builder(PrivilegeObject privilege) {
+ this.component = privilege.component;
+ this.service = privilege.service;
+ this.action = privilege.action;
+ this.grantOption = privilege.grantOption;
+ this.authorizables = privilege.authorizables;
+ }
+
+ public Builder setComponent(String component) {
+ this.component = component;
+ return this;
+ }
+
+ public Builder setService(String service) {
+ this.service = service;
+ return this;
+ }
+
+ public Builder setAction(String action) {
+ this.action = action;
+ return this;
+ }
+
+ public Builder withGrantOption(Boolean grantOption) {
+ this.grantOption = grantOption;
+ return this;
+ }
+
+ public Builder setAuthorizables(List<? extends Authorizable> authorizables) {
+ this.authorizables = authorizables;
+ return this;
+ }
+
+ /**
+ * TolowerCase the authorizable name, the authorizable type is define when it was created.
+ * Take the Solr for example, it has two Authorizable objects. They have the type Collection
+ * and Field, they are can't be changed. So we should unified the authorizable name tolowercase.
+ * @return new authorizable lists
+ */
+ private List<? extends Authorizable> toLowerAuthorizableName(List<? extends Authorizable> authorizables) {
+ List<Authorizable> newAuthorizable = Lists.newArrayList();
+ if (authorizables == null || authorizables.size() == 0) {
+ return newAuthorizable;
+ }
+ for (final Authorizable authorizable : authorizables) {
+ newAuthorizable.add(new Authorizable() {
+ @Override
+ public String getTypeName() {
+ return authorizable.getTypeName();
+ }
+ @Override
+ public String getName() {
+ return authorizable.getName();
+ }
+ });
+ }
+ return newAuthorizable;
+ }
+
+ public PrivilegeObject build() {
+ Preconditions.checkNotNull(component);
+ Preconditions.checkNotNull(service);
+ Preconditions.checkNotNull(action);
+ //CaseInsensitive authorizable name
+ List<? extends Authorizable> newAuthorizable = toLowerAuthorizableName(authorizables);
+
+ return new PrivilegeObject(component.toLowerCase(),
+ service.toLowerCase(),
+ action.toLowerCase(),
+ grantOption,
+ newAuthorizable);
+ }
+ }
+}
http://git-wip-us.apache.org/repos/asf/sentry/blob/7db84b2f/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/PrivilegeOperatePersistence.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/PrivilegeOperatePersistence.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/PrivilegeOperatePersistence.java
new file mode 100644
index 0000000..4e2290b
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/PrivilegeOperatePersistence.java
@@ -0,0 +1,566 @@
+/**
+ * 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.sentry.provider.db.generic.service.persistent;
+
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jdo.PersistenceManager;
+import javax.jdo.Query;
+
+import org.apache.hadoop.conf.Configuration;
+import org.apache.sentry.core.common.exception.SentryUserException;
+import org.apache.sentry.core.common.Action;
+import org.apache.sentry.core.common.Authorizable;
+import org.apache.sentry.core.common.BitFieldAction;
+import org.apache.sentry.core.common.BitFieldActionFactory;
+import org.apache.sentry.core.model.indexer.IndexerActionFactory;
+import org.apache.sentry.core.model.kafka.KafkaActionFactory;
+import org.apache.sentry.core.model.solr.SolrActionFactory;
+import org.apache.sentry.core.model.sqoop.SqoopActionFactory;
+import org.apache.sentry.provider.db.generic.service.persistent.PrivilegeObject.Builder;
+import org.apache.sentry.provider.db.service.model.MSentryGMPrivilege;
+import org.apache.sentry.provider.db.service.model.MSentryRole;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.apache.sentry.provider.db.service.persistent.QueryParamBuilder;
+import org.apache.sentry.provider.db.service.persistent.SentryStore;
+import org.apache.sentry.service.common.ServiceConstants;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Sentry Generic model privilege persistence support.
+ * <p>
+ * This class is similar to {@link SentryStore} but operates on generic
+ * privileges.
+ */
+public class PrivilegeOperatePersistence {
+ private static final String SERVICE_NAME = "serviceName";
+ private static final String COMPONENT_NAME = "componentName";
+ private static final String SCOPE = "scope";
+ private static final String ACTION = "action";
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(PrivilegeOperatePersistence.class);
+ private static final Map<String, BitFieldActionFactory> actionFactories = Maps.newHashMap();
+ static{
+ actionFactories.put("solr", new SolrActionFactory());
+ actionFactories.put("sqoop", new SqoopActionFactory());
+ actionFactories.put("kafka", KafkaActionFactory.getInstance());
+ actionFactories.put("hbaseindexer", new IndexerActionFactory());
+ }
+
+ private final Configuration conf;
+
+ PrivilegeOperatePersistence(Configuration conf) {
+ this.conf = conf;
+ }
+
+ /**
+ * Return query builder to execute in JDO for search the given privilege
+ * @param privilege Privilege to extract
+ * @return query builder suitable for executing the query
+ */
+ private static QueryParamBuilder toQueryParam(MSentryGMPrivilege privilege) {
+ QueryParamBuilder paramBuilder = QueryParamBuilder.newQueryParamBuilder();
+ paramBuilder.add(SERVICE_NAME, SentryStore.toNULLCol(privilege.getServiceName()), true)
+ .add(COMPONENT_NAME, SentryStore.toNULLCol(privilege.getComponentName()), true)
+ .add(SCOPE, SentryStore.toNULLCol(privilege.getScope()), true)
+ .add(ACTION, SentryStore.toNULLCol(privilege.getAction()), true);
+
+ Boolean grantOption = privilege.getGrantOption();
+ paramBuilder.addObject(SentryStore.GRANT_OPTION, grantOption);
+
+ List<? extends Authorizable> authorizables = privilege.getAuthorizables();
+ int nAuthorizables = authorizables.size();
+ for (int i = 0; i < MSentryGMPrivilege.AUTHORIZABLE_LEVEL; i++) {
+ String resourceName = MSentryGMPrivilege.PREFIX_RESOURCE_NAME + String.valueOf(i);
+ String resourceType = MSentryGMPrivilege.PREFIX_RESOURCE_TYPE + String.valueOf(i);
+
+ if (i >= nAuthorizables) {
+ paramBuilder.addNull(resourceName);
+ paramBuilder.addNull(resourceType);
+ } else {
+ paramBuilder.add(resourceName, authorizables.get(i).getName(), true);
+ paramBuilder.add(resourceType, authorizables.get(i).getTypeName(), true);
+ }
+ }
+ return paramBuilder;
+ }
+
+ /**
+ * Create a query template tha includes information from the input privilege:
+ * <ul>
+ * <li>Service name</li>
+ * <li>Component name</li>
+ * <li>Name and type for each authorizable present</li>
+ * </ul>
+ * For exmaple, for Solr may configure the following privileges:
+ * <ul>
+ * <li>{@code p1:Collection=c1->action=query}</li>
+ * <li>{@code p2:Collection=c1->Field=f1->action=query}</li>
+ * <li>{@code p3:Collection=c1->Field=f2->action=query}</li>
+ * </ul>
+ * When the request for privilege revoke has
+ * {@code p4:Collection=c1->action=query}
+ * all privileges matching {@code Collection=c1} should be revoke which means that p1, p2 and p3
+ * should all be revoked.
+ *
+ * @param privilege Source privilege
+ * @return ParamBuilder suitable for executing the query
+ */
+ private static QueryParamBuilder populateIncludePrivilegesParams(MSentryGMPrivilege privilege) {
+ QueryParamBuilder paramBuilder = QueryParamBuilder.newQueryParamBuilder();
+ paramBuilder.add(SERVICE_NAME, SentryStore.toNULLCol(privilege.getServiceName()), true);
+ paramBuilder.add(COMPONENT_NAME, SentryStore.toNULLCol(privilege.getComponentName()), true);
+
+ List<? extends Authorizable> authorizables = privilege.getAuthorizables();
+ int i = 0;
+ for(Authorizable auth: authorizables) {
+ String resourceName = MSentryGMPrivilege.PREFIX_RESOURCE_NAME + String.valueOf(i);
+ String resourceType = MSentryGMPrivilege.PREFIX_RESOURCE_TYPE + String.valueOf(i);
+ paramBuilder.add(resourceName, auth.getName(), true);
+ paramBuilder.add(resourceType, auth.getTypeName(), true);
+ i++;
+ }
+ return paramBuilder;
+ }
+
+ /**
+ * Verify whether specified privilege can be granted
+ * @param roles set of roles for the privilege
+ * @param privilege privilege being checked
+ * @param pm Persistentence manager instance
+ * @return true iff at least one privilege within the role allows for the
+ * requested privilege
+ */
+ boolean checkPrivilegeOption(Set<MSentryRole> roles, PrivilegeObject privilege, PersistenceManager pm) {
+ MSentryGMPrivilege requestPrivilege = convertToPrivilege(privilege);
+ if (roles.isEmpty()) {
+ return false;
+ }
+ // get persistent privileges by roles
+ // Find all GM privileges for all the input roles
+ Query query = pm.newQuery(MSentryGMPrivilege.class);
+ QueryParamBuilder paramBuilder = QueryParamBuilder.addRolesFilter(query, null,
+ SentryStore.rolesToRoleNames(roles));
+ query.setFilter(paramBuilder.toString());
+ List<MSentryGMPrivilege> tPrivileges =
+ (List<MSentryGMPrivilege>)query.executeWithMap(paramBuilder.getArguments());
+
+ for (MSentryGMPrivilege tPrivilege : tPrivileges) {
+ if (tPrivilege.getGrantOption() && tPrivilege.implies(requestPrivilege)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public void grantPrivilege(PrivilegeObject privilege,MSentryRole role, PersistenceManager pm) throws SentryUserException {
+ MSentryGMPrivilege mPrivilege = convertToPrivilege(privilege);
+ grantRolePartial(mPrivilege, role, pm);
+ }
+
+ private void grantRolePartial(MSentryGMPrivilege grantPrivilege,
+ MSentryRole role,PersistenceManager pm) throws SentryUserException {
+ /*
+ * If Grant is for ALL action and other actions belongs to ALL action already exists..
+ * need to remove it and GRANT ALL action
+ */
+ String component = grantPrivilege.getComponentName();
+ BitFieldAction action = getAction(component, grantPrivilege.getAction());
+ BitFieldAction allAction = getAction(component, Action.ALL);
+
+ if (action.implies(allAction)) {
+ /*
+ * ALL action is a multi-bit set action that includes some actions such as INSERT,SELECT and CREATE.
+ */
+ List<? extends BitFieldAction> actions = getActionFactory(component).getActionsByCode(allAction.getActionCode());
+ for (BitFieldAction ac : actions) {
+ grantPrivilege.setAction(ac.getValue());
+ MSentryGMPrivilege existPriv = getPrivilege(grantPrivilege, pm);
+ if (existPriv != null && role.getGmPrivileges().contains(existPriv)) {
+ /*
+ * force to load all roles related this privilege
+ * avoid the lazy-loading risk,such as:
+ * if the roles field of privilege aren't loaded, then the roles is a empty set
+ * privilege.removeRole(role) and pm.makePersistent(privilege)
+ * will remove other roles that shouldn't been removed
+ */
+ pm.retrieve(existPriv);
+ existPriv.removeRole(role);
+ pm.makePersistent(existPriv);
+ }
+ }
+ } else {
+ /*
+ * If ALL Action already exists..
+ * do nothing.
+ */
+ grantPrivilege.setAction(allAction.getValue());
+ MSentryGMPrivilege allPrivilege = getPrivilege(grantPrivilege, pm);
+ if (allPrivilege != null && role.getGmPrivileges().contains(allPrivilege)) {
+ return;
+ }
+ }
+
+ /*
+ * restore the action
+ */
+ grantPrivilege.setAction(action.getValue());
+ /*
+ * check the privilege is exist or not
+ */
+ MSentryGMPrivilege mPrivilege = getPrivilege(grantPrivilege, pm);
+ if (mPrivilege == null) {
+ mPrivilege = grantPrivilege;
+ }
+ mPrivilege.appendRole(role);
+ pm.makePersistent(mPrivilege);
+ }
+
+
+ public void revokePrivilege(PrivilegeObject privilege,MSentryRole role, PersistenceManager pm) throws SentryUserException {
+ MSentryGMPrivilege mPrivilege = getPrivilege(convertToPrivilege(privilege), pm);
+ if (mPrivilege == null) {
+ mPrivilege = convertToPrivilege(privilege);
+ } else {
+ mPrivilege = pm.detachCopy(mPrivilege);
+ }
+
+ Set<MSentryGMPrivilege> privilegeGraph = Sets.newHashSet();
+ privilegeGraph.addAll(populateIncludePrivileges(Sets.newHashSet(role), mPrivilege, pm));
+
+ /*
+ * Get the privilege graph
+ * populateIncludePrivileges will get the privileges that needed revoke
+ */
+ for (MSentryGMPrivilege persistedPriv : privilegeGraph) {
+ /*
+ * force to load all roles related this privilege
+ * avoid the lazy-loading risk,such as:
+ * if the roles field of privilege aren't loaded, then the roles is a empty set
+ * privilege.removeRole(role) and pm.makePersistent(privilege)
+ * will remove other roles that shouldn't been removed
+ */
+ revokeRolePartial(mPrivilege, persistedPriv, role, pm);
+ }
+ pm.makePersistent(role);
+ }
+
+ private Set<MSentryGMPrivilege> populateIncludePrivileges(Set<MSentryRole> roles,
+ MSentryGMPrivilege parent, PersistenceManager pm) {
+ Set<MSentryGMPrivilege> childrens = Sets.newHashSet();
+
+ Query query = pm.newQuery(MSentryGMPrivilege.class);
+ QueryParamBuilder paramBuilder = populateIncludePrivilegesParams(parent);
+
+ // add filter for role names
+ if ((roles != null) && !roles.isEmpty()) {
+ QueryParamBuilder.addRolesFilter(query, paramBuilder, SentryStore.rolesToRoleNames(roles));
+ }
+ query.setFilter(paramBuilder.toString());
+
+ List<MSentryGMPrivilege> privileges =
+ (List<MSentryGMPrivilege>)query.executeWithMap(paramBuilder.getArguments());
+ childrens.addAll(privileges);
+ return childrens;
+ }
+
+ /**
+ * Roles can be granted multi-bit set action like ALL action on resource object.
+ * Take solr component for example, When a role has been granted ALL action but
+ * QUERY or UPDATE or CREATE are revoked, we need to remove the ALL
+ * privilege and add left privileges like UPDATE and CREATE(QUERY was revoked) or
+ * QUERY and UPDATE(CREATEE was revoked).
+ */
+ private void revokeRolePartial(MSentryGMPrivilege revokePrivilege,
+ MSentryGMPrivilege persistedPriv, MSentryRole role,
+ PersistenceManager pm) throws SentryUserException {
+ String component = revokePrivilege.getComponentName();
+ BitFieldAction revokeaction = getAction(component, revokePrivilege.getAction());
+ BitFieldAction persistedAction = getAction(component, persistedPriv.getAction());
+ BitFieldAction allAction = getAction(component, Action.ALL);
+
+ if (revokeaction.implies(allAction)) {
+ /*
+ * if revoke action is ALL, directly revoke its children privileges and itself
+ */
+ persistedPriv.removeRole(role);
+ pm.makePersistent(persistedPriv);
+ } else {
+ /*
+ * if persisted action is ALL, it only revoke the requested action and left partial actions
+ * like the requested action is SELECT, the UPDATE and CREATE action are left
+ */
+ if (persistedAction.implies(allAction)) {
+ /*
+ * revoke the ALL privilege
+ */
+ persistedPriv.removeRole(role);
+ pm.makePersistent(persistedPriv);
+
+ List<? extends BitFieldAction> actions = getActionFactory(component).getActionsByCode(allAction.getActionCode());
+ for (BitFieldAction ac: actions) {
+ if (ac.getActionCode() != revokeaction.getActionCode()) {
+ /*
+ * grant the left privileges to role
+ */
+ MSentryGMPrivilege tmpPriv = new MSentryGMPrivilege(persistedPriv);
+ tmpPriv.setAction(ac.getValue());
+ MSentryGMPrivilege leftPersistedPriv = getPrivilege(tmpPriv, pm);
+ if (leftPersistedPriv == null) {
+ //leftPersistedPriv isn't exist
+ leftPersistedPriv = tmpPriv;
+ role.appendGMPrivilege(leftPersistedPriv);
+ }
+ leftPersistedPriv.appendRole(role);
+ pm.makePersistent(leftPersistedPriv);
+ }
+ }
+ } else if (revokeaction.implies(persistedAction)) {
+ /*
+ * if the revoke action is equal to the persisted action and they aren't ALL action
+ * directly remove the role from privilege
+ */
+ persistedPriv.removeRole(role);
+ pm.makePersistent(persistedPriv);
+ }
+ /*
+ * if the revoke action is not equal to the persisted action,
+ * do nothing
+ */
+ }
+ }
+
+ /**
+ * Drop any role related to the requested privilege and its children privileges
+ */
+ public void dropPrivilege(PrivilegeObject privilege,PersistenceManager pm) throws SentryUserException {
+ MSentryGMPrivilege requestPrivilege = convertToPrivilege(privilege);
+
+ if (Strings.isNullOrEmpty(privilege.getAction())) {
+ requestPrivilege.setAction(getAction(privilege.getComponent(), Action.ALL).getValue());
+ }
+ /*
+ * Get the privilege graph
+ * populateIncludePrivileges will get the privileges that need dropped,
+ */
+ Set<MSentryGMPrivilege> privilegeGraph = Sets.newHashSet();
+ privilegeGraph.addAll(populateIncludePrivileges(null, requestPrivilege, pm));
+
+ for (MSentryGMPrivilege mPrivilege : privilegeGraph) {
+ /*
+ * force to load all roles related this privilege
+ * avoid the lazy-loading
+ */
+ pm.retrieve(mPrivilege);
+ Set<MSentryRole> roles = mPrivilege.getRoles();
+ for (MSentryRole role : roles) {
+ revokeRolePartial(requestPrivilege, mPrivilege, role, pm);
+ }
+ }
+ }
+
+ private MSentryGMPrivilege convertToPrivilege(PrivilegeObject privilege) {
+ return new MSentryGMPrivilege(privilege.getComponent(),
+ privilege.getService(), privilege.getAuthorizables(),
+ privilege.getAction(), privilege.getGrantOption());
+ }
+
+ private MSentryGMPrivilege getPrivilege(MSentryGMPrivilege privilege, PersistenceManager pm) {
+ Query query = pm.newQuery(MSentryGMPrivilege.class);
+ QueryParamBuilder paramBuilder = toQueryParam(privilege);
+ query.setFilter(paramBuilder.toString());
+ query.setUnique(true);
+ MSentryGMPrivilege result = (MSentryGMPrivilege)query.executeWithMap(paramBuilder.getArguments());
+ return result;
+ }
+
+ /**
+ * Get all privileges associated with a given roles
+ * @param roles Set of roles
+ * @param pm Persistence manager instance
+ * @return Set (potentially empty) of privileges associated with roles
+ */
+ Set<PrivilegeObject> getPrivilegesByRole(Set<MSentryRole> roles, PersistenceManager pm) {
+ if (roles == null || roles.isEmpty()) {
+ return Collections.emptySet();
+ }
+
+ Query query = pm.newQuery(MSentryGMPrivilege.class);
+ // Find privileges matching all roles
+ QueryParamBuilder paramBuilder = QueryParamBuilder.addRolesFilter(query, null,
+ SentryStore.rolesToRoleNames(roles));
+ query.setFilter(paramBuilder.toString());
+ List<MSentryGMPrivilege> mPrivileges =
+ (List<MSentryGMPrivilege>)query.executeWithMap(paramBuilder.getArguments());
+ if (mPrivileges.isEmpty()) {
+ return Collections.emptySet();
+ }
+
+ Set<PrivilegeObject> privileges = new HashSet<>(mPrivileges.size());
+ for (MSentryGMPrivilege mPrivilege : mPrivileges) {
+ privileges.add(new Builder()
+ .setComponent(mPrivilege.getComponentName())
+ .setService(mPrivilege.getServiceName())
+ .setAction(mPrivilege.getAction())
+ .setAuthorizables(mPrivilege.getAuthorizables())
+ .withGrantOption(mPrivilege.getGrantOption())
+ .build());
+ }
+ return privileges;
+ }
+
+ Set<PrivilegeObject> getPrivilegesByProvider(String component,
+ String service, Set<MSentryRole> roles,
+ List<? extends Authorizable> authorizables, PersistenceManager pm) {
+ Set<PrivilegeObject> privileges = Sets.newHashSet();
+ if (roles == null || roles.isEmpty()) {
+ return privileges;
+ }
+
+ MSentryGMPrivilege parentPrivilege = new MSentryGMPrivilege(component, service, authorizables, null, null);
+ Set<MSentryGMPrivilege> privilegeGraph = Sets.newHashSet();
+ privilegeGraph.addAll(populateIncludePrivileges(roles, parentPrivilege, pm));
+
+ for (MSentryGMPrivilege mPrivilege : privilegeGraph) {
+ privileges.add(new Builder()
+ .setComponent(mPrivilege.getComponentName())
+ .setService(mPrivilege.getServiceName())
+ .setAction(mPrivilege.getAction())
+ .setAuthorizables(mPrivilege.getAuthorizables())
+ .withGrantOption(mPrivilege.getGrantOption())
+ .build());
+ }
+ return privileges;
+ }
+
+ Set<MSentryGMPrivilege> getPrivilegesByAuthorizable(String component,
+ String service, Set<MSentryRole> roles,
+ List<? extends Authorizable> authorizables, PersistenceManager pm) {
+
+ Set<MSentryGMPrivilege> privilegeGraph = Sets.newHashSet();
+
+ if (roles == null || roles.isEmpty()) {
+ return privilegeGraph;
+ }
+
+ MSentryGMPrivilege parentPrivilege = new MSentryGMPrivilege(component, service, authorizables, null, null);
+ privilegeGraph.addAll(populateIncludePrivileges(roles, parentPrivilege, pm));
+ return privilegeGraph;
+ }
+
+ public void renamePrivilege(String component, String service,
+ List<? extends Authorizable> oldAuthorizables, List<? extends Authorizable> newAuthorizables,
+ String grantorPrincipal, PersistenceManager pm)
+ throws SentryUserException {
+ MSentryGMPrivilege oldPrivilege = new MSentryGMPrivilege(component, service, oldAuthorizables, null, null);
+ oldPrivilege.setAction(getAction(component,Action.ALL).getValue());
+ /*
+ * Get the privilege graph
+ * populateIncludePrivileges will get the old privileges that need dropped
+ */
+ Set<MSentryGMPrivilege> privilegeGraph = Sets.newHashSet();
+ privilegeGraph.addAll(populateIncludePrivileges(null, oldPrivilege, pm));
+
+ for (MSentryGMPrivilege dropPrivilege : privilegeGraph) {
+ /*
+ * construct the new privilege needed to add
+ */
+ List<Authorizable> authorizables = new ArrayList<Authorizable>(
+ dropPrivilege.getAuthorizables());
+ for (int i = 0; i < newAuthorizables.size(); i++) {
+ authorizables.set(i, newAuthorizables.get(i));
+ }
+ MSentryGMPrivilege newPrivilge = new MSentryGMPrivilege(
+ component,service, authorizables, dropPrivilege.getAction(),
+ dropPrivilege.getGrantOption());
+
+ /*
+ * force to load all roles related this privilege
+ * avoid the lazy-loading
+ */
+ pm.retrieve(dropPrivilege);
+
+ Set<MSentryRole> roles = dropPrivilege.getRoles();
+ for (MSentryRole role : roles) {
+ revokeRolePartial(oldPrivilege, dropPrivilege, role, pm);
+ grantRolePartial(newPrivilge, role, pm);
+ }
+ }
+ }
+
+ private BitFieldAction getAction(String component, String name) throws SentryUserException {
+ BitFieldActionFactory actionFactory = getActionFactory(component);
+ BitFieldAction action = actionFactory.getActionByName(name);
+ if (action == null) {
+ throw new SentryUserException("Can not get BitFieldAction for name: " + name);
+ }
+ return action;
+ }
+
+ private BitFieldActionFactory getActionFactory(String component) throws SentryUserException {
+ String caseInsensitiveComponent = component.toLowerCase();
+ if (actionFactories.containsKey(caseInsensitiveComponent)) {
+ return actionFactories.get(caseInsensitiveComponent);
+ }
+ BitFieldActionFactory actionFactory = createActionFactory(caseInsensitiveComponent);
+ actionFactories.put(caseInsensitiveComponent, actionFactory);
+ LOGGER.info("Action factory for component {} is not found in cache. Loaded it from configuration as {}.",
+ component, actionFactory.getClass().getName());
+ return actionFactory;
+ }
+
+ private BitFieldActionFactory createActionFactory(String component) throws SentryUserException {
+ String actionFactoryClassName =
+ conf.get(String.format(ServiceConstants.ServerConfig.SENTRY_COMPONENT_ACTION_FACTORY_FORMAT, component));
+ if (actionFactoryClassName == null) {
+ throw new SentryUserException("ActionFactory not defined for component " + component +
+ ". Please define the parameter " +
+ "sentry." + component + ".action.factory in configuration");
+ }
+ Class<?> actionFactoryClass;
+ try {
+ actionFactoryClass = Class.forName(actionFactoryClassName);
+ } catch (ClassNotFoundException e) {
+ throw new SentryUserException("ActionFactory class " + actionFactoryClassName + " not found.");
+ }
+ if (!BitFieldActionFactory.class.isAssignableFrom(actionFactoryClass)) {
+ throw new SentryUserException("ActionFactory class " + actionFactoryClassName + " must extend "
+ + BitFieldActionFactory.class.getName());
+ }
+ BitFieldActionFactory actionFactory;
+ try {
+ Constructor<?> actionFactoryConstructor = actionFactoryClass.getDeclaredConstructor();
+ actionFactoryConstructor.setAccessible(true);
+ actionFactory = (BitFieldActionFactory) actionFactoryClass.newInstance();
+ } catch (NoSuchMethodException | InstantiationException | IllegalAccessException e) {
+ throw new SentryUserException("Could not instantiate actionFactory " + actionFactoryClassName +
+ " for component: " + component, e);
+ }
+ return actionFactory;
+ }
+}
http://git-wip-us.apache.org/repos/asf/sentry/blob/7db84b2f/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/SentryStoreLayer.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/SentryStoreLayer.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/SentryStoreLayer.java
new file mode 100644
index 0000000..eec2757
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/service/persistent/SentryStoreLayer.java
@@ -0,0 +1,186 @@
+/**
+ * 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.sentry.provider.db.generic.service.persistent;
+
+import java.util.List;
+import java.util.Set;
+
+import org.apache.sentry.core.common.Authorizable;
+import org.apache.sentry.provider.db.service.model.MSentryGMPrivilege;
+
+/**
+ * Sentry store for persistent the authorize object to database
+ */
+public interface SentryStoreLayer {
+ /**
+ * Create a role
+ * @param component: The request respond to which component
+ * @param role: The name of role
+ * @param requestor: User on whose behalf the request is launched
+ * @throws Exception
+ */
+ Object createRole(String component, String role,
+ String requestor) throws Exception;
+
+ /**
+ * Drop a role
+ * @param component: The request respond to which component
+ * @param role: The name of role
+ * @param requestor: user on whose behalf the request is launched
+ * @throws Exception
+ */
+ Object dropRole(String component, String role,
+ String requestor) throws Exception;
+
+ /**
+ * Add a role to groups.
+ * @param component: The request respond to which component
+ * @param role: The name of role
+ * @param groups: The name of groups
+ * @param requestor: User on whose behalf the request is issued
+ * @throws Exception
+ */
+ Object alterRoleAddGroups(String component, String role,
+ Set<String> groups, String requestor) throws Exception;
+
+ /**
+ * Delete a role from groups.
+ * @param component: The request respond to which component
+ * @param role: The name of role
+ * @param groups: The name of groups
+ * @param requestor: User on whose behalf the request is launched
+ * @throws Exception
+ */
+ Object alterRoleDeleteGroups(String component, String role,
+ Set<String> groups, String requestor) throws Exception;
+
+ /**
+ * Grant a privilege to role.
+ * @param component: The request respond to which component
+ * @param role: The name of role
+ * @param privilege: The privilege object will be granted
+ * @param grantorPrincipal: User on whose behalf the request is launched
+ * @throws Exception
+ */
+ Object alterRoleGrantPrivilege(String component, String role,
+ PrivilegeObject privilege, String grantorPrincipal) throws Exception;
+
+ /**
+ * Revoke a privilege from role.
+ * @param component: The request respond to which component
+ * @param role: The name of role
+ * @param privilege: The privilege object will revoked
+ * @param grantorPrincipal: User on whose behalf the request is launched
+ * @throws Exception
+ */
+ Object alterRoleRevokePrivilege(String component, String role,
+ PrivilegeObject privilege, String grantorPrincipal) throws Exception;
+
+ /**
+ * Rename privilege
+ *
+ * @param component: The request respond to which component
+ * @param service: The name of service
+ * @param oldAuthorizables: The old list of authorize objects
+ * @param newAuthorizables: The new list of authorize objects
+ * @param requestor: User on whose behalf the request is launched
+ * @throws Exception
+ */
+ Object renamePrivilege(
+ String component, String service, List<? extends Authorizable> oldAuthorizables,
+ List<? extends Authorizable> newAuthorizables, String requestor) throws Exception;
+
+ /**
+ * Drop privilege
+ * @param component: The request respond to which component
+ * @param privilege: The privilege will be dropped
+ * @param requestor: User on whose behalf the request is launched
+ * @throws Exception
+ */
+ Object dropPrivilege(String component, PrivilegeObject privilege,
+ String requestor) throws Exception;
+
+ /**
+ * Get roles
+ * @param component: The request respond to which component
+ * @param groups: The name of groups
+ * @returns the set of roles
+ * @throws Exception
+ */
+ Set<String> getRolesByGroups(String component, Set<String> groups) throws Exception;
+
+ /**
+ * Get groups
+ * @param component: The request respond to which component
+ * @param roles: The name of roles
+ * @returns the set of groups
+ * @throws Exception
+ */
+ Set<String> getGroupsByRoles(String component, Set<String> roles) throws Exception;
+
+ /**
+ * Get privileges
+ * @param component: The request respond to which component
+ * @param roles: The name of roles
+ * @returns the set of privileges
+ * @throws Exception
+ */
+ Set<PrivilegeObject> getPrivilegesByRole(String component, Set<String> roles) throws Exception;
+
+ /**
+ * get sentry privileges from provider as followings:
+ * @param component: The request respond to which component
+ * @param service: The name of service
+ * @param roles: The name of roles
+ * @param groups: The name of groups
+ * @param authorizables: The list of authorize objects
+ * @returns the set of privileges
+ * @throws Exception
+ */
+
+ Set<PrivilegeObject> getPrivilegesByProvider(String component, String service, Set<String> roles,
+ Set<String> groups, List<? extends Authorizable> authorizables)
+ throws Exception;
+
+ /**
+ * Get all roles name.
+ *
+ * @returns The set of roles name,
+ */
+ Set<String> getAllRoleNames() throws Exception;
+
+ /**
+ * Get sentry privileges based on valid active roles and the authorize objects.
+ *
+ * @param component: The request respond to which component
+ * @param service: The name of service
+ * @param validActiveRoles: The valid active roles
+ * @param authorizables: The list of authorize objects
+ * @returns The set of MSentryGMPrivilege
+ * @throws Exception
+ */
+ Set<MSentryGMPrivilege> getPrivilegesByAuthorizable(String component, String service,
+ Set<String> validActiveRoles, List<? extends Authorizable> authorizables)
+ throws Exception;
+
+ /**
+ * close sentryStore
+ */
+ void close();
+
+}
http://git-wip-us.apache.org/repos/asf/sentry/blob/7db84b2f/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/tools/GenericPrivilegeConverter.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/tools/GenericPrivilegeConverter.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/tools/GenericPrivilegeConverter.java
new file mode 100644
index 0000000..6a2c77f
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/tools/GenericPrivilegeConverter.java
@@ -0,0 +1,191 @@
+/*
+ *
+ * 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.sentry.provider.db.generic.tools;
+
+import static org.apache.sentry.core.common.utils.SentryConstants.AUTHORIZABLE_SEPARATOR;
+import static org.apache.sentry.core.common.utils.SentryConstants.KV_SEPARATOR;
+import static org.apache.sentry.core.common.utils.SentryConstants.RESOURCE_WILDCARD_VALUE;
+
+import com.google.common.collect.Lists;
+
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.apache.sentry.api.generic.thrift.TAuthorizable;
+import org.apache.sentry.api.generic.thrift.TSentryGrantOption;
+import org.apache.sentry.api.generic.thrift.TSentryPrivilege;
+import org.apache.sentry.core.common.Authorizable;
+import org.apache.sentry.core.common.exception.SentryUserException;
+import org.apache.sentry.core.common.utils.KeyValue;
+import org.apache.sentry.core.common.utils.PolicyFileConstants;
+import org.apache.sentry.core.common.utils.SentryConstants;
+import org.apache.sentry.core.common.validator.PrivilegeValidator;
+import org.apache.sentry.core.common.validator.PrivilegeValidatorContext;
+import org.apache.sentry.core.model.indexer.IndexerModelAuthorizables;
+import org.apache.sentry.core.model.indexer.IndexerPrivilegeModel;
+import org.apache.sentry.core.model.kafka.KafkaAuthorizable;
+import org.apache.sentry.core.model.kafka.KafkaModelAuthorizables;
+import org.apache.sentry.core.model.kafka.KafkaPrivilegeModel;
+import org.apache.sentry.core.model.solr.SolrModelAuthorizables;
+import org.apache.sentry.core.model.solr.SolrPrivilegeModel;
+import org.apache.sentry.core.model.sqoop.SqoopModelAuthorizables;
+import org.apache.sentry.core.model.sqoop.SqoopPrivilegeModel;
+import org.apache.sentry.provider.common.AuthorizationComponent;
+import org.apache.shiro.config.ConfigurationException;
+
+/**
+ * A TSentryPrivilegeConverter implementation for "Generic" privileges, covering Apache Kafka, Apache Solr and Apache Sqoop.
+ * It converts privilege Strings to TSentryPrivilege Objects, and vice versa, for Generic clients.
+ *
+ * When a privilege String is converted to a TSentryPrivilege in "fromString", the validators associated with the
+ * given privilege model are also called on the privilege String.
+ */
+public class GenericPrivilegeConverter implements TSentryPrivilegeConverter {
+ private String component;
+ private String service;
+ private boolean validate;
+
+ public GenericPrivilegeConverter(String component, String service) {
+ this(component, service, true);
+ }
+
+ public GenericPrivilegeConverter(String component, String service, boolean validate) {
+ this.component = component;
+ this.service = service;
+ this.validate = validate;
+ }
+
+ public TSentryPrivilege fromString(String privilegeStr) throws SentryUserException {
+ privilegeStr = parsePrivilegeString(privilegeStr);
+ if (validate) {
+ validatePrivilegeHierarchy(privilegeStr);
+ }
+
+ TSentryPrivilege tSentryPrivilege = new TSentryPrivilege();
+ List<TAuthorizable> authorizables = new LinkedList<TAuthorizable>();
+ for (String authorizable : SentryConstants.AUTHORIZABLE_SPLITTER.split(privilegeStr)) {
+ KeyValue keyValue = new KeyValue(authorizable);
+ String key = keyValue.getKey();
+ String value = keyValue.getValue();
+
+ Authorizable authz = getAuthorizable(keyValue);
+ if (authz != null) {
+ authorizables.add(new TAuthorizable(authz.getTypeName(), authz.getName()));
+ } else if (PolicyFileConstants.PRIVILEGE_ACTION_NAME.equalsIgnoreCase(key)) {
+ tSentryPrivilege.setAction(value);
+ } else {
+ throw new IllegalArgumentException("Unknown key: " + key);
+ }
+ }
+
+ if (tSentryPrivilege.getAction() == null) {
+ throw new IllegalArgumentException("Privilege is invalid: action required but not specified.");
+ }
+ tSentryPrivilege.setComponent(component);
+ tSentryPrivilege.setServiceName(service);
+ tSentryPrivilege.setAuthorizables(authorizables);
+ return tSentryPrivilege;
+ }
+
+ public String toString(TSentryPrivilege tSentryPrivilege) {
+ List<String> privileges = Lists.newArrayList();
+ if (tSentryPrivilege != null) {
+ List<TAuthorizable> authorizables = tSentryPrivilege.getAuthorizables();
+ String action = tSentryPrivilege.getAction();
+ String grantOption = (tSentryPrivilege.getGrantOption() == TSentryGrantOption.TRUE ? "true"
+ : "false");
+
+ Iterator<TAuthorizable> it = authorizables.iterator();
+ if (it != null) {
+ while (it.hasNext()) {
+ TAuthorizable tAuthorizable = it.next();
+ privileges.add(SentryConstants.KV_JOINER.join(
+ tAuthorizable.getType(), tAuthorizable.getName()));
+ }
+ }
+
+ if (!authorizables.isEmpty()) {
+ privileges.add(SentryConstants.KV_JOINER.join(
+ PolicyFileConstants.PRIVILEGE_ACTION_NAME, action));
+ }
+
+ // only append the grant option to privilege string if it's true
+ if ("true".equals(grantOption)) {
+ privileges.add(SentryConstants.KV_JOINER.join(
+ PolicyFileConstants.PRIVILEGE_GRANT_OPTION_NAME, grantOption));
+ }
+ }
+ return SentryConstants.AUTHORIZABLE_JOINER.join(privileges);
+ }
+
+ private String parsePrivilegeString(String privilegeStr) {
+ if (AuthorizationComponent.KAFKA.equals(component)) {
+ final String hostPrefix = KafkaAuthorizable.AuthorizableType.HOST.name() + KV_SEPARATOR;
+ final String hostPrefixLowerCase = hostPrefix.toLowerCase();
+ if (!privilegeStr.toLowerCase().startsWith(hostPrefixLowerCase)) {
+ return hostPrefix + RESOURCE_WILDCARD_VALUE + AUTHORIZABLE_SEPARATOR + privilegeStr;
+ }
+ }
+
+ return privilegeStr;
+ }
+
+ private void validatePrivilegeHierarchy(String privilegeStr) throws SentryUserException {
+ List<PrivilegeValidator> validators = getPrivilegeValidators();
+ PrivilegeValidatorContext context = new PrivilegeValidatorContext(null, privilegeStr);
+ for (PrivilegeValidator validator : validators) {
+ try {
+ validator.validate(context);
+ } catch (ConfigurationException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+ }
+
+ protected List<PrivilegeValidator> getPrivilegeValidators() throws SentryUserException {
+ if (AuthorizationComponent.KAFKA.equals(component)) {
+ return KafkaPrivilegeModel.getInstance().getPrivilegeValidators();
+ } else if ("SOLR".equals(component)) {
+ return SolrPrivilegeModel.getInstance().getPrivilegeValidators();
+ } else if (AuthorizationComponent.SQOOP.equals(component)) {
+ return SqoopPrivilegeModel.getInstance().getPrivilegeValidators(service);
+ } else if (AuthorizationComponent.HBASE_INDEXER.equals(component)) {
+ return IndexerPrivilegeModel.getInstance().getPrivilegeValidators();
+ }
+
+ throw new SentryUserException("Invalid component specified for GenericPrivilegeCoverter: " + component);
+ }
+
+ protected Authorizable getAuthorizable(KeyValue keyValue) throws SentryUserException {
+ if (AuthorizationComponent.KAFKA.equals(component)) {
+ return KafkaModelAuthorizables.from(keyValue);
+ } else if ("SOLR".equals(component)) {
+ return SolrModelAuthorizables.from(keyValue);
+ } else if (AuthorizationComponent.SQOOP.equals(component)) {
+ return SqoopModelAuthorizables.from(keyValue);
+ } else if (AuthorizationComponent.HBASE_INDEXER.equals(component)) {
+ return IndexerModelAuthorizables.from(keyValue);
+ }
+
+ throw new SentryUserException("Invalid component specified for GenericPrivilegeCoverter: " + component);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/sentry/blob/7db84b2f/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/tools/TSentryPrivilegeConverter.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/tools/TSentryPrivilegeConverter.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/tools/TSentryPrivilegeConverter.java
new file mode 100644
index 0000000..fc55575
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/generic/tools/TSentryPrivilegeConverter.java
@@ -0,0 +1,35 @@
+/*
+ *
+ * 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.sentry.provider.db.generic.tools;
+
+import org.apache.sentry.api.generic.thrift.TSentryPrivilege;
+import org.apache.sentry.core.common.exception.SentryUserException;
+
+public interface TSentryPrivilegeConverter {
+
+ /**
+ * Convert string to privilege
+ */
+ TSentryPrivilege fromString(String privilegeStr) throws SentryUserException;
+
+ /**
+ * Convert privilege to string
+ */
+ String toString(TSentryPrivilege tSentryPrivilege);
+}
http://git-wip-us.apache.org/repos/asf/sentry/blob/7db84b2f/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/log/appender/AuditLoggerTestAppender.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/log/appender/AuditLoggerTestAppender.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/log/appender/AuditLoggerTestAppender.java
new file mode 100644
index 0000000..8000ebd
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/log/appender/AuditLoggerTestAppender.java
@@ -0,0 +1,52 @@
+/*
+ * 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.sentry.provider.db.log.appender;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.AppenderSkeleton;
+import org.apache.log4j.Level;
+import org.apache.log4j.spi.LoggingEvent;
+
+import com.google.common.annotations.VisibleForTesting;
+
+@VisibleForTesting
+public class AuditLoggerTestAppender extends AppenderSkeleton {
+ public static final List<LoggingEvent> events = new ArrayList<LoggingEvent>();
+
+ public void close() {
+ }
+
+ public boolean requiresLayout() {
+ return false;
+ }
+
+ @Override
+ protected void append(LoggingEvent event) {
+ events.add(event);
+ }
+
+ public static String getLastLogEvent() {
+ return events.get(events.size() - 1).getMessage().toString();
+ }
+
+ public static Level getLastLogLevel() {
+ return events.get(events.size() - 1).getLevel();
+ }
+}
http://git-wip-us.apache.org/repos/asf/sentry/blob/7db84b2f/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/log/appender/RollingFileWithoutDeleteAppender.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/log/appender/RollingFileWithoutDeleteAppender.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/log/appender/RollingFileWithoutDeleteAppender.java
new file mode 100644
index 0000000..fd133f3
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/log/appender/RollingFileWithoutDeleteAppender.java
@@ -0,0 +1,175 @@
+/**
+ * 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.sentry.provider.db.log.appender;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.Writer;
+
+import org.apache.log4j.FileAppender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.helpers.CountingQuietWriter;
+import org.apache.log4j.helpers.LogLog;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.spi.LoggingEvent;
+
+public class RollingFileWithoutDeleteAppender extends FileAppender {
+ /**
+ * The default maximum file size is 10MB.
+ */
+ protected long maxFileSize = 10 * 1024 * 1024;
+
+ private long nextRollover = 0;
+
+ /**
+ * The default constructor simply calls its {@link FileAppender#FileAppender
+ * parents constructor}.
+ */
+ public RollingFileWithoutDeleteAppender() {
+ super();
+ }
+
+ /**
+ * Instantiate a RollingFileAppender and open the file designated by
+ * <code>filename</code>. The opened filename will become the ouput
+ * destination for this appender.
+ * <p>
+ * If the <code>append</code> parameter is true, the file will be appended to.
+ * Otherwise, the file desginated by <code>filename</code> will be truncated
+ * before being opened.
+ */
+ public RollingFileWithoutDeleteAppender(Layout layout, String filename,
+ boolean append) throws IOException {
+ super(layout, getLogFileName(filename), append);
+ }
+
+ /**
+ * Instantiate a FileAppender and open the file designated by
+ * <code>filename</code>. The opened filename will become the output
+ * destination for this appender.
+ * <p>
+ * The file will be appended to.
+ */
+ public RollingFileWithoutDeleteAppender(Layout layout, String filename)
+ throws IOException {
+ super(layout, getLogFileName(filename));
+ }
+
+ /**
+ * Get the maximum size that the output file is allowed to reach before being
+ * rolled over to backup files.
+ */
+ public long getMaximumFileSize() {
+ return maxFileSize;
+ }
+
+ /**
+ * Implements the usual roll over behaviour.
+ * <p>
+ * <code>File</code> is renamed <code>File.yyyyMMddHHmmss</code> and closed. A
+ * new <code>File</code> is created to receive further log output.
+ */
+ // synchronization not necessary since doAppend is alreasy synched
+ public void rollOver() {
+ if (qw != null) {
+ long size = ((CountingQuietWriter) qw).getCount();
+ LogLog.debug("rolling over count=" + size);
+ // if operation fails, do not roll again until
+ // maxFileSize more bytes are written
+ nextRollover = size + maxFileSize;
+ }
+
+ this.closeFile(); // keep windows happy.
+
+ String newFileName = getLogFileName(fileName);
+ try {
+ // This will also close the file. This is OK since multiple
+ // close operations are safe.
+ this.setFile(newFileName, false, bufferedIO, bufferSize);
+ nextRollover = 0;
+ } catch (IOException e) {
+ if (e instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LogLog.error("setFile(" + newFileName + ", false) call failed: " + e.getMessage(), e);
+ }
+ }
+
+ public synchronized void setFile(String fileName, boolean append,
+ boolean bufferedIO, int bufferSize) throws IOException {
+ super.setFile(fileName, append, this.bufferedIO, this.bufferSize);
+ if (append) {
+ File f = new File(fileName);
+ ((CountingQuietWriter) qw).setCount(f.length());
+ }
+ }
+
+ /**
+ * Set the maximum size that the output file is allowed to reach before being
+ * rolled over to backup files.
+ * <p>
+ * This method is equivalent to {@link #setMaxFileSize} except that it is
+ * required for differentiating the setter taking a <code>long</code> argument
+ * from the setter taking a <code>String</code> argument by the JavaBeans
+ * {@link java.beans.Introspector Introspector}.
+ *
+ * @see #setMaxFileSize(String)
+ */
+ public void setMaximumFileSize(long maxFileSize) {
+ this.maxFileSize = maxFileSize;
+ }
+
+ /**
+ * Set the maximum size that the output file is allowed to reach before being
+ * rolled over to backup files.
+ * <p>
+ * In configuration files, the <b>MaxFileSize</b> option takes an long integer
+ * in the range 0 - 2^63. You can specify the value with the suffixes "KB",
+ * "MB" or "GB" so that the integer is interpreted being expressed
+ * respectively in kilobytes, megabytes or gigabytes. For example, the value
+ * "10KB" will be interpreted as 10240.
+ */
+ public void setMaxFileSize(String value) {
+ maxFileSize = OptionConverter.toFileSize(value, maxFileSize + 1);
+ }
+
+ protected void setQWForFiles(Writer writer) {
+ this.qw = new CountingQuietWriter(writer, errorHandler);
+ }
+
+ /**
+ * This method differentiates RollingFileAppender from its super class.
+ */
+ protected void subAppend(LoggingEvent event) {
+ super.subAppend(event);
+
+ if (fileName != null && qw != null) {
+ long size = ((CountingQuietWriter) qw).getCount();
+ if (size >= maxFileSize && size >= nextRollover) {
+ rollOver();
+ }
+ }
+ }
+
+ // Mangled file name. Append the current timestamp
+ private static String getLogFileName(String oldFileName) {
+ return oldFileName + "." + Long.toString(System.currentTimeMillis());
+ }
+}
http://git-wip-us.apache.org/repos/asf/sentry/blob/7db84b2f/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/log/entity/AuditMetadataLogEntity.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/log/entity/AuditMetadataLogEntity.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/log/entity/AuditMetadataLogEntity.java
new file mode 100644
index 0000000..a5fe4ec
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/log/entity/AuditMetadataLogEntity.java
@@ -0,0 +1,155 @@
+/**
+ * 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.sentry.provider.db.log.entity;
+
+import java.io.IOException;
+
+import org.codehaus.jackson.JsonFactory;
+import org.codehaus.jackson.JsonNode;
+import org.codehaus.jackson.map.MappingJsonFactory;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.node.ContainerNode;
+
+abstract public class AuditMetadataLogEntity implements JsonLogEntity {
+
+ static final JsonFactory factory = new MappingJsonFactory();
+ private String serviceName;
+ private String userName;
+ private String impersonator;
+ private String ipAddress;
+ private String operation;
+ private String eventTime;
+ private String operationText;
+ private String allowed;
+ private String objectType;
+ private String component;
+
+ void setCommonAttr(String serviceName, String userName, String impersonator, String ipAddress,
+ String operation, String eventTime, String operationText, String allowed, String objectType,
+ String component) {
+ this.serviceName = serviceName;
+ this.userName = userName;
+ this.impersonator = impersonator;
+ this.ipAddress = ipAddress;
+ this.operation = operation;
+ this.eventTime = eventTime;
+ this.operationText = operationText;
+ this.allowed = allowed;
+ this.objectType = objectType;
+ this.component = component;
+ }
+
+ public String getServiceName() {
+ return serviceName;
+ }
+
+ public void setServiceName(String serviceName) {
+ this.serviceName = serviceName;
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userName) {
+ this.userName = userName;
+ }
+
+ public String getImpersonator() {
+ return impersonator;
+ }
+
+ public void setImpersonator(String impersonator) {
+ this.impersonator = impersonator;
+ }
+
+ public String getIpAddress() {
+ return ipAddress;
+ }
+
+ public void setIpAddress(String ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+ public String getOperation() {
+ return operation;
+ }
+
+ public void setOperation(String operation) {
+ this.operation = operation;
+ }
+
+ public String getEventTime() {
+ return eventTime;
+ }
+
+ public void setEventTime(String eventTime) {
+ this.eventTime = eventTime;
+ }
+
+ public String getOperationText() {
+ return operationText;
+ }
+
+ public void setOperationText(String operationText) {
+ this.operationText = operationText;
+ }
+
+ public String getAllowed() {
+ return allowed;
+ }
+
+ public void setAllowed(String allowed) {
+ this.allowed = allowed;
+ }
+
+ public String getObjectType() {
+ return objectType;
+ }
+
+ public void setObjectType(String objectType) {
+ this.objectType = objectType;
+ }
+
+ public String getComponent() {
+ return component;
+ }
+
+ public void setComponent(String component) {
+ this.component = component;
+ }
+
+ /**
+ * For use in tests
+ *
+ * @param json
+ * incoming JSON to parse
+ * @return a node tree
+ * @throws IOException
+ * on any parsing problems
+ */
+ public static ContainerNode parse(String json) throws IOException {
+ ObjectMapper mapper = new ObjectMapper(factory);
+ JsonNode jsonNode = mapper.readTree(json);
+ if (!(jsonNode instanceof ContainerNode)) {
+ throw new IOException("Wrong JSON data: " + json);
+ }
+ return (ContainerNode) jsonNode;
+ }
+}
http://git-wip-us.apache.org/repos/asf/sentry/blob/7db84b2f/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/log/entity/DBAuditMetadataLogEntity.java
----------------------------------------------------------------------
diff --git a/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/log/entity/DBAuditMetadataLogEntity.java b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/log/entity/DBAuditMetadataLogEntity.java
new file mode 100644
index 0000000..4949ac7
--- /dev/null
+++ b/sentry-service/sentry-service-server/src/main/java/org/apache/sentry/provider/db/log/entity/DBAuditMetadataLogEntity.java
@@ -0,0 +1,124 @@
+/**
+ * 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.sentry.provider.db.log.entity;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+import org.apache.sentry.provider.db.log.util.Constants;
+import org.codehaus.jackson.JsonGenerator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DBAuditMetadataLogEntity extends AuditMetadataLogEntity {
+ private static final Logger LOGGER = LoggerFactory.getLogger(DBAuditMetadataLogEntity.class);
+
+ private String databaseName;
+ private String tableName;
+ private String columnName;
+ private String resourcePath;
+
+ public DBAuditMetadataLogEntity() {
+ }
+
+ public DBAuditMetadataLogEntity(String serviceName, String userName, String impersonator,
+ String ipAddress, String operation, String eventTime, String operationText, String allowed,
+ String objectType, String component, String databaseName, String tableName,
+ String columnName, String resourcePath) {
+ setCommonAttr(serviceName, userName, impersonator, ipAddress, operation, eventTime,
+ operationText, allowed, objectType, component);
+ this.databaseName = databaseName;
+ this.tableName = tableName;
+ this.columnName = columnName;
+ this.resourcePath = resourcePath;
+ }
+
+ public String getDatabaseName() {
+ return databaseName;
+ }
+
+ public void setDatabaseName(String databaseName) {
+ this.databaseName = databaseName;
+ }
+
+ public String getTableName() {
+ return tableName;
+ }
+
+ public void setTableName(String tableName) {
+ this.tableName = tableName;
+ }
+
+ public String getColumnName() {
+ return columnName;
+ }
+
+ public void setColumnName(String columnName) {
+ this.columnName = columnName;
+ }
+
+ public String getResourcePath() {
+ return resourcePath;
+ }
+
+ public void setResourcePath(String resourcePath) {
+ this.resourcePath = resourcePath;
+ }
+
+ @Override
+ public String toJsonFormatLog() throws Exception {
+ StringWriter stringWriter = new StringWriter();
+ JsonGenerator json = null;
+ try {
+ json = factory.createJsonGenerator(stringWriter);
+ json.writeStartObject();
+ json.writeStringField(Constants.LOG_FIELD_SERVICE_NAME, getServiceName());
+ json.writeStringField(Constants.LOG_FIELD_USER_NAME, getUserName());
+ json.writeStringField(Constants.LOG_FIELD_IMPERSONATOR, getImpersonator());
+ json.writeStringField(Constants.LOG_FIELD_IP_ADDRESS, getIpAddress());
+ json.writeStringField(Constants.LOG_FIELD_OPERATION, getOperation());
+ json.writeStringField(Constants.LOG_FIELD_EVENT_TIME, getEventTime());
+ json.writeStringField(Constants.LOG_FIELD_OPERATION_TEXT, getOperationText());
+ json.writeStringField(Constants.LOG_FIELD_ALLOWED, getAllowed());
+ json.writeStringField(Constants.LOG_FIELD_DATABASE_NAME, databaseName);
+ json.writeStringField(Constants.LOG_FIELD_TABLE_NAME, tableName);
+ json.writeStringField(Constants.LOG_FIELD_COLUMN_NAME, columnName);
+ json.writeStringField(Constants.LOG_FIELD_RESOURCE_PATH, resourcePath);
+ json.writeStringField(Constants.LOG_FIELD_OBJECT_TYPE, getObjectType());
+ json.writeEndObject();
+ json.flush();
+ } catch (IOException e) {
+ String msg = "Error creating audit log in json format: " + e.getMessage();
+ LOGGER.error(msg, e);
+ throw e;
+ } finally {
+ try {
+ if (json != null) {
+ json.close();
+ }
+ } catch (IOException e) {
+ String msg = "Error when close json object: " + e.getMessage();
+ LOGGER.error(msg, e);
+ throw e;
+ }
+ }
+
+ return stringWriter.toString();
+ }
+}