001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.conn.GenericAuthorizer
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.impl.sql.conn;
023:
024: import org.apache.derby.iapi.sql.Activation;
025: import org.apache.derby.iapi.reference.Property;
026: import org.apache.derby.iapi.util.IdUtil;
027: import org.apache.derby.iapi.util.StringUtil;
028: import org.apache.derby.iapi.services.sanity.SanityManager;
029: import org.apache.derby.iapi.error.StandardException;
030: import org.apache.derby.iapi.sql.conn.Authorizer;
031: import org.apache.derby.iapi.reference.SQLState;
032: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
033: import org.apache.derby.iapi.services.property.PropertyUtil;
034: import org.apache.derby.iapi.services.property.PersistentSet;
035: import org.apache.derby.catalog.types.RoutineAliasInfo;
036: import org.apache.derby.iapi.sql.dictionary.DataDictionary;
037: import org.apache.derby.iapi.sql.dictionary.StatementPermission;
038: import org.apache.derby.iapi.store.access.TransactionController;
039:
040: import java.util.Properties;
041: import java.util.List;
042: import java.util.Iterator;
043:
044: class GenericAuthorizer implements Authorizer {
045: //
046: //Enumerations for user access levels.
047: private static final int NO_ACCESS = 0;
048: private static final int READ_ACCESS = 1;
049: private static final int FULL_ACCESS = 2;
050:
051: //
052: //Configurable userAccessLevel - derived from Database level
053: //access control lists + database boot time controls.
054: private int userAccessLevel;
055:
056: //
057: //Connection's readOnly status
058: boolean readOnlyConnection;
059:
060: private final LanguageConnectionContext lcc;
061:
062: private final String authorizationId; //the userName after parsing by IdUtil
063:
064: GenericAuthorizer(String authorizationId,
065: LanguageConnectionContext lcc, boolean sqlConnection)
066: throws StandardException {
067: this .lcc = lcc;
068: this .authorizationId = authorizationId;
069:
070: //we check the access level only if this is coming from a sql
071: //connection, not internal logSniffer or StageTrunc db connection
072: if (sqlConnection)
073: refresh();
074: }
075:
076: /*
077: Return true if the connection must remain readOnly
078: */
079: private boolean connectionMustRemainReadOnly() {
080: if (lcc.getDatabase().isReadOnly()
081: || (userAccessLevel == READ_ACCESS))
082: return true;
083: else
084: return false;
085: }
086:
087: /**
088: Used for operations that do not involve tables or routines.
089:
090: @see Authorizer#authorize
091: @exception StandardException Thrown if the operation is not allowed
092: */
093: public void authorize(int operation) throws StandardException {
094: authorize((Activation) null, operation);
095: }
096:
097: /**
098: @see Authorizer#authorize
099: @exception StandardException Thrown if the operation is not allowed
100: */
101: public void authorize(Activation activation, int operation)
102: throws StandardException {
103: int sqlAllowed = lcc.getStatementContext().getSQLAllowed();
104:
105: switch (operation) {
106: case Authorizer.SQL_ARBITARY_OP:
107: case Authorizer.SQL_CALL_OP:
108: if (sqlAllowed == RoutineAliasInfo.NO_SQL)
109: throw externalRoutineException(operation, sqlAllowed);
110: break;
111: case Authorizer.SQL_SELECT_OP:
112: if (sqlAllowed > RoutineAliasInfo.READS_SQL_DATA)
113: throw externalRoutineException(operation, sqlAllowed);
114: break;
115:
116: // SQL write operations
117: case Authorizer.SQL_WRITE_OP:
118: case Authorizer.PROPERTY_WRITE_OP:
119: if (isReadOnlyConnection())
120: throw StandardException
121: .newException(SQLState.AUTH_WRITE_WITH_READ_ONLY_CONNECTION);
122: if (sqlAllowed > RoutineAliasInfo.MODIFIES_SQL_DATA)
123: throw externalRoutineException(operation, sqlAllowed);
124: break;
125:
126: // SQL DDL operations
127: case Authorizer.JAR_WRITE_OP:
128: case Authorizer.SQL_DDL_OP:
129: if (isReadOnlyConnection())
130: throw StandardException
131: .newException(SQLState.AUTH_DDL_WITH_READ_ONLY_CONNECTION);
132:
133: if (sqlAllowed > RoutineAliasInfo.MODIFIES_SQL_DATA)
134: throw externalRoutineException(operation, sqlAllowed);
135: break;
136:
137: default:
138: if (SanityManager.DEBUG)
139: SanityManager.THROWASSERT("Bad operation code "
140: + operation);
141: }
142: if (activation != null) {
143: List requiredPermissionsList = activation
144: .getPreparedStatement()
145: .getRequiredPermissionsList();
146: DataDictionary dd = lcc.getDataDictionary();
147:
148: // Database Owner can access any object. Ignore
149: // requiredPermissionsList for Database Owner
150: if (requiredPermissionsList != null
151: && !requiredPermissionsList.isEmpty()
152: && !authorizationId.equals(dd
153: .getAuthorizationDatabaseOwner())) {
154: int ddMode = dd.startReading(lcc);
155:
156: /*
157: * The system may need to read the permission descriptor(s)
158: * from the system table(s) if they are not available in the
159: * permission cache. So start an internal read-only nested
160: * transaction for this.
161: *
162: * The reason to use a nested transaction here is to not hold
163: * locks on system tables on a user transaction. e.g.: when
164: * attempting to revoke an user, the statement may time out
165: * since the user-to-be-revoked transaction may have acquired
166: * shared locks on the permission system tables; hence, this
167: * may not be desirable.
168: *
169: * All locks acquired by StatementPermission object's check()
170: * method will be released when the system ends the nested
171: * transaction.
172: *
173: * In Derby, the locks from read nested transactions come from
174: * the same space as the parent transaction; hence, they do not
175: * conflict with parent locks.
176: */
177: lcc.beginNestedTransaction(true);
178:
179: try {
180: try {
181: // perform the permission checking
182: for (Iterator iter = requiredPermissionsList
183: .iterator(); iter.hasNext();) {
184: ((StatementPermission) iter.next()).check(
185: lcc, authorizationId, false);
186: }
187: } finally {
188: dd.doneReading(ddMode, lcc);
189: }
190: } finally {
191: // make sure we commit; otherwise, we will end up with
192: // mismatch nested level in the language connection context.
193: lcc.commitNestedTransaction();
194: }
195: }
196: }
197: }
198:
199: private static StandardException externalRoutineException(
200: int operation, int sqlAllowed) {
201:
202: String sqlState;
203: if (sqlAllowed == RoutineAliasInfo.READS_SQL_DATA)
204: sqlState = SQLState.EXTERNAL_ROUTINE_NO_MODIFIES_SQL;
205: else if (sqlAllowed == RoutineAliasInfo.CONTAINS_SQL) {
206: switch (operation) {
207: case Authorizer.SQL_WRITE_OP:
208: case Authorizer.PROPERTY_WRITE_OP:
209: case Authorizer.JAR_WRITE_OP:
210: case Authorizer.SQL_DDL_OP:
211: sqlState = SQLState.EXTERNAL_ROUTINE_NO_MODIFIES_SQL;
212: break;
213: default:
214: sqlState = SQLState.EXTERNAL_ROUTINE_NO_READS_SQL;
215: break;
216: }
217: } else
218: sqlState = SQLState.EXTERNAL_ROUTINE_NO_SQL;
219:
220: return StandardException.newException(sqlState);
221: }
222:
223: /**
224: @see Authorizer#getAuthorizationId
225: */
226: public String getAuthorizationId() {
227: return authorizationId;
228: }
229:
230: private void getUserAccessLevel() throws StandardException {
231: userAccessLevel = NO_ACCESS;
232: if (userOnAccessList(Property.FULL_ACCESS_USERS_PROPERTY))
233: userAccessLevel = FULL_ACCESS;
234:
235: if (userAccessLevel == NO_ACCESS
236: && userOnAccessList(Property.READ_ONLY_ACCESS_USERS_PROPERTY))
237: userAccessLevel = READ_ACCESS;
238:
239: if (userAccessLevel == NO_ACCESS)
240: userAccessLevel = getDefaultAccessLevel();
241: }
242:
243: private int getDefaultAccessLevel() throws StandardException {
244: PersistentSet tc = lcc.getTransactionExecute();
245:
246: String modeS = (String) PropertyUtil.getServiceProperty(tc,
247: Property.DEFAULT_CONNECTION_MODE_PROPERTY);
248: if (modeS == null)
249: return FULL_ACCESS;
250: else if (StringUtil.SQLEqualsIgnoreCase(modeS,
251: Property.NO_ACCESS))
252: return NO_ACCESS;
253: else if (StringUtil.SQLEqualsIgnoreCase(modeS,
254: Property.READ_ONLY_ACCESS))
255: return READ_ACCESS;
256: else if (StringUtil.SQLEqualsIgnoreCase(modeS,
257: Property.FULL_ACCESS))
258: return FULL_ACCESS;
259: else {
260: if (SanityManager.DEBUG)
261: SanityManager.THROWASSERT("Invalid value for property "
262: + Property.DEFAULT_CONNECTION_MODE_PROPERTY
263: + " " + modeS);
264: return FULL_ACCESS;
265: }
266: }
267:
268: private boolean userOnAccessList(String listName)
269: throws StandardException {
270: PersistentSet tc = lcc.getTransactionExecute();
271: String listS = (String) PropertyUtil.getServiceProperty(tc,
272: listName);
273: return IdUtil.idOnList(authorizationId, listS);
274: }
275:
276: /**
277: @see Authorizer#isReadOnlyConnection
278: */
279: public boolean isReadOnlyConnection() {
280: return readOnlyConnection;
281: }
282:
283: /**
284: @see Authorizer#isReadOnlyConnection
285: @exception StandardException Thrown if the operation is not allowed
286: */
287: public void setReadOnlyConnection(boolean on, boolean authorize)
288: throws StandardException {
289: if (authorize && !on) {
290: if (connectionMustRemainReadOnly())
291: throw StandardException
292: .newException(SQLState.AUTH_CANNOT_SET_READ_WRITE);
293: }
294: readOnlyConnection = on;
295: }
296:
297: /**
298: @see Authorizer#refresh
299: @exception StandardException Thrown if the operation is not allowed
300: */
301: public void refresh() throws StandardException {
302: getUserAccessLevel();
303: if (!readOnlyConnection)
304: readOnlyConnection = connectionMustRemainReadOnly();
305:
306: // Is a connection allowed.
307: if (userAccessLevel == NO_ACCESS)
308: throw StandardException
309: .newException(SQLState.AUTH_DATABASE_CONNECTION_REFUSED);
310: }
311:
312: }
|