diff --git a/awx/api/urls/webhooks.py b/awx/api/urls/webhooks.py index 33fb697fd4..3523d8f04d 100644 --- a/awx/api/urls/webhooks.py +++ b/awx/api/urls/webhooks.py @@ -1,6 +1,7 @@ from django.conf.urls import url from awx.api.views import ( + WebhookKeyView, GithubWebhookReceiver, GitlabWebhookReceiver, BitbucketWebhookReceiver, @@ -8,6 +9,7 @@ from awx.api.views import ( urlpatterns = [ + url(r'^webhook_key/$', WebhookKeyView.as_view(), name='webhook_key'), url(r'^github/$', GithubWebhookReceiver.as_view(), name='webhook_receiver_github'), url(r'^gitlab/$', GitlabWebhookReceiver.as_view(), name='webhook_receiver_gitlab'), url(r'^bitbucket/$', BitbucketWebhookReceiver.as_view(), name='webhook_receiver_bitbucket'), diff --git a/awx/api/views/__init__.py b/awx/api/views/__init__.py index 9df5567ec3..2cfce6f9b0 100644 --- a/awx/api/views/__init__.py +++ b/awx/api/views/__init__.py @@ -151,6 +151,7 @@ from awx.api.views.root import ( # noqa ApiV2SubscriptionView, ) from awx.api.views.webhooks import ( # noqa + WebhookKeyView, GithubWebhookReceiver, GitlabWebhookReceiver, BitbucketWebhookReceiver, diff --git a/awx/api/views/webhooks.py b/awx/api/views/webhooks.py index a88131e274..66c8e51ced 100644 --- a/awx/api/views/webhooks.py +++ b/awx/api/views/webhooks.py @@ -2,16 +2,55 @@ from hashlib import sha1 import hmac from django.utils.encoding import force_bytes -from rest_framework.exceptions import PermissionDenied +from django.views.decorators.csrf import csrf_exempt -from awx.api.generics import APIView +from rest_framework import status +from rest_framework.exceptions import PermissionDenied +from rest_framework.response import Response + +from awx.api import serializers +from awx.api.generics import APIView, GenericAPIView from awx.main.models import JobTemplate, WorkflowJobTemplate +class WebhookKeyView(GenericAPIView): + serializer_class = serializers.EmptySerializer + + @property + def model(self): + qs_models = { + 'job_templates': JobTemplate, + 'workflow_job_templates': WorkflowJobTemplate, + } + model = qs_models.get(self.kwargs['model_kwarg']) + if model is None: + raise PermissionDenied + + return model + + def get_queryset(self): + return self.request.user.get_queryset(self.model) + + def get(self, request, *args, **kwargs): + obj = self.get_object() + + return Response({'webhook_key': obj.webhook_key}) + + def post(self, request, *args, **kwargs): + obj = self.get_object() + obj.rotate_webhook_key() + + return Response({'webhook_key': obj.webhook_key}, status=status.HTTP_201_CREATED) + + class WebhookReceiverBase(APIView): lookup_url_kwarg = None lookup_field = 'pk' + @csrf_exempt + def dispatch(self, *args, **kwargs): + return super().dispatch(*args, **kwargs) + def get_queryset(self): qs_models = { 'job_templates': JobTemplate, diff --git a/awx/main/models/mixins.py b/awx/main/models/mixins.py index 66195703f1..c0e37faec4 100644 --- a/awx/main/models/mixins.py +++ b/awx/main/models/mixins.py @@ -12,6 +12,7 @@ from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db import models from django.db.models.query import QuerySet +from django.utils.crypto import get_random_string from django.utils.translation import ugettext_lazy as _ # AWX @@ -511,3 +512,7 @@ class WebhookMixin(models.Model): on_delete=models.SET_NULL, related_name='%(class)ss' ) + + def rotate_webhook_key(self): + self.webhook_key = get_random_string(length=50) + self.save(update_fields=['webhook_key'])