States for some specific countries should not be offered as filtering options for crm.iap.lead.mining.request because they drastically reduce the amount of IAP reveal results. For example, in Belgium, only 11% of companies have a defined state within the reveal service while the rest of them have no state defined at all. Meaning specifying states for that country will yield a lot less results than what you could expect, which is not the desired behavior. Obviously all companies are active within a state, it's just a lack of data in the reveal service side. To help users create meaningful iap searches, we only keep the states filtering for several whitelisted countries (based on their country code). The complete list and reasons for this change can be found on the task. Task-2471703
210 lines
9.0 KiB
Python
210 lines
9.0 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import contextlib
|
|
import logging
|
|
import json
|
|
import requests
|
|
import uuid
|
|
from unittest.mock import patch
|
|
|
|
from odoo import exceptions, _
|
|
from odoo.tests.common import BaseCase
|
|
from odoo.tools import pycompat
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
DEFAULT_ENDPOINT = 'https://iap.odoo.com'
|
|
|
|
|
|
# We need to mock iap_jsonrpc during tests as we don't want to perform real calls to RPC endpoints
|
|
def iap_jsonrpc_mocked(*args, **kwargs):
|
|
raise exceptions.AccessError("Unavailable during tests.")
|
|
|
|
|
|
iap_patch = patch('odoo.addons.iap.tools.iap_tools.iap_jsonrpc', iap_jsonrpc_mocked)
|
|
|
|
|
|
def setUp(self):
|
|
old_setup_func(self)
|
|
iap_patch.start()
|
|
self.addCleanup(iap_patch.stop)
|
|
|
|
|
|
old_setup_func = BaseCase.setUp
|
|
BaseCase.setUp = setUp
|
|
|
|
#----------------------------------------------------------
|
|
# Tools globals
|
|
#----------------------------------------------------------
|
|
|
|
_MAIL_DOMAIN_BLACKLIST = set([
|
|
# Top 100 email providers on SaaS at 2020-10
|
|
'gmail.com', 'hotmail.com', 'yahoo.com', 'qq.com', 'outlook.com', '163.com', 'yahoo.fr', 'live.com', 'hotmail.fr', 'icloud.com', '126.com',
|
|
'me.com', 'free.fr', 'ymail.com', 'msn.com', 'mail.com', 'orange.fr', 'aol.com', 'wanadoo.fr', 'live.fr', 'mail.ru', 'yahoo.co.in',
|
|
'rediffmail.com', 'hku.hk', 'googlemail.com', 'gmx.de', 'sina.com', 'skynet.be', 'laposte.net', 'yahoo.co.uk', 'yahoo.co.id', 'web.de',
|
|
'gmail.com ', 'outlook.fr', 'telenet.be', 'yahoo.es', 'naver.com', 'hotmail.co.uk', 'gmai.com', 'foxmail.com', 'hku.hku', 'bluewin.ch',
|
|
'sfr.fr', 'libero.it', 'mac.com', 'rocketmail.com', 'protonmail.com', 'gmx.com', 'gamil.com', 'hotmail.es', 'gmx.net', 'comcast.net',
|
|
'yahoo.com.mx', 'linkedin.com', 'yahoo.com.br', 'yahoo.in', 'yahoo.ca', 't-online.de', '139.com', 'yandex.ru', 'yahoo.com.hk','yahoo.de',
|
|
'yeah.net', 'yandex.com', 'nwytg.net', 'neuf.fr', 'yahoo.com.ar', 'outlook.es', 'abv.bg', 'aliyun.com', 'yahoo.com.tw', 'ukr.net', 'live.nl',
|
|
'wp.pl', 'hotmail.it', 'live.com.mx', 'zoho.com', 'live.co.uk', 'sohu.com', 'twoomail.com', 'yahoo.com.sg', 'odoo.com', 'yahoo.com.vn',
|
|
'windowslive.com', 'gmail', 'vols.utk.edu', 'email.com', 'tiscali.it', 'yahoo.it', 'gmx.ch', 'trbvm.com', 'nwytg.com', 'mvrht.com', 'nyit.edu',
|
|
'o2.pl', 'live.cn', 'gmial.com', 'seznam.cz', 'live.be', 'videotron.ca', 'gmil.com', 'live.ca', 'hotmail.de', 'sbcglobal.net', 'connect.hku.hk',
|
|
'yahoo.com.au', 'att.net', 'live.in', 'btinternet.com', 'gmx.fr', 'voila.fr', 'shaw.ca', 'prodigy.net.mx', 'vip.qq.com', 'yahoo.com.ph',
|
|
'bigpond.com', '7thcomputing.com', 'freenet.de', 'alice.it', 'esi.dz',
|
|
'bk.ru', 'mail.odoo.com', 'gmail.con', 'fiu.edu', 'gmal.com', 'useemlikefun.com', 'google.com', 'trbvn.com', 'yopmail.com', 'ya.ru',
|
|
'hotmail.co.th', 'arcor.de', 'hotmail.ca', '21cn.com', 'live.de', 'outlook.de', 'gmailcom', 'unal.edu.co', 'tom.com', 'yahoo.gr',
|
|
'gmx.at', 'inbox.lv', 'ziggo.nl', 'xs4all.nl', 'sapo.pt', 'live.com.au', 'nate.com', 'online.de', 'sina.cn', 'gmail.co', 'rogers.com',
|
|
'mailinator.com', 'cox.net', 'hotmail.be', 'verizon.net', 'yahoo.co.jp', 'usa.com', 'consultant.com', 'hotmai.com', '189.cn',
|
|
'sky.com', 'eezee-it.com', 'opayq.com', 'maildrop.cc', 'home.nl', 'virgilio.it', 'outlook.be', 'hanmail.net', 'uol.com.br', 'hec.ca',
|
|
'terra.com.br', 'inbox.ru', 'tin.it', 'list.ru', 'hotmail.com ', 'safecoms.com', 'smile.fr', 'sprintit.fi', 'uniminuto.edu.co',
|
|
'bol.com.br', 'bellsouth.net', 'nirmauni.ac.in', 'ldc.edu.in', 'ig.com.br', 'engineer.com', 'scarlet.be', 'inbox.com', 'gmaill.com',
|
|
'freemail.hu', 'live.it', 'blackwaretech.com', 'byom.de', 'dispostable.com', 'dayrep.com', 'aim.com', 'prixgen.com', 'gmail.om',
|
|
'asterisk-tech.mn', 'in.com', 'aliceadsl.fr', 'lycos.com', 'topnet.tn', 'teleworm.us', 'kedgebs.com', 'supinfo.com', 'posteo.de',
|
|
'yahoo.com ', 'op.pl', 'gmail.fr', 'grr.la', 'oci.fr', 'aselcis.com', 'optusnet.com.au', 'mailcatch.com', 'rambler.ru', 'protonmail.ch',
|
|
'prisme.ch', 'bbox.fr', 'orbitalu.com', 'netcourrier.com', 'iinet.net.au',
|
|
# Dummy entries
|
|
'example.com',
|
|
])
|
|
|
|
# List of country codes for which we should offer state filtering when mining new leads.
|
|
# See crm.iap.lead.mining.request#_compute_available_state_ids() or task-2471703 for more details.
|
|
_STATES_FILTER_COUNTRIES_WHITELIST = set([
|
|
'AR', 'AU', 'BR', 'CA', 'IN', 'MY', 'MX', 'NZ', 'AE', 'US'
|
|
])
|
|
|
|
#----------------------------------------------------------
|
|
# Helpers for both clients and proxy
|
|
#----------------------------------------------------------
|
|
|
|
def iap_get_endpoint(env):
|
|
url = env['ir.config_parameter'].sudo().get_param('iap.endpoint', DEFAULT_ENDPOINT)
|
|
return url
|
|
|
|
#----------------------------------------------------------
|
|
# Helpers for clients
|
|
#----------------------------------------------------------
|
|
|
|
class InsufficientCreditError(Exception):
|
|
pass
|
|
|
|
|
|
def iap_jsonrpc(url, method='call', params=None, timeout=15):
|
|
"""
|
|
Calls the provided JSON-RPC endpoint, unwraps the result and
|
|
returns JSON-RPC errors as exceptions.
|
|
"""
|
|
payload = {
|
|
'jsonrpc': '2.0',
|
|
'method': method,
|
|
'params': params,
|
|
'id': uuid.uuid4().hex,
|
|
}
|
|
|
|
_logger.info('iap jsonrpc %s', url)
|
|
try:
|
|
req = requests.post(url, json=payload, timeout=timeout)
|
|
req.raise_for_status()
|
|
response = req.json()
|
|
if 'error' in response:
|
|
name = response['error']['data'].get('name').rpartition('.')[-1]
|
|
message = response['error']['data'].get('message')
|
|
if name == 'InsufficientCreditError':
|
|
e_class = InsufficientCreditError
|
|
elif name == 'AccessError':
|
|
e_class = exceptions.AccessError
|
|
elif name == 'UserError':
|
|
e_class = exceptions.UserError
|
|
else:
|
|
raise requests.exceptions.ConnectionError()
|
|
e = e_class(message)
|
|
e.data = response['error']['data']
|
|
raise e
|
|
return response.get('result')
|
|
except (ValueError, requests.exceptions.ConnectionError, requests.exceptions.MissingSchema, requests.exceptions.Timeout, requests.exceptions.HTTPError) as e:
|
|
raise exceptions.AccessError(
|
|
_('The url that this service requested returned an error. Please contact the author of the app. The url it tried to contact was %s', url)
|
|
)
|
|
|
|
#----------------------------------------------------------
|
|
# Helpers for proxy
|
|
#----------------------------------------------------------
|
|
|
|
class IapTransaction(object):
|
|
|
|
def __init__(self):
|
|
self.credit = None
|
|
|
|
|
|
def iap_authorize(env, key, account_token, credit, dbuuid=False, description=None, credit_template=None):
|
|
endpoint = iap_get_endpoint(env)
|
|
params = {
|
|
'account_token': account_token,
|
|
'credit': credit,
|
|
'key': key,
|
|
'description': description,
|
|
}
|
|
if dbuuid:
|
|
params.update({'dbuuid': dbuuid})
|
|
try:
|
|
transaction_token = iap_jsonrpc(endpoint + '/iap/1/authorize', params=params)
|
|
except InsufficientCreditError as e:
|
|
if credit_template:
|
|
arguments = json.loads(e.args[0])
|
|
arguments['body'] = pycompat.to_text(env['ir.qweb']._render(credit_template))
|
|
e.args = (json.dumps(arguments),)
|
|
raise e
|
|
return transaction_token
|
|
|
|
|
|
def iap_cancel(env, transaction_token, key):
|
|
endpoint = iap_get_endpoint(env)
|
|
params = {
|
|
'token': transaction_token,
|
|
'key': key,
|
|
}
|
|
r = iap_jsonrpc(endpoint + '/iap/1/cancel', params=params)
|
|
return r
|
|
|
|
|
|
def iap_capture(env, transaction_token, key, credit):
|
|
endpoint = iap_get_endpoint(env)
|
|
params = {
|
|
'token': transaction_token,
|
|
'key': key,
|
|
'credit_to_capture': credit,
|
|
}
|
|
r = iap_jsonrpc(endpoint + '/iap/1/capture', params=params)
|
|
return r
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def iap_charge(env, key, account_token, credit, dbuuid=False, description=None, credit_template=None):
|
|
"""
|
|
Account charge context manager: takes a hold for ``credit``
|
|
amount before executing the body, then captures it if there
|
|
is no error, or cancels it if the body generates an exception.
|
|
|
|
:param str key: service identifier
|
|
:param str account_token: user identifier
|
|
:param int credit: cost of the body's operation
|
|
:param description: a description of the purpose of the charge,
|
|
the user will be able to see it in their
|
|
dashboard
|
|
:type description: str
|
|
:param credit_template: a QWeb template to render and show to the
|
|
user if their account does not have enough
|
|
credits for the requested operation
|
|
:type credit_template: str
|
|
"""
|
|
transaction_token = iap_authorize(env, key, account_token, credit, dbuuid, description, credit_template)
|
|
try:
|
|
transaction = IapTransaction()
|
|
transaction.credit = credit
|
|
yield transaction
|
|
except Exception as e:
|
|
r = iap_cancel(env,transaction_token, key)
|
|
raise e
|
|
else:
|
|
r = iap_capture(env,transaction_token, key, transaction.credit)
|