/*

   Derby - Class org.apache.derby.iapi.sql.dictionary.StatementPermission

   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
   this work for additional information regarding copyright ownership.
   The ASF licenses this file to you under the Apache License, Version 2.0
   (the "License"); you may not use this file except in compliance with
   the License.  You may obtain a copy of the License at

	  http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

 */

package org.apache.derby.iapi.sql.dictionary;

import org.apache.derby.iapi.reference.SQLState;
import org.apache.derby.iapi.sql.conn.Authorizer;
import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
import org.apache.derby.iapi.error.StandardException;
import org.apache.derby.iapi.sql.Activation;
import org.apache.derby.iapi.store.access.TransactionController;
import org.apache.derby.iapi.sql.execute.ExecPreparedStatement;
import org.apache.derby.iapi.sql.depend.DependencyManager;
import org.apache.derby.iapi.services.context.ContextManager;

/**
 * This class describes a permission require by a statement.
 */

public abstract class StatementPermission
{
	/**
	 * Restrict implementations to this package to reduce
	 * risk of external code spoofing the GRANT/REVOKE system
	 * by providing its own fake implementations.
	 *
	 */
	StatementPermission()
	{
	}
	/**
	 * @param lcc				LanguageConnectionContext
	 * @param forGrant
	 * @param activation        activation for statement needing check
	 *
	 * @exception StandardException if the permission has not been granted
	 */
	public abstract void check( LanguageConnectionContext lcc,
								boolean forGrant,
								Activation activation) throws StandardException;

	/**
	 * 
	 * Get the PermissionsDescriptor for the passed authorization id for this
	 * object. This method gets called during the execution phase of create 
	 * view/constraint/trigger. The return value of this method is saved in
	 * dependency system to keep track of views/constraints/triggers 
	 * dependencies on required permissions. This happens in execution phase 
	 * after it has been established that passed authorization id has all the 
	 * permissions it needs to create that view/constraint/trigger. Which means 
	 * that we can only get to writing into dependency system once all the required 
	 * privileges are confirmed. 
	 *   
	 * @param authid	AuthorizationId
	 * @param dd	DataDictionary
	 * 
	 * @return PermissionsDescriptor	The PermissionsDescriptor for the passed
	 *  authorization id on this object
	 * 
	 * @exception StandardException
	 */
	public abstract PermissionsDescriptor getPermissionDescriptor(String authid, DataDictionary dd)
	throws StandardException;

    /**
     * Return true if the passed in permission matches the one required by this
     * StatementPermission.
     */
    public boolean isCorrectPermission( PermissionsDescriptor pd ) throws StandardException
    { return false; }

    /**
     * Get the privileged object associated with this permission.
     */
    public PrivilegedSQLObject getPrivilegedObject( DataDictionary dd ) throws StandardException
    { return null; }

    /**
     * Get the type of the privileged object.
     */
    public String getObjectType()
    { return null; }

    /**
     * Generic logic called by check() for USAGE and EXECUTE privileges. Throws
     * an exception if the correct permission cannot be found.
     */
	public void genericCheck
        (
         LanguageConnectionContext lcc,
         boolean forGrant,
         Activation activation,
         String privilegeType )
        throws StandardException
	{
		DataDictionary dd = lcc.getDataDictionary();
		TransactionController tc = lcc.getTransactionExecute();
		ExecPreparedStatement ps = activation.getPreparedStatement();

        PermissionsDescriptor perm =
            getPermissionDescriptor( lcc.getCurrentUserId(activation), dd );
		if( !isCorrectPermission( perm ) ) { perm = getPermissionDescriptor(Authorizer.PUBLIC_AUTHORIZATION_ID, dd ); }

        // if the user has the correct permission, we're done
		if ( isCorrectPermission( perm ) ) { return; }

		boolean resolved = false;

		// Since no permission exists for the current user or PUBLIC,
		// check if a permission exists for the current role (if set).
		String role = lcc.getCurrentRoleId(activation);

		if (role != null) {

			// Check that role is still granted to current user or
			// to PUBLIC: A revoked role which is current for this
			// session, is lazily set to none when it is attemped
			// used.
			String dbo = dd.getAuthorizationDatabaseOwner();
			RoleGrantDescriptor rd = dd.getRoleGrantDescriptor
                (role, lcc.getCurrentUserId(activation), dbo);

			if (rd == null) {
				rd = dd.getRoleGrantDescriptor(
					role,
					Authorizer.PUBLIC_AUTHORIZATION_ID,
					dbo);
			}

			if (rd == null) {
				// We have lost the right to set this role, so we can't
				// make use of any permission granted to it or its
				// ancestors.
				lcc.setCurrentRole(activation, null);
			} else {
				// The current role is OK, so we can make use of
				// any permission granted to it.
				//
				// Look at the current role and, if necessary, the
				// transitive closure of roles granted to current role to
				// see if permission has been granted to any of the
				// applicable roles.

				RoleClosureIterator rci =
					dd.createRoleClosureIterator
					(activation.getTransactionController(),
					 role, true );

				String r;
				while (!resolved && (r = rci.next()) != null)
                {
					perm = getPermissionDescriptor( r, dd );

					if ( isCorrectPermission( perm ) ) { resolved = true; }
				}
			}

			if (resolved ) {
				// Also add a dependency on the role (qua provider), so that if
				// role is no longer available to the current user (e.g. grant
				// is revoked, role is dropped, another role has been set), we
				// are able to invalidate the ps or activation (the latter is
				// used if the current role changes).
				DependencyManager dm = dd.getDependencyManager();
				RoleGrantDescriptor rgd = dd.getRoleDefinitionDescriptor(role);
				ContextManager cm = lcc.getContextManager();
				dm.addDependency(ps, rgd, cm);
				dm.addDependency(activation, rgd, cm);
			}
		}

		if (!resolved)
        {
            PrivilegedSQLObject pso = getPrivilegedObject( dd );

			if( pso == null )
            {
				throw StandardException.newException
                    ( SQLState.AUTH_INTERNAL_BAD_UUID, getObjectType() );
            }

			SchemaDescriptor sd = pso.getSchemaDescriptor();

			if( sd == null)
            {
				throw StandardException.newException(
					SQLState.AUTH_INTERNAL_BAD_UUID, "SCHEMA");
            }

			throw StandardException.newException(
				(forGrant
				 ? SQLState.AUTH_NO_GENERIC_PERMISSION_FOR_GRANT
				 : SQLState.AUTH_NO_GENERIC_PERMISSION),
                lcc.getCurrentUserId(activation),
                privilegeType,
				getObjectType(),
				sd.getSchemaName(),
				pso.getName());
		}

	} // end of genericCheck

}
