mirror of
https://github.com/bringout/oca-ocb-core.git
synced 2026-04-21 22:52:04 +02:00
17.0 vanilla
This commit is contained in:
parent
2e65bf056a
commit
df627a6bba
328 changed files with 578149 additions and 759311 deletions
|
|
@ -116,6 +116,7 @@ import functools
|
|||
import glob
|
||||
import hashlib
|
||||
import hmac
|
||||
import importlib.metadata
|
||||
import inspect
|
||||
import json
|
||||
import logging
|
||||
|
|
@ -127,7 +128,7 @@ import time
|
|||
import traceback
|
||||
import warnings
|
||||
from abc import ABC, abstractmethod
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from io import BytesIO
|
||||
from os.path import join as opj
|
||||
from pathlib import Path
|
||||
|
|
@ -135,6 +136,19 @@ from urllib.parse import urlparse
|
|||
from zlib import adler32
|
||||
|
||||
import babel.core
|
||||
|
||||
try:
|
||||
import geoip2.database
|
||||
import geoip2.models
|
||||
import geoip2.errors
|
||||
except ImportError:
|
||||
geoip2 = None
|
||||
|
||||
try:
|
||||
import maxminddb
|
||||
except ImportError:
|
||||
maxminddb = None
|
||||
|
||||
import psycopg2
|
||||
import werkzeug.datastructures
|
||||
import werkzeug.exceptions
|
||||
|
|
@ -164,10 +178,8 @@ from .modules.registry import Registry
|
|||
from .service import security, model as service_model
|
||||
from .tools import (config, consteq, date_utils, file_path, parse_version,
|
||||
profiler, submap, unique, ustr,)
|
||||
from .tools.geoipresolver import GeoIPResolver
|
||||
from .tools.facade import Proxy, ProxyAttr, ProxyFunc
|
||||
from .tools.func import filter_kwargs, lazy_property
|
||||
from .tools.mimetypes import guess_mimetype
|
||||
from .tools.misc import pickle
|
||||
from .tools._vendor import sessions
|
||||
from .tools._vendor.useragents import UserAgent
|
||||
|
|
@ -184,6 +196,7 @@ _logger = logging.getLogger(__name__)
|
|||
mimetypes.add_type('application/font-woff', '.woff')
|
||||
mimetypes.add_type('application/vnd.ms-fontobject', '.eot')
|
||||
mimetypes.add_type('application/x-font-ttf', '.ttf')
|
||||
mimetypes.add_type('image/webp', '.webp')
|
||||
# Add potentially wrong (detected on windows) svg mime types
|
||||
mimetypes.add_type('image/svg+xml', '.svg')
|
||||
# this one can be present on windows with the value 'text/plain' which
|
||||
|
|
@ -221,6 +234,15 @@ def get_default_session():
|
|||
'session_token': None,
|
||||
}
|
||||
|
||||
DEFAULT_MAX_CONTENT_LENGTH = 128 * 1024 * 1024 # 128MiB
|
||||
|
||||
# Two empty objects used when the geolocalization failed. They have the
|
||||
# sames attributes as real countries/cities except that accessing them
|
||||
# evaluates to None.
|
||||
if geoip2:
|
||||
GEOIP_EMPTY_COUNTRY = geoip2.models.Country({})
|
||||
GEOIP_EMPTY_CITY = geoip2.models.City({})
|
||||
|
||||
# The request mimetypes that transport JSON in their body.
|
||||
JSON_MIMETYPES = ('application/json', 'application/json-rpc')
|
||||
|
||||
|
|
@ -229,7 +251,7 @@ No CSRF validation token provided for path %r
|
|||
|
||||
Odoo URLs are CSRF-protected by default (when accessed with unsafe
|
||||
HTTP methods). See
|
||||
https://www.odoo.com/documentation/16.0/developer/reference/addons/http.html#csrf
|
||||
https://www.odoo.com/documentation/17.0/developer/reference/addons/http.html#csrf
|
||||
for more details.
|
||||
|
||||
* if this endpoint is accessed through Odoo via py-QWeb form, embed a CSRF
|
||||
|
|
@ -255,7 +277,7 @@ ROUTING_KEYS = {
|
|||
'alias', 'host', 'methods',
|
||||
}
|
||||
|
||||
if parse_version(werkzeug.__version__) >= parse_version('2.0.2'):
|
||||
if parse_version(importlib.metadata.version('werkzeug')) >= parse_version('2.0.2'):
|
||||
# Werkzeug 2.0.2 adds the websocket option. If a websocket request
|
||||
# (ws/wss) is trying to access an HTTP route, a WebsocketMismatch
|
||||
# exception is raised. On the other hand, Werkzeug 0.16 does not
|
||||
|
|
@ -507,7 +529,7 @@ class Stream:
|
|||
elif attachment.db_datas:
|
||||
self.type = 'data'
|
||||
self.data = attachment.raw
|
||||
self.last_modified = attachment['__last_update']
|
||||
self.last_modified = attachment.write_date
|
||||
self.size = len(self.data)
|
||||
|
||||
elif attachment.url:
|
||||
|
|
@ -553,7 +575,7 @@ class Stream:
|
|||
type='data',
|
||||
data=data,
|
||||
etag=request.env['ir.attachment']._compute_checksum(data),
|
||||
last_modified=record['__last_update'] if record._log_access else None,
|
||||
last_modified=record.write_date if record._log_access else None,
|
||||
size=len(data),
|
||||
public=record.env.user._is_public() # good enough
|
||||
)
|
||||
|
|
@ -738,6 +760,9 @@ def route(route=None, **routing):
|
|||
:param bool csrf: Whether CSRF protection should be enabled for the
|
||||
route. Enabled by default for ``'http'``-type requests, disabled
|
||||
by default for ``'json'``-type requests.
|
||||
:param Callable[[Exception], Response] handle_params_access_error:
|
||||
Implement a custom behavior if an error occurred when retrieving the record
|
||||
from the URL parameters (access error or missing error).
|
||||
"""
|
||||
def decorator(endpoint):
|
||||
fname = f"<function {endpoint.__module__}.{endpoint.__name__}>"
|
||||
|
|
@ -851,13 +876,7 @@ def _generate_routing_rules(modules, nodb_only, converters=None):
|
|||
_logger.warning("The endpoint %s is not decorated by @route(), decorating it myself.", f'{cls.__module__}.{cls.__name__}.{method_name}')
|
||||
submethod = route()(submethod)
|
||||
|
||||
# Ensure "type" is defined on each method's own routing,
|
||||
# also ensure overrides don't change the routing type.
|
||||
default_type = submethod.original_routing.get('type', 'http')
|
||||
routing_type = merged_routing.setdefault('type', default_type)
|
||||
if submethod.original_routing.get('type') not in (None, routing_type):
|
||||
_logger.warning("The endpoint %s changes the route type, using the original type: %r.", f'{cls.__module__}.{cls.__name__}.{method_name}', routing_type)
|
||||
submethod.original_routing['type'] = routing_type
|
||||
_check_and_complete_route_definition(cls, submethod, merged_routing)
|
||||
|
||||
merged_routing.update(submethod.original_routing)
|
||||
|
||||
|
|
@ -881,6 +900,24 @@ def _generate_routing_rules(modules, nodb_only, converters=None):
|
|||
yield (url, endpoint)
|
||||
|
||||
|
||||
def _check_and_complete_route_definition(controller_cls, submethod, merged_routing):
|
||||
"""Verify and complete the route definition.
|
||||
|
||||
* Ensure 'type' is defined on each method's own routing.
|
||||
* also ensure overrides don't change the routing type.
|
||||
|
||||
:param submethod: route method
|
||||
:param dict merged_routing: accumulated routing values (defaults + submethod ancestor methods)
|
||||
"""
|
||||
default_type = submethod.original_routing.get('type', 'http')
|
||||
routing_type = merged_routing.setdefault('type', default_type)
|
||||
if submethod.original_routing.get('type') not in (None, routing_type):
|
||||
_logger.warning(
|
||||
"The endpoint %s changes the route type, using the original type: %r.",
|
||||
f'{controller_cls.__module__}.{controller_cls.__name__}.{submethod.__name__}',
|
||||
routing_type)
|
||||
submethod.original_routing['type'] = routing_type
|
||||
|
||||
# =========================================================
|
||||
# Session
|
||||
# =========================================================
|
||||
|
|
@ -1062,10 +1099,121 @@ class Session(collections.abc.MutableMapping):
|
|||
self.context['lang'] = request.default_lang() if request else DEFAULT_LANG
|
||||
self.should_rotate = True
|
||||
|
||||
if request and request.env:
|
||||
request.env['ir.http']._post_logout()
|
||||
|
||||
def touch(self):
|
||||
self.is_dirty = True
|
||||
|
||||
|
||||
|
||||
# =========================================================
|
||||
# GeoIP
|
||||
# =========================================================
|
||||
|
||||
class GeoIP(collections.abc.Mapping):
|
||||
"""
|
||||
Ip Geolocalization utility, determine information such as the
|
||||
country or the timezone of the user based on their IP Address.
|
||||
|
||||
The instances share the same API as `:class:`geoip2.models.City`
|
||||
<https://geoip2.readthedocs.io/en/latest/#geoip2.models.City>`_.
|
||||
|
||||
When the IP couldn't be geolocalized (missing database, bad address)
|
||||
then an empty object is returned. This empty object can be used like
|
||||
a regular one with the exception that all info are set None.
|
||||
|
||||
:param str ip: The IP Address to geo-localize
|
||||
|
||||
.. note:
|
||||
|
||||
The geoip info the the current request are available at
|
||||
:attr:`~odoo.http.request.geoip`.
|
||||
|
||||
.. code-block:
|
||||
|
||||
>>> GeoIP('127.0.0.1').country.iso_code
|
||||
>>> odoo_ip = socket.gethostbyname('odoo.com')
|
||||
>>> GeoIP(odoo_ip).country.iso_code
|
||||
'FR'
|
||||
"""
|
||||
|
||||
def __init__(self, ip):
|
||||
self.ip = ip
|
||||
|
||||
@lazy_property
|
||||
def _city_record(self):
|
||||
try:
|
||||
return root.geoip_city_db.city(self.ip)
|
||||
except (OSError, maxminddb.InvalidDatabaseError):
|
||||
return GEOIP_EMPTY_CITY
|
||||
except geoip2.errors.AddressNotFoundError:
|
||||
return GEOIP_EMPTY_CITY
|
||||
|
||||
@lazy_property
|
||||
def _country_record(self):
|
||||
if '_city_record' in vars(self):
|
||||
# the City class inherits from the Country class and the
|
||||
# city record is in cache already, save a geolocalization
|
||||
return self._city_record
|
||||
try:
|
||||
return root.geoip_country_db.country(self.ip)
|
||||
except (OSError, maxminddb.InvalidDatabaseError):
|
||||
return self._city_record
|
||||
except geoip2.errors.AddressNotFoundError:
|
||||
return GEOIP_EMPTY_COUNTRY
|
||||
|
||||
@property
|
||||
def country_name(self):
|
||||
return self.country.name or self.continent.name
|
||||
|
||||
@property
|
||||
def country_code(self):
|
||||
return self.country.iso_code or self.continent.code
|
||||
|
||||
def __getattr__(self, attr):
|
||||
# Be smart and determine whether the attribute exists on the
|
||||
# country object or on the city object.
|
||||
if hasattr(GEOIP_EMPTY_COUNTRY, attr):
|
||||
return getattr(self._country_record, attr)
|
||||
if hasattr(GEOIP_EMPTY_CITY, attr):
|
||||
return getattr(self._city_record, attr)
|
||||
raise AttributeError(f"{self} has no attribute {attr!r}")
|
||||
|
||||
def __bool__(self):
|
||||
return self.country_name is not None
|
||||
|
||||
# Old dict API, undocumented for now, will be deprecated some day
|
||||
def __getitem__(self, item):
|
||||
if item == 'country_name':
|
||||
return self.country_name
|
||||
|
||||
if item == 'country_code':
|
||||
return self.country_code
|
||||
|
||||
if item == 'city':
|
||||
return self.city.name
|
||||
|
||||
if item == 'latitude':
|
||||
return self.location.latitude
|
||||
|
||||
if item == 'longitude':
|
||||
return self.location.longitude
|
||||
|
||||
if item == 'region':
|
||||
return self.subdivisions[0].iso_code if self.subdivisions else None
|
||||
|
||||
if item == 'time_zone':
|
||||
return self.location.time_zone
|
||||
|
||||
raise KeyError(item)
|
||||
|
||||
def __iter__(self):
|
||||
raise NotImplementedError("The dictionnary GeoIP API is deprecated.")
|
||||
|
||||
def __len__(self):
|
||||
raise NotImplementedError("The dictionnary GeoIP API is deprecated.")
|
||||
|
||||
# =========================================================
|
||||
# Request and Response
|
||||
# =========================================================
|
||||
|
|
@ -1099,6 +1247,7 @@ class HTTPRequest:
|
|||
httprequest = werkzeug.wrappers.Request(environ)
|
||||
httprequest.user_agent_class = UserAgent # use vendored userAgent since it will be removed in 2.1
|
||||
httprequest.parameter_storage_class = werkzeug.datastructures.ImmutableMultiDict
|
||||
httprequest.max_content_length = DEFAULT_MAX_CONTENT_LENGTH
|
||||
httprequest.max_form_memory_size = 10 * 1024 * 1024 # 10 MB
|
||||
self._session_id__ = httprequest.cookies.get('session_id')
|
||||
|
||||
|
|
@ -1119,10 +1268,10 @@ HTTPREQUEST_ATTRIBUTES = [
|
|||
'accept_charsets', 'accept_languages', 'accept_mimetypes', 'access_route', 'args', 'authorization', 'base_url',
|
||||
'charset', 'content_encoding', 'content_length', 'content_md5', 'content_type', 'cookies', 'data', 'date',
|
||||
'encoding_errors', 'files', 'form', 'full_path', 'get_data', 'get_json', 'headers', 'host', 'host_url', 'if_match',
|
||||
'if_modified_since', 'if_none_match', 'if_range', 'if_unmodified_since', 'is_json', 'is_secure', 'json', 'method',
|
||||
'mimetype', 'mimetype_params', 'origin', 'path', 'pragma', 'query_string', 'range', 'referrer', 'remote_addr',
|
||||
'remote_user', 'root_path', 'root_url', 'scheme', 'script_root', 'server', 'session', 'trusted_hosts', 'url',
|
||||
'url_charset', 'url_root', 'user_agent', 'values',
|
||||
'if_modified_since', 'if_none_match', 'if_range', 'if_unmodified_since', 'is_json', 'is_secure', 'json',
|
||||
'max_content_length', 'method', 'mimetype', 'mimetype_params', 'origin', 'path', 'pragma', 'query_string', 'range',
|
||||
'referrer', 'remote_addr', 'remote_user', 'root_path', 'root_url', 'scheme', 'script_root', 'server', 'session',
|
||||
'trusted_hosts', 'url', 'url_charset', 'url_root', 'user_agent', 'values',
|
||||
]
|
||||
for attr in HTTPREQUEST_ATTRIBUTES:
|
||||
setattr(HTTPRequest, attr, property(*make_request_wrap_methods(attr)))
|
||||
|
|
@ -1183,7 +1332,7 @@ class _Response(werkzeug.wrappers.Response):
|
|||
return response
|
||||
|
||||
if isinstance(result, (bytes, str, type(None))):
|
||||
return cls(result)
|
||||
return Response(result)
|
||||
|
||||
raise TypeError(f"{fname} returns an invalid value: {result}")
|
||||
|
||||
|
|
@ -1211,9 +1360,17 @@ class _Response(werkzeug.wrappers.Response):
|
|||
self.response.append(self.render())
|
||||
self.template = None
|
||||
|
||||
def set_cookie(self, key, value='', max_age=None, expires=None, path='/', domain=None, secure=False, httponly=False, samesite=None, cookie_type='required'):
|
||||
def set_cookie(self, key, value='', max_age=None, expires=-1, path='/', domain=None, secure=False, httponly=False, samesite=None, cookie_type='required'):
|
||||
"""
|
||||
The default expires in Werkzeug is None, which means a session cookie.
|
||||
We want to continue to support the session cookie, but not by default.
|
||||
Now the default is arbitrary 1 year.
|
||||
So if you want a cookie of session, you have to explicitly pass expires=None.
|
||||
"""
|
||||
if expires == -1: # not provided value -> default value -> 1 year
|
||||
expires = datetime.now() + timedelta(days=365)
|
||||
|
||||
if request.db and not request.env['ir.http']._is_allowed_cookie(cookie_type):
|
||||
expires = 0
|
||||
max_age = 0
|
||||
super().set_cookie(key, value=value, max_age=max_age, expires=expires, path=path, domain=domain, secure=secure, httponly=httponly, samesite=samesite)
|
||||
|
||||
|
|
@ -1241,13 +1398,11 @@ class Headers(Proxy):
|
|||
remove = ProxyFunc(None)
|
||||
set = ProxyFunc(None)
|
||||
setdefault = ProxyFunc()
|
||||
setlist = ProxyFunc(None)
|
||||
setlistdefault = ProxyFunc()
|
||||
to_wsgi_list = ProxyFunc()
|
||||
update = ProxyFunc(None)
|
||||
values = ProxyFunc()
|
||||
if hasattr(werkzeug.datastructures.Headers, "setlist"):
|
||||
# werkzeug >= 1.0
|
||||
setlist = ProxyFunc(None)
|
||||
setlistdefault = ProxyFunc()
|
||||
update = ProxyFunc(None)
|
||||
|
||||
|
||||
class ResponseCacheControl(Proxy):
|
||||
|
|
@ -1302,6 +1457,7 @@ class Response(Proxy):
|
|||
freeze = ProxyFunc(None)
|
||||
get_data = ProxyFunc()
|
||||
get_etag = ProxyFunc()
|
||||
get_json = ProxyFunc()
|
||||
headers = ProxyAttr(Headers)
|
||||
is_json = ProxyAttr(bool)
|
||||
is_sequence = ProxyAttr(bool)
|
||||
|
|
@ -1322,9 +1478,6 @@ class Response(Proxy):
|
|||
status = ProxyAttr(str)
|
||||
status_code = ProxyAttr(int)
|
||||
stream = ProxyAttr(ResponseStream)
|
||||
if hasattr(_Response, "get_json"):
|
||||
# werkzeug >= 2.3.0
|
||||
get_json = ProxyFunc()
|
||||
|
||||
# odoo.http._response attributes
|
||||
load = ProxyFunc()
|
||||
|
|
@ -1357,10 +1510,7 @@ __wz_get_response = HTTPException.get_response
|
|||
|
||||
|
||||
def get_response(self, environ=None, scope=None):
|
||||
if scope is None: # compatible with werkzeug 0.16.x
|
||||
return Response(__wz_get_response(self, environ))
|
||||
else:
|
||||
return Response(__wz_get_response(self, environ, scope)) # werkzeug 2.0.2
|
||||
return Response(__wz_get_response(self, environ, scope))
|
||||
|
||||
|
||||
HTTPException.get_response = get_response
|
||||
|
|
@ -1395,9 +1545,11 @@ class FutureResponse:
|
|||
return self.charset
|
||||
|
||||
@functools.wraps(werkzeug.Response.set_cookie)
|
||||
def set_cookie(self, key, value='', max_age=None, expires=None, path='/', domain=None, secure=False, httponly=False, samesite=None, cookie_type='required'):
|
||||
def set_cookie(self, key, value='', max_age=None, expires=-1, path='/', domain=None, secure=False, httponly=False, samesite=None, cookie_type='required'):
|
||||
if expires == -1: # not forced value -> default value -> 1 year
|
||||
expires = datetime.now() + timedelta(days=365)
|
||||
|
||||
if request.db and not request.env['ir.http']._is_allowed_cookie(cookie_type):
|
||||
expires = 0
|
||||
max_age = 0
|
||||
werkzeug.Response.set_cookie(self, key, value=value, max_age=max_age, expires=expires, path=path, domain=domain, secure=secure, httponly=httponly, samesite=samesite)
|
||||
|
||||
|
|
@ -1414,6 +1566,7 @@ class Request:
|
|||
self.dispatcher = _dispatchers['http'](self) # until we match
|
||||
#self.params = {} # set by the Dispatcher
|
||||
|
||||
self.geoip = GeoIP(httprequest.remote_addr)
|
||||
self.registry = None
|
||||
self.env = None
|
||||
|
||||
|
|
@ -1503,27 +1656,6 @@ class Request:
|
|||
|
||||
_cr = cr
|
||||
|
||||
@property
|
||||
def geoip(self):
|
||||
"""
|
||||
Get the remote address geolocalisation.
|
||||
|
||||
When geolocalization is successful, the return value is a
|
||||
dictionary whose format is:
|
||||
|
||||
{'city': str, 'country_code': str, 'country_name': str,
|
||||
'latitude': float, 'longitude': float, 'region': str,
|
||||
'time_zone': str}
|
||||
|
||||
When geolocalization fails, an empty dict is returned.
|
||||
"""
|
||||
if '_geoip' not in self.session:
|
||||
was_dirty = self.session.is_dirty
|
||||
self.session._geoip = (self.registry['ir.http']._geoip_resolve()
|
||||
if self.db else self._geoip_resolve())
|
||||
self.session.is_dirty = was_dirty
|
||||
return self.session._geoip
|
||||
|
||||
@lazy_property
|
||||
def best_lang(self):
|
||||
lang = self.httprequest.accept_languages.best
|
||||
|
|
@ -1604,11 +1736,6 @@ class Request:
|
|||
"""
|
||||
return self.best_lang or DEFAULT_LANG
|
||||
|
||||
def _geoip_resolve(self):
|
||||
if not (root.geoip_resolver and self.httprequest.remote_addr):
|
||||
return {}
|
||||
return root.geoip_resolver.resolve(self.httprequest.remote_addr) or {}
|
||||
|
||||
def get_http_params(self):
|
||||
"""
|
||||
Extract key=value pairs from the query string and the forms
|
||||
|
|
@ -1755,10 +1882,8 @@ class Request:
|
|||
return
|
||||
|
||||
if sess.should_rotate:
|
||||
sess['_geoip'] = self.geoip
|
||||
root.session_store.rotate(sess, self.env) # it saves
|
||||
elif sess.is_dirty:
|
||||
sess['_geoip'] = self.geoip
|
||||
root.session_store.save(sess)
|
||||
|
||||
cookie_sid = self.httprequest.cookies.get('session_id')
|
||||
|
|
@ -1927,6 +2052,9 @@ class Dispatcher(ABC):
|
|||
'Origin, X-Requested-With, Content-Type, Accept, Authorization')
|
||||
werkzeug.exceptions.abort(Response(status=204))
|
||||
|
||||
if 'max_content_length' in routing:
|
||||
self.request.httprequest.max_content_length = routing['max_content_length']
|
||||
|
||||
@abstractmethod
|
||||
def dispatch(self, endpoint, args):
|
||||
"""
|
||||
|
|
@ -2048,13 +2176,13 @@ class JsonRPCDispatcher(Dispatcher):
|
|||
|
||||
Successful request::
|
||||
|
||||
--> {"jsonrpc": "2.0", "method": "call", "params": {"context": {}, "arg1": "val1" }, "id": null}
|
||||
--> {"jsonrpc": "2.0", "method": "call", "params": {"arg1": "val1" }, "id": null}
|
||||
|
||||
<-- {"jsonrpc": "2.0", "result": { "res1": "val1" }, "id": null}
|
||||
|
||||
Request producing a error::
|
||||
|
||||
--> {"jsonrpc": "2.0", "method": "call", "params": {"context": {}, "arg1": "val1" }, "id": null}
|
||||
--> {"jsonrpc": "2.0", "method": "call", "params": {"arg1": "val1" }, "id": null}
|
||||
|
||||
<-- {"jsonrpc": "2.0", "error": {"code": 1, "message": "End user error message.", "data": {"code": "codestring", "debug": "traceback" } }, "id": null}
|
||||
|
||||
|
|
@ -2070,9 +2198,6 @@ class JsonRPCDispatcher(Dispatcher):
|
|||
werkzeug.exceptions.abort(Response("Invalid JSON-RPC data", status=400))
|
||||
|
||||
self.request.params = dict(self.jsonrequest.get('params', {}), **args)
|
||||
ctx = self.request.params.pop('context', None)
|
||||
if ctx is not None and self.request.db:
|
||||
self.request.update_context(**ctx)
|
||||
|
||||
if self.request.db:
|
||||
result = self.request.registry['ir.http']._dispatch(endpoint)
|
||||
|
|
@ -2191,20 +2316,34 @@ class Application:
|
|||
_logger.debug('HTTP sessions stored in: %s', path)
|
||||
return FilesystemSessionStore(path, session_class=Session, renew_missing=True)
|
||||
|
||||
@lazy_property
|
||||
def geoip_resolver(self):
|
||||
try:
|
||||
return GeoIPResolver.open(config.get('geoip_database'))
|
||||
except Exception as e:
|
||||
_logger.warning('Cannot load GeoIP: %s', e)
|
||||
|
||||
def get_db_router(self, db):
|
||||
if not db:
|
||||
return self.nodb_routing_map
|
||||
return request.registry['ir.http'].routing_map()
|
||||
return request.env['ir.http'].routing_map()
|
||||
|
||||
@lazy_property
|
||||
def geoip_city_db(self):
|
||||
try:
|
||||
return geoip2.database.Reader(config['geoip_city_db'])
|
||||
except (OSError, maxminddb.InvalidDatabaseError):
|
||||
_logger.debug(
|
||||
"Couldn't load Geoip City file at %s. IP Resolver disabled.",
|
||||
config['geoip_city_db'], exc_info=True
|
||||
)
|
||||
raise
|
||||
|
||||
@lazy_property
|
||||
def geoip_country_db(self):
|
||||
try:
|
||||
return geoip2.database.Reader(config['geoip_country_db'])
|
||||
except (OSError, maxminddb.InvalidDatabaseError) as exc:
|
||||
_logger.debug("Couldn't load Geoip Country file (%s). Fallbacks on Geoip City.", exc,)
|
||||
raise
|
||||
|
||||
def set_csp(self, response):
|
||||
headers = response.headers
|
||||
headers['X-Content-Type-Options'] = 'nosniff'
|
||||
|
||||
if 'Content-Security-Policy' in headers:
|
||||
return
|
||||
|
||||
|
|
@ -2212,7 +2351,6 @@ class Application:
|
|||
return
|
||||
|
||||
headers['Content-Security-Policy'] = "default-src 'none'"
|
||||
headers['X-Content-Type-Options'] = 'nosniff'
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue