/
release_webhook.py
98 lines (80 loc) · 3.28 KB
/
release_webhook.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
from __future__ import absolute_import, print_function
from hashlib import sha256
import hmac
import logging
from simplejson import JSONDecodeError
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.generic import View
from django.utils.crypto import constant_time_compare
from django.utils.decorators import method_decorator
from sentry.api import client
from sentry.models import ApiKey, Project, ProjectOption
from sentry.plugins import plugins
from sentry.utils import json
class ReleaseWebhookView(View):
def verify(self, plugin_id, project_id, token, signature):
return constant_time_compare(signature, hmac.new(
key=str(token),
msg='{}-{}'.format(plugin_id, project_id),
digestmod=sha256
).hexdigest())
@method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
return super(ReleaseWebhookView, self).dispatch(*args, **kwargs)
def _handle_builtin(self, request, project):
endpoint = '/projects/{}/{}/releases/'.format(
project.organization.slug,
project.slug,
)
try:
data = json.loads(request.body)
except JSONDecodeError as exc:
return HttpResponse(
status=400,
content=json.dumps({'error': unicode(exc)}),
content_type='application/json',
)
try:
# Ideally the API client would support some kind of god-mode here
# as we've already confirmed credentials and simply want to execute
# the view code. Instead we hack around it with an ApiKey instance
god = ApiKey(
organization=project.organization,
scopes=getattr(ApiKey.scopes, 'project:write'),
)
resp = client.post(
endpoint,
data=data,
auth=god,
)
except client.ApiError as exc:
return HttpResponse(
status=exc.status_code,
content=exc.body,
content_type='application/json',
)
return HttpResponse(
status=resp.status_code,
content=json.dumps(resp.data),
content_type='application/json',
)
def post(self, request, plugin_id, project_id, signature):
project = Project.objects.get_from_cache(id=project_id)
token = ProjectOption.objects.get_value(project, 'sentry:release-token')
logging.info('Incoming webhook for project_id=%s, plugin_id=%s',
project_id, plugin_id)
if not self.verify(plugin_id, project_id, token, signature):
logging.warn('Unable to verify signature for release hook')
return HttpResponse(status=403)
if plugin_id == 'builtin':
return self._handle_builtin(request, project)
plugin = plugins.get(plugin_id)
if not plugin.is_enabled(project):
logging.warn('Disabled release hook received for project_id=%s, plugin_id=%s',
project_id, plugin_id)
return HttpResponse(status=403)
cls = plugin.get_release_hook()
hook = cls(project)
hook.handle(request)
return HttpResponse(status=204)