You are viewing a plain text version of this content. The canonical link for it is here.
Posted to derby-commits@db.apache.org by da...@apache.org on 2008/05/20 20:38:33 UTC
svn commit: r658385 - in /db/derby/code/trunk/java:
engine/org/apache/derby/iapi/services/property/
engine/org/apache/derby/iapi/sql/dictionary/
engine/org/apache/derby/iapi/util/
engine/org/apache/derby/impl/sql/catalog/ engine/org/apache/derby/impl/s...
Author: dag
Date: Tue May 20 11:38:32 2008
New Revision: 658385
URL: http://svn.apache.org/viewvc?rev=658385&view=rev
Log:
DERBY-3673 Add checks that a new role isn't already a user authorization id
Patch derby-3673-2.
Modified:
db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/property/PropertyUtil.java
db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java
db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/IdUtil.java
db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/StringUtil.java
db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java
db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj
db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CreateRoleConstantAction.java
db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java
db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/RolesTest.java
Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/property/PropertyUtil.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/property/PropertyUtil.java?rev=658385&r1=658384&r2=658385&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/property/PropertyUtil.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/services/property/PropertyUtil.java Tue May 20 11:38:32 2008
@@ -29,10 +29,12 @@
import org.apache.derby.iapi.services.monitor.ModuleFactory;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.util.StringUtil;
+import org.apache.derby.iapi.util.IdUtil;
import java.util.Properties;
import java.io.Serializable;
import java.util.Dictionary;
+import java.util.Enumeration;
/**
There are 5 property objects within a JBMS system.
@@ -517,5 +519,109 @@
if (key.equals(PropertyUtil.servicePropertyList[i])) return true;
return false;
}
+
+
+ /**
+ * Return true if username is defined as an effective property
+ * i.e. there exists a property "<code>derby.user.</code><userid>"
+ * in the database (or, possibly, in system properties if not
+ * forbidden by derby.database.propertiesOnly). Note that <userid>
+ * found in a property will be normalized to internal form before
+ * comparison is performed against username, which is presumed
+ * normalized already.
+ *
+ * @param object which implements PersistentSet interface
+ * (TransactionController)
+ * @param username Normalized authorization identifier
+ *
+ * @returns true if match found
+ *
+ * @exception StandardException
+ */
+ public static boolean existsBuiltinUser (
+ PersistentSet set,
+ String username)
+ throws StandardException
+ {
+ if (propertiesContainsBuiltinUser(set.getProperties(), username)) {
+ return true;
+ }
+
+ // check system level propery, if allowed by
+ // derby.database.propertiesOnly
+ boolean dbOnly = false;
+ dbOnly = Boolean.valueOf(
+ PropertyUtil.getDatabaseProperty(
+ set,
+ Property.DATABASE_PROPERTIES_ONLY)).booleanValue();
+
+ if (!dbOnly &&
+ systemPropertiesExistsBuiltinUser(username)){
+ return true;
+ }
+
+ return false;
+ }
+
+
+ /**
+ * Return true if username is defined as a system property
+ * i.e. there exists a property "<code>derby.user.</code><userid>"
+ * in the system properties. Note that <userid> will be
+ * normalized to internal form before comparison is performed
+ * against username, which is presumed normalized already.
+ * @param username Normalized authorization identifier
+ * @returns true if match found
+ */
+ private static boolean systemPropertiesExistsBuiltinUser(String username)
+ {
+ ModuleFactory monitor = Monitor.getMonitorLite();
+
+ try {
+ Properties JVMProperties = System.getProperties();
+
+ if (propertiesContainsBuiltinUser(JVMProperties, username)) {
+ return true;
+ }
+ } catch (SecurityException e) {
+ // Running with security manager and we can't get at all
+ // JVM properties, to try to map the back the authid to
+ // how the user may have specified a matching id (1->many,
+ // since userids are subject to SQL up-casing).
+ String key= Property.USER_PROPERTY_PREFIX +
+ IdUtil.SQLIdentifier2CanonicalPropertyUsername(username);
+
+ if (monitor.getJVMProperty(key) != null) {
+ return true;
+ }
+ }
+
+ Properties applicationProperties = monitor.getApplicationProperties();
+
+ return propertiesContainsBuiltinUser(applicationProperties, username);
+ }
+
+ private static boolean propertiesContainsBuiltinUser(Properties props,
+ String username)
+ {
+ if (props != null) {
+ Enumeration e = props.propertyNames();
+
+ while (e.hasMoreElements()) {
+ String p = (String)e.nextElement();
+
+ if (p.startsWith(Property.USER_PROPERTY_PREFIX)) {
+ String userAsSpecified = StringUtil.normalizeSQLIdentifier(
+ p.substring(Property.USER_PROPERTY_PREFIX.length()));
+
+ if (username.equals(userAsSpecified)) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
}
Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java?rev=658385&r1=658384&r2=658385&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/sql/dictionary/DataDictionary.java Tue May 20 11:38:32 2008
@@ -350,6 +350,20 @@
throws StandardException;
/**
+ * Return true of there exists a schema whose authorizationId
+ * equals authid, i.e. SYSSCHEMAS contains a row whose column
+ * AUTHORIZATIONID equals authid.
+ *
+ * @param authid authorizationId
+ * @param tc TransactionController
+ * @returns true iff there is a matching schema
+ * @exception StandardException
+ */
+ public boolean existsSchemaOwnedBy(String authid,
+ TransactionController tc)
+ throws StandardException;
+
+ /**
* Get the descriptor for the system schema. Schema descriptors include
* authorization ids and schema ids.
*
@@ -1934,4 +1948,15 @@
boolean wait)
throws StandardException;
+ /**
+ * Check all dictionary tables and return true if there is any GRANT
+ * descriptor containing <code>authId</code> as its grantee.
+ *
+ * @param authId grantee for which a grant exists or not
+ * @param tc TransactionController for the transaction
+ * @return boolean true if such a grant exists
+ */
+ public boolean existsGrantToAuthid(String authId,
+ TransactionController tc)
+ throws StandardException;
}
Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/IdUtil.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/IdUtil.java?rev=658385&r1=658384&r2=658385&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/IdUtil.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/IdUtil.java Tue May 20 11:38:32 2008
@@ -1,6 +1,6 @@
/*
- Derby - Class com.ihost.cs.IdUtil
+ Derby - Class org.apache.derby.iapi.util.IdUtil
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
@@ -211,6 +211,73 @@
}
}
+
+ /**
+ * Given an internal SQL authorization identifier, convert it to a
+ * form that may be compared with the username of Derby builtin
+ * authentication, which uses Java properties of the from
+ * derby.user.<username>.
+ *
+ * The returned form is suitable for comparing to the property
+ * string after it has been parsed by Java, e.g. any backslash
+ * quotes has been processed. That is, this method does not add
+ * backslash quotes, either.
+ *
+ * E.g.
+ * EVE -> eve (never eVe, or EVE, or EVe: we use a lower case canonical
+ * form) [external property form: derby.user.eve]
+ * eVe -> "eVe" [external property form: derby.user."eVe"]
+ * "eve" -> "eve" [external property form: derby.user."\"eVe\""]
+ * \eve\ -> "\eve\" [external property form: derby.user."\\eve\\"]
+ *
+ * Since parseSQLIdentifier maps many-to-one, the backward mapping
+ * is non-unique, so the chosen lower case canonical from is
+ * arbitrary.
+ *
+ * E.g. we will not be able to correctly map back the non-canonical:
+ *
+ * [external property form: derby.user.eVe]
+ *
+ * since this is internally EVE (but see DERBY-3150), and maps back as eve.
+ *
+ * Note that the returned form is not necessarily parsable back
+ * using parseSQLIdentifier; it may need further quoting, cf. examples
+ * above of external property forms.
+ *
+ */
+ public static String SQLIdentifier2CanonicalPropertyUsername(String authid){
+ boolean needsQuote = false;
+ String result;
+
+ for (int i=0; i < authid.length(); i++) {
+ char c = authid.charAt(i);
+ // The only external form that needs no quoting contains
+ // only uppercase ASCII, underscore, and if not the first
+ // character, a decimal number. In all other cases, we
+ // envelop in double quotes.
+ if (!( (c >= 'A' && c <= 'Z') ||
+ (c == '_') ||
+ (i > 0 && (c >= '0' && c <= '9')))) {
+ needsQuote = true;
+ break;
+ }
+ }
+
+ if (!needsQuote) {
+ result = authid.toLowerCase();
+ } else {
+ StringBuffer b = new StringBuffer();
+ b.append("\"");
+ for (int i=0; i < authid.length(); i++) {
+ b.append(authid.charAt(i));
+ }
+ b.append("\"");
+ result = b.toString();
+ }
+
+ return result;
+ }
+
/**
* Parse a regular identifier (unquoted) returning returning either
* the value of the identifier or a delimited identifier. Ensures
Modified: db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/StringUtil.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/StringUtil.java?rev=658385&r1=658384&r2=658385&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/StringUtil.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/iapi/util/StringUtil.java Tue May 20 11:38:32 2008
@@ -363,5 +363,65 @@
}
+
+ /**
+ * Normalize a SQL identifer, up-casing if <regular identifer>,
+ * and handling of <delimited identifer> (SQL 2003, section 5.2).
+ * The normal form is used internally in Derby.
+ *
+ * @param id syntacically correct SQL identifier
+ */
+ public static String normalizeSQLIdentifier(String id) {
+ if (id.length() == 0) {
+ return id;
+ }
+
+ if (id.charAt(0) == '"' &&
+ id.length() >= 3 &&
+ id.charAt(id.length() - 1) == '"') {
+ // assume syntax is OK, thats is, any quotes inside are doubled:
+
+ return StringUtil.compressQuotes(
+ id.substring(1, id.length() - 1), "\"\"");
+
+ } else {
+ return StringUtil.SQLToUpperCase(id);
+ }
+ }
+
+
+ /**
+ * Compress 2 adjacent (single or double) quotes into a single (s or d)
+ * quote when found in the middle of a String.
+ *
+ * NOTE: """" or '''' will be compressed into "" or ''.
+ * This function assumes that the leading and trailing quote from a
+ * string or delimited identifier have already been removed.
+ * @param source string to be compressed
+ * @param quotes string containing two single or double quotes.
+ * @returns String where quotes have been compressed
+ */
+ public static String compressQuotes(String source, String quotes)
+ {
+ String result = source;
+ int index;
+
+ /* Find the first occurrence of adjacent quotes. */
+ index = result.indexOf(quotes);
+
+ /* Replace each occurrence with a single quote and begin the
+ * search for the next occurrence from where we left off.
+ */
+ while (index != -1) {
+ result = result.substring(0, index + 1) +
+ result.substring(index + 2);
+ index = result.indexOf(quotes, index + 1);
+ }
+
+ return result;
+ }
+
+
+
}
Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java?rev=658385&r1=658384&r2=658385&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/catalog/DataDictionaryImpl.java Tue May 20 11:38:32 2008
@@ -142,11 +142,13 @@
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Hashtable;
+import java.util.HashMap;
import java.util.Properties;
import java.util.Vector;
import java.util.List;
import java.util.Iterator;
+import java.util.LinkedList;
import java.util.Enumeration;
import java.io.InputStream;
@@ -1564,6 +1566,7 @@
false);
}
+
/**
* Get the SchemaDescriptor for the given schema identifier.
*
@@ -1629,6 +1632,76 @@
return locateSchemaRow(schemaId, tc);
}
+
+ /**
+ * Return true of there exists a schema whose authorizationId equals
+ * authid, i.e. SYS.SYSSCHEMAS contains a row whose column
+ * (AUTHORIZATIONID) equals authid.
+ *
+ * @param authid authorizationId
+ * @param tc TransactionController
+ * @returns true iff there is a matching schema
+ * @exception StandardException
+ */
+ public boolean existsSchemaOwnedBy(String authid,
+ TransactionController tc)
+ throws StandardException {
+
+ TabInfoImpl ti = coreInfo[SYSSCHEMAS_CORE_NUM];
+ SYSSCHEMASRowFactory
+ rf = (SYSSCHEMASRowFactory)ti.getCatalogRowFactory();
+ ConglomerateController
+ heapCC = tc.openConglomerate(
+ ti.getHeapConglomerate(), false, 0,
+ TransactionController.MODE_RECORD,
+ TransactionController.ISOLATION_REPEATABLE_READ);
+
+ DataValueDescriptor authIdOrderable = new SQLVarchar(authid);
+ ScanQualifier[][] scanQualifier = exFactory.getScanQualifier(1);
+
+ scanQualifier[0][0].setQualifier(
+ SYSSCHEMASRowFactory.SYSSCHEMAS_SCHEMAAID - 1, /* to zero-based */
+ authIdOrderable,
+ Orderable.ORDER_OP_EQUALS,
+ false,
+ false,
+ false);
+
+ ScanController sc = tc.openScan(
+ ti.getHeapConglomerate(),
+ false, // don't hold open across commit
+ 0, // for update
+ TransactionController.MODE_RECORD,
+ TransactionController.ISOLATION_REPEATABLE_READ,
+ (FormatableBitSet) null, // all fields as objects
+ (DataValueDescriptor[]) null, // start position -
+ 0, // startSearchOperation - none
+ scanQualifier, //
+ (DataValueDescriptor[]) null, // stop position -through last row
+ 0); // stopSearchOperation - none
+
+ boolean result = false;
+
+ try {
+ ExecRow outRow = rf.makeEmptyRow();
+
+ if (sc.fetchNext(outRow.getRowArray())) {
+ result = true;
+ }
+ } finally {
+ if (sc != null) {
+ sc.close();
+ }
+
+ if (heapCC != null) {
+ heapCC.close();
+ }
+ }
+
+ return result;
+ }
+
+
/**
* @see DataDictionary#addDescriptor
*/
@@ -2692,11 +2765,38 @@
TabInfoImpl ti = getNonCoreTI(SYSROLES_CATALOG_NUM);
SYSROLESRowFactory rf = (SYSROLESRowFactory)ti.getCatalogRowFactory();
- dropRoleGrants(ti,
- rf,
- rf.SYSROLES_GRANTEE_COLPOS_IN_INDEX_ID_EE_OR,
- grantee,
- tc);
+ visitRoleGrants(ti,
+ rf,
+ rf.SYSROLES_GRANTEE_COLPOS_IN_INDEX_ID_EE_OR,
+ grantee,
+ tc,
+ DataDictionaryImpl.DROP);
+ }
+
+
+ /**
+ * Return true if there exists a role grant to authorization
+ * identifier.
+ *
+ * @param grantee authorization identifier
+ * @param tc Transaction Controller
+ *
+ * @return true if there exists such a grant
+ * @exception StandardException Thrown on failure
+ */
+ public boolean existsRoleGrantByGrantee(String grantee,
+ TransactionController tc)
+ throws StandardException
+ {
+ TabInfoImpl ti = getNonCoreTI(SYSROLES_CATALOG_NUM);
+ SYSROLESRowFactory rf = (SYSROLESRowFactory)ti.getCatalogRowFactory();
+
+ return visitRoleGrants(ti,
+ rf,
+ rf.SYSROLES_GRANTEE_COLPOS_IN_INDEX_ID_EE_OR,
+ grantee,
+ tc,
+ DataDictionaryImpl.EXISTS);
}
@@ -2716,28 +2816,42 @@
TabInfoImpl ti = getNonCoreTI(SYSROLES_CATALOG_NUM);
SYSROLESRowFactory rf = (SYSROLESRowFactory)ti.getCatalogRowFactory();
- dropRoleGrants(ti,
- rf,
- rf.SYSROLES_ROLEID_COLPOS_IN_INDEX_ID_EE_OR,
- roleName,
- tc);
+ visitRoleGrants(ti,
+ rf,
+ rf.SYSROLES_ROLEID_COLPOS_IN_INDEX_ID_EE_OR,
+ roleName,
+ tc,
+ DataDictionaryImpl.DROP);
}
- /*
- * There is no index on roleid/grantee column only on SYSROLES, so
- * we use the index which contains roleid/grantee and scan that,
- * setting up a scan qualifier to match the roleid/grantee, then
- * delete the catalog entry.
- *
- * If this proves too slow, we should add an index on
- * roleid/grantee only.
- */
- private void dropRoleGrants(TabInfoImpl ti,
- SYSROLESRowFactory rf,
- int columnInIndex1,
- String authId,
- TransactionController tc)
- throws StandardException
+ /**
+ * Scan the {roleid, grantee, grantor} index on SYSROLES,
+ * locate rows containing authId in column columnNo.
+ *
+ * The action argument can be either <code>EXISTS</code> or
+ * <code>DROP</code> (to check for existence, or to drop that row).
+ *
+ * If the scan proves too slow, we should add more indexes. only.
+ *
+ * @param ti <code>TabInfoImpl</code> for SYSROLES.
+ * @param rf row factory for SYSROLES
+ * @param columnNo the column number to match <code>authId</code> against
+ * @param tc transaction controller
+ * @param action drop matching rows (<code>DROP</code>), or return
+ * <code>true</code> if there is a matching row
+ * (<code>EXISTS</code>)
+ *
+ * @returns boolean action=EXISTS: return <code>true</true> if there is a
+ * matching row else return <code>false</code>.
+ * @exception StandardException
+ */
+ private boolean visitRoleGrants(TabInfoImpl ti,
+ SYSROLESRowFactory rf,
+ int columnNo,
+ String authId,
+ TransactionController tc,
+ int action)
+ throws StandardException
{
ConglomerateController heapCC = tc.openConglomerate(
ti.getHeapConglomerate(), false, 0,
@@ -2748,7 +2862,7 @@
ScanQualifier[][] scanQualifier = exFactory.getScanQualifier(1);
scanQualifier[0][0].setQualifier(
- columnInIndex1 - 1, /* to zero-based */
+ columnNo - 1, /* to zero-based */
authIdOrderable,
Orderable.ORDER_OP_EQUALS,
false,
@@ -2776,8 +2890,12 @@
outRow);
while (sc.fetchNext(indexRow.getRowArray())) {
- ti.deleteRow(tc, indexRow,
- rf.SYSROLES_INDEX_ID_EE_OR_IDX);
+ if (action == DataDictionaryImpl.EXISTS) {
+ return true;
+ } else if (action == DataDictionaryImpl.DROP) {
+ ti.deleteRow(tc, indexRow,
+ rf.SYSROLES_INDEX_ID_EE_OR_IDX);
+ }
}
} finally {
if (sc != null) {
@@ -2788,6 +2906,7 @@
heapCC.close();
}
}
+ return false;
}
@@ -2830,27 +2949,93 @@
}
- /*
- * Presently only used when dropping roles - user dropping is not
- * under Derby control (well, built-in users are), any permissions
- * granted to users remain in place even if the user is no more.
+ /**
+ * Presently only used when dropping roles - user dropping is not under
+ * Derby control (well, built-in users are if properties are stored in
+ * database), any permissions granted to users remain in place even if the
+ * user is no more.
+ */
+ private void dropPermsByGrantee(String authId,
+ TransactionController tc,
+ int catalog,
+ int indexNo,
+ int granteeColnoInIndex)
+ throws StandardException
+ {
+ visitPermsByGrantee(authId,
+ tc,
+ catalog,
+ indexNo,
+ granteeColnoInIndex,
+ DataDictionaryImpl.DROP);
+ }
+
+ /**
+ * Return true if there exists a permission grant descriptor to this
+ * authorization id.
+ */
+ private boolean existsPermByGrantee(String authId,
+ TransactionController tc,
+ int catalog,
+ int indexNo,
+ int granteeColnoInIndex)
+ throws StandardException
+ {
+ return visitPermsByGrantee(authId,
+ tc,
+ catalog,
+ indexNo,
+ granteeColnoInIndex,
+ DataDictionaryImpl.EXISTS);
+ }
+
+
+ /**
+ * Possible action for visitPermsByGrantee and visitRoleGrants.
+ */
+ static final int DROP = 0;
+ /**
+ * Possible action for visitPermsByGrantee and visitRoleGrants.
+ */
+ static final int EXISTS = 1;
+
+ /**
+ * Scan <code>indexNo</code> index on a permission table
+ * <code>catalog</code>, looking for match(es) for the grantee column
+ * (given by granteeColnoInIndex for the catalog in question).
+ *
+ * The action argument can be either <code>EXISTS</code> or
+ * <code>DROP</code> (to check for existence, or to drop that row).
*
* There is no index on grantee column only on on any of the
* permissions tables, so we use the index which contain grantee
* and scan that, setting up a scan qualifier to match the
- * grantee, then fetch the case row to set up the permission
- * descriptor, then remove any cached entry, then finally delete
- * the catalog entry.
+ * grantee, then fetch the base row.
*
* If this proves too slow, we should add an index on grantee
* only.
+ *
+ * @param authId grantee to match against
+ * @param tc transaction controller
+ * @param catalog the underlying permission table to visit
+ * @param indexNo the number of the index by which to access the catalog
+ * @param granteeColnoInIndex the column number to match
+ * <code>authId</code> against
+ * @param action drop matching rows (<code>DROP</code>), or return
+ * <code>true</code> if there is a matching row
+ * (<code>EXISTS</code>)
+ *
+ * @returns boolean action=EXISTS: return <code>true</true> if there is a
+ * matching row else return <code>false</code>.
+ * @exception StandardException
*/
- private void dropPermsByGrantee(String authId,
- TransactionController tc,
- int catalog,
- int indexNo,
- int granteeColnoInIndex)
- throws StandardException
+ private boolean visitPermsByGrantee(String authId,
+ TransactionController tc,
+ int catalog,
+ int indexNo,
+ int granteeColnoInIndex,
+ int action)
+ throws StandardException
{
TabInfoImpl ti = getNonCoreTI(catalog);
PermissionsCatalogRowFactory rf =
@@ -2909,12 +3094,16 @@
"base row doesn't exist");
}
- PermissionsDescriptor perm = (PermissionsDescriptor)rf.
- buildDescriptor(outRow,
- (TupleDescriptor) null,
- this);
- removePermEntryInCache(perm);
- ti.deleteRow(tc, indexRow, indexNo);
+ if (action == DataDictionaryImpl.EXISTS) {
+ return true;
+ } else if (action == DataDictionaryImpl.DROP) {
+ PermissionsDescriptor perm = (PermissionsDescriptor)rf.
+ buildDescriptor(outRow,
+ (TupleDescriptor) null,
+ this);
+ removePermEntryInCache(perm);
+ ti.deleteRow(tc, indexRow, indexNo);
+ }
}
} finally {
if (sc != null) {
@@ -2925,6 +3114,7 @@
heapCC.close();
}
}
+ return false;
}
@@ -11670,4 +11860,42 @@
(List) null,
false);
}
+
+
+ /**
+ * Check all dictionary tables and return true if there is any GRANT
+ * descriptor containing <code>authId</code> as its grantee.
+ *
+ * @param authId grantee for which a grant exists or not
+ * @param tc TransactionController for the transaction
+ * @return boolean true if such a grant exists
+ */
+ public boolean existsGrantToAuthid(String authId,
+ TransactionController tc)
+ throws StandardException {
+
+ return
+ (existsPermByGrantee(
+ authId,
+ tc,
+ SYSTABLEPERMS_CATALOG_NUM,
+ SYSTABLEPERMSRowFactory.GRANTEE_TABLE_GRANTOR_INDEX_NUM,
+ SYSTABLEPERMSRowFactory.
+ GRANTEE_COL_NUM_IN_GRANTEE_TABLE_GRANTOR_INDEX) ||
+ existsPermByGrantee(
+ authId,
+ tc,
+ SYSCOLPERMS_CATALOG_NUM,
+ SYSCOLPERMSRowFactory.GRANTEE_TABLE_TYPE_GRANTOR_INDEX_NUM,
+ SYSCOLPERMSRowFactory.
+ GRANTEE_COL_NUM_IN_GRANTEE_TABLE_TYPE_GRANTOR_INDEX) ||
+ existsPermByGrantee(
+ authId,
+ tc,
+ SYSROUTINEPERMS_CATALOG_NUM,
+ SYSROUTINEPERMSRowFactory.GRANTEE_ALIAS_GRANTOR_INDEX_NUM,
+ SYSROUTINEPERMSRowFactory.
+ GRANTEE_COL_NUM_IN_GRANTEE_ALIAS_GRANTOR_INDEX) ||
+ existsRoleGrantByGrantee(authId, tc));
+ }
}
Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj?rev=658385&r1=658384&r2=658385&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/compile/sqlgrammar.jj Tue May 20 11:38:32 2008
@@ -393,34 +393,6 @@
return cm;
}
- /*
- ** Compress 2 adjacent (single or double) quotes into a single (s or d) quote when
- ** found in the middle of a String.
- ** NOTE: """" or '''' will be compressed into "" or ''.
- ** This function assumes that the leading and trailing quote from a
- ** string or delimited identifier have already been removed.
- */
- private static String compressQuotes(String source, String quotes)
- {
- String result = source;
- int index;
-
- /* Find the first occurrence of adjacent quotes. */
- index = result.indexOf(quotes);
-
- /* Replace each occurrence with a single quote and begin the
- * search for the next occurrence from where we left off.
- */
- while (index != -1)
- {
- result = result.substring(0, index + 1) + result.substring(index + 2);
-
- index = result.indexOf(quotes, index + 1);
- }
-
- return result;
- }
-
private static void verifyImageLength(String image) throws StandardException
{
// beetle 2758. For right now throw an error for literals > 64K
@@ -437,7 +409,7 @@
*/
private static String normalizeDelimitedID(String str)
{
- str = compressQuotes(str, DOUBLEQUOTES);
+ str = StringUtil.compressQuotes(str, DOUBLEQUOTES);
return str;
}
private static boolean isDATETIME(int val)
@@ -9865,10 +9837,10 @@
verifyImageLength(value);
/* Trim off the leading and trailing ', and compress all '' to ' */
if (value.startsWith("'") && value.endsWith("'"))
- value = compressQuotes(value.substring(1, value.length() - 1), SINGLEQUOTES);
+ value = StringUtil.compressQuotes(value.substring(1, value.length() - 1), SINGLEQUOTES);
/* Trim off the leading and trailing ", and compress all "" to " */
else if (value.startsWith("\"") && value.endsWith("\""))
- value = compressQuotes(value.substring(1, value.length() - 1), DOUBLEQUOTES);
+ value = StringUtil.compressQuotes(value.substring(1, value.length() - 1), DOUBLEQUOTES);
else
value = value.toUpperCase();
// Do not allow user to specify multiple values for the same key
@@ -11065,7 +11037,7 @@
{
verifyImageLength(tok.image);
/* Trim off the leading and trailing ', and compress all '' to ' */
- return compressQuotes(tok.image.substring(1, tok.image.length() - 1),
+ return StringUtil.compressQuotes(tok.image.substring(1, tok.image.length() - 1),
SINGLEQUOTES);
}
}
@@ -11085,7 +11057,7 @@
//there is a maximum limit on the length of the string
if (tok.image.length()-2 > Limits.DB2_MAX_CHARACTER_LITERAL_LENGTH)//-2 is for the beginning and ending quote
throw StandardException.newException(SQLState.LANG_DB2_STRING_CONSTANT_TOO_LONG, StringUtil.formatForPrint(tok.image));
- string = compressQuotes(tok.image.substring(1, tok.image.length() - 1), SINGLEQUOTES);
+ string = StringUtil.compressQuotes(tok.image.substring(1, tok.image.length() - 1), SINGLEQUOTES);
/* Trim quotes from string. */
return (CharConstantNode) nodeFactory.getNode(
C_NodeTypes.CHAR_CONSTANT_NODE,
Modified: db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CreateRoleConstantAction.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CreateRoleConstantAction.java?rev=658385&r1=658384&r2=658385&view=diff
==============================================================================
--- db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CreateRoleConstantAction.java (original)
+++ db/derby/code/trunk/java/engine/org/apache/derby/impl/sql/execute/CreateRoleConstantAction.java Tue May 20 11:38:32 2008
@@ -23,7 +23,10 @@
import org.apache.derby.iapi.sql.execute.ConstantAction;
+import org.apache.derby.iapi.services.property.PropertyUtil;
import org.apache.derby.iapi.error.StandardException;
+import org.apache.derby.iapi.util.IdUtil;
+import org.apache.derby.iapi.jdbc.AuthenticationService;
import org.apache.derby.iapi.sql.Activation;
import org.apache.derby.iapi.sql.conn.Authorizer;
import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
@@ -31,8 +34,9 @@
import org.apache.derby.iapi.sql.dictionary.RoleDescriptor;
import org.apache.derby.iapi.sql.dictionary.DataDictionary;
import org.apache.derby.iapi.store.access.TransactionController;
+import org.apache.derby.impl.jdbc.authentication.BasicAuthenticationServiceImpl;
import org.apache.derby.shared.common.reference.SQLState;
-
+import org.apache.derby.iapi.reference.Property;
/**
* This class performs actions that are ALWAYS performed for a
@@ -96,13 +100,18 @@
if (rd != null) {
throw StandardException.
newException(SQLState.LANG_OBJECT_ALREADY_EXISTS,
- "Role" , roleName);
+ rd.getDescriptorType(), roleName);
}
- // FIXME: Check if the proposed role id exists as a user id in
+ // Check if the proposed role id exists as a user id in
// a privilege grant or as a built-in user ("best effort"; we
// can't guarantee against collision if users are externally
// defined or added later).
+ if (knownUser(roleName, currentAuthId, lcc, dd, tc)) {
+ throw StandardException.
+ newException(SQLState.LANG_OBJECT_ALREADY_EXISTS,
+ "User", roleName);
+ }
rd = ddg.newRoleDescriptor(
dd.getUUIDFactory().createUUID(),
@@ -128,4 +137,57 @@
// error reporting.
return "CREATE ROLE " + roleName;
}
+
+ // PRIVATE METHODS
+
+ /**
+ * Heuristically, try to determine is a proposed role identifier
+ * is already known to Derby as a user name. Method: If BUILTIN
+ * authentication is used, check if there is such a user. If
+ * external authentication is used, we lose. If there turns out
+ * to be collision, and we can't detect it here, we should block
+ * such a user from connecting (FIXME), since there is now a role
+ * with that name.
+ */
+ private boolean knownUser(String roleName,
+ String currentUser,
+ LanguageConnectionContext lcc,
+ DataDictionary dd,
+ TransactionController tc)
+ throws StandardException {
+ //
+ AuthenticationService s = lcc.getDatabase().getAuthenticationService();
+
+ if (currentUser.equals(roleName)) {
+ return true;
+ }
+
+ if (s instanceof BasicAuthenticationServiceImpl) {
+ // Derby builtin authentication
+
+ if (PropertyUtil.existsBuiltinUser(tc,roleName)) {
+ return true;
+ }
+ } else {
+ // Does LDAP offer a way to ask if a user exists?
+ // User supplied authentication?
+ // See DERBY-866. Would be nice to have a dictionary table of users
+ // synchronized against external authentication providers.
+ }
+
+ // Goto through all grants to see if there is a grant to an
+ // authorization identifier which is not a role (hence, it
+ // must be a user).
+ if (dd.existsGrantToAuthid(roleName, tc)) {
+ return true;
+ }
+
+ // Go through all schemas to see if any one of them is owned by a authid
+ // the same as the proposed roleName.
+ if (dd.existsSchemaOwnedBy(roleName, tc)) {
+ return true;
+ }
+
+ return false;
+ }
}
Modified: db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java?rev=658385&r1=658384&r2=658385&view=diff
==============================================================================
--- db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java (original)
+++ db/derby/code/trunk/java/storeless/org/apache/derby/impl/storeless/EmptyDictionary.java Tue May 20 11:38:32 2008
@@ -135,6 +135,13 @@
return null;
}
+ public boolean existsSchemaOwnedBy(String authid,
+ TransactionController tc)
+ throws StandardException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
public SchemaDescriptor getSystemSchemaDescriptor()
throws StandardException {
// TODO Auto-generated method stub
@@ -217,6 +224,13 @@
return null;
}
+ public boolean existsGrantToAuthid(String authId,
+ TransactionController tc)
+ throws StandardException {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
public TableDescriptor getTableDescriptor(String tableName,
SchemaDescriptor schema, TransactionController tc) throws StandardException {
// TODO Auto-generated method stub
@@ -765,10 +779,9 @@
public void addDescriptor(TupleDescriptor tuple, TupleDescriptor parent,
int catalogNumber, boolean allowsDuplicates,
TransactionController tc, boolean wait) throws StandardException {
- // TODO Auto-generated method stub
-
}
+
public void dropDependentsStoredDependencies(UUID dependentsUUID,
TransactionController tc, boolean wait) throws StandardException {
// TODO Auto-generated method stub
Modified: db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/RolesTest.java
URL: http://svn.apache.org/viewvc/db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/RolesTest.java?rev=658385&r1=658384&r2=658385&view=diff
==============================================================================
--- db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/RolesTest.java (original)
+++ db/derby/code/trunk/java/testing/org/apache/derbyTesting/functionTests/tests/lang/RolesTest.java Tue May 20 11:38:32 2008
@@ -71,8 +71,9 @@
private final static String revokeWarn = "01007";
private final static String notIdle = "25001";
private final static String invalidRoleName = "4293A";
+ private final static String userException = "38000";
+ private final static String userAlreadyExists = "X0Y68";
private final static String invalidPUBLIC = "4251B";
- private final static String userException = "38000";
private int MAX_IDENTIFIER_LENGTH = 128;
/**
@@ -81,9 +82,15 @@
* TEST_DBO as dbo, so add it to set of valid users. It uses a fresh db
* 'dbsqlauth', not 'wombat'.
*/
- private final static String[] users = {"TEST_DBO", "DonaldDuck"};
- private final static int dboIndex = 0;
- private final static int nonDboIndex = 1;
+ private final static String[] users =
+ {"TEST_DBO", "DonaldDuck", "\"additional\"\"user\""};
+
+ private final static int
+ dboIndex = 0; // used for connections
+ private final static int
+ nonDboIndex = 1; // used for connections
+ private final static int
+ additionaluserIndex = 2; // *not* used for connections
private boolean isDbo()
{
@@ -135,13 +142,13 @@
TestConfiguration.clientServerDecorator(
positiveSyntaxSuite("suite: positive syntax, client")));
- /* Positive tests */
+ /* Semantic tests */
suite.addTest(
- positiveSuite("suite: positive, embedded"));
+ semanticSuite("suite: semantic, embedded"));
suite.addTest(
TestConfiguration.clientServerDecorator(
- positiveSuite("suite: positive, client")));
+ semanticSuite("suite: semantic, client")));
return suite;
}
@@ -304,17 +311,17 @@
/**
*
- * Construct suite of positive tests
+ * Construct suite of semantic tests
*
* @param framework Derby framework indication
*
- * @return A suite containing the positive test cases incarnated only
+ * @return A suite containing the semantic test cases incarnated only
* for security level sqlAuthorization.
*
* It has one instance for dbo, and one for an ordinary user, so there
* are in all three incarnations of tests.
*/
- private static Test positiveSuite(String framework)
+ private static Test semanticSuite(String framework)
{
/*
* Tests running without sql authorization set. The purpose
@@ -322,7 +329,7 @@
*/
TestSuite noauthSuite = new TestSuite(
"suite: security level=noSqlAuthorization");
- noauthSuite.addTest(new RolesTest("testPositive",
+ noauthSuite.addTest(new RolesTest("testSemantics",
NO_SQLAUTHORIZATION,
null,
null));
@@ -334,7 +341,7 @@
TestSuite suite = new TestSuite("roles:"+framework);
suite.addTest(noauthSuite);
- suite.addTest(wrapInAuthorization("testPositive"));
+ suite.addTest(wrapInAuthorization("testSemantics"));
return suite;
}
@@ -351,9 +358,9 @@
TestSuite usersSuite =
new TestSuite("suite: security level=sqlAuthorization");
- // First decorate with users, then with authorization
- // decorator
- for (int userNo = 0; userNo < users.length; userNo++) {
+ // First decorate with users (except "additionaluser"), then
+ // with authorization decorator
+ for (int userNo = 0; userNo <= users.length - 2; userNo++) {
usersSuite.addTest
(TestConfiguration.changeUserDecorator
(new RolesTest(testName,
@@ -370,15 +377,15 @@
}
/**
- * Positive tests for roles (well, positive for dbo at least!)
+ * Semantic tests for roles.
* Side effect from the dbo run are needed for the nonDbo run
* which follows (since only dbo can create and grant roles).
*
* @throws SQLException
*/
- public void testPositive() throws SQLException
+ public void testSemantics() throws SQLException
{
- println("testPositive: auth=" + this._authLevel +
+ println("testSemantics: auth=" + this._authLevel +
" user="+getTestConfiguration().getUserName());
_conn = getConnection();
@@ -399,6 +406,33 @@
doStmt("create role \"NONE\"", // quoted role id should work
sqlAuthorizationRequired, null , roleDboOnly);
+ // Verify that we can't create a role which has the same auth
+ // id as a known user.
+ //
+ // a) built-in user:
+ doStmt("create role " + users[dboIndex], sqlAuthorizationRequired,
+ userAlreadyExists, roleDboOnly);
+
+ // specified with mixed case : DonalDuck
+ doStmt("create role " + users[nonDboIndex],
+ sqlAuthorizationRequired, userAlreadyExists, roleDboOnly);
+
+ // delimited identifier with embedded text quote inside
+ doStmt("create role " + users[additionaluserIndex],
+ sqlAuthorizationRequired, userAlreadyExists, roleDboOnly);
+
+
+ // b) A grant to this auth id exists (see setup), even though
+ // it is not a built-in user, so the presumption is, it is a
+ // user defined externally:
+ doStmt("create role whoever", sqlAuthorizationRequired,
+ userAlreadyExists, roleDboOnly);
+
+ // c) A schema exists which has an authid we did not see
+ // through properties; user has been removed, but his schema
+ // lingers..
+ doStmt("create role schemaowner", sqlAuthorizationRequired,
+ userAlreadyExists, roleDboOnly);
/*
* GRANT <role>
@@ -441,7 +475,7 @@
sqlAuthorizationRequired, null , null /* through public */);
doStmt("set role 'FOO'",
sqlAuthorizationRequired, null, null);
-
+
doStmt("set role none",
sqlAuthorizationRequired, null , null);
@@ -533,10 +567,11 @@
assertSysTablePermsRowCount(0,
// role admin not dropped yet:
- 1,
+ // + grant to whoever int setup
+ 2,
// role admin has been dropped, so
// this run's grant to admin is de
- // facto to a user named admin:
+ // facto to a user named admin
1);
assertSysColPermsRowCount(0, 2, 2);
@@ -557,11 +592,13 @@
doStmt("drop role admin",
sqlAuthorizationRequired, null , roleDboOnly);
- assertSysTablePermsRowCount(0, 0,
+ assertSysTablePermsRowCount(0,
+ // grant to whoever in setup:
+ 1,
// nonDbo run: role admin has
// been dropped, so this run's
// grant to admin is de facto to a
- // user named admin:
+ // user named admin
1);
assertSysColPermsRowCount(0, 0,
// nonDbo run: role admin has
@@ -590,8 +627,11 @@
doStmt("revoke execute on function f1 from admin restrict",
sqlAuthorizationRequired, null , null );
- // assert blank slate
- assertSysTablePermsRowCount(0,0,0);
+ // assert (almost) blank slate
+ assertSysTablePermsRowCount(0,
+ // grant to whoever in setup:
+ 1,
+ 0);
assertSysColPermsRowCount(0,0,0);
assertSysRoutinePermsRowCount(5,5,5);
@@ -628,12 +668,40 @@
} catch (SQLException se) {
}
+ if (_authLevel == SQLAUTHORIZATION && isDbo()) {
+ // create a table grant to an (uknown) user WHOEVER.
+ // This is used to test that create role detects the
+ // presence of existing user ids before allowing a
+ // role creation with that id.
+ _stm.executeUpdate("create table t1(i int)");
+ _stm.executeUpdate("grant select on t1 to whoever");
+
+ // create a schema for (uknown) user SCHEMAOWNER.
+ // This is used to test that create role detects the
+ // presence of existing user ids before allowing a
+ // role creation with that id.
+ _stm.executeUpdate(
+ "create schema lingerSchema authorization schemaowner");
+ }
+
_stm.close();
}
protected void tearDown() throws Exception
{
+ if (_authLevel == SQLAUTHORIZATION && isDbo()) {
+ _stm = createStatement();
+
+ try {
+ _stm.executeUpdate("revoke select on t1 from whoever");
+ _stm.executeUpdate("drop table t1");
+ _stm.executeUpdate("drop schema lingerSchema restrict");
+ } catch (SQLException se) {
+ System.err.println("Test error + " + se);
+ }
+ }
+
if (_stm != null) {
_stm.close();
_stm = null;
@@ -648,9 +716,9 @@
private void doStmt(String stmt,
- String noAuthState,
- String authDboState,
- String authNotDboState)
+ String noAuthState,
+ String authDboState,
+ String authNotDboState)
{
doStmt(stmt, noAuthState, authDboState, authNotDboState, false);
}
@@ -709,7 +777,7 @@
} else { // SQLAUTHORIZATION
if (isDbo()) {
if (authDboState[0] != null) {
- fail("exception " + noAuthState[0] + " expected: (" +
+ fail("exception " + authDboState[0] + " expected: (" +
stmt);
}
if (authDboState[1] != null) {
@@ -719,8 +787,8 @@
}
} else {
if (authNotDboState[0] != null) {
- fail("exception " + noAuthState[0] + " expected: (" +
- stmt);
+ fail("exception " + authNotDboState[0] +
+ " expected: (" + stmt);
}
if (authNotDboState[1] != null) {
SQLWarning w = _stm.getWarnings();