class CustomFolderHelper(object):
    """
    Shared code for custom (user-defined) folders in the model.
    These require special handling, since they do not have alias definitions.
    """
    __class_name = 'CustomFolderHelper'
    __cipher_text_prefixes = ["{AES}", "{AES-256}"]

    def __init__(self, aliases, logger, model_context, exception_type):
        self.logger = logger
        self.model_context = model_context
        self.exception_type = exception_type
        self.alias_helper = AliasHelper(aliases, self.logger,
                                        self.exception_type)
        self.weblogic_helper = WebLogicHelper(self.logger)
        self.wlst_helper = WlstHelper(self.logger, self.exception_type)

    def update_security_folder(self, location, model_type, model_subtype,
                               model_name, model_nodes):
        """
        Update the specified security model nodes in WLST.
        :param location: the location for the provider
        :param model_type: the type of the provider to be updated, such as AuthenticationProvider
        :param model_subtype: the subtype of the provider to be updated, such as 'custom.my.CustomIdentityAsserter'
        :param model_name: the name of the provider to be updated, such as 'My custom IdentityAsserter'
        :param model_nodes: a child model nodes of the provider to be updated
        :raises: BundleAwareException of the specified type: if an error occurs
        """
        _method_name = 'update_security_folder'

        location_path = self.alias_helper.get_model_folder_path(location)
        self.logger.entering(location_path,
                             model_subtype,
                             model_name,
                             class_name=self.__class_name,
                             method_name=_method_name)

        self.logger.info('WLSDPLY-12124',
                         model_type,
                         model_name,
                         model_subtype,
                         location_path,
                         class_name=self.__class_name,
                         method_name=_method_name)

        create_path = self.alias_helper.get_wlst_subfolders_path(location)
        self.wlst_helper.cd(create_path)

        # create the MBean using the model type, name, and subtype

        type_location = LocationContext(location).append_location(model_type)
        token = self.alias_helper.get_name_token(type_location)
        type_location.add_name_token(token, model_name)

        mbean_type = self.alias_helper.get_wlst_mbean_type(type_location)
        self.wlst_helper.create(model_name, model_subtype, mbean_type)

        provider_path = self.alias_helper.get_wlst_attributes_path(
            type_location)
        provider_mbean = self.wlst_helper.cd(provider_path)

        interface_name = model_subtype + 'MBean'
        bean_info = self.weblogic_helper.get_bean_info_for_interface(
            interface_name)
        if bean_info is None:
            ex = exception_helper.create_exception(self.exception_type,
                                                   'WLSDPLY-12125',
                                                   interface_name)
            self.logger.throwing(ex,
                                 class_name=self.__class_name,
                                 method_name=_method_name)
            raise ex

        property_map = dict()
        for property_descriptor in bean_info.getPropertyDescriptors():
            self.logger.finer('WLSDPLY-12126',
                              str(property_descriptor),
                              class_name=self.__class_name,
                              method_name=_method_name)
            property_map[property_descriptor.getName()] = property_descriptor

        for model_key in model_nodes:
            model_value = model_nodes[model_key]
            property_descriptor = property_map.get(model_key)

            if not property_descriptor:
                ex = exception_helper.create_exception(self.exception_type,
                                                       'WLSDPLY-12128',
                                                       model_key)
                self.logger.throwing(ex,
                                     class_name=self.__class_name,
                                     method_name=_method_name)
                raise ex

            # find the setter method for the attribute

            method = property_descriptor.writeMethod
            if not method:
                # this must be a read-only attribute, just log it and continue with next attribute
                self.logger.info('WLSDPLY-12129',
                                 str(model_key),
                                 class_name=self.__class_name,
                                 method_name=_method_name)
                continue

            self.logger.finer('WLSDPLY-12127',
                              str(model_key),
                              str(model_value),
                              class_name=self.__class_name,
                              method_name=_method_name)

            # determine the data type from the set method

            parameter_types = method.getParameterTypes()
            parameter_count = len(parameter_types)

            if parameter_count != 1:
                ex = exception_helper.create_exception(self.exception_type,
                                                       'WLSDPLY-12130',
                                                       model_key,
                                                       parameter_count)
                self.logger.throwing(ex,
                                     class_name=self.__class_name,
                                     method_name=_method_name)
                raise ex

            # if the property requires encryption, and the value is not encrypted,
            # encrypt the value with domain encryption.

            requires_encrypted = property_descriptor.getValue('encrypted')
            if requires_encrypted and not self.is_encrypted(
                    model_value) and isinstance(model_value, str):
                model_value = self.weblogic_helper.encrypt(
                    model_value, self.model_context.get_domain_home())

            property_type = parameter_types[0]

            # convert the model value to the target type and call the setter with the target value.
            # these are done together in Java to avoid automatic Jython type conversions.

            try:
                CustomBeanUtils.callMethod(provider_mbean, method,
                                           property_type, model_value)

            # failure converting value or calling method
            except (IllegalAccessException, IllegalArgumentException,
                    InvocationTargetException), ex:
                ex = exception_helper.create_exception(
                    self.exception_type,
                    'WLSDPLY-12131',
                    method,
                    str(model_value),
                    ex.getLocalizedMessage(),
                    error=ex)
                self.logger.throwing(ex,
                                     class_name=self.__class_name,
                                     method_name=_method_name)
                raise ex
class CredentialMapHelper(object):
    """
    Creates .ldift initialization file for user/password credential mappings
    """
    _class_name = 'CredentialMapHelper'

    def __init__(self, model_context, exception_type):
        """
        Initialize an instance of CredentialMapHelper.
        :param model_context: used to find domain home
        :param exception_type: the type of exception to be thrown
        """
        self._model_context = model_context
        self._exception_type = exception_type
        self._logger = PlatformLogger('wlsdeploy.tool.util')
        self._weblogic_helper = WebLogicHelper(self._logger)
        self._resource_escaper = ResourcePolicyIdUtil.getEscaper()
        self._b64_encoder = BASE64Encoder()

    def create_default_init_file(self, default_mapping_nodes):
        """
        Create a .ldift file to initialize default credential mappers.
        Build a hash map for use with a template file resource.
        Write the file to a known location in the domain.
        :param default_mapping_nodes: the credential mapping elements from the model
        """
        _method_name = 'create_default_init_file'

        template_hash = self._build_default_template_hash(
            default_mapping_nodes)
        template_path = TEMPLATE_PATH + '/' + DEFAULT_MAPPER_INIT_FILE

        output_dir = File(self._model_context.get_domain_home(),
                          SECURITY_SUBDIR)
        output_file = File(output_dir, DEFAULT_MAPPER_INIT_FILE)

        self._logger.info('WLSDPLY-01790',
                          output_file,
                          class_name=self._class_name,
                          method_name=_method_name)

        file_template_helper.create_file(template_path, template_hash,
                                         output_file, self._exception_type)

    def _build_default_template_hash(self, mapping_section_nodes):
        """
        Create a dictionary of substitution values to apply to the default credential mappers template.
        :param mapping_section_nodes: the credential mapping elements from the model
        :return: the template hash dictionary
        """
        template_hash = dict()

        credential_mappings = []
        resource_mappings = []

        for mapping_type in mapping_section_nodes.keys():
            mapping_name_nodes = mapping_section_nodes[mapping_type]
            for mapping_name in mapping_name_nodes.keys():
                mapping = mapping_name_nodes[mapping_name]
                mapping_hash = self._build_mapping_hash(
                    mapping_type, mapping_name, mapping)

                # add a hash with remote target details to create a single passwordCredentialMap element
                credential_mappings.append(mapping_hash)

                # add a modified hash for each local user to create resourceMap elements
                resource_name = mapping_hash[HASH_RESOURCE_NAME]
                local_users = self._get_local_users(mapping_type, mapping_name,
                                                    mapping)
                for local_user in local_users:
                    resource_hash = dict(mapping_hash)
                    resource_hash[HASH_LOCAL_USER] = local_user
                    resource_hash[HASH_RESOURCE_CN] = self._create_cn(
                        resource_name, local_user)
                    resource_mappings.append(resource_hash)

        template_hash[CREDENTIAL_MAPPINGS] = credential_mappings
        template_hash[RESOURCE_MAPPINGS] = resource_mappings
        return template_hash

    def _build_mapping_hash(self, mapping_type, mapping_name, mapping):
        """
        Build a template hash for the specified mapping element from the model.
        :param mapping_type: the type of the mapping, such as 'CrossDomain'
        :param mapping_name: the mapping name from the model, such as 'map1'
        :param mapping: the mapping element from the model
        :return: the template hash
        """
        resource_name = self._build_resource_name(mapping_type, mapping_name,
                                                  mapping)

        remote_user = self._get_required_attribute(mapping, REMOTE_USER,
                                                   mapping_type, mapping_name)
        credential_cn = self._create_cn(resource_name, remote_user)

        # the password needs to be encrypted, then base64 encoded
        password = self._get_required_attribute(mapping, REMOTE_PASSWORD,
                                                mapping_type, mapping_name)
        encrypted = self._weblogic_helper.encrypt(
            password, self._model_context.get_domain_home())
        password_encoded = self._b64_encoder.encodeBuffer(
            String(encrypted).getBytes("UTF-8"))

        # the local user and resource CN will be updated later for each user
        return {
            HASH_CREDENTIAL_CN: credential_cn,
            HASH_LOCAL_USER: NULL,
            HASH_PASSWORD_ENCODED: password_encoded,
            HASH_REMOTE_USER: remote_user,
            HASH_RESOURCE_CN: NULL,
            HASH_RESOURCE_NAME: resource_name
        }

    def _build_resource_name(self, mapping_type, mapping_name, mapping):
        """
        Build the resource name based on elements in the mapping element from the model.
        Example: type=<remote>, protocol=http, remoteHost=my.host, remotePort=7020, path=/myapp, method=POST
        :param mapping_type: the type of the mapping, such as 'CrossDomain'
        :param mapping_name: the mapping name from the model, such as 'map1'
        :param mapping: the mapping element from the model
        :return: the resource name
        """

        # for cross-domain mapping, use domain for remote host, and set cross-domain protocol
        if mapping_type == CROSS_DOMAIN:
            remote_host = self._get_required_attribute(mapping, REMOTE_DOMAIN,
                                                       mapping_type,
                                                       mapping_name)
            protocol = CROSS_DOMAIN_PROTOCOL
        else:
            remote_host = self._get_required_attribute(mapping, REMOTE_HOST,
                                                       mapping_type,
                                                       mapping_name)
            protocol = dictionary_utils.get_element(mapping, PROTOCOL)

        # build a map of available values, some may be None
        resource_name_values = {
            ID_METHOD: dictionary_utils.get_element(mapping, METHOD),
            ID_PATH: dictionary_utils.get_element(mapping, PATH),
            ID_PROTOCOL: protocol,
            ID_REMOTE_HOST: remote_host,
            ID_REMOTE_PORT: dictionary_utils.get_element(mapping, REMOTE_PORT)
        }

        # build the resource name string
        resource_name = 'type=<remote>'
        for field in RESOURCE_FIELDS:
            value = dictionary_utils.get_element(resource_name_values, field)
            if value is not None:
                resource_name += ', %s=%s' % (field, value)

        return resource_name

    def _get_local_users(self, mapping_type, mapping_name, mapping):
        """
        Get the local users list, based on the mapping element from the model.
        :param mapping_type: the type of the mapping, such as 'CrossDomain'
        :param mapping_name: the mapping name from the model, such as 'map1'
        :param mapping: the mapping element from the model
        :return: a list of local users
        """
        if mapping_type == CROSS_DOMAIN:
            return [CROSS_DOMAIN_USER]

        local_user_value = self._get_required_attribute(
            mapping, USER, mapping_type, mapping_name)
        return TypeUtils.convertToType(list, local_user_value)

    def _get_required_attribute(self, dictionary, name, mapping_type,
                                mapping_name):
        """
        Return the value of the specified attribute from the specified dictionary.
        Log and throw an exception if the attribute is not found.
        :param dictionary: the dictionary to be checked
        :param name: the name of the attribute to find
        :param mapping_type: the type of the mapping, such as 'CrossDomain'
        :param mapping_name: the mapping name from the model, such as 'map1'
        :return: the value of the attribute
        :raises: Tool type exception: if an the attribute is not found
        """
        _method_name = '_get_required_attribute'

        result = dictionary_utils.get_element(dictionary, name)
        if result is None:
            pwe = exception_helper.create_exception(self._exception_type,
                                                    'WLSDPLY-01791', name,
                                                    mapping_type, mapping_name)
            self._logger.throwing(class_name=self._class_name,
                                  method_name=_method_name,
                                  error=pwe)
            raise pwe
        return result

    def _create_cn(self, resource_name, user):
        """
        Create a CN string from the specified resource name and user name.
        The result should be escaped for use as a CN.
        :param resource_name: the name of the resource
        :param user: the user name
        :return: the CN string
        """
        name = resource_name + "." + user
        return self._resource_escaper.escapeString(name)