Files
odoo_source/addons/website/tools.py
T
61a7328c2f [FIX] website_sale: block employees from updating their billing address
Reproduction:
1. Install Event, Sales, Webiste
2. Login as Admin, go to Website -> Go to website -> Events
3. Click the Open wood event, Register, buy one VIP ticket
4. In Address step, Edit the billing address, change the name to “Test
Name”, click next
5. The user name “Mitchell Admin” is changed to  “Test Name”, we
shouldn’t be able to change the info

Reason: In the fix to block name change here: https://github.com/odoo/odoo/commit/d823033ad67702b1b92d27a3f66c7a4ec304c644
we use the can_edit_vat to check if we have existing invoice(s) or
SO(s). However, we should block the route that an employee changes the
billing address when placing an order. If they are placing an order for
external people, it should be done from the back end.

Fix: add an extra error case when it's an employee trying to change the
name or email address when editing billing address. This is the case
when an employee tries to order for external people. They should do it
from the back end. They can still buy for themselves without changing
the billing address. Also added translation in pot. Edited the test for
editing address of log in user, added tests for portal user. Reformat
the invoice exsits check for name change to have better readability

The adding of can_edit_vat:
https://github.com/odoo/odoo/commit/f8b05f52f5ea7f31135f700b0e240ff563204085

Related fix to block the name change:
https://github.com/odoo/odoo/commit/d823033ad67702b1b92d27a3f66c7a4ec304c644

A patch to not block the checkout process when name is not set:
https://github.com/odoo/odoo/commit/781dbeaccac76a6ec4f4b8cac1b607810697e394

opw-3126325

closes odoo/odoo#126261

X-original-commit: 972c55bf76f4c5f1b1bedec4a77ddaeac8be1483
Signed-off-by: Antoine Vandevenne (anv) <anv@odoo.com>
Signed-off-by: Jinjiu Liu (jili) <jili@odoo.com>
Co-authored-by: Jeremy Kersten <jke@odoo.com>
Co-authored-by: Antoine Vandevenne (anv) <anv@odoo.com>
2023-06-23 18:00:27 +02:00

190 lines
5.9 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import contextlib
import re
import werkzeug.urls
from lxml import etree
from unittest.mock import Mock, MagicMock, patch
from werkzeug.exceptions import NotFound
from werkzeug.test import EnvironBuilder
import odoo
from odoo.tests.common import HttpCase, HOST
from odoo.tools.misc import DotDict, frozendict
@contextlib.contextmanager
def MockRequest(
env, *, path='/mockrequest', routing=True, multilang=True,
context=frozendict(), cookies=frozendict(), country_code=None,
website=None, remote_addr=HOST, environ_base=None,
# website_sale
sale_order_id=None, website_sale_current_pl=None,
):
lang_code = context.get('lang', env.context.get('lang', 'en_US'))
env = env(context=dict(context, lang=lang_code))
request = Mock(
# request
httprequest=Mock(
host='localhost',
path=path,
app=odoo.http.root,
environ=dict(
EnvironBuilder(
path=path,
base_url=HttpCase.base_url(),
environ_base=environ_base,
).get_environ(),
REMOTE_ADDR=remote_addr,
),
cookies=cookies,
referrer='',
remote_addr=remote_addr,
),
type='http',
future_response=odoo.http.FutureResponse(),
params={},
redirect=env['ir.http']._redirect,
session=DotDict(
odoo.http.get_default_session(),
geoip={'country_code': country_code},
sale_order_id=sale_order_id,
website_sale_current_pl=website_sale_current_pl,
),
geoip=odoo.http.GeoIP('127.0.0.1'),
db=env.registry.db_name,
env=env,
registry=env.registry,
cr=env.cr,
uid=env.uid,
context=env.context,
lang=env['res.lang']._lang_get(lang_code),
website=website,
render=lambda *a, **kw: '<MockResponse>',
)
if website:
request.website_routing = website.id
# The following code mocks match() to return a fake rule with a fake
# 'routing' attribute (routing=True) or to raise a NotFound
# exception (routing=False).
#
# router = odoo.http.root.get_db_router()
# rule, args = router.bind(...).match(path)
# # arg routing is True => rule.endpoint.routing == {...}
# # arg routing is False => NotFound exception
router = MagicMock()
match = router.return_value.bind.return_value.match
if routing:
match.return_value[0].routing = {
'type': 'http',
'website': True,
'multilang': multilang
}
else:
match.side_effect = NotFound
def update_context(**overrides):
request.context = dict(request.context, **overrides)
request.update_context = update_context
with contextlib.ExitStack() as s:
odoo.http._request_stack.push(request)
s.callback(odoo.http._request_stack.pop)
s.enter_context(patch('odoo.http.root.get_db_router', router))
yield request
# Fuzzy matching tools
def distance(s1="", s2="", limit=4):
"""
Limited Levenshtein-ish distance (inspired from Apache text common)
Note: this does not return quick results for simple cases (empty string, equal strings)
those checks should be done outside loops that use this function.
:param s1: first string
:param s2: second string
:param limit: maximum distance to take into account, return -1 if exceeded
:return: number of character changes needed to transform s1 into s2 or -1 if this exceeds the limit
"""
BIG = 100000 # never reached integer
if len(s1) > len(s2):
s1, s2 = s2, s1
l1 = len(s1)
l2 = len(s2)
if l2 - l1 > limit:
return -1
boundary = min(l1, limit) + 1
p = [i if i < boundary else BIG for i in range(0, l1 + 1)]
d = [BIG for _ in range(0, l1 + 1)]
for j in range(1, l2 + 1):
j2 = s2[j - 1]
d[0] = j
range_min = max(1, j - limit)
range_max = min(l1, j + limit)
if range_min > 1:
d[range_min - 1] = BIG
for i in range(range_min, range_max + 1):
if s1[i - 1] == j2:
d[i] = p[i - 1]
else:
d[i] = 1 + min(d[i - 1], p[i], p[i - 1])
p, d = d, p
return p[l1] if p[l1] <= limit else -1
def similarity_score(s1, s2):
"""
Computes a score that describes how much two strings are matching.
:param s1: first string
:param s2: second string
:return: float score, the higher the more similar
pairs returning non-positive scores should be considered non similar
"""
dist = distance(s1, s2)
if dist == -1:
return -1
set1 = set(s1)
score = len(set1.intersection(s2)) / len(set1)
score -= dist / len(s1)
score -= len(set1.symmetric_difference(s2)) / (len(s1) + len(s2))
return score
def text_from_html(html_fragment, collapse_whitespace=False):
"""
Returns the plain non-tag text from an html
:param html_fragment: document from which text must be extracted
:return: text extracted from the html
"""
# lxml requires one single root element
tree = etree.fromstring('<p>%s</p>' % html_fragment, etree.XMLParser(recover=True))
content = ' '.join(tree.itertext())
if collapse_whitespace:
content = re.sub('\\s+', ' ', content).strip()
return content
def get_base_domain(url, strip_www=False):
"""
Returns the domain of a given url without the scheme and the www. and the
final '/' if any.
:param url: url from which the domain must be extracted
:param strip_www: if True, strip the www. from the domain
:return: domain of the url
"""
if not url:
return ''
url = werkzeug.urls.url_parse(url).netloc
if strip_www and url.startswith('www.'):
url = url[4:]
return url