|
1 | 1 | """Project models."""
|
2 | 2 | import fnmatch
|
| 3 | +import hashlib |
| 4 | +import hmac |
3 | 5 | import logging
|
4 | 6 | import os
|
5 | 7 | import re
|
|
10 | 12 | from django.conf import settings
|
11 | 13 | from django.conf.urls import include
|
12 | 14 | from django.contrib.auth.models import User
|
| 15 | +from django.contrib.contenttypes.fields import GenericRelation |
13 | 16 | from django.core.validators import MaxValueValidator, MinValueValidator
|
14 | 17 | from django.db import models
|
15 | 18 | from django.db.models import Prefetch
|
16 | 19 | from django.urls import re_path, reverse
|
| 20 | +from django.utils.crypto import get_random_string |
17 | 21 | from django.utils.functional import cached_property
|
18 | 22 | from django.utils.translation import ugettext_lazy as _
|
19 | 23 | from django.views import defaults
|
@@ -1465,14 +1469,103 @@ def __str__(self):
|
1465 | 1469 | return self.email
|
1466 | 1470 |
|
1467 | 1471 |
|
1468 |
| -class WebHook(Notification): |
| 1472 | +class WebHookEvent(models.Model): |
| 1473 | + |
| 1474 | + BUILD_TRIGGERED = 'build:triggered' |
| 1475 | + BUILD_PASSED = 'build:passed' |
| 1476 | + BUILD_FAILED = 'build:failed' |
| 1477 | + |
| 1478 | + EVENTS = ( |
| 1479 | + (BUILD_TRIGGERED, _('Build triggered')), |
| 1480 | + (BUILD_PASSED, _('Build passed')), |
| 1481 | + (BUILD_FAILED, _('Build failed')), |
| 1482 | + ) |
| 1483 | + |
| 1484 | + name = models.CharField( |
| 1485 | + max_length=256, |
| 1486 | + unique=True, |
| 1487 | + choices=EVENTS, |
| 1488 | + ) |
| 1489 | + |
| 1490 | + def __str__(self): |
| 1491 | + return self.name |
| 1492 | + |
| 1493 | + |
| 1494 | +class WebHook(Notification, TimeStampedModel): |
| 1495 | + |
1469 | 1496 | url = models.URLField(
|
| 1497 | + _('URL'), |
1470 | 1498 | max_length=600,
|
1471 | 1499 | help_text=_('URL to send the webhook to'),
|
1472 | 1500 | )
|
| 1501 | + secret = models.CharField( |
| 1502 | + help_text=_('Secret used to sign the payload of the webhook'), |
| 1503 | + max_length=255, |
| 1504 | + blank=True, |
| 1505 | + null=True, |
| 1506 | + ) |
| 1507 | + events = models.ManyToManyField( |
| 1508 | + WebHookEvent, |
| 1509 | + related_name='webhooks', |
| 1510 | + help_text=_('Events to subscribe'), |
| 1511 | + ) |
| 1512 | + payload = models.TextField( |
| 1513 | + _('JSON payload'), |
| 1514 | + help_text=_( |
| 1515 | + 'JSON payload to send to the webhook. ' |
| 1516 | + 'Check the docs for available substitutions.', |
| 1517 | + ), |
| 1518 | + blank=True, |
| 1519 | + null=True, |
| 1520 | + ) |
| 1521 | + exchanges = GenericRelation( |
| 1522 | + 'integrations.HttpExchange', |
| 1523 | + related_query_name='webhook', |
| 1524 | + ) |
| 1525 | + |
| 1526 | + SUBSTITUTIONS = ( |
| 1527 | + ('${event}', _('Event that triggered the request')), |
| 1528 | + ('${build.id}', _('Build ID')), |
| 1529 | + ('${build.commit}', _('Commit being built')), |
| 1530 | + ('${project.slug}', _('Project slug')), |
| 1531 | + ('${project.name}', _('Project name')), |
| 1532 | + ('${version.id}', _('Version ID')), |
| 1533 | + ('${version.slug}', _('Version slug')), |
| 1534 | + ) |
| 1535 | + |
| 1536 | + def save(self, *args, **kwargs): |
| 1537 | + if not self.secret: |
| 1538 | + self.secret = get_random_string(length=32) |
| 1539 | + super().save(*args, **kwargs) |
| 1540 | + |
| 1541 | + def get_payload(self, event, build): |
| 1542 | + project = build.project |
| 1543 | + version = build.version |
| 1544 | + substitutions = { |
| 1545 | + '${event}': event, |
| 1546 | + '${build.id}': build.id, |
| 1547 | + '${build.commit}': build.commit, |
| 1548 | + '${project.slug}': project.slug, |
| 1549 | + '${project.name}': project.name, |
| 1550 | + '${version.slug}': version.slug, |
| 1551 | + } |
| 1552 | + payload = self.payload |
| 1553 | + # Small protection for DDoS. |
| 1554 | + max_substitutions = 9 |
| 1555 | + for substitution, value in substitutions.items(): |
| 1556 | + payload = payload.replace(substitution, value, max_substitutions) |
| 1557 | + return payload |
| 1558 | + |
| 1559 | + def sign_payload(self, payload): |
| 1560 | + digest = hmac.new( |
| 1561 | + self.secret.encode(), |
| 1562 | + msg=payload.encode(), |
| 1563 | + digestmod=hashlib.sha1, |
| 1564 | + ) |
| 1565 | + return digest.hexdigest() |
1473 | 1566 |
|
1474 | 1567 | def __str__(self):
|
1475 |
| - return self.url |
| 1568 | + return f'{self.project.slug} {self.url}' |
1476 | 1569 |
|
1477 | 1570 |
|
1478 | 1571 | class Domain(TimeStampedModel, models.Model):
|
|
0 commit comments