/*
 *   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
 *
 *     https://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.directory.fortress.core.impl;

import org.apache.directory.fortress.core.SecurityException;
import org.apache.directory.fortress.core.ValidationException;
import org.apache.directory.fortress.core.model.FortEntity;
import org.apache.directory.fortress.core.model.Permission;
import org.apache.directory.fortress.core.model.Session;
import org.apache.directory.fortress.core.util.VUtil;

/**
 * Abstract class allows outside clients to manage security and multi-tenant concerns within the Fortress runtime.
 * The {@link #setAdmin(org.apache.directory.fortress.core.model.Session)} method allows A/RBAC sessions to be loaded and 
 * allows authorization
 * to be performed on behalf of the user who is contained within the Session object itself.
 * The ARBAC permissions will be checked each time outside client makes calls into Fortress API.
 * This allows Fortress clients to operate in a multi-tenant context: {@link #setContextId(String)}.
 * <p>
 * Implementers of this abstract class will NOT be thread safe iff the instance variables are set.
 *
 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
 */
public abstract class Manageable implements org.apache.directory.fortress.core.Manageable
{
    // These instance variables are the reason why children of this abstract class will not be thread safe:
    protected Session adminSess;
    protected String contextId;

    /**
     * Use this method to load an administrative user's ARBAC Session object into Manager object will enable authorization to
     * be performed on behalf of admin user.  Setting Session into this object will enforce ARBAC controls and render this 
     * class's implementer thread unsafe.
     *
     * @param session contains a valid Fortress A/RBAC Session object.
     */
    public final void setAdmin(Session session)
    {
        this.adminSess = session;
    }

    /**
     * Use this method to set the tenant id onto function call into Fortress which allows segregation of data by customer.
     * The contextId is used for multi-tenancy to isolate data sets within a particular sub-tree within DIT.
     * Setting contextId into this object will render this class' implementer thread unsafe.
     *
     * @param contextId maps to sub-tree in DIT, e.g. ou=contextId, dc=example, dc=com.
     */
    public final void setContextId(String contextId)
    {
        this.contextId = contextId;
    }

    /**
     * Set A/RBAC session on entity and perform authorization on behalf of the caller if the {@link #adminSess} is set.
     *
     * @param className contains the class name.
     * @param opName contains operation name.
     * @param entity contains {@link org.apache.directory.fortress.core.model.FortEntity} instance.
     * @throws org.apache.directory.fortress.core.SecurityException
     *          in the event of data validation or system error.
     */
    protected final void setEntitySession(String className, String opName, FortEntity entity) throws SecurityException
    {
        entity.setContextId(this.contextId);
        if (this.adminSess != null)
        {
            Permission perm = new Permission(className, opName);
            perm.setContextId(this.contextId);
            AdminUtil.setEntitySession( this.adminSess, perm, entity, this.contextId );
        }
    }

    /**
     * Every Fortress Manager API (e.g. addUser, updateUser, addRole, ...) will perform authorization on behalf of the 
     * caller IFF the {@link AuditMgrImpl#adminSess} has been set before invocation.
     *
     * @param className contains the class name.
     * @param opName contains operation name.
     * @throws org.apache.directory.fortress.core.SecurityException
     *          in the event of data validation or system error.
     */
    protected final void checkAccess(String className, String opName) throws SecurityException
    {
        if (this.adminSess != null)
        {
            Permission perm = new Permission(className, opName);
            perm.setContextId(this.contextId);
            AdminUtil.checkAccess(this.adminSess, perm, this.contextId);
        }
    }

    /**
     * Method is called by Manager APIs to load contextual information on {@link FortEntity}.
     * <p>
     * The information is used to
     * <ol>
     *   <li>
     *     Load the administrative User's {@link Session} object into entity.  This is used for checking to ensure 
     *     administrator has privilege to perform administrative operation.
     *   </li>
     *   <li>
     *     Load the target operation's permission into the audit context.  This is used for Fortress audit log stored in 
     *     OpenLDAP
     *   </li>
     * </ol>
     *
     * @param className contains the class name.
     * @param opName contains operation name.
     * @param entity  used to pass contextual information through Fortress layers for administrative security checks and 
     * audit.
     */
    protected final void setAdminData(String className, String opName, FortEntity entity)
    {
        if (this.adminSess != null)
        {
            Permission perm = new Permission(className, opName);
            entity.setAdminSession(this.adminSess);
            entity.setModCode(AdminUtil.getObjName(perm.getObjName()) + "." + perm.getOpName());
        }
        
        entity.setContextId(this.contextId);
    }

    /**
     * Method will throw exception if entity reference is null, otherwise will set the contextId of the tenant onto the 
     * supplied entity reference.
     * @param className contains the class name of caller.
     * @param opName contains operation name of caller.
     * @param entity  used here to pass the tenant id into the Fortress DAO layer..
     * @param errorCode contains the error id to use if null.
     * @throws ValidationException in the event object is null.
     */
    protected final void assertContext( String className, String opName, FortEntity entity, int errorCode ) throws ValidationException
    {
        VUtil.assertNotNull( entity, errorCode, getFullMethodName( className, opName ) );
        entity.setContextId( contextId );
    }

    /**
     * Method will throw exception if entity reference is null, otherwise will set the contextId of the tenant onto the 
     * supplied entity reference.
     * 
     * @param methodName contains the full method name of caller.
     * @param entity  used here to pass the tenant id into the Fortress DAO layer..
     * @param errorCode contains the error id to use if null.
     * @throws ValidationException in the event object is null.
     */
    protected final void assertContext( String methodName, FortEntity entity, int errorCode ) throws ValidationException
    {
        VUtil.assertNotNull( entity, errorCode, methodName );
        entity.setContextId( contextId );
    }

    /**
     * This method is used to generate log statements and returns the concatenation of class name to the operation name.
     * @param className of the caller
     * @param opName of the caller
     * @return className + '.' + opName
     */
    protected final String getFullMethodName(String className, String opName)
    {
        return className + "." + opName;
    }
}
