예제 #1
0
    def _parse_import_data(self, data, import_fields, options):
        # Get fields of type date/datetime
        all_fields = self.env[self.res_model].fields_get()
        for name, field in all_fields.items():
            if field['type'] in ('date', 'datetime') and name in import_fields:
                # Parse date
                index = import_fields.index(name)
                dt = datetime.datetime
                server_format = DEFAULT_SERVER_DATE_FORMAT if field['type'] == 'date' else DEFAULT_SERVER_DATETIME_FORMAT

                if options.get('%s_format' % field['type'], server_format) != server_format:
                    # datetime.str[fp]time takes *native strings* in both
                    # versions, for both data and pattern
                    user_format = pycompat.to_native(options.get('%s_format' % field['type']))
                    for num, line in enumerate(data):
                        if line[index]:
                            try:
                                line[index] = dt.strftime(dt.strptime(pycompat.to_native(line[index].strip()), user_format), server_format)
                            except ValueError as e:
                                raise ValueError(_("Column %s contains incorrect values. Error in line %d: %s") % (name, num + 1, e))
                            except Exception as e:
                                raise ValueError(_("Error Parsing Date [%s:L%d]: %s") % (name, num + 1, e))

            elif field['type'] in ('float', 'monetary') and name in import_fields:
                # Parse float, sometimes float values from file have currency symbol or () to denote a negative value
                # We should be able to manage both case
                index = import_fields.index(name)
                self._parse_float_from_data(data, index, name, options)
        return data
예제 #2
0
    def _parse_import_data_recursive(self, model, prefix, data, import_fields, options):
        # Get fields of type date/datetime
        all_fields = self.env[model].fields_get()
        for name, field in all_fields.items():
            name = prefix + name
            if field['type'] in ('date', 'datetime') and name in import_fields:
                # Parse date
                index = import_fields.index(name)
                dt = datetime.datetime
                server_format = DEFAULT_SERVER_DATE_FORMAT if field['type'] == 'date' else DEFAULT_SERVER_DATETIME_FORMAT

                if options.get('%s_format' % field['type'], server_format) != server_format:
                    # datetime.str[fp]time takes *native strings* in both
                    # versions, for both data and pattern
                    user_format = pycompat.to_native(options.get('%s_format' % field['type']))
                    for num, line in enumerate(data):
                        if line[index]:
                            try:
                                line[index] = dt.strftime(dt.strptime(pycompat.to_native(line[index].strip()), user_format), server_format)
                            except ValueError as e:
                                raise ValueError(_("Column %s contains incorrect values. Error in line %d: %s") % (name, num + 1, e))
                            except Exception as e:
                                raise ValueError(_("Error Parsing Date [%s:L%d]: %s") % (name, num + 1, e))
            # Check if the field is in import_field and is a relational (followed by /)
            # Also verify that the field name exactly match the import_field at the correct level.
            elif any(name + '/' in import_field and name == import_field.split('/')[prefix.count('/')] for import_field in import_fields):
                # Recursive call with the relational as new model and add the field name to the prefix
                self._parse_import_data_recursive(field['relation'], name + '/', data, import_fields, options)
            elif field['type'] in ('float', 'monetary') and name in import_fields:
                # Parse float, sometimes float values from file have currency symbol or () to denote a negative value
                # We should be able to manage both case
                index = import_fields.index(name)
                self._parse_float_from_data(data, index, name, options)
        return data
예제 #3
0
    def _adyen_form_get_tx_from_data(self, data):
        reference, pspReference = data.get('merchantReference'), data.get('pspReference')
        if not reference or not pspReference:
            error_msg = _('Adyen: received data with missing reference (%s) or missing pspReference (%s)') % (reference, pspReference)
            _logger.info(error_msg)
            raise ValidationError(error_msg)

        # find tx -> @TDENOTE use pspReference ?
        tx = self.env['payment.transaction'].search([('reference', '=', reference)])
        if not tx or len(tx) > 1:
            error_msg = _('Adyen: received data for reference %s') % (reference)
            if not tx:
                error_msg += _('; no order found')
            else:
                error_msg += _('; multiple order found')
            _logger.info(error_msg)
            raise ValidationError(error_msg)

        # verify shasign
        if len(tx.acquirer_id.adyen_skin_hmac_key) == 64:
            shasign_check = tx.acquirer_id._adyen_generate_merchant_sig_sha256('out', data)
        else:
            shasign_check = tx.acquirer_id._adyen_generate_merchant_sig('out', data)
        if to_native(shasign_check) != to_native(data.get('merchantSig')):
            error_msg = _('Adyen: invalid merchantSig, received %s, computed %s') % (data.get('merchantSig'), shasign_check)
            _logger.warning(error_msg)
            raise ValidationError(error_msg)

        return tx
예제 #4
0
파일: payment.py 프로젝트: Bubbles-IT/odoo
    def _adyen_form_get_tx_from_data(self, data):
        reference, pspReference = data.get('merchantReference'), data.get('pspReference')
        if not reference or not pspReference:
            error_msg = _('Adyen: received data with missing reference (%s) or missing pspReference (%s)') % (reference, pspReference)
            _logger.info(error_msg)
            raise ValidationError(error_msg)

        # find tx -> @TDENOTE use pspReference ?
        tx = self.env['payment.transaction'].search([('reference', '=', reference)])
        if not tx or len(tx) > 1:
            error_msg = _('Adyen: received data for reference %s') % (reference)
            if not tx:
                error_msg += _('; no order found')
            else:
                error_msg += _('; multiple order found')
            _logger.info(error_msg)
            raise ValidationError(error_msg)

        # verify shasign
        if len(tx.acquirer_id.adyen_skin_hmac_key) == 64:
            shasign_check = tx.acquirer_id._adyen_generate_merchant_sig_sha256('out', data)
        else:
            shasign_check = tx.acquirer_id._adyen_generate_merchant_sig('out', data)
        if to_native(shasign_check) != to_native(data.get('merchantSig')):
            error_msg = _('Adyen: invalid merchantSig, received %s, computed %s') % (data.get('merchantSig'), shasign_check)
            _logger.warning(error_msg)
            raise ValidationError(error_msg)

        return tx
예제 #5
0
    def _parse_import_data_recursive(self, model, prefix, data, import_fields, options):
        # Get fields of type date/datetime
        all_fields = self.env[model].fields_get()
        for name, field in all_fields.items():
            name = prefix + name
            if field['type'] in ('date', 'datetime') and name in import_fields:
                # Parse date
                index = import_fields.index(name)
                dt = datetime.datetime
                server_format = DEFAULT_SERVER_DATE_FORMAT if field['type'] == 'date' else DEFAULT_SERVER_DATETIME_FORMAT

                if options.get('%s_format' % field['type'], server_format) != server_format:
                    # datetime.str[fp]time takes *native strings* in both
                    # versions, for both data and pattern
                    user_format = pycompat.to_native(options.get('%s_format' % field['type']))
                    for num, line in enumerate(data):
                        if line[index]:
                            line[index] = line[index].strip()
                        if line[index]:
                            try:
                                line[index] = dt.strftime(dt.strptime(pycompat.to_native(line[index]), user_format), server_format)
                            except ValueError as e:
                                try:
                                    # Allow to import date in datetime fields
                                    if field['type'] == 'datetime':
                                        user_format = pycompat.to_native(options.get('date_format'))
                                        line[index] = dt.strftime(dt.strptime(pycompat.to_native(line[index]), user_format), server_format)
                                except ValueError as e:
                                    raise ValueError(_("Column %s contains incorrect values. Error in line %d: %s") % (name, num + 1, e))
                            except Exception as e:
                                raise ValueError(_("Error Parsing Date [%s:L%d]: %s") % (name, num + 1, e))
            # Check if the field is in import_field and is a relational (followed by /)
            # Also verify that the field name exactly match the import_field at the correct level.
            elif any(name + '/' in import_field and name == import_field.split('/')[prefix.count('/')] for import_field in import_fields):
                # Recursive call with the relational as new model and add the field name to the prefix
                self._parse_import_data_recursive(field['relation'], name + '/', data, import_fields, options)
            elif field['type'] in ('float', 'monetary') and name in import_fields:
                # Parse float, sometimes float values from file have currency symbol or () to denote a negative value
                # We should be able to manage both case
                index = import_fields.index(name)
                self._parse_float_from_data(data, index, name, options)
            elif field['type'] == 'binary' and field.get('attachment') and any(f in name for f in IMAGE_FIELDS) and name in import_fields:
                index = import_fields.index(name)

                with requests.Session() as session:
                    session.stream = True

                    for num, line in enumerate(data):
                        if re.match(config.get("import_image_regex", DEFAULT_IMAGE_REGEX), line[index]):
                            if not self.env.user._can_import_remote_urls():
                                raise AccessError(_("You can not import images via URL, check with your administrator or support for the reason."))

                            line[index] = self._import_image_by_url(line[index], session, name, num)

        return data
예제 #6
0
 def _get_date_formats(self):
     format_date, format_time = super()._get_date_formats()
     date_format = self.env.user.date_format \
         or self.env.user.company_id.date_format
     if date_format:
         format_date = pycompat.to_native(date_format)
     time_format = self.env.user.time_format \
         or self.env.user.company_id.time_format
     if time_format:
         format_time = pycompat.to_native(time_format)
     return (format_date, format_time)
예제 #7
0
    def _parse_import_data_recursive(self, model, prefix, data, import_fields,
                                     options):
        # Get fields of type date/datetime
        all_fields = self.env[model].fields_get()
        for name, field in all_fields.items():
            name = prefix + name
            if field['type'] in ('date', 'datetime') and name in import_fields:
                # Parse date
                index = import_fields.index(name)
                dt = datetime.datetime
                server_format = DEFAULT_SERVER_DATE_FORMAT if field[
                    'type'] == 'date' else DEFAULT_SERVER_DATETIME_FORMAT

                if options.get('%s_format' % field['type'],
                               server_format) != server_format:
                    # datetime.str[fp]time takes *native strings* in both
                    # versions, for both data and pattern
                    user_format = pycompat.to_native(
                        options.get('%s_format' % field['type']))
                    for num, line in enumerate(data):
                        if line[index]:
                            line[index] = line[index].strip()
                        if line[index]:
                            try:
                                line[index] = dt.strftime(
                                    dt.strptime(
                                        pycompat.to_native(line[index]),
                                        user_format), server_format)
                            except ValueError as e:
                                raise ValueError(
                                    _("Column %s contains incorrect values. Error in line %d: %s"
                                      ) % (name, num + 1, e))
                            except Exception as e:
                                raise ValueError(
                                    _("Error Parsing Date [%s:L%d]: %s") %
                                    (name, num + 1, e))
            # Check if the field is in import_field and is a relational (followed by /)
            # Also verify that the field name exactly match the import_field at the correct level.
            elif any(name + '/' in import_field
                     and name == import_field.split('/')[prefix.count('/')]
                     for import_field in import_fields):
                # Recursive call with the relational as new model and add the field name to the prefix
                self._parse_import_data_recursive(field['relation'],
                                                  name + '/', data,
                                                  import_fields, options)
            elif field['type'] in ('float',
                                   'monetary') and name in import_fields:
                # Parse float, sometimes float values from file have currency symbol or () to denote a negative value
                # We should be able to manage both case
                index = import_fields.index(name)
                self._parse_float_from_data(data, index, name, options)
        return data
예제 #8
0
 class CustomModel(models.Model):
     _name = pycompat.to_native(model_data['model'])
     _description = model_data['name']
     _module = False
     _custom = True
     _transient = bool(model_data['transient'])
     __doc__ = model_data['info']
예제 #9
0
def encode_rfc2822_address_header(header_text):
    """If ``header_text`` contains non-ASCII characters,
       attempts to locate patterns of the form
       ``"Name" <address@domain>`` and replace the
       ``"Name"`` portion by the RFC2047-encoded
       version, preserving the address part untouched.
    """
    def encode_addr(addr):
        name, email = addr
        # If s is a <text string>, then charset is a hint specifying the
        # character set of the characters in the string. The Unicode string
        # will be encoded using the following charsets in order: us-ascii,
        # the charset hint, utf-8. The first character set to not provoke a
        # UnicodeError is used.
        # -> always pass a text string to Header

        # also Header.__str__ in Python 3 "Returns an approximation of the
        # Header as a string, using an unlimited line length.", the old one
        # was "A synonym for Header.encode()." so call encode() directly?
        name = Header(pycompat.to_text(name)).encode()
        # if the from does not follow the (name <addr>),* convention, we might
        # try to encode meaningless strings as address, as getaddresses is naive
        # note it would also fail on real addresses with non-ascii characters
        try:
            return formataddr((name, email))
        except UnicodeEncodeError:
            _logger.warning(_('Failed to encode the address %s\n'
                              'from mail header:\n%s') % (addr, header_text))
            return ""

    addresses = getaddresses([pycompat.to_native(ustr(header_text))])
    return COMMASPACE.join(a for a in (encode_addr(addr) for addr in addresses) if a)
    def _remove_gmail_signatures(self, body):
        if not body:
            return body
        try:
            root = lxml.html.fromstring(body)
        except ValueError:
            # In case the email client sent XHTML, fromstring will fail because 'Unicode strings
            # with encoding declaration are not supported'.
            root = lxml.html.fromstring(body.encode('utf-8'))

        postprocessed = False
        to_remove = []
        for node in root.iter():
            # TODO: we should probably remove only the first occurence, so
            # a context var could be used to force remove all
            if 'gmail_signature' in (node.get('data-smartmail') or ''):
                postprocessed = True
                if node.getparent() is not None:
                    to_remove.append(node)

        for node in to_remove:
            node.getparent().remove(node)
        if postprocessed:
            body = lxml.etree.tostring(root,
                                       pretty_print=False,
                                       encoding='UTF-8')
            body = pycompat.to_native(body)
        return body
예제 #11
0
 def message_parse(self, message, save_original=False):
     msg_dict = super().message_parse(message, save_original)
     if not isinstance(message, Message):
         # message_from_string works on a native str
         message = pycompat.to_native(message)
         message = email.message_from_string(message)
     msg_dict['reply_to'] = tools.decode_smtp_header(message.get('Reply-To'))
     return msg_dict
예제 #12
0
 class DefaultAerooParser(models.AbstractModel):
     _inherit = 'report.report_aeroo.abstract'
     _name = 'report.%s' % pycompat.to_native(model_data['model'])
     _description = model_data['name'],
     _module = False
     _custom = True
     __doc__ = ''
     _transient = False
예제 #13
0
def load_information_from_description_file(module, mod_path=None):
    """
    :param module: The name of the module (sale, purchase, ...)
    :param mod_path: Physical path of module, if not providedThe name of the module (sale, purchase, ...)
    """
    if not mod_path:
        mod_path = get_module_path(module, downloaded=True)
    manifest_file = module_manifest(mod_path)
    if manifest_file:
        # default values for descriptor
        info = {
            'application': False,
            'author': 'Odoo S.A.',
            'auto_install': False,
            'category': 'Uncategorized',
            'depends': [],
            'description': '',
            'icon': get_module_icon(module),
            'installable': True,
            'license': 'LGPL-3',
            'post_load': None,
            'version': '1.0',
            'web': False,
            'sequence': 100,
            'summary': '',
            'website': '',
        }
        info.update(
            pycompat.izip(
                'depends data demo test init_xml update_xml demo_xml'.split(),
                iter(list, None)))

        f = tools.file_open(manifest_file, mode='rb')
        try:
            info.update(ast.literal_eval(pycompat.to_native(f.read())))
        finally:
            f.close()

        if not info.get('description'):
            readme_path = [
                opj(mod_path, x) for x in README
                if os.path.isfile(opj(mod_path, x))
            ]
            if readme_path:
                readme_text = tools.file_open(readme_path[0]).read()
                info['description'] = readme_text

        if 'active' in info:
            # 'active' has been renamed 'auto_install'
            info['auto_install'] = info['active']

        info['version'] = adapt_version(info['version'])
        return info

    _logger.debug('module %s: no manifest file found %s', module,
                  MANIFEST_NAMES)
    return {}
예제 #14
0
    def _query(self, conf, filter, retrieve_attributes=None):
        """
        Query an LDAP server with the filter argument and scope subtree.

        Allow for all authentication methods of the simple authentication
        method:

        - authenticated bind (non-empty binddn + valid password)
        - anonymous bind (empty binddn + empty password)
        - unauthenticated authentication (non-empty binddn + empty password)

        .. seealso::
           :rfc:`4513#section-5.1` - LDAP: Simple Authentication Method.

        :param dict conf: LDAP configuration
        :param filter: valid LDAP filter
        :param list retrieve_attributes: LDAP attributes to be retrieved. \
        If not specified, return all attributes.
        :return: ldap entries
        :rtype: list of tuples (dn, attrs)

        """

        results = []
        try:
            conn = self._connect(conf)
            ldap_password = conf['ldap_password'] or ''
            ldap_binddn = conf['ldap_binddn'] or ''
            conn.simple_bind_s(to_native(ldap_binddn),
                               to_native(ldap_password))
            results = conn.search_st(to_native(conf['ldap_base']),
                                     ldap.SCOPE_SUBTREE,
                                     filter,
                                     retrieve_attributes,
                                     timeout=60)
            conn.unbind()
        except ldap.INVALID_CREDENTIALS:
            _logger.error('LDAP bind failed.')
        except ldap.LDAPError as e:
            _logger.error('An LDAP exception occurred: %s', e)
        return results
예제 #15
0
파일: module.py 프로젝트: astirpe/odoo
def load_information_from_description_file(module, mod_path=None):
    """
    :param module: The name of the module (sale, purchase, ...)
    :param mod_path: Physical path of module, if not providedThe name of the module (sale, purchase, ...)
    """
    if not mod_path:
        mod_path = get_module_path(module, downloaded=True)
    manifest_file = module_manifest(mod_path)
    if manifest_file:
        # default values for descriptor
        info = {
            'application': False,
            'author': 'Odoo S.A.',
            'auto_install': False,
            'category': 'Uncategorized',
            'depends': [],
            'description': '',
            'icon': get_module_icon(module),
            'installable': True,
            'license': 'LGPL-3',
            'post_load': None,
            'version': '1.0',
            'web': False,
            'sequence': 100,
            'summary': '',
            'website': '',
        }
        info.update(pycompat.izip(
            'depends data demo test init_xml update_xml demo_xml'.split(),
            iter(list, None)))

        f = tools.file_open(manifest_file, mode='rb')
        try:
            info.update(ast.literal_eval(pycompat.to_native(f.read())))
        finally:
            f.close()

        if not info.get('description'):
            readme_path = [opj(mod_path, x) for x in README
                           if os.path.isfile(opj(mod_path, x))]
            if readme_path:
                readme_text = tools.file_open(readme_path[0]).read()
                info['description'] = readme_text

        if 'active' in info:
            # 'active' has been renamed 'auto_install'
            info['auto_install'] = info['active']

        info['version'] = adapt_version(info['version'])
        return info

    _logger.debug('module %s: no manifest file found %s', module, MANIFEST_NAMES)
    return {}
예제 #16
0
    def query(self, conf, filter, retrieve_attributes=None):
        """
        Query an LDAP server with the filter argument and scope subtree.

        Allow for all authentication methods of the simple authentication
        method:

        - authenticated bind (non-empty binddn + valid password)
        - anonymous bind (empty binddn + empty password)
        - unauthenticated authentication (non-empty binddn + empty password)

        .. seealso::
           :rfc:`4513#section-5.1` - LDAP: Simple Authentication Method.

        :param dict conf: LDAP configuration
        :param filter: valid LDAP filter
        :param list retrieve_attributes: LDAP attributes to be retrieved. \
        If not specified, return all attributes.
        :return: ldap entries
        :rtype: list of tuples (dn, attrs)

        """

        results = []
        try:
            conn = self.connect(conf)
            ldap_password = conf['ldap_password'] or ''
            ldap_binddn = conf['ldap_binddn'] or ''
            conn.simple_bind_s(to_native(ldap_binddn), to_native(ldap_password))
            results = conn.search_st(to_native(conf['ldap_base']), ldap.SCOPE_SUBTREE, filter, retrieve_attributes, timeout=60)
            conn.unbind()
        except ldap.INVALID_CREDENTIALS:
            _logger.error('LDAP bind failed.')
        except ldap.LDAPError as e:
            _logger.error('An LDAP exception occurred: %s', e)
        return results
예제 #17
0
def encode_rfc2822_address_header(header_text):
    """If ``header_text`` contains non-ASCII characters,
       attempts to locate patterns of the form
       ``"Name" <address@domain>`` and replace the
       ``"Name"`` portion by the RFC2047-encoded
       version, preserving the address part untouched.
    """
    def encode_addr(addr):
        name, email = addr
        try:
            return formataddr((name, email), 'ascii')
        except UnicodeEncodeError:
            _logger.warning(_('Failed to encode the address %s\n'
                              'from mail header:\n%s') % (addr, header_text))
            return ""

    addresses = getaddresses([pycompat.to_native(ustr(header_text))])
    return COMMASPACE.join(a for a in (encode_addr(addr) for addr in addresses) if a)
예제 #18
0
    def authenticate(self, conf, login, password):
        """
        Authenticate a user against the specified LDAP server.

        In order to prevent an unintended 'unauthenticated authentication',
        which is an anonymous bind with a valid dn and a blank password,
        check for empty passwords explicitely (:rfc:`4513#section-6.3.1`)
        :param dict conf: LDAP configuration
        :param login: username
        :param password: Password for the LDAP user
        :return: LDAP entry of authenticated user or False
        :rtype: dictionary of attributes
        """

        if not password:
            return False

        entry = False
        try:
            pattern = r"[^%](%s)"
            match_count = re.findall(pattern, conf['ldap_filter']).__len__()
            filter = filter_format(conf['ldap_filter'],
                                   (login, ) * match_count)
        except TypeError:
            _logger.warning(
                'Could not format LDAP filter. Your filter should contain one \'%s\'.'
            )
            return False
        try:
            results = self.query(conf, tools.ustr(filter))

            # Get rid of (None, attrs) for searchResultReference replies
            results = [i for i in results if i[0]]
            if len(results) == 1:
                dn = results[0][0]
                conn = self.connect(conf)
                conn.simple_bind_s(dn, to_native(password))
                conn.unbind()
                entry = results[0]
        except ldap.INVALID_CREDENTIALS:
            return False
        except ldap.LDAPError as e:
            _logger.error('An LDAP exception occurred: %s', e)
        return entry
예제 #19
0
def encode_header(header_text):
    """Returns an appropriate representation of the given header value,
       suitable for direct assignment as a header value in an
       email.message.Message. RFC2822 assumes that headers contain
       only 7-bit characters, so we ensure it is the case, using
       RFC2047 encoding when needed.

       :param header_text: unicode or utf-8 encoded string with header value
       :rtype: string | email.header.Header
       :return: if ``header_text`` represents a plain ASCII string,
                return the same 7-bit string, otherwise returns an email.header.Header
                that will perform the appropriate RFC2047 encoding of
                non-ASCII values.
    """
    if not header_text:
        return ""
    header_text = ustr(header_text) # FIXME: require unicode higher up?
    if is_ascii(header_text):
        return pycompat.to_native(header_text)
    return Header(header_text, 'utf-8')
예제 #20
0
def encode_header_param(param_text):
    """Returns an appropriate RFC2047 encoded representation of the given
       header parameter value, suitable for direct assignation as the
       param value (e.g. via Message.set_param() or Message.add_header())
       RFC2822 assumes that headers contain only 7-bit characters,
       so we ensure it is the case, using RFC2047 encoding when needed.

       :param param_text: unicode or utf-8 encoded string with header value
       :rtype: string
       :return: if ``param_text`` represents a plain ASCII string,
                return the same 7-bit string, otherwise returns an
                ASCII string containing the RFC2047 encoded text.
    """
    # For details see the encode_header() method that uses the same logic
    if not param_text:
        return ""
    param_text = ustr(param_text) # FIXME: require unicode higher up?
    if is_ascii(param_text):
        return pycompat.to_native(param_text) # TODO: is that actually necessary?
    return Charset("utf-8").header_encode(param_text)
예제 #21
0
def encode_header(header_text):
    """Returns an appropriate representation of the given header value,
       suitable for direct assignment as a header value in an
       email.message.Message. RFC2822 assumes that headers contain
       only 7-bit characters, so we ensure it is the case, using
       RFC2047 encoding when needed.

       :param header_text: unicode or utf-8 encoded string with header value
       :rtype: string | email.header.Header
       :return: if ``header_text`` represents a plain ASCII string,
                return the same 7-bit string, otherwise returns an email.header.Header
                that will perform the appropriate RFC2047 encoding of
                non-ASCII values.
    """
    if not header_text:
        return ""
    header_text = ustr(header_text)  # FIXME: require unicode higher up?
    if is_ascii(header_text):
        return pycompat.to_native(header_text)
    return Header(header_text, 'utf-8')
예제 #22
0
def encode_header_param(param_text):
    """Returns an appropriate RFC2047 encoded representation of the given
       header parameter value, suitable for direct assignation as the
       param value (e.g. via Message.set_param() or Message.add_header())
       RFC2822 assumes that headers contain only 7-bit characters,
       so we ensure it is the case, using RFC2047 encoding when needed.

       :param param_text: unicode or utf-8 encoded string with header value
       :rtype: string
       :return: if ``param_text`` represents a plain ASCII string,
                return the same 7-bit string, otherwise returns an
                ASCII string containing the RFC2047 encoded text.
    """
    # For details see the encode_header() method that uses the same logic
    if not param_text:
        return ""
    param_text = ustr(param_text) # FIXME: require unicode higher up?
    if is_ascii(param_text):
        return pycompat.to_native(param_text) # TODO: is that actually necessary?
    return Charset("utf-8").header_encode(param_text)
예제 #23
0
    def authenticate(self, conf, login, password):
        """
        Authenticate a user against the specified LDAP server.

        In order to prevent an unintended 'unauthenticated authentication',
        which is an anonymous bind with a valid dn and a blank password,
        check for empty passwords explicitely (:rfc:`4513#section-6.3.1`)
        :param dict conf: LDAP configuration
        :param login: username
        :param password: Password for the LDAP user
        :return: LDAP entry of authenticated user or False
        :rtype: dictionary of attributes
        """

        if not password:
            return False

        entry = False
        try:
            filter = filter_format(conf['ldap_filter'], (login,))
        except TypeError:
            _logger.warning('Could not format LDAP filter. Your filter should contain one \'%s\'.')
            return False
        try:
            results = self.query(conf, tools.ustr(filter))

            # Get rid of (None, attrs) for searchResultReference replies
            results = [i for i in results if i[0]]
            if len(results) == 1:
                dn = results[0][0]
                conn = self.connect(conf)
                conn.simple_bind_s(dn, to_native(password))
                conn.unbind()
                entry = results[0]
        except ldap.INVALID_CREDENTIALS:
            return False
        except ldap.LDAPError as e:
            _logger.error('An LDAP exception occurred: %s', e)
        return entry
예제 #24
0
def encode_rfc2822_address_header(header_text):
    """If ``header_text`` contains non-ASCII characters,
       attempts to locate patterns of the form
       ``"Name" <address@domain>`` and replace the
       ``"Name"`` portion by the RFC2047-encoded
       version, preserving the address part untouched.
    """
    def encode_addr(addr):
        name, email = addr
        # If s is a <text string>, then charset is a hint specifying the
        # character set of the characters in the string. The Unicode string
        # will be encoded using the following charsets in order: us-ascii,
        # the charset hint, utf-8. The first character set to not provoke a
        # UnicodeError is used.
        # -> always pass a text string to Header

        # also Header.__str__ in Python 3 "Returns an approximation of the
        # Header as a string, using an unlimited line length.", the old one
        # was "A synonym for Header.encode()." so call encode() directly?
        name = Header(pycompat.to_text(name)).encode()
        return formataddr((name, email))

    addresses = getaddresses([pycompat.to_native(ustr(header_text))])
    return COMMASPACE.join(encode_addr(a) for a in addresses)
예제 #25
0
def encode_rfc2822_address_header(header_text):
    """If ``header_text`` contains non-ASCII characters,
       attempts to locate patterns of the form
       ``"Name" <address@domain>`` and replace the
       ``"Name"`` portion by the RFC2047-encoded
       version, preserving the address part untouched.
    """
    def encode_addr(addr):
        name, email = addr
        # If s is a <text string>, then charset is a hint specifying the
        # character set of the characters in the string. The Unicode string
        # will be encoded using the following charsets in order: us-ascii,
        # the charset hint, utf-8. The first character set to not provoke a
        # UnicodeError is used.
        # -> always pass a text string to Header

        # also Header.__str__ in Python 3 "Returns an approximation of the
        # Header as a string, using an unlimited line length.", the old one
        # was "A synonym for Header.encode()." so call encode() directly?
        name = Header(pycompat.to_text(name)).encode()
        return formataddr((name, email))

    addresses = getaddresses([pycompat.to_native(ustr(header_text))])
    return COMMASPACE.join(encode_addr(a) for a in addresses)
예제 #26
0
    def _parse_import_data_recursive(self, model, prefix, data, import_fields,
                                     options):
        # Get fields of type date/datetime
        all_fields = self.env[model].fields_get()
        for name, field in all_fields.items():
            name = prefix + name
            if field['type'] in ('date', 'datetime') and name in import_fields:
                # Parse date
                index = import_fields.index(name)
                dt = datetime.datetime
                server_format = DEFAULT_SERVER_DATE_FORMAT if field[
                    'type'] == 'date' else DEFAULT_SERVER_DATETIME_FORMAT

                if options.get('%s_format' % field['type'],
                               server_format) != server_format:
                    # datetime.str[fp]time takes *native strings* in both
                    # versions, for both data and pattern
                    user_format = pycompat.to_native(
                        options.get('%s_format' % field['type']))
                    for num, line in enumerate(data):
                        if line[index]:
                            line[index] = line[index].strip()
                        if line[index]:
                            try:
                                line[index] = dt.strftime(
                                    dt.strptime(
                                        pycompat.to_native(line[index]),
                                        user_format), server_format)
                            except ValueError as e:
                                try:
                                    # Allow to import date in datetime fields
                                    if field['type'] == 'datetime':
                                        user_format = pycompat.to_native(
                                            options.get('date_format'))
                                        line[index] = dt.strftime(
                                            dt.strptime(
                                                pycompat.to_native(
                                                    line[index]), user_format),
                                            server_format)
                                except ValueError as e:
                                    raise ValueError(
                                        _("Column %s contains incorrect values. Error in line %d: %s"
                                          ) % (name, num + 1, e))
                            except Exception as e:
                                raise ValueError(
                                    _("Error Parsing Date [%s:L%d]: %s") %
                                    (name, num + 1, e))
            # Check if the field is in import_field and is a relational (followed by /)
            # Also verify that the field name exactly match the import_field at the correct level.
            elif any(name + '/' in import_field
                     and name == import_field.split('/')[prefix.count('/')]
                     for import_field in import_fields):
                # Recursive call with the relational as new model and add the field name to the prefix
                self._parse_import_data_recursive(field['relation'],
                                                  name + '/', data,
                                                  import_fields, options)
            elif field['type'] in ('float',
                                   'monetary') and name in import_fields:
                # Parse float, sometimes float values from file have currency symbol or () to denote a negative value
                # We should be able to manage both case
                index = import_fields.index(name)
                self._parse_float_from_data(data, index, name, options)
            elif field['type'] == 'binary' and field.get('attachment') and any(
                    f in name for f in IMAGE_FIELDS) and name in import_fields:
                index = import_fields.index(name)

                with requests.Session() as session:
                    session.stream = True

                    for num, line in enumerate(data):
                        if re.match(
                                config.get("import_image_regex",
                                           DEFAULT_IMAGE_REGEX), line[index]):
                            if not self.env.user._can_import_remote_urls():
                                raise AccessError(
                                    _("You can not import images via URL, check with your administrator or support for the reason."
                                      ))

                            line[index] = self._import_image_by_url(
                                line[index], session, name, num)

        return data
예제 #27
0
    def build_email(self,
                    email_from,
                    email_to,
                    subject,
                    body,
                    email_cc=None,
                    email_bcc=None,
                    reply_to=False,
                    attachments=None,
                    message_id=None,
                    references=None,
                    object_id=False,
                    subtype='plain',
                    headers=None,
                    body_alternative=None,
                    subtype_alternative='plain'):
        """Constructs an RFC2822 email.message.Message object based on the keyword arguments passed, and returns it.

           :param string email_from: sender email address
           :param list email_to: list of recipient addresses (to be joined with commas) 
           :param string subject: email subject (no pre-encoding/quoting necessary)
           :param string body: email body, of the type ``subtype`` (by default, plaintext).
                               If html subtype is used, the message will be automatically converted
                               to plaintext and wrapped in multipart/alternative, unless an explicit
                               ``body_alternative`` version is passed.
           :param string body_alternative: optional alternative body, of the type specified in ``subtype_alternative``
           :param string reply_to: optional value of Reply-To header
           :param string object_id: optional tracking identifier, to be included in the message-id for
                                    recognizing replies. Suggested format for object-id is "res_id-model",
                                    e.g. "12345-crm.lead".
           :param string subtype: optional mime subtype for the text body (usually 'plain' or 'html'),
                                  must match the format of the ``body`` parameter. Default is 'plain',
                                  making the content part of the mail "text/plain".
           :param string subtype_alternative: optional mime subtype of ``body_alternative`` (usually 'plain'
                                              or 'html'). Default is 'plain'.
           :param list attachments: list of (filename, filecontents) pairs, where filecontents is a string
                                    containing the bytes of the attachment
           :param list email_cc: optional list of string values for CC header (to be joined with commas)
           :param list email_bcc: optional list of string values for BCC header (to be joined with commas)
           :param dict headers: optional map of headers to set on the outgoing mail (may override the
                                other headers, including Subject, Reply-To, Message-Id, etc.)
           :rtype: email.message.Message (usually MIMEMultipart)
           :return: the new RFC2822 email message
        """
        email_from = email_from or tools.config.get('email_from')
        assert email_from, "You must either provide a sender address explicitly or configure "\
                           "a global sender address in the server configuration or with the "\
                           "--email-from startup parameter."

        # Note: we must force all strings to to 8-bit utf-8 when crafting message,
        #       or use encode_header() for headers, which does it automatically.

        headers = headers or {}  # need valid dict later
        email_cc = email_cc or []
        email_bcc = email_bcc or []
        body = body or u''

        email_body = ustr(body)
        email_text_part = MIMEText(email_body,
                                   _subtype=subtype,
                                   _charset='utf-8')
        msg = MIMEMultipart()

        if not message_id:
            if object_id:
                message_id = tools.generate_tracking_message_id(object_id)
            else:
                message_id = make_msgid()
        msg['Message-Id'] = encode_header(message_id)
        if references:
            msg['references'] = encode_header(references)
        msg['Subject'] = encode_header(subject)
        msg['From'] = encode_rfc2822_address_header(email_from)
        del msg['Reply-To']
        if reply_to:
            msg['Reply-To'] = encode_rfc2822_address_header(reply_to)
        else:
            msg['Reply-To'] = msg['From']
        msg['To'] = encode_rfc2822_address_header(COMMASPACE.join(email_to))
        if email_cc:
            msg['Cc'] = encode_rfc2822_address_header(
                COMMASPACE.join(email_cc))
        if email_bcc:
            msg['Bcc'] = encode_rfc2822_address_header(
                COMMASPACE.join(email_bcc))
        msg['Date'] = formatdate()
        # Custom headers may override normal headers or provide additional ones
        for key, value in headers.items():
            msg[pycompat.to_native(ustr(key))] = encode_header(value)

        if subtype == 'html' and not body_alternative:
            # Always provide alternative text body ourselves if possible.
            text = html2text.html2text(email_body)
            alternative_part = MIMEMultipart(_subtype="alternative")
            alternative_part.attach(
                MIMEText(text, _charset='utf-8', _subtype='plain'))
            alternative_part.attach(email_text_part)
            msg.attach(alternative_part)
        elif body_alternative:
            # Include both alternatives, as specified, within a multipart/alternative part
            alternative_part = MIMEMultipart(_subtype="alternative")
            body_alternative_ = ustr(body_alternative)
            alternative_body_part = MIMEText(body_alternative_,
                                             _subtype=subtype_alternative,
                                             _charset='utf-8')
            alternative_part.attach(alternative_body_part)
            alternative_part.attach(email_text_part)
            msg.attach(alternative_part)
        else:
            msg.attach(email_text_part)

        if attachments:
            for (fname, fcontent, mime) in attachments:
                filename_rfc2047 = encode_header_param(fname)
                if mime and '/' in mime:
                    maintype, subtype = mime.split('/', 1)
                    part = MIMEBase(maintype, subtype)
                else:
                    part = MIMEBase('application', "octet-stream")

                # The default RFC2231 encoding of Message.add_header() works in Thunderbird but not GMail
                # so we fix it by using RFC2047 encoding for the filename instead.
                part.set_param('name', filename_rfc2047)
                part.add_header('Content-Disposition',
                                'attachment',
                                filename=filename_rfc2047)

                part.set_payload(fcontent)
                encoders.encode_base64(part)
                msg.attach(part)
        return msg
예제 #28
0
    def connect(self,
                host=None,
                port=None,
                user=None,
                password=None,
                encryption=None,
                smtp_debug=False,
                mail_server_id=None):
        """Returns a new SMTP connection to the given SMTP server.
           When running in test mode, this method does nothing and returns `None`.

           :param host: host or IP of SMTP server to connect to, if mail_server_id not passed
           :param int port: SMTP port to connect to
           :param user: optional username to authenticate with
           :param password: optional password to authenticate with
           :param string encryption: optional, ``'ssl'`` | ``'starttls'``
           :param bool smtp_debug: toggle debugging of SMTP sessions (all i/o
                              will be output in logs)
           :param mail_server_id: ID of specific mail server to use (overrides other parameters)
        """
        # Do not actually connect while running in test mode
        if getattr(threading.currentThread(), 'testing', False):
            return None

        mail_server = smtp_encryption = None
        if mail_server_id:
            mail_server = self.sudo().browse(mail_server_id)
        elif not host:
            mail_server = self.sudo().search([], order='sequence', limit=1)

        if mail_server:
            smtp_server = mail_server.smtp_host
            smtp_port = mail_server.smtp_port
            smtp_user = mail_server.smtp_user
            smtp_password = mail_server.smtp_pass
            smtp_encryption = mail_server.smtp_encryption
            smtp_debug = smtp_debug or mail_server.smtp_debug
        else:
            # we were passed individual smtp parameters or nothing and there is no default server
            smtp_server = host or tools.config.get('smtp_server')
            smtp_port = tools.config.get('smtp_port',
                                         25) if port is None else port
            smtp_user = user or tools.config.get('smtp_user')
            smtp_password = password or tools.config.get('smtp_password')
            smtp_encryption = encryption
            if smtp_encryption is None and tools.config.get('smtp_ssl'):
                smtp_encryption = 'starttls'  # smtp_ssl => STARTTLS as of v7

        if not smtp_server:
            raise UserError((_("Missing SMTP Server") + "\n" +
                             _("Please define at least one SMTP server, "
                               "or provide the SMTP parameters explicitly.")))

        if smtp_encryption == 'ssl':
            if 'SMTP_SSL' not in smtplib.__all__:
                raise UserError(
                    _("Your Odoo Server does not support SMTP-over-SSL. "
                      "You could use STARTTLS instead. "
                      "If SSL is needed, an upgrade to Python 2.6 on the server-side "
                      "should do the trick."))
            connection = smtplib.SMTP_SSL(smtp_server,
                                          smtp_port,
                                          timeout=SMTP_TIMEOUT)
        else:
            connection = smtplib.SMTP(smtp_server,
                                      smtp_port,
                                      timeout=SMTP_TIMEOUT)
        connection.set_debuglevel(smtp_debug)
        if smtp_encryption == 'starttls':
            # starttls() will perform ehlo() if needed first
            # and will discard the previous list of services
            # after successfully performing STARTTLS command,
            # (as per RFC 3207) so for example any AUTH
            # capability that appears only on encrypted channels
            # will be correctly detected for next step
            connection.starttls()

        if smtp_user:
            # Attempt authentication - will raise if AUTH service not supported
            # The user/password must be converted to bytestrings in order to be usable for
            # certain hashing schemes, like HMAC.
            # See also bug #597143 and python issue #5285
            smtp_user = pycompat.to_native(ustr(smtp_user))
            smtp_password = pycompat.to_native(ustr(smtp_password))
            connection.login(smtp_user, smtp_password)
        return connection
예제 #29
0
    def set_default(self, model, field_name, value, for_all_users=True, company_id=False, condition=False):
        """Defines a default value for the given model and field_name. Any previous
           default for the same scope (model, field_name, value, for_all_users, company_id, condition)
           will be replaced and lost in the process.

           Defaults can be later retrieved via :meth:`~.get_defaults`, which will return
           the highest priority default for any given field. Defaults that are more specific
           have a higher priority, in the following order (highest to lowest):

               * specific to user and company
               * specific to user only
               * specific to company only
               * global to everyone

           :param string model: model name
           :param string field_name: field name to which the default applies
           :param value: the default field value to set
           :type value: any serializable Python value
           :param bool for_all_users: whether the default should apply to everybody or only
                                      the user calling the method
           :param int company_id: optional ID of the company to which the default should
                                  apply. If omitted, the default will be global. If True
                                  is passed, the current user's company will be used.
           :param string condition: optional condition specification that can be used to
                                    restrict the applicability of the default values
                                    (e.g. based on another field's value). This is an
                                    opaque string as far as the API is concerned, but client
                                    stacks typically use single-field conditions in the
                                    form ``'key=stringified_value'``.
                                    (Currently, the condition is trimmed to 200 characters,
                                    so values that share the same first 200 characters always
                                    match)
           :return: the newly created ir.values entry
        """
        if isinstance(value, pycompat.string_types):
            value = pycompat.to_native(value)
        if company_id is True:
            # should be company-specific, need to get company id
            company_id = self.env.user.company_id.id

        # check consistency of model, field_name and value
        try:
            field = self.env[model]._fields[field_name]
            field.convert_to_cache(value, self.browse())
        except KeyError:
            _logger.warning("Invalid field %s.%s", model, field_name)
        except Exception:
            raise ValidationError(_("Invalid value for %s.%s: %s") % (model, field_name, value))

        # remove existing defaults for the same scope
        search_criteria = [
            ('key', '=', 'default'),
            ('key2', '=', condition and condition[:200]),
            ('model', '=', model),
            ('name', '=', field_name),
            ('user_id', '=', False if for_all_users else self._uid),
            ('company_id', '=', company_id)
        ]
        self.search(search_criteria).unlink()

        return self.create({
            'name': field_name,
            'value': pickle.dumps(value),
            'model': model,
            'key': 'default',
            'key2': condition and condition[:200],
            'user_id': False if for_all_users else self._uid,
            'company_id': company_id,
        })
 class InnerCustomModel(models.Model):
     _name = pycompat.to_native(model_data['model'])
예제 #31
0
    def fetch_mail(self):
        for server in self:
            count, failed = 0, 0
            pop_server = None
            if server.type == 'pop':
                _logger.info('Server tpye is POP')
                try:
                    while True:
                        pop_server = server.connect()
                        (num_messages, total_size) = pop_server.stat()
                        pop_server.list()
                        _logger.info('Server tpye is POP inside while')
                        _logger.info('total_size = %d', total_size)
                        _logger.info('num_messages = %d', num_messages)
                        for num in range(
                                1,
                                min(MAX_POP_MESSAGES, num_messages) + 1):
                            _logger.info(
                                'Server tpye is POP inside while INSIDE FOR')
                            (header, messages, octets) = pop_server.retr(num)
                            message = (b'\n').join(messages)
                            res_id = None
                            response = {
                                'errorCode': 100,
                                'message': 'File Uploaded Successfully'
                            }
                            try:
                                if isinstance(message, xmlrpclib.Binary):
                                    message = bytes(message.data)
                                if isinstance(message, pycompat.text_type):
                                    message = message.encode('utf-8')
                                extract = getattr(email, 'message_from_bytes',
                                                  email.message_from_string)
                                message = extract(message)
                                if not isinstance(message, Message):
                                    message = pycompat.to_native(message)
                                    message = email.message_from_string(
                                        message)
                                email_to = tools.decode_message_header(
                                    message, 'To')
                                match = re.search(r'[\w\.-]+@[\w\.-]+',
                                                  email_to)
                                email_to = str(match.group(0))
                                _logger.info('Email to %r', email_to)
                                # if email_to == INCOMING_EMAIL_ID:
                                _Attachment = namedtuple(
                                    'Attachment', ('fname', 'content', 'info'))
                                attachments = []
                                body = u''
                                email_from = tools.decode_message_header(
                                    message, 'From')
                                _logger.info('Email from %r', email_from)
                                match = re.search(r'[\w\.-]+@[\w\.-]+',
                                                  email_from)
                                email_from = str(match.group(0))
                                subject = tools.decode_message_header(
                                    message, 'Subject')
                                tmpl_type = None
                                if 'Inventory' in subject:
                                    tmpl_type = "Inventory"
                                elif 'Requirement' in subject:
                                    tmpl_type = "Requirement"
                                if message.get_content_maintype() != 'text':
                                    alternative = False
                                    for part in message.walk():
                                        if part.get_content_type(
                                        ) == 'multipart/alternative':
                                            alternative = True
                                        if part.get_content_maintype(
                                        ) == 'multipart':
                                            continue  # skip container
                                        filename = part.get_param(
                                            'filename', None,
                                            'content-disposition')
                                        if not filename:
                                            filename = part.get_param(
                                                'name', None)
                                        if filename:
                                            if isinstance(filename, tuple):
                                                filename = email.utils.collapse_rfc2231_value(
                                                    filename).strip()
                                            else:
                                                filename = tools.decode_smtp_header(
                                                    filename)
                                        encoding = part.get_content_charset()
                                        if filename and part.get('content-id'):
                                            inner_cid = part.get(
                                                'content-id').strip('><')
                                            attachments.append(
                                                _Attachment(
                                                    filename,
                                                    part.get_payload(
                                                        decode=True),
                                                    {'cid': inner_cid}))
                                            continue
                                        if filename or part.get(
                                                'content-disposition', ''
                                        ).strip().startswith('attachment'):
                                            attachments.append(
                                                _Attachment(
                                                    filename or 'attachment',
                                                    part.get_payload(
                                                        decode=True), {}))
                                            continue
                                        if part.get_content_type(
                                        ) == 'text/plain' and (not alternative
                                                               or not body):
                                            body = tools.append_content_to_html(
                                                body,
                                                tools.ustr(part.get_payload(
                                                    decode=True),
                                                           encoding,
                                                           errors='replace'),
                                                preserve=True)
                                        elif part.get_content_type(
                                        ) == 'text/html':
                                            body = tools.ustr(
                                                part.get_payload(decode=True),
                                                encoding,
                                                errors='replace')
                                        else:
                                            attachments.append(
                                                _Attachment(
                                                    filename or 'attachment',
                                                    part.get_payload(
                                                        decode=True), {}))
                                    if len(attachments) > 0:
                                        encoding = message.get_content_charset(
                                        )
                                        plain_text = html2text.HTML2Text()
                                        message_payload = plain_text.handle(
                                            tools.ustr(body,
                                                       encoding,
                                                       errors='replace'))
                                        if '- Forwarded message -' in message_payload:
                                            messages = message_payload.split(
                                                '- Forwarded message -')
                                            _logger.info(
                                                'Forwarded message payload: %r',
                                                messages)
                                            total_parts = len(messages)
                                            originator_part = messages[
                                                total_parts - 1]
                                            _logger.info(
                                                'originator_part: %r',
                                                originator_part)
                                            match = re.search(
                                                r'[\w\.-]+@[\w\.-]+',
                                                originator_part)
                                            _logger.info('match: %r', match)
                                            if match:
                                                email_from_domain = re.search(
                                                    "@[\w.]+",
                                                    email_from).group(0)
                                                _logger.info(
                                                    'email_from_domain: %r',
                                                    email_from_domain)
                                                email_to_domain = re.search(
                                                    "@[\w.]+",
                                                    email_to).group(0)
                                                _logger.info(
                                                    'email_to_domain: %r',
                                                    email_to_domain)
                                                if email_to_domain != email_from_domain:
                                                    email_from = None
                                                else:
                                                    email_from = str(
                                                        match.group(0))
                                                    _logger.info(
                                                        'email_to_domain email_from: %r',
                                                        email_from)
                                        #_logger.info('message payload: %r %r', message_payload, email_from)
                                        if not email_from is None:
                                            users_model = self.env[
                                                'res.partner'].search([
                                                    ("email", "=", email_from)
                                                ])
                                            if users_model:
                                                if len(users_model) == 1:
                                                    user_attachment_dir = ATTACHMENT_DIR + str(
                                                        datetime.now(
                                                        ).strftime("%d%m%Y")
                                                    ) + "/" + str(
                                                        users_model.id) + "/"
                                                    if not os.path.exists(
                                                            os.path.dirname(
                                                                user_attachment_dir
                                                            )):
                                                        try:
                                                            os.makedirs(
                                                                os.path.
                                                                dirname(
                                                                    user_attachment_dir
                                                                ))
                                                        except OSError as exc:
                                                            if exc.errno != errno.EEXIST:
                                                                raise
                                                    for attachment in attachments:
                                                        filename = getattr(
                                                            attachment,
                                                            'fname')
                                                        if not filename is None:
                                                            try:
                                                                file_contents_bytes = getattr(
                                                                    attachment,
                                                                    'content')
                                                                file_path = user_attachment_dir + str(
                                                                    filename)
                                                                file_ref = open(
                                                                    str(file_path
                                                                        ),
                                                                    "wb+")
                                                                file_ref.write(
                                                                    file_contents_bytes
                                                                )
                                                                file_ref.close(
                                                                )
                                                                response = self.env[
                                                                    'sps.document.process'].process_document(
                                                                        users_model,
                                                                        file_path,
                                                                        tmpl_type,
                                                                        filename,
                                                                        'Email'
                                                                    )
                                                            except Exception as e:
                                                                _logger.info(
                                                                    str(e))
                                                else:
                                                    _logger.error(
                                                        'Presents Same Email Id for multiple users %r',
                                                        email_from)
                                                    response = dict(
                                                        errorCode=101,
                                                        message=
                                                        'Presents Same Email Id for multiple users : '
                                                        + str(email_from))
                                            else:
                                                _logger.info(
                                                    'user not found for %r',
                                                    email_from)
                                                response = dict(
                                                    errorCode=102,
                                                    message=
                                                    'User not found for : ' +
                                                    str(email_from))
                                        else:
                                            _logger.info(
                                                'domain not matched for forwarded email'
                                            )
                                            response = dict(
                                                errorCode=103,
                                                message=
                                                'Domain not matched for forwarded email : '
                                                + str(email_from))
                                    else:
                                        _logger.info("No attachements found")
                                        response = dict(
                                            errorCode=104,
                                            message='No attachements found : '
                                            + str(email_from))
                                else:
                                    _logger.info('Not a Multipart email')
                                    response = dict(
                                        errorCode=105,
                                        message='Not a Multipart email' +
                                        str(email_from))
                                pop_server.dele(num)

                                if "errorCode" in response:
                                    self.send_mail(
                                        "Sending Email Response as " +
                                        str(response['message']) +
                                        " for user " + str(email_from))

                            except Exception:
                                _logger.info(
                                    'Failed to process mail from %s server %s.',
                                    server.type,
                                    server.name,
                                    exc_info=True)
                                failed += 1

                            if res_id and server.action_id:
                                server.action_id.with_context({
                                    'active_id':
                                    res_id,
                                    'active_ids': [res_id],
                                    'active_model':
                                    self.env.context.get(
                                        "thread_model", server.object_id.model)
                                }).run()
                            self.env.cr.commit()
                        _logger.info('num_messages = %d', num_messages)
                        if num_messages < MAX_POP_MESSAGES:
                            break
                        pop_server.quit()
                        _logger.info(
                            "Fetched %d email(s) on %s server %s; %d succeeded, %d failed.",
                            num_messages, server.type, server.name,
                            (num_messages - failed), failed)
                except Exception:
                    _logger.info(
                        "General failure when trying to fetch mail from %s server %s.",
                        server.type,
                        server.name,
                        exc_info=True)
                finally:
                    _logger.info('Server tpye is POP inside finally')
                    if pop_server:
                        pop_server.quit()
            server.write({'date': fields.Datetime.now()})

        return super(IncomingMailCronModel, self).fetch_mail()
예제 #32
0
    def connect(self, host=None, port=None, user=None, password=None, encryption=None,
                smtp_debug=False, mail_server_id=None):
        """Returns a new SMTP connection to the given SMTP server.
           When running in test mode, this method does nothing and returns `None`.

           :param host: host or IP of SMTP server to connect to, if mail_server_id not passed
           :param int port: SMTP port to connect to
           :param user: optional username to authenticate with
           :param password: optional password to authenticate with
           :param string encryption: optional, ``'ssl'`` | ``'starttls'``
           :param bool smtp_debug: toggle debugging of SMTP sessions (all i/o
                              will be output in logs)
           :param mail_server_id: ID of specific mail server to use (overrides other parameters)
        """
        # Do not actually connect while running in test mode
        if getattr(threading.currentThread(), 'testing', False):
            return None

        mail_server = smtp_encryption = None
        if mail_server_id:
            mail_server = self.sudo().browse(mail_server_id)
        elif not host:
            mail_server = self.sudo().search([], order='sequence', limit=1)

        if mail_server:
            smtp_server = mail_server.smtp_host
            smtp_port = mail_server.smtp_port
            smtp_user = mail_server.smtp_user
            smtp_password = mail_server.smtp_pass
            smtp_encryption = mail_server.smtp_encryption
            smtp_debug = smtp_debug or mail_server.smtp_debug
        else:
            # we were passed individual smtp parameters or nothing and there is no default server
            smtp_server = host or tools.config.get('smtp_server')
            smtp_port = tools.config.get('smtp_port', 25) if port is None else port
            smtp_user = user or tools.config.get('smtp_user')
            smtp_password = password or tools.config.get('smtp_password')
            smtp_encryption = encryption
            if smtp_encryption is None and tools.config.get('smtp_ssl'):
                smtp_encryption = 'starttls' # smtp_ssl => STARTTLS as of v7

        if not smtp_server:
            raise UserError(
                (_("Missing SMTP Server") + "\n" +
                 _("Please define at least one SMTP server, "
                   "or provide the SMTP parameters explicitly.")))

        if smtp_encryption == 'ssl':
            if 'SMTP_SSL' not in smtplib.__all__:
                raise UserError(
                    _("Your Odoo Server does not support SMTP-over-SSL. "
                      "You could use STARTTLS instead. "
                       "If SSL is needed, an upgrade to Python 2.6 on the server-side "
                       "should do the trick."))
            connection = smtplib.SMTP_SSL(smtp_server, smtp_port)
        else:
            connection = smtplib.SMTP(smtp_server, smtp_port)
        connection.set_debuglevel(smtp_debug)
        if smtp_encryption == 'starttls':
            # starttls() will perform ehlo() if needed first
            # and will discard the previous list of services
            # after successfully performing STARTTLS command,
            # (as per RFC 3207) so for example any AUTH
            # capability that appears only on encrypted channels
            # will be correctly detected for next step
            connection.starttls()

        if smtp_user:
            # Attempt authentication - will raise if AUTH service not supported
            # The user/password must be converted to bytestrings in order to be usable for
            # certain hashing schemes, like HMAC.
            # See also bug #597143 and python issue #5285
            smtp_user = pycompat.to_native(ustr(smtp_user))
            smtp_password = pycompat.to_native(ustr(smtp_password))
            connection.login(smtp_user, smtp_password)
        return connection
예제 #33
0
    def build_email(self, email_from, email_to, subject, body, email_cc=None, email_bcc=None, reply_to=False,
                    attachments=None, message_id=None, references=None, object_id=False, subtype='plain', headers=None,
                    body_alternative=None, subtype_alternative='plain'):
        """Constructs an RFC2822 email.message.Message object based on the keyword arguments passed, and returns it.

           :param string email_from: sender email address
           :param list email_to: list of recipient addresses (to be joined with commas) 
           :param string subject: email subject (no pre-encoding/quoting necessary)
           :param string body: email body, of the type ``subtype`` (by default, plaintext).
                               If html subtype is used, the message will be automatically converted
                               to plaintext and wrapped in multipart/alternative, unless an explicit
                               ``body_alternative`` version is passed.
           :param string body_alternative: optional alternative body, of the type specified in ``subtype_alternative``
           :param string reply_to: optional value of Reply-To header
           :param string object_id: optional tracking identifier, to be included in the message-id for
                                    recognizing replies. Suggested format for object-id is "res_id-model",
                                    e.g. "12345-crm.lead".
           :param string subtype: optional mime subtype for the text body (usually 'plain' or 'html'),
                                  must match the format of the ``body`` parameter. Default is 'plain',
                                  making the content part of the mail "text/plain".
           :param string subtype_alternative: optional mime subtype of ``body_alternative`` (usually 'plain'
                                              or 'html'). Default is 'plain'.
           :param list attachments: list of (filename, filecontents) pairs, where filecontents is a string
                                    containing the bytes of the attachment
           :param list email_cc: optional list of string values for CC header (to be joined with commas)
           :param list email_bcc: optional list of string values for BCC header (to be joined with commas)
           :param dict headers: optional map of headers to set on the outgoing mail (may override the
                                other headers, including Subject, Reply-To, Message-Id, etc.)
           :rtype: email.message.Message (usually MIMEMultipart)
           :return: the new RFC2822 email message
        """
        email_from = email_from or tools.config.get('email_from')
        assert email_from, "You must either provide a sender address explicitly or configure "\
                           "a global sender address in the server configuration or with the "\
                           "--email-from startup parameter."

        # Note: we must force all strings to to 8-bit utf-8 when crafting message,
        #       or use encode_header() for headers, which does it automatically.

        headers = headers or {}         # need valid dict later
        email_cc = email_cc or []
        email_bcc = email_bcc or []
        body = body or u''

        email_body = ustr(body)
        email_text_part = MIMEText(email_body, _subtype=subtype, _charset='utf-8')
        msg = MIMEMultipart()

        if not message_id:
            if object_id:
                message_id = tools.generate_tracking_message_id(object_id)
            else:
                message_id = make_msgid()
        msg['Message-Id'] = encode_header(message_id)
        if references:
            msg['references'] = encode_header(references)
        msg['Subject'] = encode_header(subject)
        msg['From'] = encode_rfc2822_address_header(email_from)
        del msg['Reply-To']
        if reply_to:
            msg['Reply-To'] = encode_rfc2822_address_header(reply_to)
        else:
            msg['Reply-To'] = msg['From']
        msg['To'] = encode_rfc2822_address_header(COMMASPACE.join(email_to))
        if email_cc:
            msg['Cc'] = encode_rfc2822_address_header(COMMASPACE.join(email_cc))
        if email_bcc:
            msg['Bcc'] = encode_rfc2822_address_header(COMMASPACE.join(email_bcc))
        msg['Date'] = formatdate()
        # Custom headers may override normal headers or provide additional ones
        for key, value in headers.items():
            msg[pycompat.to_native(ustr(key))] = encode_header(value)

        if subtype == 'html' and not body_alternative:
            # Always provide alternative text body ourselves if possible.
            text = html2text.html2text(email_body)
            alternative_part = MIMEMultipart(_subtype="alternative")
            alternative_part.attach(MIMEText(text, _charset='utf-8', _subtype='plain'))
            alternative_part.attach(email_text_part)
            msg.attach(alternative_part)
        elif body_alternative:
            # Include both alternatives, as specified, within a multipart/alternative part
            alternative_part = MIMEMultipart(_subtype="alternative")
            body_alternative_ = ustr(body_alternative)
            alternative_body_part = MIMEText(body_alternative_, _subtype=subtype_alternative, _charset='utf-8')
            alternative_part.attach(alternative_body_part)
            alternative_part.attach(email_text_part)
            msg.attach(alternative_part)
        else:
            msg.attach(email_text_part)

        if attachments:
            for (fname, fcontent, mime) in attachments:
                filename_rfc2047 = encode_header_param(fname)
                if mime and '/' in mime:
                    maintype, subtype = mime.split('/', 1)
                    part = MIMEBase(maintype, subtype)
                else:
                    part = MIMEBase('application', "octet-stream")

                # The default RFC2231 encoding of Message.add_header() works in Thunderbird but not GMail
                # so we fix it by using RFC2047 encoding for the filename instead.
                part.set_param('name', filename_rfc2047)
                part.add_header('Content-Disposition', 'attachment', filename=filename_rfc2047)

                part.set_payload(fcontent)
                encoders.encode_base64(part)
                msg.attach(part)
        return msg
예제 #34
0
    def set_default(self,
                    model,
                    field_name,
                    value,
                    for_all_users=True,
                    company_id=False,
                    condition=False):
        """Defines a default value for the given model and field_name. Any previous
           default for the same scope (model, field_name, value, for_all_users, company_id, condition)
           will be replaced and lost in the process.

           Defaults can be later retrieved via :meth:`~.get_defaults`, which will return
           the highest priority default for any given field. Defaults that are more specific
           have a higher priority, in the following order (highest to lowest):

               * specific to user and company
               * specific to user only
               * specific to company only
               * global to everyone

           :param string model: model name
           :param string field_name: field name to which the default applies
           :param value: the default field value to set
           :type value: any serializable Python value
           :param bool for_all_users: whether the default should apply to everybody or only
                                      the user calling the method
           :param int company_id: optional ID of the company to which the default should
                                  apply. If omitted, the default will be global. If True
                                  is passed, the current user's company will be used.
           :param string condition: optional condition specification that can be used to
                                    restrict the applicability of the default values
                                    (e.g. based on another field's value). This is an
                                    opaque string as far as the API is concerned, but client
                                    stacks typically use single-field conditions in the
                                    form ``'key=stringified_value'``.
                                    (Currently, the condition is trimmed to 200 characters,
                                    so values that share the same first 200 characters always
                                    match)
           :return: the newly created ir.values entry
        """
        if isinstance(value, pycompat.string_types):
            value = pycompat.to_native(value)
        if company_id is True:
            # should be company-specific, need to get company id
            company_id = self.env.user.company_id.id

        # check consistency of model, field_name and value
        try:
            field = self.env[model]._fields[field_name]
            field.convert_to_cache(value, self.browse())
        except KeyError:
            _logger.warning("Invalid field %s.%s", model, field_name)
        except Exception:
            raise ValidationError(
                _("Invalid value for %s.%s: %s") % (model, field_name, value))

        # remove existing defaults for the same scope
        search_criteria = [('key', '=', 'default'),
                           ('key2', '=', condition and condition[:200]),
                           ('model', '=', model), ('name', '=', field_name),
                           ('user_id', '=',
                            False if for_all_users else self._uid),
                           ('company_id', '=', company_id)]
        self.search(search_criteria).unlink()

        return self.create({
            'name': field_name,
            'value': pickle.dumps(value),
            'model': model,
            'key': 'default',
            'key2': condition and condition[:200],
            'user_id': False if for_all_users else self._uid,
            'company_id': company_id,
        })