package org.identityconnectors.oracle;
import static org.identityconnectors.oracle.OracleMessages.MSG_ERROR_EXECUTING_SEARCH;
import static org.identityconnectors.oracle.OracleMessages.MSG_SEARCH_ATTRIBUTE_NOT_SUPPORTED_FOR_ATTRIBUTESTOGET;
import static org.identityconnectors.oracle.OracleMessages.MSG_SEARCH_ATTRIBUTE_NOT_SUPPORTED_FOR_SEARCHBY;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.TreeSet;
import org.identityconnectors.common.logging.Log;
import org.identityconnectors.dbcommon.DatabaseFilterTranslator;
import org.identityconnectors.dbcommon.DatabaseQueryBuilder;
import org.identityconnectors.dbcommon.FilterWhereBuilder;
import org.identityconnectors.dbcommon.SQLParam;
import org.identityconnectors.dbcommon.SQLUtil;
import org.identityconnectors.framework.common.exceptions.ConnectorException;
import org.identityconnectors.framework.common.objects.Attribute;
import org.identityconnectors.framework.common.objects.AttributeBuilder;
import org.identityconnectors.framework.common.objects.AttributeUtil;
import org.identityconnectors.framework.common.objects.ConnectorMessages;
import org.identityconnectors.framework.common.objects.ConnectorObjectBuilder;
import org.identityconnectors.framework.common.objects.Name;
import org.identityconnectors.framework.common.objects.ObjectClass;
import org.identityconnectors.framework.common.objects.OperationOptions;
import org.identityconnectors.framework.common.objects.OperationalAttributes;
import org.identityconnectors.framework.common.objects.ResultsHandler;
import org.identityconnectors.framework.common.objects.Uid;
import org.identityconnectors.framework.common.objects.filter.Filter;
import org.identityconnectors.framework.common.objects.filter.FilterTranslator;
import org.identityconnectors.framework.spi.operations.SearchOp;
/**
* Oracle search actually executes query to search for users. It uses
* DBA_USERS,DBA_TS_QUOTAS,DBA_ROLE_PRIVS,DBA_SYS_PRIVS,USER_TAB_PRIVS views to
* perform query
*
* @author kitko
*
*/
final class OracleOperationSearch extends AbstractOracleOperation implements
SearchOp<Pair<String, FilterWhereBuilder>> {
private static final String SQL = "SELECT DISTINCT DBA_USERS.* FROM DBA_USERS";
private static final String ADVANCED_SQL1 =
SQL
+ " LEFT JOIN DBA_TS_QUOTAS DEF_QUOTA "
+ "ON DBA_USERS.USERNAME = DEF_QUOTA.USERNAME AND DBA_USERS.DEFAULT_TABLESPACE=DEF_QUOTA.TABLESPACE_NAME "
+ "LEFT JOIN DBA_TS_QUOTAS TEMP_QUOTA "
+ "ON DBA_USERS.USERNAME = DEF_QUOTA.USERNAME AND DBA_USERS.TEMPORARY_TABLESPACE=TEMP_QUOTA.TABLESPACE_NAME";
private static final String ADVANCED_SQL2 = ADVANCED_SQL1 + " LEFT JOIN DBA_ROLE_PRIVS "
+ "ON DBA_USERS.USERNAME=DBA_ROLE_PRIVS.GRANTEE "
+ "LEFT JOIN DBA_SYS_PRIVS ON DBA_USERS.USERNAME=DBA_SYS_PRIVS.GRANTEE "
+ "LEFT JOIN DBA_TAB_PRIVS ON DBA_USERS.USERNAME=USER_TAB_PRIVS.GRANTEE";
static final Collection<String> VALID_ATTRIBUTES_TO_GET;
private static final Collection<String> VALID_SEARCH_BY_ATTRIBUTES;
private static final Log LOG = Log.getLog(OracleOperationSearch.class);
static {
Collection<String> tmp =
new TreeSet<String>(OracleConnectorHelper.getAttributeNamesComparator());
tmp.addAll(OracleConstants.ALL_ATTRIBUTE_NAMES);
tmp.remove(OperationalAttributes.PASSWORD_NAME);
tmp.add(Uid.NAME);
VALID_ATTRIBUTES_TO_GET = Collections.unmodifiableCollection(tmp);
VALID_SEARCH_BY_ATTRIBUTES = Collections.unmodifiableCollection(tmp);
}
OracleOperationSearch(OracleConfiguration cfg, Connection adminConn) {
super(cfg, adminConn);
}
public FilterTranslator<Pair<String, FilterWhereBuilder>> createFilterTranslator(
ObjectClass oclass, OperationOptions options) {
return new OracleFilterTranslator(oclass, options, cfg.getConnectorMessages(), cfg
.getCSSetup());
}
public void executeQuery(ObjectClass oclass, Pair<String, FilterWhereBuilder> pair,
ResultsHandler handler, OperationOptions options) {
final DatabaseQueryBuilder query = new DatabaseQueryBuilder(pair.getFirst());
query.setWhere(pair.getSecond());
final String sql = query.getSQL();
PreparedStatement st = null;
ResultSet rs = null;
try {
LOG.info("Executing search query : {0}", sql);
st = this.adminConn.prepareStatement(sql);
SQLUtil.setParams(st, query.getParams());
rs = st.executeQuery();
OracleUserReader userReader =
new OracleUserReader(adminConn, cfg.getConnectorMessages());
Collection<String> attributesToGet = null;
checkAttributesToGet(options.getAttributesToGet());
if (options.getAttributesToGet() != null && options.getAttributesToGet().length > 0) {
attributesToGet = new HashSet<String>(Arrays.asList(options.getAttributesToGet()));
} else {
// Now all attributes are read by default
// There is small performance problem with reading
// roles,privileges,quotas
attributesToGet = VALID_ATTRIBUTES_TO_GET;
}
boolean found = false;
while (rs.next()) {
found = true;
ConnectorObjectBuilder builder =
buildConnectorObject(rs, userReader, attributesToGet);
if (!handler.handle(builder.build())) {
break;
}
}
rs.close();
if (!found) {
// This is hack to search case insensitive by name
// If case sensitivity policy does not upper case user names
if (OracleNormalizerName.INPUT_AUTH.equals(cfg.getNormalizerName())
&& cfg.getCSSetup().getAttributeFormatterAndNormalizer(
OracleUserAttribute.USER).isToUpper()) {
handleCaseInsensitiveNames(pair, attributesToGet, handler, userReader);
}
}
adminConn.commit();
} catch (Exception e) {
throw new ConnectorException(cfg.getConnectorMessages().format(
MSG_ERROR_EXECUTING_SEARCH, null), e);
} finally {
SQLUtil.closeQuietly(rs);
SQLUtil.closeQuietly(st);
}
}
// This is hack for dummy applications, that send Name with wrong case
private void handleCaseInsensitiveNames(Pair<String, FilterWhereBuilder> pair,
Collection<String> attributesToGet, ResultsHandler handler, OracleUserReader userReader)
throws SQLException {
DatabaseQueryBuilder query = new DatabaseQueryBuilder(pair.getFirst());
query.setWhere(pair.getSecond());
String sql = query.getSQL();
if ("SELECT DISTINCT DBA_USERS.* FROM DBA_USERS WHERE DBA_USERS.USERNAME = ?".equals(sql)) {
sql =
"SELECT DISTINCT DBA_USERS.* FROM DBA_USERS WHERE UPPER(DBA_USERS.USERNAME) = UPPER(?)";
PreparedStatement st = null;
ResultSet rs = null;
try {
st = this.adminConn.prepareStatement(sql);
SQLUtil.setParams(st, query.getParams());
rs = st.executeQuery();
if (rs.next()) {
ConnectorObjectBuilder builder =
buildConnectorObject(rs, userReader, attributesToGet);
// If there are more accounts, do not return any of them
if (rs.next()) {
return;
}
// We must set name to value from filter, otherwise
// framework would filter it
String name = (String) query.getParams().get(0).getValue();
builder.addAttribute(new Name(name));
handler.handle(builder.build());
}
} finally {
SQLUtil.closeQuietly(rs);
SQLUtil.closeQuietly(st);
}
}
}
private ConnectorObjectBuilder buildConnectorObject(ResultSet rs, OracleUserReader userReader,
Collection<String> attributesToGet) throws SQLException {
ConnectorObjectBuilder bld = new ConnectorObjectBuilder();
final String userName = rs.getString("USERNAME");
bld.setUid(new Uid(userName));
bld.setName(userName);
bld.setObjectClass(ObjectClass.ACCOUNT);
if (attributesToGet.contains(Name.NAME)) {
bld.addAttribute(new Name(userName));
}
UserRecord record = OracleUserReader.translateRowToUserRecord(rs);
if (attributesToGet.contains(OracleConstants.ORACLE_DEF_TS_ATTR_NAME)) {
bld.addAttribute(OracleConnectorHelper.buildSingleAttribute(
OracleConstants.ORACLE_DEF_TS_ATTR_NAME, record.getDefaultTableSpace()));
}
if (attributesToGet.contains(OracleConstants.ORACLE_TEMP_TS_ATTR_NAME)) {
bld.addAttribute(OracleConnectorHelper.buildSingleAttribute(
OracleConstants.ORACLE_TEMP_TS_ATTR_NAME, record.getTemporaryTableSpace()));
}
if (attributesToGet.contains(OracleConstants.ORACLE_AUTHENTICATION_ATTR_NAME)) {
bld.addAttribute(OracleConnectorHelper.buildSingleAttribute(
OracleConstants.ORACLE_AUTHENTICATION_ATTR_NAME, OracleUserReader
.resolveAuthentication(record).toString()));
}
if (attributesToGet.contains(OracleConstants.ORACLE_GLOBAL_ATTR_NAME)) {
bld.addAttribute(OracleConnectorHelper.buildSingleAttribute(
OracleConstants.ORACLE_GLOBAL_ATTR_NAME, record.getExternalName()));
}
if (attributesToGet.contains(OracleConstants.ORACLE_PROFILE_ATTR_NAME)) {
bld.addAttribute(OracleConnectorHelper.buildSingleAttribute(
OracleConstants.ORACLE_PROFILE_ATTR_NAME, record.getProfile()));
}
if (attributesToGet.contains(OracleConstants.ORACLE_DEF_TS_QUOTA_ATTR_NAME)) {
Long quota = userReader.readUserTSQuota(userName, record.getDefaultTableSpace());
bld.addAttribute(OracleConnectorHelper.buildSingleAttribute(
OracleConstants.ORACLE_DEF_TS_QUOTA_ATTR_NAME, quota != null ? quota.toString()
: null));
}
if (attributesToGet.contains(OracleConstants.ORACLE_TEMP_TS_QUOTA_ATTR_NAME)) {
Long quota = userReader.readUserTSQuota(userName, record.getTemporaryTableSpace());
bld.addAttribute(OracleConnectorHelper.buildSingleAttribute(
OracleConstants.ORACLE_TEMP_TS_QUOTA_ATTR_NAME, quota != null ? quota
.toString() : null));
}
if (attributesToGet.contains(OracleConstants.ORACLE_PRIVS_ATTR_NAME)) {
bld.addAttribute(AttributeBuilder.build(OracleConstants.ORACLE_PRIVS_ATTR_NAME,
new OracleRolePrivReader(adminConn).readAllPrivileges(userName)));
}
if (attributesToGet.contains(OracleConstants.ORACLE_ROLES_ATTR_NAME)) {
bld.addAttribute(AttributeBuilder.build(OracleConstants.ORACLE_ROLES_ATTR_NAME,
new OracleRolePrivReader(adminConn).readRoles(userName)));
}
if (attributesToGet.contains(OperationalAttributes.PASSWORD_EXPIRED_NAME)) {
bld.addAttribute(AttributeBuilder.build(OperationalAttributes.PASSWORD_EXPIRED_NAME,
OracleUserReader.isPasswordExpired(record)));
}
if (attributesToGet.contains(OperationalAttributes.ENABLE_NAME)) {
bld.addAttribute(AttributeBuilder.build(OperationalAttributes.ENABLE_NAME, Boolean
.valueOf(!OracleUserReader.isUserLocked(record))));
}
if (attributesToGet.contains(OperationalAttributes.LOCK_OUT_NAME)) {
bld.addAttribute(AttributeBuilder.build(OperationalAttributes.LOCK_OUT_NAME, Boolean
.valueOf(OracleUserReader.isUserLocked(record))));
}
if (attributesToGet.contains(OperationalAttributes.PASSWORD_EXPIRATION_DATE_NAME)) {
Long date = record.getExpireDate() != null ? record.getExpireDate().getTime() : null;
bld.addAttribute(OracleConnectorHelper.buildSingleAttribute(
OperationalAttributes.PASSWORD_EXPIRATION_DATE_NAME, date));
}
if (attributesToGet.contains(OperationalAttributes.DISABLE_DATE_NAME)) {
Long date = record.getLockDate() != null ? record.getLockDate().getTime() : null;
bld.addAttribute(OracleConnectorHelper.buildSingleAttribute(
OperationalAttributes.DISABLE_DATE_NAME, date));
}
return bld;
}
private void checkAttributesToGet(String[] attributesToGet) {
if (attributesToGet == null) {
return;
}
for (String attribute : attributesToGet) {
// We do not need to use Attribute.is, we use Attribute comparator
if (!VALID_ATTRIBUTES_TO_GET.contains(attribute)) {
throw new IllegalArgumentException(cfg.getConnectorMessages().format(
MSG_SEARCH_ATTRIBUTE_NOT_SUPPORTED_FOR_ATTRIBUTESTOGET, null, attribute));
}
}
}
private static final class OracleDBFilterTranslator extends DatabaseFilterTranslator {
private String select = SQL;
private final ConnectorMessages cm;
private final OracleCaseSensitivitySetup cs;
OracleDBFilterTranslator(ObjectClass oclass, OperationOptions options,
ConnectorMessages cm, OracleCaseSensitivitySetup cs) {
super(oclass, options);
this.cm = OracleConnectorHelper.assertNotNull(cm, "cm");
this.cs = OracleConnectorHelper.assertNotNull(cs, "cs");
}
private void checkSearchByAttribute(Attribute attribute) {
if (!VALID_SEARCH_BY_ATTRIBUTES.contains(attribute.getName())) {
throw new IllegalArgumentException(cm.format(
MSG_SEARCH_ATTRIBUTE_NOT_SUPPORTED_FOR_SEARCHBY, null, attribute.getName()));
}
}
@Override
protected SQLParam getSQLParam(Attribute attribute, ObjectClass oclass,
OperationOptions options) {
checkSearchByAttribute(attribute);
String columnName = null;
if (attribute.is(Name.NAME)) {
columnName = cs.formatSQLColumn(OracleUserAttribute.USER, "DBA_USERS.USERNAME");
return createSingleVarcharParam(attribute, columnName);
}
// we do not normalize UID
else if (attribute.is(Uid.NAME)) {
columnName = cs.formatSQLColumn(OracleUserAttribute.USER, "DBA_USERS.USERNAME");
return createSingleVarcharParam(attribute, columnName);
} else if (attribute.is(OracleConstants.ORACLE_DEF_TS_ATTR_NAME)) {
columnName =
cs.formatSQLColumn(OracleUserAttribute.DEF_TABLESPACE,
"DBA_USERS.DEFAULT_TABLESPACE");
return createSingleVarcharParam(attribute, columnName);
} else if (attribute.is(OracleConstants.ORACLE_TEMP_TS_ATTR_NAME)) {
columnName =
cs.formatSQLColumn(OracleUserAttribute.TEMP_TABLESPACE,
"DBA_USERS.TEMPORARY_TABLESPACE");
return createSingleVarcharParam(attribute, columnName);
} else if (attribute.is(OracleConstants.ORACLE_PROFILE_ATTR_NAME)) {
columnName = cs.formatSQLColumn(OracleUserAttribute.PROFILE, "DBA_USERS.PROFILE");
return createSingleVarcharParam(attribute, columnName);
} else if (attribute.is(OracleConstants.ORACLE_GLOBAL_ATTR_NAME)) {
columnName =
cs.formatSQLColumn(OracleUserAttribute.GLOBAL_NAME,
"DBA_USERS.EXTERNAL_NAME");
return createSingleVarcharParam(attribute, columnName);
} else if (attribute.is(OracleConstants.ORACLE_DEF_TS_QUOTA_ATTR_NAME)) {
if (select == SQL) {
select = ADVANCED_SQL1;
}
columnName = "DEF_QUOTA.MAX_BYTES";
return createSingleVarcharParam(attribute, columnName);
} else if (attribute.is(OracleConstants.ORACLE_TEMP_TS_QUOTA_ATTR_NAME)) {
if (select == SQL) {
select = ADVANCED_SQL1;
}
columnName = "TEMP_QUOTA.MAX_BYTES";
return createSingleVarcharParam(attribute, columnName);
} else if (attribute.is(OracleConstants.ORACLE_ROLES_ATTR_NAME)) {
select = ADVANCED_SQL2;
columnName =
cs.formatSQLColumn(OracleUserAttribute.ROLE, "DBA_ROLE_PRIVS.GRANTED_ROLE");
return createSingleVarcharParam(attribute, columnName);
} else if (attribute.is(OracleConstants.ORACLE_PRIVS_ATTR_NAME)) {
select = ADVANCED_SQL2;
columnName =
cs.formatSQLColumn(OracleUserAttribute.PRIVILEGE,
"DBA_SYS_PRIVS.GRANTED_ROLE");
return createSingleVarcharParam(attribute, columnName);
} else if (attribute.is(OracleConstants.ORACLE_AUTHENTICATION_ATTR_NAME)) {
columnName =
"(CASE WHEN DBA_USERS.PASSWORD='EXTERNAL' THEN 'EXTERNAL' ELSE (CASE WHEN DBA_USERS.EXTERNAL_NAME IS NOT NULL THEN 'GLOBAL' ELSE 'LOCAL' END) END)";
return createSingleVarcharParam(attribute, columnName);
} else if (attribute.is(OperationalAttributes.PASSWORD_EXPIRED_NAME)) {
columnName =
"(CASE WHEN DBA_USERS.ACCOUNT_STATUS LIKE '%EXPIRED%' THEN 'EXPIRED' ELSE 'NOT_EXPIRED' END)";
Boolean value = (Boolean) AttributeUtil.getSingleValue(attribute);
if (value == null) {
return null;
}
return value ? new SQLParam(columnName, "EXPIRED", Types.VARCHAR) : new SQLParam(
columnName, "NOT_EXPIRED", Types.VARCHAR);
} else if (attribute.is(OperationalAttributes.ENABLE_NAME)) {
columnName =
"(CASE WHEN DBA_USERS.ACCOUNT_STATUS LIKE '%LOCKED%' THEN 'LOCKED' ELSE 'NOT_LOCKED' END)";
Boolean value = (Boolean) AttributeUtil.getSingleValue(attribute);
if (value == null) {
return null;
}
return value ? new SQLParam(columnName, "NOT_LOCKED", Types.VARCHAR)
: new SQLParam(columnName, "LOCKED", Types.VARCHAR);
} else if (attribute.is(OperationalAttributes.LOCK_OUT_NAME)) {
columnName =
"(CASE WHEN DBA_USERS.ACCOUNT_STATUS LIKE '%LOCKED%' THEN 'LOCKED' ELSE 'NOT_LOCKED' END)";
Boolean value = (Boolean) AttributeUtil.getSingleValue(attribute);
if (value == null) {
return null;
}
return value ? new SQLParam(columnName, "LOCKED", Types.VARCHAR) : new SQLParam(
columnName, "NOT_LOCKED", Types.VARCHAR);
} else if (attribute.is(OperationalAttributes.DISABLE_DATE_NAME)) {
columnName = "DBA_USERS.LOCK_DATE";
Object date = AttributeUtil.getSingleValue(attribute);
if (date instanceof Long) {
date = new java.sql.Timestamp(((Long) date));
}
return new SQLParam(columnName, date, Types.TIMESTAMP);
} else if (attribute.is(OperationalAttributes.PASSWORD_EXPIRATION_DATE_NAME)) {
columnName = "DBA_USERS.EXPIRY_DATE";
Object date = AttributeUtil.getSingleValue(attribute);
if (date instanceof Long) {
date = new java.sql.Timestamp(((Long) date));
}
return new SQLParam(columnName, date, Types.TIMESTAMP);
}
throw new IllegalArgumentException("Cannot map db column for attribute : "
+ attribute.getName());
}
private SQLParam createSingleVarcharParam(Attribute attribute, String columnName) {
return new SQLParam(columnName, AttributeUtil.getSingleValue(attribute), Types.VARCHAR);
}
@Override
protected boolean validateSearchAttribute(Attribute attribute) {
checkSearchByAttribute(attribute);
// Currently We do not support in filter
if (attribute.is(OracleConstants.ORACLE_ROLES_ATTR_NAME)) {
return false;
}
if (attribute.is(OracleConstants.ORACLE_PRIVS_ATTR_NAME)) {
return false;
}
return true;
}
}
private static final class OracleFilterTranslator implements
FilterTranslator<Pair<String, FilterWhereBuilder>> {
private final OracleDBFilterTranslator delegate;
OracleFilterTranslator(ObjectClass oclass, OperationOptions options, ConnectorMessages cm,
OracleCaseSensitivitySetup cs) {
delegate = new OracleDBFilterTranslator(oclass, options, cm, cs);
}
public List<Pair<String, FilterWhereBuilder>> translate(Filter filter) {
List<FilterWhereBuilder> list = delegate.translate(filter);
List<Pair<String, FilterWhereBuilder>> result =
new ArrayList<Pair<String, FilterWhereBuilder>>();
if (list == null || list.isEmpty()) {
// We will send at least sql
result.add(new Pair<String, FilterWhereBuilder>(SQL, null));
return result;
}
for (FilterWhereBuilder where : list) {
result.add(new Pair<String, FilterWhereBuilder>(delegate.select, where));
}
return result;
}
}
}