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();