1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/l10n_uk_reports/models/hmrc_service.py
2024-12-10 09:04:09 +07:00

221 lines
11 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta
import pytz
import ipaddress
import json
import requests
import socket
import hmac
from hashlib import sha256
from werkzeug import urls
from odoo import api, models, _
from odoo.http import request
import logging
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
SANDBOX_API_URL = 'https://test-api.service.hmrc.gov.uk'
PRODUCTION_API_URL = 'https://api.service.hmrc.gov.uk'
TIMEOUT = 10
class HmrcService(models.AbstractModel):
"""
Service in order to pass through our authentication proxy
"""
_name = 'hmrc.service'
_description = 'HMRC service'
@api.model
def _login(self):
"""
Checks if there is a userlogin (proxy) or a refresh of the tokens needed and ask for new ones
If needed, it returns the url action to log in with HMRC
Raise when something unexpected happens (enterprise contract not valid e.g.)
:return: False when no login through hmrc needed by the user, otherwise the url action
"""
user = self.env.user
login_needed = False
if user.l10n_uk_user_token:
if not user.l10n_uk_hmrc_vat_token or user.l10n_uk_hmrc_vat_token_expiration_time < datetime.now() + timedelta(minutes=1):
try:
url = self._get_proxy_server() + '/onlinesync/l10n_uk/get_tokens'
dbuuid = self.env['ir.config_parameter'].sudo().get_param('database.uuid')
data = json.dumps({'params': {'user_token': self.env.user.l10n_uk_user_token, 'dbuuid': dbuuid}})
resp = requests.request('GET', url, data=data,
headers={'content-type': "application/json"}, timeout=TIMEOUT) #json-rpc
resp.raise_for_status()
response = resp.json()
response = response.get('result', {})
self._write_tokens(response)
except:
# If it is a connection error, don't delete credentials and re-raise
raise
else: #In case no error was thrown, but an error is indicated
if response.get('error'):
self._clean_tokens()
self._cr.commit() # Even with the raise, we want to commit the cleaning of the tokens in the db
raise UserError(_(
'There was a problem refreshing the tokens. Please log in again. %(error)s',
error=response.get('message'),
))
else:
# Let the user know they established the connection and can submit the report.
self.env['bus.bus']._sendone(self.env.user.partner_id, 'simple_notification', {
'type': 'success',
'message': _("You are successfully connected to HMRC. You can now send the VAT report."),
})
else:
# if no user_token, ask for one
url = self._get_proxy_server() + '/onlinesync/l10n_uk/get_user'
dbuuid = self.env['ir.config_parameter'].sudo().get_param('database.uuid')
data = json.dumps({'params': {'dbuuid': dbuuid}})
resp = requests.request('POST', url, data=data, headers={'content-type': 'application/json', 'Accept': 'text/plain'})
resp.raise_for_status()
contents = resp.json()
contents = contents.get('result')
if contents.get('error'):
raise UserError(contents.get('message'))
user.sudo().write({'l10n_uk_user_token': contents.get('user_token')})
login_needed = True
if login_needed:
url = self.env['hmrc.service']._get_oauth_url(user.l10n_uk_user_token)
return {
'type': 'ir.actions.act_url',
'url': url,
'target': 'self',
}
return False
@api.model
def _get_fraud_prevention_info(self, client_data=None):
"""
https://developer.service.hmrc.gov.uk/api-documentation/docs/fraud-prevention
"""
gov_dict = {}
try:
environ = request.httprequest.environ
headers = request.httprequest.headers
remote_address = request.httprequest.remote_addr
remote_needed = not ipaddress.ip_address(remote_address).is_private
hostname = request.httprequest.host.split(":")[0]
server_public_ip = socket.gethostbyname(hostname)
public_ip_needed = not ipaddress.ip_address(server_public_ip).is_private
tz = self.env.context.get('tz')
if tz:
tz_hour = datetime.now(pytz.timezone(tz)).strftime('%z')
utc_offset = 'UTC' + tz_hour[:3] + ':' + tz_hour[-2:]
else:
utc_offset = 'UTC+00:00'
ICP = self.env['ir.config_parameter'].sudo()
enterprise_code = ICP.get_param('database.enterprise_code')
db_secret = ICP.get_param('database.secret')
if enterprise_code:
hashed_license = hmac.new(enterprise_code.encode(),
db_secret.encode(),
sha256).hexdigest()
else:
hashed_license = ''
gov_vendor_version = self.sudo().env.ref('base.module_base').latest_version
gov_dict['Gov-Client-Connection-Method'] = 'WEB_APP_VIA_SERVER'
if remote_needed: #no need when on a private network
gov_dict['Gov-Client-Public-IP'] = urls.url_quote(remote_address)
gov_dict['Gov-Client-Public-Port'] = urls.url_quote(str(environ.get('REMOTE_PORT')))
if 'totp_enabled' in self.env.user._fields and self.env.user.totp_enabled:
# We can not percent encode the separator, so we have to split the string as such to percent encode each key and value
gov_dict['Gov-Client-Multi-Factor'] = "{}={type}&{}={time}&{}={unique}".format(
urls.url_quote('type'),
urls.url_quote('timestamp'),
urls.url_quote('unique-reference'),
type=urls.url_quote('TOTP'),
time=urls.url_quote(datetime.utcnow().isoformat(timespec='milliseconds')+'Z', unsafe=':'), # We need to specify to percent encode ':'
unique=urls.url_quote(self.env.user._l10n_uk_hmrc_unique_reference()))
gov_dict['Gov-Client-Timezone'] = utc_offset
gov_dict['Gov-Client-Browser-JS-User-Agent'] = (environ.get('HTTP_USER_AGENT'))
gov_dict['Gov-Vendor-Version'] = '&'.join([urls.url_quote("Odoo") + "=" + urls.url_quote(gov_vendor_version)]*2) # We can not percent encode the separator and we need to do it for the key and the value. Client and Server sides are the same
gov_dict['Gov-Client-User-IDs'] = urls.url_quote("Odoo") + "=" + urls.url_quote(self.env.user.name) # We can not percent encode the separator, same as Gov-Vendor-Version
gov_dict['Gov-Client-Browser-Do-Not-Track'] = 'true' if headers.get('DNT') == '1' else 'false'
gov_dict['Gov-Client-Screens'] = f"width={client_data['screen_width']}&height={client_data['screen_height']}&scaling-factor={client_data['screen_scaling_factor']}&colour-depth={client_data['screen_color_depth']}" if client_data else ''
gov_dict['Gov-Client-Window-Size'] = f"width={client_data['window_width']}&height={client_data['window_height']}" if client_data else ''
gov_dict['Gov-Client-Public-Ip-Timestamp'] = datetime.utcnow().isoformat(timespec='milliseconds')+'Z'
gov_dict['Gov-Vendor-Product-Name'] = urls.url_quote("Odoo")
gov_dict['Gov-Client-Device-Id'] = client_data['hmrc_gov_client_device_id'] if client_data else ''
gov_dict['Gov-Vendor-Forwarded'] = f"by={server_public_ip}&for={remote_address}"
if public_ip_needed: # No need when on a private network
gov_dict['Gov-Vendor-Public-IP'] = server_public_ip
if hashed_license:
gov_dict['Gov-Vendor-License-IDs'] = urls.url_quote("Odoo") + "=" + urls.url_quote(hashed_license)
except Exception:
_logger.warning("Could not construct fraud prevention headers", exc_info=True)
return gov_dict
@api.model
def _write_tokens(self, tokens):
vals = {}
vals['l10n_uk_hmrc_vat_token_expiration_time'] = tokens.get('expiration_time')
vals['l10n_uk_hmrc_vat_token'] = tokens.get('access_token')
self.env.user.sudo().write(vals)
@api.model
def _clean_tokens(self):
vals = {}
vals['l10n_uk_user_token'] = ''
vals['l10n_uk_hmrc_vat_token_expiration_time'] = False
vals['l10n_uk_hmrc_vat_token'] = ''
self.env.user.sudo().write(vals)
def _get_local_hmrc_oauth_url(self):
""" The user will be redirected to this url after accepting (or not) permission grant.
"""
return self._get_proxy_server() + '/onlinesync/l10n_uk/hmrc'
@api.model
def _get_state(self, userlogin):
action = self.env.ref('account_reports.action_account_report_gt')
# Search your own host
url = request.httprequest.scheme + '://' + request.httprequest.host
return json.dumps({
'url': url,
'user': userlogin,
'action': action.id,
})
@api.model
def _get_oauth_url(self, login):
""" Generates the url to hmrc oauth endpoint.
"""
oauth_url = self._get_endpoint_url('/oauth/authorize')
url_params = {
'response_type': 'code',
'client_id': self._get_hmrc_client_id(),
'scope': 'read:vat write:vat',
'state': self._get_state(login),
'redirect_uri': self._get_local_hmrc_oauth_url(),
}
return oauth_url + '?' + urls.url_encode(url_params)
@api.model
def _get_endpoint_url(self, endpoint):
mode = self.env['ir.config_parameter'].sudo().get_param("l10n_uk_reports.hmrc_mode", 'production')
base_url = PRODUCTION_API_URL if mode == 'production' else SANDBOX_API_URL
return base_url + endpoint
@api.model
def _get_proxy_server(self):
mode = self.env['ir.config_parameter'].sudo().get_param("l10n_uk_reports.hmrc_mode", 'production')
proxy_server = 'https://l10n-uk-hmrc.api.odoo.com' if mode == 'production' else 'https://l10n-uk-hmrc.test.odoo.com'
return proxy_server
@api.model
def _get_hmrc_client_id(self):
mode = self.env['ir.config_parameter'].sudo().get_param("l10n_uk_reports.hmrc_mode", 'production')
hmrc_client_id = 'GqJgi8Hal1hsEwbG6rY6i9Ag1qUa' if mode == 'production' else 'dTdANDSeX4fiw63DicmUaAVQDSMa'
return hmrc_client_id