January 4th, 2023

Bitbucket Webhooks in Pimcore

Official Webhook Documentation:
https://support.atlassian.com/bitbucket-cloud/docs/manage-webhooks/

Event Types:
https://support.atlassian.com/bitbucket-cloud/docs/event-payloads/

Building the Webhook Controller

These concepts are presented in a format native to Pimcore, however the concept is a generalization and can easily be translated into other PHP frameworks with minimal effort.

    namespace App\Controller;

    use Symfony\Component\HttpFoundation\Request;
    use Symfony\Component\Routing\Annotation\Route;
    use Pimcore\Controller\FrontendController;

    class WebhookController extends FrontendController {

        /**
         * @Route("/webhook", name="Webhook")
         */
        public function WebhookAction( Request $request ) {

            // Check for a recognized network...
            if( !self::GetNetworkValid( $request ) ) {
                return $this->json( [
                    'error' => 'Request made from an unrecognized network.'
                ] );
            }

            // Process the webhook event...
            if( self::WebhookPush( $request ) ) {
                return $this->json( [
                    'status' => 'ok'
                ] );
            }

            return $this->json( [
                'error' => 'Could not process event.'
            ] );

        }

    }

In this sample block, we define the namespace, library usage, and structure of the Controller class. We also define a public method and bind the /webhook URI to it using Annotations. There are two calls to static methods in the same class (self::), GetNetworkValid and WebhookPush. Ultimately, this function returns one of three possible states, an failure state, a success state, and an undefined state.

GetNetworkValid

    use Symfony\Component\HttpFoundation\IpUtils;

    ...

    private static function GetNetworkValid( $request ) {

        // Retrieve the remote IP making the request...
        $client_ip = $request->getClientIp();

        // Retrieve networks from the network discovery URL...
        $json = json_decode( file_get_contents( 'https://ip-ranges.atlassian.com/' ), true );
        if( !$json || empty( $json[ 'items' ] ) ) return false;

        // Compile a list cidr ranges...
        $cidrs = [];
        foreach( $json[ 'items' ] as $item ) {
            $cidrs[] = $item[ 'cidr' ];
        }

        return IpUtils::checkIp( $client_ip, $cidrs );

    }

    ...

The GetNetworkValid method takes a Symfony\Component\HttpFoundation\Request object and extracts the IP from the remote resource, comparing it to a live-list of IP ranges supplied by Atlassian, using the IpUtils library. This is a Quality of Life feature that prevents the need for manually managing allowed IP ranges and CIDRs.

WebhookPush

    use Pimcore\Model\DataObject\WebhookPush;

    ...

    private static function WebhookPush( $request ) {

        // Decode the JSON payload...
        if( $json = json_decode( $request->getContent(), true ) ) {

            // Create a new ORM object...
            $item = new WebhookPush();

            // Set headers...
            $item->setXEventKey( $request->headers->get( 'X-Event-Key' ) );
            $item->setXHookUUID( $request->headers->get( 'X-Hook-UUID' ) );
            $item->setXRequestUUID( $request->headers->get( 'X-Request-UUID' ) );
            $item->setXAttemptNumber( $request->headers->get( 'X-Attempt-Number' ) );

            // Set fields...
            $item->setActor( json_encode( $json[ 'actor' ] ) );
            $item->setRepository( json_encode( $json[ 'repository' ] ) );
            $item->setPush( json_encode( $json[ 'push' ] ) );

            // Store it!
            $item->setParentId( 1 );
            $item->save();

            return true;

        }

        return false;

    }

    ...

The WebhookPush static method takes the request and forms a Pimcore DataObject from the request. This can be dumped to a log file instead if logging is all that is needed.

Topics

## PAGE REGION UNDER MAINTENANCE ##

Related Articles

## PAGE REGION UNDER MAINTENANCE ##