/*
* Copyright (c) 2008 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description:
*
*/

package com.nokia.mj.impl.security.midp.authorization;

import com.nokia.mj.impl.utils.Uid;
import com.nokia.mj.impl.utils.exception.InvalidAttributeException;
import com.nokia.mj.impl.security.midp.common.MIDPPermission;
import com.nokia.mj.impl.security.midp.common.UserSecuritySettings;
import com.nokia.mj.impl.security.midp.common.UserSecuritySettingsImpl;
import com.nokia.mj.impl.security.midp.common.PermissionAttribute;
import com.nokia.mj.impl.security.midp.common.PolicyBasedPermissionImpl;
import com.nokia.mj.impl.security.midp.common.PolicyBasedPermission;
import com.nokia.mj.impl.security.midp.common.AuthenticationCredentials;
import com.nokia.mj.impl.security.midp.common.SecurityAttributes;
import com.nokia.mj.impl.security.midp.common.ProtectionDomain;
import com.nokia.mj.impl.utils.Attribute;
import com.nokia.mj.impl.security.midp.storage.*;
import com.nokia.mj.impl.storage.StorageSession;
import com.nokia.mj.impl.security.utils.Logger;
import java.util.Vector;
import java.util.Hashtable;
import java.util.Enumeration;

/**
 * Grants permissions to MIDlet suites at installation time based on the
 * MIDlet's signing information and based on the permissions requested by the
 * MIDlet suite
 */
public final class PermissionGranter
{
    // self
    private static PermissionGranter self;

    /*
     * Hashtable containing the granted permissions of
     * different aplications being installed
     */
    private Hashtable iGrantedPermissions = new Hashtable();

    /*
     * Hashtable containing the details of the blanket permissions of
     * different aplications being installed
     */
    private Hashtable iBlanketPermissionsDetails = new Hashtable();

    /**
     * Creates an instance of the PermissionGranter
     *
     * @return An instance of PermissionGranter
     */
    public static PermissionGranter getInstance()
    {
        if (self == null)
        {
            self = new PermissionGranter();
        }
        return self;
    }

    /**
     * Grants permissions to a certain MIDlet suite. This method is called
     * when/if the JAD is available
     *
     * @param msUID                the UID if the MIDlet suite to whom the
     *                             permissions are granted
     * @param oldMSUID             the UID if the MIDlet suite being updated
     *                             (if applicable) or null otherwise
     * @param requestedPermissions the permissions requested in the JAD by
     *                             the MIDlet (the set of requested permissions
     *                             is retrieved from
     *                             SecurityAttributes.getPermissionAttributes()
     *                             called after calling
     *                             SecurityAttributes.addDescriptorAttributes())
     * @param authCredentials      a set of credentials assigned to the MIDlet
     *                             suite as a result of calling
     *                             AuthenticationModule.authenticateJAD
     */
    public void grantJadPermissions(
        Uid msUID,
        Uid oldMSUID,
        PermissionAttribute[] requestedPermissions,
        AuthenticationCredentials[] authCredentials)
    {
        if (msUID == null
                || authCredentials == null)
        {
            return;
        }

        Vector grantedPermissions = new Vector();
        InvalidAttributeException excThrown = null;
        boolean validPermissionsGranted = false;
        for (int i=0; i< authCredentials.length; i++)
        {
            if (iGrantedPermissions.containsKey(msUID.getStringValue()
                                                + authCredentials[i].getProtectionDomainName()))
            {
                continue;
            }
            try
            {
                // try resolving the requested permissions
                Vector resolvedPermissions = resolvePermissions(
                                                 msUID,
                                                 authCredentials[i].getProtectionDomainName(),
                                                 requestedPermissions,
                                                 false /* skip adding the assigned permissions */);
                if (resolvedPermissions == null)
                {
                    continue;
                }

                // store granted permissions to cache
                iGrantedPermissions.put(msUID.getStringValue()
                                        + authCredentials[i].getProtectionDomainName(),
                                        resolvedPermissions);
                // mark down that we have granted valid permissions
                validPermissionsGranted = true;
            }
            catch (InvalidAttributeException e)
            {
                // keep the exception instead of the granted permissions
                // and throw it later if needed
                iGrantedPermissions.put(msUID.getStringValue()
                                        + authCredentials[i].getProtectionDomainName(), e);
                if (excThrown == null)
                {
                    excThrown = e;
                }
            }
        }
        // if granting permissions resulted in nothing but exceptions,
        // just throw the first one
        if (!validPermissionsGranted && excThrown != null)
        {
            throw excThrown;
        }
    }

    /**
     * Grants permissions to a certain MIDlet suite. This method is called
     * when the JAR is available
     *
     * @param storageSession       the JavaStorage session to be used when
     *                             storing the granted permissions
     * @param msUID                the UID if the MIDlet suite to whom the
     *                             permissions are granted
     * @param oldMSUID             the UID if the MIDlet suite being updated
     *                             (if applicable) or null otherwise
     * @param requestedPermissions the permissions requested in the JAR by
     *                             the MIDlet (the set of requested permissions
     *                             is retrieved from
     *                             SecurityAttributes.getPermissionAttributes()
     *                             called after calling
     *                             SecurityAttributes.addManifestAttributes())
     */
    public void grantJarPermissions(
        StorageSession storageSession,
        Uid msUID,
        Uid oldMSUID,
        PermissionAttribute[] requestedPermissions)
    {
        if (storageSession == null
                || msUID == null)
        {
            return;
        }

        // take the domain from storage
        SecurityStorage storage = new SecurityStorage(storageSession);
        String protectionDomainName = storage.readProtectionDomain(msUID);
        // filter the already granted permissions
        Object o = iGrantedPermissions.remove(
                       msUID.getStringValue() + protectionDomainName);
        Vector preGrantedPermissions = null;
        if (o != null)
        {
            if (o instanceof InvalidAttributeException)
            {
                throw(InvalidAttributeException)o;
            }
            preGrantedPermissions = (Vector)o;
        }
        // resolved the requested permissions
        Vector resolvedPermissions = resolvePermissions(msUID,
                                     protectionDomainName,
                                     requestedPermissions);

        // store the pre-granted and the resolved permissions
        if (preGrantedPermissions != null)
        {
            for (int i=0; i<preGrantedPermissions.size(); i++)
            {
                if (!resolvedPermissions.contains(
                            preGrantedPermissions.elementAt(i)))
                {
                    resolvedPermissions.addElement(
                        preGrantedPermissions.elementAt(i));
                }
            }
        }
        storage.writeGrantedPermissions(
            msUID,
            oldMSUID,
            resolvedPermissions);
        // update the cache with the full set of permissions
        iGrantedPermissions.put(msUID.getStringValue() + protectionDomainName,
                                resolvedPermissions);
        Logger.logGrantedPermissions(resolvedPermissions);
    }

    /**
     * Grants all the permissions from a specified protection domain to a
     * certain MIDlet suite.
     *
     * @param storageSession       the JavaStorage session to be used when
     *                             storing the granted permissions
     * @param uid                  the Uid if the MIDlet suite to whom the
     *                             permissions are granted
     * @param oldUid               the Uid if the MIDlet suite being updated
     *                             (if applicable) or null otherwise
     * @param protectionDomain     the protection domain containing the
     *                             permissions which are to be assigned to
     *                             the MIDlet suite.
     */
    public void grantJarPermissions(
        StorageSession storageSession,
        Uid uid,
        Uid oldUid,
        ProtectionDomain protectionDomain)
    {
        if (uid == null || protectionDomain == null)
        {
            return;
        }

        // Build in the securityAttributes object with MIDP version only
        // (same as MIDP2 unsigned MIDlets suites do). this combined with
        // a certain protection domain guarantees that all the permissions
        // from a certain protection domain are retrieved
        SecurityAttributes securityAttributes = new SecurityAttributes();
        Hashtable allAttributes = new Hashtable();
        allAttributes.put(SecurityAttributes.MIDP_VERSION_ATTRIBUTE_NAME, new Attribute("", "MIDP-2.0"));
        securityAttributes.addDescriptorAttributes(allAttributes);

        // try resolving the requested permissions
        Vector resolvedPermissions = resolvePermissions(
                                         uid,
                                         protectionDomain.getName(),
                                         securityAttributes.getPermissionAttributes(),
                                         true /* add the assigned permissions */);

        // store the permissions
        SecurityStorage storage = new SecurityStorage(storageSession);
        storage.writeGrantedPermissions(
            uid,
            oldUid,
            resolvedPermissions);
        // update the cache with the full set of permissions
        iGrantedPermissions.put(uid.getStringValue() + protectionDomain,
                                resolvedPermissions);
        Logger.logGrantedPermissions(resolvedPermissions);
    }

    /**
     * Retrieves the localized names of the "blanket permission"s granted to a
     * certain MIDlet suite.
     * The concept of blanket permission granted to a MIDlet suite is defined
     * by the following conditions:
     * <ul>
     * <li>The MIDlet suite is signed</li>
     * <li>The maximum allowed setting of each of the granted permissions
     *     requested by the MIDlet suite is Blanket</li>
     * <li>The default setting of at least one of the requested permissions
     *     is something else than Blanket</li>
     * <li>The MIDlet suite is not granted permissions in the following
     *     combination: Application Auto Invocation AND Net Access</li>
     * </ul>
     *
     * @param msUID          the UID of the MIDlet suite for whom the Blanket
     *                       permissions are queried
     * @return               the localized names of the blanket permissions
     *                       granted to the MIDlet suite or null otherwise
     */
    public String[] getBlanketPermissions(
        Uid msUID)
    {
        // sanity checks
        if (msUID == null)
        {
            return null;
        }
        // get the set of granted permissions
        String msUidKey = msUID.getStringValue();
        Vector grantedPermissions = null;
        for (Enumeration e = iGrantedPermissions.keys() ;
                e.hasMoreElements() ;)
        {
            String key = (String)e.nextElement();
            if (key.startsWith(msUidKey))
            {
                grantedPermissions = (Vector)iGrantedPermissions.remove(key);
                if (key.endsWith("UnidentifiedThirdParty"))
                {
                    // if the suite is not signed, there is no blanket
                    // permissions concept either
                    Logger.log("Suite " + msUID.toString() + " is not signed, therefore there are no blanket permissions returned");
                    return null;
                }
                break;
            }
        }
        // Step1: go through each of the granted permissions and do the
        //        following:
        //        a) ensure that the maximum interaction mode is Blanket
        //        b) mark down if the default setting is something else than
        //           Blanket
        //        c) mark down to which of the following lists it belongs
        //           - list 1: Application Auto Invocation
        //           - list 2: Net Access
        //           - list 3: Net Access, Low Level Net Access, Messaging, Restricted messaging, Call control, Local connectivity
        //           - list 4: Multimedia, Read user data
        // Step 2: check the following combinations:
        //        a) permissions in list 1 and permissions list 2
        //        b) permissions in list 3 and permissions list 4
        if (grantedPermissions != null && grantedPermissions.size() > 0)
        {
            boolean defaultNotBlanket = false;
            boolean permissions_from_mutually_exclusive_list_1 = false;
            boolean permissions_from_mutually_exclusive_list_2 = false;
            boolean permissions_from_sensitive_combination_list_1 = false;
            boolean permissions_from_sensitive_combination_list_2 = false;
            boolean local_connectivity = false;
            boolean net_access = false;
            boolean multimedia = false;
            boolean read_user_data = false;
            boolean call_control = false;
            boolean messaging = false;
            Vector blanketPermissions =
                new Vector();
            for (int i=0; i<grantedPermissions.size(); i++)
            {
                PolicyBasedPermission permission =
                    ((PolicyBasedPermission)grantedPermissions.elementAt(i));
                UserSecuritySettings settings =
                    permission.getUserSecuritySettings();
                if (permission.getType() != PolicyBasedPermission.USER_TYPE 
                    || settings == null)
                {
                    // not a user permission -> move on to the next permission
                    Logger.log("Permission " + permission.getName() + " is not a user permission, therefore is is not returned as part of the group of blanket permissions");
                    continue;
                }
                if (!settings.isInteractionModeAllowed(
                            UserSecuritySettings.BLANKET_INTERACTION_MODE))
                {
                    // blanket is not an allowed interaction mode
                    // -> no blanket permission
                    Logger.log("Permission " + permission.getName() + " does not allow Blanket, therefore there are no blanket permissions returned");
                    return null;
                }
                String settingsLocName = UserSecuritySettingsImpl
                                         .getLocalizedName(settings.getName());
                if (settings.getCurrentInteractionMode()
                        != UserSecuritySettings.BLANKET_INTERACTION_MODE)
                {
                    if (!blanketPermissions.contains(settingsLocName))
                    {
                        blanketPermissions.addElement(settingsLocName);
                    }
                    defaultNotBlanket = true;
                }
                // mark down to which mutually exclusive list the permission belongs
                if (settings.getName().equalsIgnoreCase(
                            UserSecuritySettings.APPLICATION_AUTO_INVOCATION_SETTINGS))
                {
                    permissions_from_mutually_exclusive_list_1 = true;
                }
                else if (settings.getName().equalsIgnoreCase(
                             UserSecuritySettings.NET_ACCESS_SETTINGS))
                {
                    permissions_from_mutually_exclusive_list_2 = true;
                    permissions_from_sensitive_combination_list_1 = true;
                    net_access = true;
                }
                else if (settings.getName().equalsIgnoreCase(
                             UserSecuritySettings.LOW_LEVEL_NET_ACCESS_SETTINGS))
                {
                    permissions_from_sensitive_combination_list_1 = true;
                    net_access = true;
                }
                else if (settings.getName().equalsIgnoreCase(
                             UserSecuritySettings.MESSAGING_SETTINGS)
                         || settings.getName().equalsIgnoreCase(
                             UserSecuritySettings.RESTRICTED_MESSAGING_SETTINGS))
                {
                    permissions_from_sensitive_combination_list_1 = true;
                    messaging = true;
                }
                else if (settings.getName().equalsIgnoreCase(
                             UserSecuritySettings.CALL_CONTROL_SETTINGS))
                {
                    permissions_from_sensitive_combination_list_1 = true;
                    call_control = true;
                }
                else if (settings.getName().equalsIgnoreCase(
                             UserSecuritySettings.LOCAL_CONNECTIVITY_SETTINGS))
                {
                    permissions_from_sensitive_combination_list_1 = true;
                    local_connectivity = true;
                }
                else if (settings.getName().equalsIgnoreCase(
                             UserSecuritySettings.MULTIMEDIA_RECORDING_SETTINGS))
                {
                    permissions_from_sensitive_combination_list_2 = true;
                    multimedia = true;
                }
                else if (settings.getName().equalsIgnoreCase(
                             UserSecuritySettings.READ_USER_DATA_ACCESS_SETTINGS))
                {
                    permissions_from_sensitive_combination_list_2 = true;
                    read_user_data = true;
                }
            }
            if (!defaultNotBlanket)
            {
                // none of the granted permissions has the default set to
                // something else than Blanket -> there is no blanket
                // permission concept
                Logger.log("All of the granted permissions are set to Blanket already, therefore there are no blanket permissions returned");
                return null;
            }
            // check for "forbidden" combinations
            if (permissions_from_mutually_exclusive_list_1
                    && permissions_from_mutually_exclusive_list_2)
            {
                // forget about blanket permissions
                Logger.log("The mutual exclusive rules would be violated if the granted permissions were all set to Blanket, therefore there are no blanket permissions returned");
                return null;
            }
            // check for sensitive combinations
            if (permissions_from_sensitive_combination_list_1
                    && permissions_from_sensitive_combination_list_2)
            {
                String blanketPermissionsDetails = ( 
                    ((call_control == true && multimedia == true)
                    || (call_control == true && read_user_data == true) 
                    || (net_access == true && multimedia == true)
                    || (net_access == true && read_user_data == true)
                    || (messaging == true && multimedia == true)
                    || (messaging == true && read_user_data == true)) ? 
                    "settings_inst_query_perm_net" : 
                    "settings_inst_query_perm_sec");
                iBlanketPermissionsDetails.put(msUidKey,
                                               UserSecuritySettingsImpl.getLocalizedString(
                                                   blanketPermissionsDetails));
            }
            if (blanketPermissions.size() > 0)
            {
                String[] ret = new String[blanketPermissions.size()];
                blanketPermissions.copyInto(ret);
                Logger.logBlanketPermissions(ret);
                return ret;
            }
        }
        return null;
    }

    /**
     * Retrieves details associated with the "blanket permission"s granted to a
     * certain MIDlet suite.
     *
     * @param msUID          the UID of the MIDlet suite for whom the Blanket
     *                       permissions's details are queried
     * @return               the details (as localized string) associated with
     *                       the blanket permissions or null otherwise
     */
    public String getBlanketPermissionsDetails(
        Uid msUID)
    {
        // sanity checks
        if (msUID == null)
        {
            return null;
        }
        return (String)iBlanketPermissionsDetails.remove(
                   msUID.getStringValue());
    }


    /**
     * Sets the permissions granted to a certain MIDlet suite to Blanket
     *
     * @param storageSession the JavaStorage session to be used when setting
     *                       the permissions to Blanket
     * @param msUID          the UID of the MIDlet suite whose permissions
     *                       are set to Blanket
     */
    public void setPermissionsToBlanket(
        StorageSession storageSession,
        Uid msUID)
    {
        Logger.log("The granted permissions are all set to Blanket, therefore there won't be any runtime security prompts");
        SecurityStorage storage = new SecurityStorage(storageSession);
        storage.writeUserSecuritySettings(msUID,
                                          UserSecuritySettings.BLANKET_INTERACTION_MODE,
                                          true /* blanket prompt shown */);
    }

    /**
     * Performs a cleanup (e.g. on cached data)
     *
     */
    public void cleanup()
    {
        Logger.log("Cleanup permission granter cache");
        iGrantedPermissions.clear();
        iBlanketPermissionsDetails.clear();
    }

    /**
     * Removes all the security data related to a certain MIDlet suite
     *
     * @param storageSession the JavaStorage session to be used when
     *                       removing the security data
     * @param msUID          the UID if the MIDlet suite whose security data
     *                       is being removed
     */
    public void removeSecurityData(StorageSession storageSession, Uid msUID)
    {
        Logger.log("Remove granted permissions");
        SecurityStorage storage = new SecurityStorage(storageSession);
        storage.removeGrantedPermissions(msUID);
        // clear the cache
        String msUidKey = msUID.getStringValue();
        for (Enumeration e = iGrantedPermissions.keys() ;
                e.hasMoreElements() ;)
        {
            String key = (String)e.nextElement();
            if (key.startsWith(msUidKey))
            {
                iGrantedPermissions.remove(key);
                break;
            }
        }
        iBlanketPermissionsDetails.remove(msUidKey);
    }

    private Vector resolvePermissions(Uid msUID,
                                      String protectionDomainName,
                                      PermissionAttribute[] requestedPermissions,
                                      boolean addAssignedPerms)
    {
        // retrieve the policy permissions
        PolicyBasedPermission[] policyPermissions = SecurityPolicyModule
                .getInstance().getPermissions(protectionDomainName);

        // resovle requested permissions (if not already done)
        Vector resolvedPermissions = PermissionResolver
                                     .resolvePermissions(msUID, requestedPermissions, policyPermissions);

        // add the "assigned" permissions from the policy to the set of granted
        // permissions
        if (addAssignedPerms)
        {
            if (resolvedPermissions == null)
            {
                resolvedPermissions = new Vector();
            }
            if (policyPermissions != null)
            {
                for (int i=0; i<policyPermissions.length; i++)
                {
                    if (policyPermissions[i].getType()
                            == PolicyBasedPermission.ASSIGNED_TYPE 
                            || policyPermissions[i].getType() 
                            == PolicyBasedPermission.USER_ASSIGNED_TYPE)
                    {
                        PolicyBasedPermissionImpl p1 = new PolicyBasedPermissionImpl(
                            policyPermissions[i]);
                        // add it only if not contained already
                        boolean found = false;
                        for (int j=0; j<resolvedPermissions.size(); j++)
                        {
                            PolicyBasedPermissionImpl p2 = new PolicyBasedPermissionImpl(
                                (PolicyBasedPermission)resolvedPermissions.elementAt(j));
                            if (p1.equals(p2))
                            {
                                found = true;
                                break;
                            }
                        }
                        if (!found)
                        {
                            resolvedPermissions.addElement(
                                new PolicyBasedPermissionImpl(
                                    policyPermissions[i].getName(),
                                    policyPermissions[i].getTarget(),
                                    policyPermissions[i].getActionList(),
                                    policyPermissions[i].getType(),
                                    policyPermissions[i].getUserSecuritySettings()));
                        }
                    }
                }
            }
        }
        return resolvedPermissions;
    }

    private Vector resolvePermissions(Uid msUID,
                                      String protectionDomainName,
                                      PermissionAttribute[] requestedPermissions)
    {
        return resolvePermissions(msUID, protectionDomainName,
                                  requestedPermissions, true /* add assigned permissions */);
    }
}
