blob: f1e502a84fc2b3ece578e02874b24c7ddecd3720 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.sentry.provider.db.service.persistent;
import java.util.HashSet;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.Query;
import javax.jdo.Transaction;
import org.apache.sentry.provider.db.service.model.MSentryGroup;
import org.apache.sentry.provider.db.service.model.MSentryPrivilege;
import org.apache.sentry.provider.db.service.model.MSentryRole;
import org.apache.sentry.provider.db.service.thrift.TSentryGroup;
import org.apache.sentry.provider.db.service.thrift.TSentryPrivilege;
import org.apache.sentry.provider.db.service.thrift.TSentryRole;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
public class SentryStore {
private static final UUID SERVER_UUID = UUID.randomUUID();
static final String DEFAULT_DATA_DIR = "sentry_policy_db";
/**
* Commit order sequence id. This is used by notification handlers
* to know the order in which events where committed to the database.
* This instance variable is incremented in incrementGetSequenceId
* and read in commitUpdateTransaction. Synchronization on this
* is required to read commitSequenceId.
*/
private long commitSequenceId;
private final Properties prop;
private final PersistenceManagerFactory pmf;
private final String databaseName;
public SentryStore(String dataDir) {
commitSequenceId = 0;
databaseName = (dataDir = dataDir.trim()).isEmpty() ? DEFAULT_DATA_DIR : dataDir;
prop = getDataSourceProperties();
pmf = JDOHelper.getPersistenceManagerFactory(prop);
}
public SentryStore() {
this("");
}
public synchronized void stop() {
if (pmf != null) {
pmf.close();
}
}
private Properties getDataSourceProperties() {
Properties prop = new Properties();
// FIXME: Read from configuration, override the default
//prop.setProperty("datanucleus.connectionPoolingType", "BONECP");
prop.setProperty("datanucleus.validateTables", "false");
prop.setProperty("datanucleus.validateColumns", "false");
prop.setProperty("datanucleus.validateConstraints", "false");
prop.setProperty("datanucleus.storeManagerType", "rdbms");
prop.setProperty("datanucleus.autoCreateSchema", "true");
prop.setProperty("datanucleus.fixedDatastore", "false");
prop.setProperty("datanucleus.autoStartMechanismMode", "checked");
prop.setProperty("datanucleus.transactionIsolation", "read-committed");
prop.setProperty("datanucleus.cache.level2", "false");
prop.setProperty("datanucleus.cache.level2.type", "none");
prop.setProperty("datanucleus.identifierFactory", "datanucleus1");
prop.setProperty("datanucleus.rdbms.useLegacyNativeValueStrategy", "true");
prop.setProperty("datanucleus.plugin.pluginRegistryBundleCheck", "LOG");
prop.setProperty("javax.jdo.option.ConnectionDriverName",
"org.apache.derby.jdbc.EmbeddedDriver");
prop.setProperty("javax.jdo.PersistenceManagerFactoryClass",
"org.datanucleus.api.jdo.JDOPersistenceManagerFactory");
prop.setProperty("javax.jdo.option.DetachAllOnCommit", "true");
prop.setProperty("javax.jdo.option.NonTransactionalRead", "false");
prop.setProperty("javax.jdo.option.NonTransactionalWrite", "false");
prop.setProperty("javax.jdo.option.ConnectionUserName", "Sentry");
prop.setProperty("javax.jdo.option.ConnectionPassword", "Sentry");
prop.setProperty("javax.jdo.option.Multithreaded", "true");
prop.setProperty("javax.jdo.option.ConnectionURL",
"jdbc:derby:;databaseName=" + databaseName + ";create=true");
return prop;
}
/**
* PersistenceManager object and Transaction object have a one to one
* correspondence. Each PersistenceManager object is associated with a
* transaction object and vice versa. Hence we create a persistence manager
* instance when we create a new transaction. We create a new transaction
* for every store API since we want that unit of work to behave as a
* transaction.
*
* Note that there's only one instance of PersistenceManagerFactory object
* for the service.
*
* Synchronized because we obtain persistence manager
*/
private synchronized PersistenceManager openTransaction() {
PersistenceManager pm = pmf.getPersistenceManager();
Transaction currentTransaction = pm.currentTransaction();
currentTransaction.begin();
return pm;
}
/**
* Synchronized due to sequence id generation
*/
private synchronized CommitContext commitUpdateTransaction(PersistenceManager pm) {
commitTransaction(pm);
return new CommitContext(SERVER_UUID, incrementGetSequenceId());
}
/**
* Increments commitSequenceId which should not be modified outside
* this method.
*
* @return sequence id
*/
private synchronized long incrementGetSequenceId() {
return ++commitSequenceId;
}
private void commitTransaction(PersistenceManager pm) {
Transaction currentTransaction = pm.currentTransaction();
try {
Preconditions.checkState(currentTransaction.isActive(), "Transaction is not active");
currentTransaction.commit();
} finally {
pm.close();
}
}
private void rollbackTransaction(PersistenceManager pm) {
if (pm == null || pm.isClosed()) {
return;
}
Transaction currentTransaction = pm.currentTransaction();
if (currentTransaction.isActive()) {
try {
currentTransaction.rollback();
} finally {
pm.close();
}
}
}
public CommitContext createSentryRole(TSentryRole role)
throws SentryAlreadyExistsException {
boolean rollbackTransaction = true;
PersistenceManager pm = null;
try {
pm = openTransaction();
Query query = pm.newQuery(MSentryRole.class);
query.setFilter("this.roleName == t");
query.declareParameters("java.lang.String t");
query.setUnique(true);
MSentryRole sentryRole = (MSentryRole) query.execute(role.getRoleName());
if (sentryRole == null) {
MSentryRole mRole = convertToMSentryRole(role);
pm.makePersistent(mRole);
CommitContext commit = commitUpdateTransaction(pm);
rollbackTransaction = false;
return commit;
} else {
throw new SentryAlreadyExistsException("Role: " + role.getRoleName());
}
} finally {
if (rollbackTransaction) {
rollbackTransaction(pm);
}
}
}
//TODO: handle case where a) privilege already exists, b) role to privilege mapping already exists
public CommitContext alterSentryRoleGrantPrivilege(String roleName,
TSentryPrivilege privilege) throws SentryNoSuchObjectException {
boolean rollbackTransaction = true;
PersistenceManager pm = null;
try {
pm = openTransaction();
Query query = pm.newQuery(MSentryRole.class);
query.setFilter("this.roleName == t");
query.declareParameters("java.lang.String t");
query.setUnique(true);
MSentryRole mRole = (MSentryRole) query.execute(roleName);
if (mRole == null) {
throw new SentryNoSuchObjectException("Role: " + roleName);
} else {
MSentryPrivilege mPrivilege = convertToMSentryPrivilege(privilege);
// add privilege and role objects to each other. needed by datanucleus to model
// m:n relationships correctly through a join table.
mPrivilege.appendRole(mRole);
mRole.appendPrivilege(mPrivilege);
pm.makePersistent(mRole);
pm.makePersistent(mPrivilege);
CommitContext commit = commitUpdateTransaction(pm);
rollbackTransaction = false;
return commit;
}
} finally {
if (rollbackTransaction) {
rollbackTransaction(pm);
}
}
}
public CommitContext alterSentryRoleRevokePrivilege(String roleName,
String privilegeName) throws SentryNoSuchObjectException {
boolean rollbackTransaction = true;
PersistenceManager pm = null;
try {
pm = openTransaction();
Query query = pm.newQuery(MSentryRole.class);
query.setFilter("this.roleName == t");
query.declareParameters("java.lang.String t");
query.setUnique(true);
MSentryRole mRole = (MSentryRole) query.execute(roleName);
if (mRole == null) {
throw new SentryNoSuchObjectException("Role: " + roleName);
} else {
query = pm.newQuery(MSentryPrivilege.class);
query.setFilter("this.privilegeName == t");
query.declareParameters("java.lang.String t");
query.setUnique(true);
MSentryPrivilege mPrivilege = (MSentryPrivilege) query.execute(privilegeName);
if (mPrivilege == null) {
throw new SentryNoSuchObjectException("Privilege: " + privilegeName);
} else {
// remove privilege and role objects from each other's set. needed by datanucleus to model
// m:n relationships correctly through a join table.
mRole.removePrivilege(mPrivilege);
CommitContext commit = commitUpdateTransaction(pm);
rollbackTransaction = false;
return commit;
}
}
} finally {
if (rollbackTransaction) {
rollbackTransaction(pm);
}
}
}
public CommitContext dropSentryRole(String roleName)
throws SentryNoSuchObjectException {
boolean rollbackTransaction = true;
PersistenceManager pm = null;
roleName = roleName.trim();
try {
pm = openTransaction();
Query query = pm.newQuery(MSentryRole.class);
query.setFilter("this.roleName == t");
query.declareParameters("java.lang.String t");
query.setUnique(true);
MSentryRole sentryRole = (MSentryRole) query.execute(roleName);
if (sentryRole == null) {
throw new SentryNoSuchObjectException("Role " + roleName);
} else {
pm.retrieve(sentryRole);
sentryRole.removePrivileges();
pm.deletePersistent(sentryRole);
}
CommitContext commit = commitUpdateTransaction(pm);
rollbackTransaction = false;
return commit;
} finally {
if (rollbackTransaction) {
rollbackTransaction(pm);
}
}
}
public CommitContext alterSentryRoleAddGroups(String grantorPrincipal,
String roleName, Set<TSentryGroup> groupNames)
throws SentryNoSuchObjectException {
boolean rollbackTransaction = true;
PersistenceManager pm = null;
try {
pm = openTransaction();
Query query = pm.newQuery(MSentryRole.class);
query.setFilter("this.roleName == t");
query.declareParameters("java.lang.String t");
query.setUnique(true);
MSentryRole role = (MSentryRole) query.execute(roleName);
if (role == null) {
throw new SentryNoSuchObjectException("Role: " + roleName);
} else {
query = pm.newQuery(MSentryGroup.class);
query.setFilter("this.groupName == t");
query.declareParameters("java.lang.String t");
query.setUnique(true);
List<MSentryGroup> groups = Lists.newArrayList();
for (TSentryGroup tGroup : groupNames) {
MSentryGroup group = (MSentryGroup) query.execute(tGroup.getGroupName());
if (group == null) {
group = new MSentryGroup(tGroup.getGroupName(), System.currentTimeMillis(),
grantorPrincipal, Sets.newHashSet(role));
}
group.appendRole(role);
groups.add(group);
}
pm.makePersistentAll(groups);
CommitContext commit = commitUpdateTransaction(pm);
rollbackTransaction = false;
return commit;
}
} finally {
if (rollbackTransaction) {
rollbackTransaction(pm);
}
}
}
public CommitContext alterSentryRoleDeleteGroups(String roleName,
Set<TSentryGroup> groupNames)
throws SentryNoSuchObjectException {
boolean rollbackTransaction = true;
PersistenceManager pm = null;
try {
pm = openTransaction();
Query query = pm.newQuery(MSentryRole.class);
query.setFilter("this.roleName == t");
query.declareParameters("java.lang.String t");
query.setUnique(true);
MSentryRole role = (MSentryRole) query.execute(roleName);
if (role == null) {
throw new SentryNoSuchObjectException("Role: " + roleName);
} else {
query = pm.newQuery(MSentryGroup.class);
query.setFilter("this.groupName == t");
query.declareParameters("java.lang.String t");
query.setUnique(true);
List<MSentryGroup> groups = Lists.newArrayList();
for (TSentryGroup tGroup : groupNames) {
MSentryGroup group = (MSentryGroup) query.execute(tGroup.getGroupName());
if (group != null) {
group.removeRole(role);
groups.add(group);
}
}
pm.makePersistentAll(groups);
CommitContext commit = commitUpdateTransaction(pm);
rollbackTransaction = false;
return commit;
}
} finally {
if (rollbackTransaction) {
rollbackTransaction(pm);
}
}
}
@VisibleForTesting
MSentryRole getMSentryRoleByName(String roleName)
throws SentryNoSuchObjectException {
boolean rollbackTransaction = true;
PersistenceManager pm = null;
roleName = roleName.trim();
try {
pm = openTransaction();
Query query = pm.newQuery(MSentryRole.class);
query.setFilter("this.roleName == t");
query.declareParameters("java.lang.String t");
query.setUnique(true);
MSentryRole sentryRole = (MSentryRole) query.execute(roleName);
if (sentryRole == null) {
throw new SentryNoSuchObjectException("Role " + roleName);
} else {
pm.retrieve(sentryRole);
}
rollbackTransaction = false;
commitTransaction(pm);
return sentryRole;
} finally {
if (rollbackTransaction) {
rollbackTransaction(pm);
}
}
}
public TSentryRole getSentryRoleByName(String roleName)
throws SentryNoSuchObjectException {
return convertToSentryRole(getMSentryRoleByName(roleName));
}
private MSentryRole convertToMSentryRole(TSentryRole role) {
MSentryRole mRole = new MSentryRole();
mRole.setCreateTime(role.getCreateTime());
mRole.setRoleName(role.getRoleName());
mRole.setGrantorPrincipal(role.getGrantorPrincipal());
return mRole;
}
private TSentryRole convertToSentryRole(MSentryRole mSentryRole) {
TSentryRole role = new TSentryRole();
role.setCreateTime(mSentryRole.getCreateTime());
role.setRoleName(mSentryRole.getRoleName());
role.setGrantorPrincipal(mSentryRole.getGrantorPrincipal());
Set<TSentryPrivilege> sentryPrivileges = new HashSet<TSentryPrivilege>();
for(MSentryPrivilege mSentryPrivilege:mSentryRole.getPrivileges()) {
TSentryPrivilege privilege = convertToSentryPrivilege(mSentryPrivilege);
sentryPrivileges.add(privilege);
}
role.setPrivileges(sentryPrivileges);
return role;
}
private TSentryPrivilege convertToSentryPrivilege(MSentryPrivilege mSentryPrivilege) {
TSentryPrivilege privilege = new TSentryPrivilege();
privilege.setCreateTime(mSentryPrivilege.getCreateTime());
privilege.setPrivilegeName(mSentryPrivilege.getPrivilegeName());
privilege.setAction(mSentryPrivilege.getAction());
privilege.setPrivilegeScope(mSentryPrivilege.getPrivilegeScope());
privilege.setServerName(mSentryPrivilege.getServerName());
privilege.setDbName(mSentryPrivilege.getDbName());
privilege.setTableName(mSentryPrivilege.getTableName());
privilege.setURI(mSentryPrivilege.getURI());
privilege.setGrantorPrincipal(mSentryPrivilege.getGrantorPrincipal());
return privilege;
}
private MSentryPrivilege convertToMSentryPrivilege(TSentryPrivilege privilege) {
MSentryPrivilege mSentryPrivilege = new MSentryPrivilege();
mSentryPrivilege.setServerName(privilege.getServerName());
mSentryPrivilege.setDbName(privilege.getDbName());
mSentryPrivilege.setTableName(privilege.getTableName());
mSentryPrivilege.setPrivilegeScope(privilege.getPrivilegeScope());
mSentryPrivilege.setAction(privilege.getAction());
mSentryPrivilege.setCreateTime(privilege.getCreateTime());
mSentryPrivilege.setGrantorPrincipal(privilege.getGrantorPrincipal());
mSentryPrivilege.setURI(privilege.getURI());
mSentryPrivilege.setPrivilegeName(privilege.getPrivilegeName());
return mSentryPrivilege;
}
}