--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.conn.GenericAuthorizer\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.sql.conn;\r
+\r
+import org.apache.derby.iapi.sql.Activation;\r
+import org.apache.derby.iapi.reference.Property;\r
+import org.apache.derby.iapi.util.IdUtil;\r
+import org.apache.derby.iapi.util.StringUtil;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.sql.conn.Authorizer;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;\r
+import org.apache.derby.iapi.services.property.PropertyUtil;\r
+import org.apache.derby.iapi.services.property.PersistentSet;\r
+import org.apache.derby.catalog.types.RoutineAliasInfo;\r
+import org.apache.derby.iapi.sql.dictionary.DataDictionary;\r
+import org.apache.derby.iapi.sql.dictionary.StatementPermission;\r
+import org.apache.derby.iapi.store.access.TransactionController;\r
+\r
+import java.util.Properties;\r
+import java.util.List;\r
+import java.util.Iterator;\r
+\r
+class GenericAuthorizer\r
+implements Authorizer\r
+{\r
+ //\r
+ //Enumerations for user access levels.\r
+ private static final int NO_ACCESS = 0;\r
+ private static final int READ_ACCESS = 1;\r
+ private static final int FULL_ACCESS = 2;\r
+ \r
+ //\r
+ //Configurable userAccessLevel - derived from Database level\r
+ //access control lists + database boot time controls.\r
+ private int userAccessLevel;\r
+\r
+ //\r
+ //Connection's readOnly status\r
+ boolean readOnlyConnection;\r
+\r
+ private final LanguageConnectionContext lcc;\r
+ \r
+ private final String authorizationId; //the userName after parsing by IdUtil \r
+ \r
+ GenericAuthorizer(String authorizationId, \r
+ LanguageConnectionContext lcc)\r
+ throws StandardException\r
+ {\r
+ this.lcc = lcc;\r
+ this.authorizationId = authorizationId;\r
+\r
+ refresh();\r
+ }\r
+\r
+ /*\r
+ Return true if the connection must remain readOnly\r
+ */\r
+ private boolean connectionMustRemainReadOnly()\r
+ {\r
+ if (lcc.getDatabase().isReadOnly() ||\r
+ (userAccessLevel==READ_ACCESS))\r
+ return true;\r
+ else\r
+ return false;\r
+ }\r
+\r
+ /**\r
+ Used for operations that do not involve tables or routines.\r
+ \r
+ @see Authorizer#authorize\r
+ @exception StandardException Thrown if the operation is not allowed\r
+ */\r
+ public void authorize( int operation) throws StandardException\r
+ {\r
+ authorize( (Activation) null, operation);\r
+ }\r
+\r
+ /**\r
+ @see Authorizer#authorize\r
+ @exception StandardException Thrown if the operation is not allowed\r
+ */\r
+ public void authorize( Activation activation, int operation) throws StandardException\r
+ {\r
+ int sqlAllowed = lcc.getStatementContext().getSQLAllowed();\r
+\r
+ switch (operation)\r
+ {\r
+ case Authorizer.SQL_ARBITARY_OP:\r
+ case Authorizer.SQL_CALL_OP:\r
+ if (sqlAllowed == RoutineAliasInfo.NO_SQL)\r
+ throw externalRoutineException(operation, sqlAllowed);\r
+ break;\r
+ case Authorizer.SQL_SELECT_OP:\r
+ if (sqlAllowed > RoutineAliasInfo.READS_SQL_DATA)\r
+ throw externalRoutineException(operation, sqlAllowed);\r
+ break;\r
+\r
+ // SQL write operations\r
+ case Authorizer.SQL_WRITE_OP:\r
+ case Authorizer.PROPERTY_WRITE_OP:\r
+ if (isReadOnlyConnection())\r
+ throw StandardException.newException(SQLState.AUTH_WRITE_WITH_READ_ONLY_CONNECTION);\r
+ if (sqlAllowed > RoutineAliasInfo.MODIFIES_SQL_DATA)\r
+ throw externalRoutineException(operation, sqlAllowed);\r
+ break;\r
+\r
+ // SQL DDL operations\r
+ case Authorizer.JAR_WRITE_OP:\r
+ case Authorizer.SQL_DDL_OP:\r
+ if (isReadOnlyConnection())\r
+ throw StandardException.newException(SQLState.AUTH_DDL_WITH_READ_ONLY_CONNECTION);\r
+\r
+ if (sqlAllowed > RoutineAliasInfo.MODIFIES_SQL_DATA)\r
+ throw externalRoutineException(operation, sqlAllowed);\r
+ break;\r
+\r
+ default:\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.THROWASSERT("Bad operation code "+operation);\r
+ }\r
+ if( activation != null)\r
+ {\r
+ List requiredPermissionsList = activation.getPreparedStatement().getRequiredPermissionsList();\r
+ DataDictionary dd = lcc.getDataDictionary();\r
+\r
+ // Database Owner can access any object. Ignore \r
+ // requiredPermissionsList for Database Owner\r
+ if( requiredPermissionsList != null && \r
+ !requiredPermissionsList.isEmpty() && \r
+ !authorizationId.equals(dd.getAuthorizationDatabaseOwner()))\r
+ {\r
+ int ddMode = dd.startReading(lcc);\r
+ \r
+ /*\r
+ * The system may need to read the permission descriptor(s) \r
+ * from the system table(s) if they are not available in the \r
+ * permission cache. So start an internal read-only nested \r
+ * transaction for this.\r
+ * \r
+ * The reason to use a nested transaction here is to not hold\r
+ * locks on system tables on a user transaction. e.g.: when\r
+ * attempting to revoke an user, the statement may time out\r
+ * since the user-to-be-revoked transaction may have acquired \r
+ * shared locks on the permission system tables; hence, this\r
+ * may not be desirable. \r
+ * \r
+ * All locks acquired by StatementPermission object's check()\r
+ * method will be released when the system ends the nested \r
+ * transaction.\r
+ * \r
+ * In Derby, the locks from read nested transactions come from\r
+ * the same space as the parent transaction; hence, they do not\r
+ * conflict with parent locks.\r
+ */ \r
+ lcc.beginNestedTransaction(true);\r
+ \r
+ try \r
+ {\r
+ try \r
+ {\r
+ // perform the permission checking\r
+ for (Iterator iter = requiredPermissionsList.iterator(); \r
+ iter.hasNext();) \r
+ {\r
+ ((StatementPermission) iter.next()).check(lcc, \r
+ authorizationId, false);\r
+ }\r
+ } \r
+ finally \r
+ {\r
+ dd.doneReading(ddMode, lcc);\r
+ }\r
+ } \r
+ finally \r
+ {\r
+ // make sure we commit; otherwise, we will end up with \r
+ // mismatch nested level in the language connection context.\r
+ lcc.commitNestedTransaction();\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ private static StandardException externalRoutineException(int operation, int sqlAllowed) {\r
+\r
+ String sqlState;\r
+ if (sqlAllowed == RoutineAliasInfo.READS_SQL_DATA)\r
+ sqlState = SQLState.EXTERNAL_ROUTINE_NO_MODIFIES_SQL;\r
+ else if (sqlAllowed == RoutineAliasInfo.CONTAINS_SQL)\r
+ {\r
+ switch (operation)\r
+ {\r
+ case Authorizer.SQL_WRITE_OP:\r
+ case Authorizer.PROPERTY_WRITE_OP:\r
+ case Authorizer.JAR_WRITE_OP:\r
+ case Authorizer.SQL_DDL_OP:\r
+ sqlState = SQLState.EXTERNAL_ROUTINE_NO_MODIFIES_SQL;\r
+ break;\r
+ default:\r
+ sqlState = SQLState.EXTERNAL_ROUTINE_NO_READS_SQL;\r
+ break;\r
+ }\r
+ }\r
+ else\r
+ sqlState = SQLState.EXTERNAL_ROUTINE_NO_SQL;\r
+\r
+ return StandardException.newException(sqlState);\r
+ }\r
+ \r
+\r
+ /**\r
+ @see Authorizer#getAuthorizationId\r
+ */\r
+ public String getAuthorizationId()\r
+ {\r
+ return authorizationId;\r
+ }\r
+\r
+ private void getUserAccessLevel() throws StandardException\r
+ {\r
+ userAccessLevel = NO_ACCESS;\r
+ if (userOnAccessList(Property.FULL_ACCESS_USERS_PROPERTY))\r
+ userAccessLevel = FULL_ACCESS;\r
+\r
+ if (userAccessLevel == NO_ACCESS &&\r
+ userOnAccessList(Property.READ_ONLY_ACCESS_USERS_PROPERTY))\r
+ userAccessLevel = READ_ACCESS;\r
+\r
+ if (userAccessLevel == NO_ACCESS)\r
+ userAccessLevel = getDefaultAccessLevel();\r
+ }\r
+\r
+ private int getDefaultAccessLevel() throws StandardException\r
+ {\r
+ PersistentSet tc = lcc.getTransactionExecute();\r
+\r
+ String modeS = (String)\r
+ PropertyUtil.getServiceProperty(\r
+ tc,\r
+ Property.DEFAULT_CONNECTION_MODE_PROPERTY);\r
+ if (modeS == null)\r
+ return FULL_ACCESS;\r
+ else if(StringUtil.SQLEqualsIgnoreCase(modeS, Property.NO_ACCESS))\r
+ return NO_ACCESS;\r
+ else if(StringUtil.SQLEqualsIgnoreCase(modeS, Property.READ_ONLY_ACCESS))\r
+ return READ_ACCESS;\r
+ else if(StringUtil.SQLEqualsIgnoreCase(modeS, Property.FULL_ACCESS))\r
+ return FULL_ACCESS;\r
+ else\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.THROWASSERT("Invalid value for property "+\r
+ Property.DEFAULT_CONNECTION_MODE_PROPERTY+\r
+ " "+\r
+ modeS);\r
+ return FULL_ACCESS;\r
+ }\r
+ }\r
+\r
+ private boolean userOnAccessList(String listName) throws StandardException\r
+ {\r
+ PersistentSet tc = lcc.getTransactionExecute();\r
+ String listS = (String)\r
+ PropertyUtil.getServiceProperty(tc, listName);\r
+ return IdUtil.idOnList(authorizationId,listS);\r
+ }\r
+\r
+ /**\r
+ @see Authorizer#isReadOnlyConnection\r
+ */\r
+ public boolean isReadOnlyConnection()\r
+ {\r
+ return readOnlyConnection;\r
+ }\r
+\r
+ /**\r
+ @see Authorizer#isReadOnlyConnection\r
+ @exception StandardException Thrown if the operation is not allowed\r
+ */\r
+ public void setReadOnlyConnection(boolean on, boolean authorize)\r
+ throws StandardException\r
+ {\r
+ if (authorize && !on) {\r
+ if (connectionMustRemainReadOnly())\r
+ throw StandardException.newException(SQLState.AUTH_CANNOT_SET_READ_WRITE);\r
+ }\r
+ readOnlyConnection = on;\r
+ }\r
+\r
+ /**\r
+ @see Authorizer#refresh\r
+ @exception StandardException Thrown if the operation is not allowed\r
+ */\r
+ public void refresh() throws StandardException\r
+ {\r
+ getUserAccessLevel();\r
+ if (!readOnlyConnection)\r
+ readOnlyConnection = connectionMustRemainReadOnly();\r
+\r
+ // Is a connection allowed.\r
+ if (userAccessLevel == NO_ACCESS)\r
+ throw StandardException.newException(SQLState.AUTH_DATABASE_CONNECTION_REFUSED);\r
+ }\r
+ \r
+}\r