buildbot-transifex/buildbot_transifex/webhook.py

141 lines
5.0 KiB
Python
Raw Permalink Normal View History

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