2023-01-06 16:08:43 +01:00
|
|
|
"""Transifex webhook handler """
|
2022-12-28 09:04:44 +01:00
|
|
|
import base64
|
2021-03-09 21:54:06 +01:00
|
|
|
import hashlib
|
2023-01-09 17:26:28 +01:00
|
|
|
import hmac
|
|
|
|
import json
|
|
|
|
import os
|
2023-01-06 16:08:43 +01:00
|
|
|
import requests
|
2023-01-09 17:26:28 +01:00
|
|
|
import re
|
|
|
|
import time
|
2023-01-04 16:21:06 +01:00
|
|
|
|
2021-04-16 03:55:32 +02:00
|
|
|
from buildbot.process.properties import Properties
|
|
|
|
from buildbot.util import bytes2unicode, unicode2bytes
|
2018-09-04 12:40:36 +02:00
|
|
|
from buildbot.www.hooks.base import BaseHookHandler
|
|
|
|
|
2021-04-16 03:55:32 +02:00
|
|
|
from twisted.internet import defer
|
2018-09-04 12:40:36 +02:00
|
|
|
from twisted.python import log
|
2021-04-16 03:55:32 +02:00
|
|
|
|
2018-09-04 12:40:36 +02:00
|
|
|
|
2022-12-28 09:04:44 +01:00
|
|
|
_HEADER_USER_AGENT = 'User-Agent'
|
|
|
|
_HEADER_SIGNATURE = 'X-TX-Signature'
|
2023-01-06 16:08:43 +01:00
|
|
|
_HEADER_URL_PATH = 'X-TX-Url'
|
2023-01-09 17:26:28 +01:00
|
|
|
_HTTP_DATE = 'date'
|
2022-12-28 09:04:44 +01:00
|
|
|
_EVENT_KEY = 'event'
|
2023-01-09 17:26:28 +01:00
|
|
|
author = 'buildbot-transifex'
|
2023-01-04 16:21:06 +01:00
|
|
|
|
2018-09-04 12:40:36 +02:00
|
|
|
|
2022-12-28 09:04:44 +01:00
|
|
|
class TransifexHandler(BaseHookHandler):
|
2018-09-04 12:40:36 +02:00
|
|
|
|
2023-01-09 17:26:28 +01:00
|
|
|
def __init__(self, master, secret, transifex_to_github_map, options=None):
|
2023-01-06 16:08:43 +01:00
|
|
|
if not options:
|
|
|
|
options = {}
|
2023-01-04 16:21:06 +01:00
|
|
|
self.secret = secret
|
|
|
|
self.master = master
|
2023-01-06 16:08:43 +01:00
|
|
|
self.options = options
|
2023-01-09 17:26:28 +01:00
|
|
|
self.transifex_to_github_map = transifex_to_github_map
|
2023-01-04 16:21:06 +01:00
|
|
|
|
|
|
|
|
|
|
|
def returnMessage(self, status = False, message = "Unimplemented"):
|
|
|
|
output = json.dumps({"status": "OK" if status else "FAIL", "message": message})
|
2023-01-06 16:08:43 +01:00
|
|
|
return [output, [('Content-type', 'application/json')]]
|
2023-01-04 16:21:06 +01:00
|
|
|
|
2023-01-09 17:26:28 +01:00
|
|
|
def _verifyTransifexSignature(
|
2023-01-06 16:08:43 +01:00
|
|
|
self, request, content, signature, header_signature
|
2023-01-04 16:21:06 +01:00
|
|
|
):
|
|
|
|
http_verb = 'POST'
|
2023-01-06 16:08:43 +01:00
|
|
|
http_url_path = request.getHeader(_HEADER_URL_PATH)
|
2023-01-09 17:26:28 +01:00
|
|
|
http_gmt_date = request.getHeader(_HTTP_DATE)
|
2023-01-04 16:21:06 +01:00
|
|
|
content_md5 = hashlib.md5(content).hexdigest()
|
|
|
|
msg = b'\n'.join([
|
|
|
|
http_verb, http_url_path, http_gmt_date, content_md5
|
|
|
|
])
|
|
|
|
tx_signature = base64.b64encode(
|
|
|
|
hmac.new(
|
2023-01-06 16:08:43 +01:00
|
|
|
key=self.rendered_secret,
|
2023-01-04 16:21:06 +01:00
|
|
|
msg=msg,
|
|
|
|
digestmod=hashlib.sha256
|
|
|
|
).digest()
|
|
|
|
)
|
2023-01-06 16:08:43 +01:00
|
|
|
if tx_signature != header_signature:
|
2023-01-09 17:26:28 +01:00
|
|
|
raise ValueError("Tx Signature mismatch")
|
|
|
|
|
2023-01-06 16:08:43 +01:00
|
|
|
if signature != request.getHeader(_HEADER_SIGNATURE):
|
2023-01-09 17:26:28 +01:00
|
|
|
raise ValueError("Signature mismatch")
|
|
|
|
return True
|
2023-01-04 16:21:06 +01:00
|
|
|
|
2023-01-09 17:26:28 +01:00
|
|
|
def process_translation_completed(self, payload, codebase):
|
2018-09-04 12:40:36 +02:00
|
|
|
changes = []
|
2023-01-09 17:26:28 +01:00
|
|
|
translated_request = self._transform_variables(payload['project'], payload['resource'])
|
2023-01-13 08:00:22 +01:00
|
|
|
ts = int(time.time())
|
2023-01-06 16:08:43 +01:00
|
|
|
change = {
|
2023-01-13 08:00:22 +01:00
|
|
|
'author': author,
|
2023-01-09 17:26:28 +01:00
|
|
|
'branch': translated_request["branch"],
|
2023-01-13 08:00:22 +01:00
|
|
|
'branch': translated_request["repository"],
|
2023-01-09 17:26:28 +01:00
|
|
|
'project': translated_request["project"],
|
2023-01-06 16:08:43 +01:00
|
|
|
'properties': {
|
2023-01-09 17:26:28 +01:00
|
|
|
"transifex_language": payload.get("language", "None"),
|
|
|
|
"transifex_event": payload.get("event", "None"),
|
|
|
|
"transifex_project": payload.get("project", "None"),
|
|
|
|
"transifex_resource": payload.get("resource", "None"),
|
2023-01-13 08:00:22 +01:00
|
|
|
"transifex_branch": "translate_" + payload['language'] + "_" + str(ts)
|
2023-01-06 16:08:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if codebase is not None:
|
|
|
|
change['codebase'] = codebase
|
|
|
|
changes.insert(0, change)
|
2018-09-04 12:40:36 +02:00
|
|
|
return changes
|
|
|
|
|
2023-01-13 08:00:22 +01:00
|
|
|
def _transform_variables(self, transifex_project):
|
2023-01-09 17:26:28 +01:00
|
|
|
if transifex_project is None:
|
|
|
|
raise ValueError("Unknown project %s from transifex".format(transifex_project))
|
2023-01-13 08:00:22 +01:00
|
|
|
key = transifex_project
|
2023-01-09 17:26:28 +01:00
|
|
|
_map = self.map[key]
|
|
|
|
repository = _map["repository"]
|
|
|
|
project = re.sub(r'^.*/(.*?)(\.git)?$', r'\1', repository)
|
|
|
|
return{
|
|
|
|
'project': project,
|
|
|
|
'repository': repository,
|
|
|
|
'branch': _map["branch"],
|
2022-12-28 09:04:44 +01:00
|
|
|
}
|
|
|
|
|
2021-04-16 03:55:32 +02:00
|
|
|
@defer.inlineCallbacks
|
2018-09-04 12:40:36 +02:00
|
|
|
def getChanges(self, request):
|
2023-01-09 17:26:28 +01:00
|
|
|
change = {}
|
2023-01-04 16:21:06 +01:00
|
|
|
self.secret = None
|
2018-09-04 15:52:49 +02:00
|
|
|
if isinstance(self.options, dict):
|
2023-01-04 16:21:06 +01:00
|
|
|
self.secret = self.options.get('secret')
|
2018-09-04 12:40:36 +02:00
|
|
|
try:
|
|
|
|
content = request.content.read()
|
2021-03-09 21:54:06 +01:00
|
|
|
content_text = bytes2unicode(content)
|
|
|
|
payload = json.loads(content_text)
|
2018-09-27 13:21:10 +02:00
|
|
|
except Exception as exception:
|
|
|
|
raise ValueError('Error loading JSON: ' + str(exception))
|
2021-03-09 21:54:06 +01:00
|
|
|
|
2023-01-04 16:21:06 +01:00
|
|
|
if self.secret is not None:
|
2021-04-16 03:55:32 +02:00
|
|
|
p = Properties()
|
|
|
|
p.master = self.master
|
2023-01-06 16:08:43 +01:00
|
|
|
option = self.options
|
2023-01-09 17:26:28 +01:00
|
|
|
rendered_secret = yield p.render(self.secret)
|
2021-03-09 21:54:06 +01:00
|
|
|
signature = hmac.new(
|
2023-01-09 17:26:28 +01:00
|
|
|
unicode2bytes(rendered_secret),
|
2021-04-16 03:55:32 +02:00
|
|
|
unicode2bytes(content_text.strip()),
|
2021-03-09 21:54:06 +01:00
|
|
|
digestmod=hashlib.sha256)
|
|
|
|
header_signature = bytes2unicode(
|
|
|
|
request.getHeader(_HEADER_SIGNATURE))
|
2023-01-09 17:26:28 +01:00
|
|
|
self._verifyTransifexSignature(request, content, rendered_secret, signature, header_signature)
|
2023-01-06 16:08:43 +01:00
|
|
|
event_type = payload.get("event", "None")
|
2022-12-28 09:04:44 +01:00
|
|
|
log.msg("Received event '{}' from transifex".format(event_type))
|
2018-09-04 12:40:36 +02:00
|
|
|
|
2022-12-28 09:04:44 +01:00
|
|
|
codebase = ""
|
2018-09-04 15:04:29 +02:00
|
|
|
changes = []
|
2019-10-09 18:33:06 +02:00
|
|
|
|
|
|
|
handler_function = getattr(self, 'process_{}'.format(event_type), None)
|
|
|
|
if not handler_function:
|
2022-12-28 09:04:44 +01:00
|
|
|
log.msg("Ignoring transifex event '{}'".format(event_type))
|
2019-10-09 18:33:06 +02:00
|
|
|
else:
|
|
|
|
changes = handler_function(payload, event_type, codebase)
|
2018-09-04 15:04:29 +02:00
|
|
|
|
2022-12-28 09:04:44 +01:00
|
|
|
return (changes, 'transifex')
|
2019-10-09 18:33:06 +02:00
|
|
|
|
2023-01-09 17:26:28 +01:00
|
|
|
transifex = TransifexHandler
|