98 lines
4.1 KiB
Python
98 lines
4.1 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
import json
|
|
import hmac
|
|
import logging
|
|
from datetime import datetime
|
|
|
|
from odoo import _, http
|
|
from odoo.exceptions import AccessError, ValidationError
|
|
from odoo.http import request
|
|
from odoo.tools import hmac as hmac_tool
|
|
|
|
from odoo.addons.sale_tiktok import utils
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class TikTokController(http.Controller):
|
|
@http.route("/tiktok/return_from_authorization", type="http", methods=["GET"], auth="user")
|
|
def tiktok_return_from_authorization(self, code=None, state=None, **_kwargs):
|
|
if not request.env.user.has_group("sales_team.group_sale_manager"):
|
|
raise AccessError(_("You are not allowed to access this page."))
|
|
if not state:
|
|
raise ValidationError(_("Missing OAuth state."))
|
|
try:
|
|
payload = json.loads(state)
|
|
except json.JSONDecodeError as exc:
|
|
raise ValidationError(_("Invalid OAuth state.")) from exc
|
|
shop_id = payload.get("shop_id")
|
|
timestamp = payload.get("timestamp")
|
|
signature = payload.get("signature")
|
|
|
|
if not timestamp or not signature:
|
|
raise ValidationError(_("Malformed OAuth state."))
|
|
expected = hmac_tool(request.env, "tiktok-authorization-request", f"{shop_id}|{timestamp}")
|
|
if not hmac.compare_digest(signature, expected):
|
|
raise AccessError(_("Invalid authorization signature"))
|
|
|
|
shop = (
|
|
request
|
|
.env["tiktok.shop"]
|
|
.browse(shop_id)
|
|
.exists()
|
|
.with_context(allowed_company_ids=request.env.user._get_company_ids())
|
|
)
|
|
if not shop:
|
|
raise request.not_found(_("Shop not found."))
|
|
try:
|
|
# Fetch authorization code
|
|
token = utils.request_access_token(shop, authorization_code=code)
|
|
shop.write({
|
|
"access_token": token["access_token"],
|
|
"access_token_expire_datetime": datetime.fromtimestamp(
|
|
token["access_token_expire_in"]
|
|
),
|
|
"refresh_token": token["refresh_token"],
|
|
"refresh_token_expire_datetime": datetime.fromtimestamp(
|
|
token["refresh_token_expire_in"]
|
|
),
|
|
"name": token.get("seller_name"),
|
|
})
|
|
|
|
# Resolve TikTok shop identity
|
|
if not shop.tiktok_shop_ref:
|
|
response = utils.make_tiktok_api_request(shop, "get_authorized_shop")
|
|
authorized = response.get("shops", [])
|
|
existing = request.env["tiktok.shop"].search([("app_key", "=", shop.app_key)])
|
|
|
|
existing_keys = {
|
|
(s.tiktok_shop_ref, s.tiktok_cipher)
|
|
for s in existing
|
|
if s.tiktok_shop_ref and s.tiktok_cipher
|
|
}
|
|
candidates = [
|
|
s for s in authorized if (s.get("id"), s.get("cipher")) not in existing_keys
|
|
]
|
|
|
|
# If multiple candidates remain, we select the first one.
|
|
# Users must revoke unused authorizations in TikTok to disambiguate.
|
|
chosen = candidates[0]
|
|
country = request.env["res.country"].search(
|
|
[("code", "=", chosen["region"])], limit=1
|
|
)
|
|
shop.write({
|
|
"tiktok_shop_ref": chosen["id"],
|
|
"tiktok_cipher": chosen["cipher"],
|
|
"name": chosen.get("name") or shop.name,
|
|
"country_id": country.id if country else False,
|
|
})
|
|
|
|
# Craft the URL of the TikTok Shop form view
|
|
return request.redirect(f"/odoo/action-sale_tiktok.action_tiktok_shop_list/{shop.id}")
|
|
except (utils.TikTokApiError, IndexError) as error:
|
|
error_message = str(error) or _("An unexpected error occurred during authorization.")
|
|
_logger.exception("Failed to authorize TikTok shop %s: %s", shop.id, error_message)
|
|
return request.redirect(
|
|
f"/odoo/action-sale_tiktok.action_tiktok_shop_authorization_error/{shop.id}"
|
|
)
|