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. -->
+
+
-
-
-
-
-
-
-
-
+
+
+
+
diff --git a/modules/stock_package_shipping_ups/view/ups_credential_list.xml b/modules/stock_package_shipping_ups/view/ups_credential_list.xml
index 3ea329081b..ff092e289b 100644
--- a/modules/stock_package_shipping_ups/view/ups_credential_list.xml
+++ b/modules/stock_package_shipping_ups/view/ups_credential_list.xml
@@ -3,6 +3,6 @@
this repository contains the full copyright notices and license terms. -->
-
+