diff --git a/modules/stock_package_shipping_ups/CHANGELOG b/modules/stock_package_shipping_ups/CHANGELOG index fc882fb965..abc2a6e701 100644 --- a/modules/stock_package_shipping_ups/CHANGELOG +++ b/modules/stock_package_shipping_ups/CHANGELOG @@ -1,3 +1,4 @@ +* Update to OAuth Version 6.8.0 - 2023-05-01 -------------------------- diff --git a/modules/stock_package_shipping_ups/carrier.py b/modules/stock_package_shipping_ups/carrier.py index 713ed9c5bd..0d13c91d56 100644 --- a/modules/stock_package_shipping_ups/carrier.py +++ b/modules/stock_package_shipping_ups/carrier.py @@ -1,5 +1,9 @@ # This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. + +from oauthlib.oauth2 import BackendApplicationClient +from requests_oauthlib import OAuth2Session + from trytond.model import ( MatchMixin, ModelSQL, ModelView, fields, sequence_ordered) from trytond.pool import PoolMeta @@ -9,11 +13,11 @@ from trytond.pyson import Eval class CredentialUPS(sequence_ordered(), ModelSQL, ModelView, MatchMixin): 'UPS Credential' __name__ = 'carrier.credential.ups' + _rec_name = 'account_number' company = fields.Many2One('company.company', 'Company') - user_id = fields.Char('User ID', required=True) - password = fields.Char('Password', required=True) - license = fields.Char('License', required=True) + client_id = fields.Char("Client ID", required=True) + client_secret = fields.Char("Client Secret", required=True) account_number = fields.Char('Account Number', required=True) use_metric = fields.Boolean('Use Metric') server = fields.Selection([ @@ -21,6 +25,16 @@ class CredentialUPS(sequence_ordered(), ModelSQL, ModelView, MatchMixin): ('production', 'Production'), ], 'Server') + @classmethod + def __register__(cls, module): + table_h = cls.__table_handler__(module) + super().__register__(module) + + # Migration from 6.8: switch to OAuth + table_h.drop_column('user_id') + table_h.drop_column('password') + table_h.drop_column('license') + @classmethod def default_use_metric(cls): return True @@ -29,6 +43,24 @@ class CredentialUPS(sequence_ordered(), ModelSQL, ModelView, MatchMixin): def default_server(cls): return 'testing' + def get_token(self): + if self.server == 'production': + url = 'https://onlinetools.ups.com/security/v1/oauth/token' + else: + url = 'https://wwwcie.ups.com/security/v1/oauth/token' + client = BackendApplicationClient(client_id=self.client_id) + oauth = OAuth2Session(client=client) + token = oauth.fetch_token( + token_url=url, client_id=self.client_id, + client_secret=self.client_secret) + return token['access_token'] + + def get_shipment_url(self): + if self.server == 'production': + return 'https://onlinetools.ups.com/api/shipments/v1/ship' + else: + return 'https://wwwcie.ups.com/api/shipments/v1/ship' + class Carrier(metaclass=PoolMeta): __name__ = 'carrier' diff --git a/modules/stock_package_shipping_ups/doc/index.rst b/modules/stock_package_shipping_ups/doc/index.rst index 07eb9ce7ae..41eabdb891 100644 --- a/modules/stock_package_shipping_ups/doc/index.rst +++ b/modules/stock_package_shipping_ups/doc/index.rst @@ -2,9 +2,9 @@ Stock Package Shipping UPS Module ################################# The Stock Package Shipping UPS module allows you to generate the UPS labels per -package using the UPS webservices. +package using the UPS API. -The credential for the webservice is specified by the following fields: +The credential for the API is specified by the following fields: UPS Carrier Credential ********************** @@ -14,15 +14,13 @@ The credentials used to connect to the UPS API will be automatically retrieved based on the company of the Shipment. - *Company*: The company for which those credentials are valid -- *User ID*: The User ID provided by UPS. -- *Password*: The Password used to access the MyUPS Portal. +- *Client ID*: The Client ID of the App. +- *Client Secret*: The Client Secret of the App. - *Account Number*: The account number UPS assigned to you when you requested the API credentials (also known as the Shipper Number). -- *License*: The License number UPS assigned to you when you requested the API - credentials - *Server*: Are those credentials used for accessing the Testing or the Production server of UPS. -- *Use Metric*: Use Metric units when communicating with the UPS webservice. +- *Use Metric*: Use Metric units when communicating with the UPS API. Carrier ******* diff --git a/modules/stock_package_shipping_ups/setup.py b/modules/stock_package_shipping_ups/setup.py index b42fc49a33..6e02672126 100755 --- a/modules/stock_package_shipping_ups/setup.py +++ b/modules/stock_package_shipping_ups/setup.py @@ -41,7 +41,7 @@ else: download_url = 'http://downloads.tryton.org/%s.%s/' % ( major_version, minor_version) -requires = ['requests'] +requires = ['requests', 'requests-oauthlib', 'oauthlib'] for dep in info.get('depends', []): if not re.match(r'(ir|res)(\W|$)', dep): requires.append(get_require_version('trytond_%s' % dep)) diff --git a/modules/stock_package_shipping_ups/stock.py b/modules/stock_package_shipping_ups/stock.py index 2f1b613ff2..3df5d7fcd3 100644 --- a/modules/stock_package_shipping_ups/stock.py +++ b/modules/stock_package_shipping_ups/stock.py @@ -21,10 +21,6 @@ from trytond.wizard import StateAction, StateTransition, Wizard from .exceptions import UPSError -SERVER_URLS = { - 'testing': 'https://wwwcie.ups.com/json/Ship', - 'production': 'https://onlinetools.ups.com/json/Ship', - } TRACKING_URL = 'https://www.ups.com/track' TIMEOUT = config.getfloat( 'stock_package_shipping_ups', 'requests_timeout', default=300) @@ -178,15 +174,20 @@ class CreateShippingUPS(Wizard): carrier = shipment.carrier packages = shipment.root_packages shipment_request = self.get_request(shipment, packages, credential) - api_url = config.get('stock_package_shipping_ups', credential.server, - default=SERVER_URLS[credential.server]) + token = credential.get_token() + api_url = credential.get_shipment_url() + headers = { + 'transactionSrc': "Tryton", + 'Authorization': f"Bearer {token}", + } nb_tries, response = 0, None error_message = '' try: while nb_tries < 5 and response is None: try: req = requests.post( - api_url, json=shipment_request, timeout=TIMEOUT) + api_url, json=shipment_request, headers=headers, + timeout=TIMEOUT) except ssl.SSLError as e: error_message = e.reason nb_tries += 1 @@ -201,15 +202,6 @@ class CreateShippingUPS(Wizard): gettext('stock_package_shipping_ups.msg_ups_webservice_error', message=error_message)) - if 'Fault' in response: - error = response['Fault']['detail']['Errors'] - message = '%s\n\n%s - %s' % (response['Fault']['faultstring'], - error['ErrorDetail']['PrimaryErrorCode']['Code'], - error['ErrorDetail']['PrimaryErrorCode']['Description']) - raise UPSError( - gettext('stock_package_shipping_ups.msg_ups_webservice_error', - message=message)) - shipment_response = response['ShipmentResponse'] response_status = shipment_response['Response']['ResponseStatus'] if response_status['Code'] != '1': @@ -251,17 +243,6 @@ class CreateShippingUPS(Wizard): if credential.match(credential_pattern): return credential - def get_security(self, credential): - return { - 'UsernameToken': { - 'Username': credential.user_id, - 'Password': credential.password, - }, - 'ServiceAccessToken': { - 'AccessLicenseNumber': credential.license, - }, - } - def get_request_container(self, shipment): return { 'RequestOption': 'validate', @@ -396,7 +377,6 @@ class CreateShippingUPS(Wizard): for pkg in packages: pkg['ShipmentServiceOptions'] = options return { - 'UPSSecurity': self.get_security(credential), 'ShipmentRequest': { 'Request': self.get_request_container(shipment), 'Shipment': { diff --git a/modules/stock_package_shipping_ups/tests/scenario_shipping_ups.rst b/modules/stock_package_shipping_ups/tests/scenario_shipping_ups.rst index 814632534f..2b73c0c255 100644 --- a/modules/stock_package_shipping_ups/tests/scenario_shipping_ups.rst +++ b/modules/stock_package_shipping_ups/tests/scenario_shipping_ups.rst @@ -155,9 +155,8 @@ Create a UPS Carrier and the related credential:: >>> credential = UPSCredential() >>> credential.company = company - >>> credential.user_id = os.getenv('UPS_USER_ID') - >>> credential.password = os.getenv('UPS_PASSWORD') - >>> credential.license = os.getenv('UPS_LICENSE') + >>> credential.client_id = os.getenv('UPS_CLIENT_ID') + >>> credential.client_secret = os.getenv('UPS_CLIENT_SECRET') >>> credential.account_number = os.getenv('UPS_ACCOUNT_NUMBER') >>> credential.server = 'testing' >>> credential.save() diff --git a/modules/stock_package_shipping_ups/view/ups_credential_form.xml b/modules/stock_package_shipping_ups/view/ups_credential_form.xml index 7a400076ef..a3fa6fee76 100644 --- a/modules/stock_package_shipping_ups/view/ups_credential_form.xml +++ b/modules/stock_package_shipping_ups/view/ups_credential_form.xml @@ -7,15 +7,13 @@ this repository contains the full copyright notices and license terms. -->