• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

klinge / sl-webapp / 18124637236

30 Sep 2025 09:04AM UTC coverage: 55.3% (+4.1%) from 51.199%
18124637236

push

github

web-flow
Merge pull request #101 from klinge/dependabot/composer/league/container-tw-5.1

Update league/container requirement from ^4.2 to ^5.1

1132 of 2047 relevant lines covered (55.3%)

2.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.26
/App/Controllers/WebhookController.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace App\Controllers;
6

7
use App\Application;
8
use App\Services\Github\GitHubService;
9
use App\Services\Github\GitRepositoryService;
10
use App\Services\Github\DeploymentService;
11
use Psr\Http\Message\ServerRequestInterface;
12
use Psr\Http\Message\ResponseInterface;
13
use Monolog\Logger;
14

15
class WebhookController extends BaseController
16
{
17
    private string $remoteIp;
18

19
    public function __construct(
20
        Application $app,
21
        ServerRequestInterface $request,
22
        Logger $logger,
23
        private GitHubService $githubService,
24
        private GitRepositoryService $gitRepositoryService,
25
        private DeploymentService $deploymentService
26
    ) {
27
        parent::__construct($app, $request, $logger);
12✔
28
        $this->remoteIp = $this->request->getServerParams()['REMOTE_ADDR'];
12✔
29
    }
30

31
    public function handle(): ResponseInterface
32
    {
33
        $this->logger->info(
4✔
34
            'Starting to process webhook call from: ' . $this->remoteIp,
4✔
35
            ['class' => __CLASS__, 'function' => __FUNCTION__]
4✔
36
        );
4✔
37
        $this->logger->debug(
4✔
38
            'Headers: ' . json_encode($this->request->getHeaders()),
4✔
39
            ['class' => __CLASS__, 'function' => __FUNCTION__]
4✔
40
        );
4✔
41
        $this->logger->debug(
4✔
42
            'Payload: ' . json_encode($this->request->getParsedBody()),
4✔
43
            ['class' => __CLASS__, 'function' => __FUNCTION__]
4✔
44
        );
4✔
45

46
        $verificationResult = $this->verifyRequest();
4✔
47

48
        // Handle verification failures with appropriate responses
49
        if (!$verificationResult) {
4✔
50
            if (!$this->request->hasHeader('X-GitHub-Event')) {
×
51
                return $this->jsonResponse(['status' => 'error', 'message' => 'Missing event header'], 400);
×
52
            }
53

54
            $event = $this->request->getHeader('X-GitHub-Event')[0] ?? '';
×
55

56
            if ($event !== 'push' && $event !== 'ping') {
×
57
                return $this->jsonResponse(['status' => 'error', 'message' => 'Event not supported'], 400);
×
58
            }
59

60
            if (!$this->request->hasHeader('X-Hub-Signature-256')) {
×
61
                return $this->jsonResponse(['status' => 'error', 'message' => 'Missing signature header'], 400);
×
62
            }
63

64
            $signature = $this->request->getHeader('X-Hub-Signature-256')[0] ?? '';
×
65
            $signature_parts = explode('=', $signature);
×
66

67
            if (count($signature_parts) != 2 || $signature_parts[0] != 'sha256') {
×
68
                return $this->jsonResponse(['status' => 'error', 'message' => 'Bad header format'], 400);
×
69
            }
70

71
            $rawBody = (string) $this->request->getBody();
×
72

73
            if (!$this->githubService->validateSignature($rawBody, $signature_parts[1])) {
×
74
                return $this->jsonResponse(['status' => 'error', 'message' => 'Invalid signature'], 401);
×
75
            }
76

77
            $payload = json_decode($rawBody, true);
×
78

79
            if (!$this->githubService->isValidRepository($payload)) {
×
80
                return $this->jsonResponse(
×
81
                    ['status' => 'ignored', 'message' => "Not handling requests for this repo, {$payload['repository']['full_name']}"],
×
82
                    200
×
83
                );
×
84
            }
85

86
            if ($event === 'ping') {
×
87
                return $this->jsonResponse(['status' => 'ok', 'message' => 'Pong'], 200);
×
88
            }
89

90
            return $this->jsonResponse(['status' => 'ok', 'message' => 'Request processed']);
×
91
        }
92

93
        $payload = $verificationResult;
4✔
94
        $branch = $this->githubService->extractBranchName($payload['ref']);
4✔
95

96
        // Check if it's a release branch
97
        if (!$this->githubService->isReleaseBranch($branch)) {
4✔
98
            $this->logger->debug('Not a push to the release branch', ['class' => __CLASS__, 'function' => __FUNCTION__]);
1✔
99
            return $this->jsonResponse(['status' => 'ignored', 'message' => 'Not a push to the release branch']);
1✔
100
        }
101

102
        $this->logger->info('Received a push on the release branch. Continuing to deploy.. ');
3✔
103

104
        $repoUrl = $payload['repository']['clone_url'];
3✔
105
        $result = $this->gitRepositoryService->updateRepository($branch, $repoUrl);
3✔
106
        if ($result['status'] !== 'success') {
3✔
107
            $this->logger->error(
1✔
108
                "Error occurred while handling repository operations. Message: {$result['message']}",
1✔
109
                ['class' => __CLASS__, 'function' => __FUNCTION__]
1✔
110
            );
1✔
111
            return $this->jsonResponse(['status' => 'error', 'message' => $result['message']], 500);
1✔
112
        }
113

114
        //Finally schedule the deploy of the repository to the web server
115
        $deploymentScheduled = $this->deploymentService->scheduleDeployment();
2✔
116

117
        if (!$deploymentScheduled) {
2✔
118
            return $this->jsonResponse(['status' => 'error', 'message' => 'Failed to schedule deployment'], 500);
1✔
119
        }
120

121
        return $this->jsonResponse(['status' => 'success', 'message' => 'Successfully received push and scheduled deployment']);
1✔
122
    }
123

124
    /**
125
     * Verifies and validates the incoming GitHub webhook request.
126
     *
127
     * This method performs several checks on the incoming request to ensure its authenticity and integrity.
128
     *
129
     * If it's a 'ping' event, it responds with a 'pong'.
130
     * For 'push' events, it processes the payload for further action.
131
     *
132
     * @return array The validated payload for 'push' events, or an empty array for other cases
133
     */
134
    public function verifyRequest(): array
135
    {
136
        $payload = [];
8✔
137
        $event = '';
8✔
138

139
        // Verify that it's a github webhook request
140
        if (!$this->request->hasHeader('X-GitHub-Event')) {
8✔
141
            $this->logger->warning("Missing X-GitHub-Event header. Headers was: " . json_encode($this->request->getHeaders()));
1✔
142
            return $payload;
1✔
143
        } else {
144
            $event = $this->request->getHeader('X-GitHub-Event')[0];
7✔
145
        }
146
        // Verify that it's ping or a push
147
        if (!empty($event) && $event !== 'push' && $event !== 'ping') {
7✔
148
            $this->logger->warning("Github event was not push or ping");
1✔
149
            return $payload;
1✔
150
        }
151
        // Verify that the signature header is there
152
        if (!$this->request->hasHeader('X-Hub-Signature-256')) {
6✔
153
            $this->logger->warning("Github signature header missing");
1✔
154
            return $payload;
1✔
155
        }
156
        // Verify signature header format
157
        $signature = $this->request->getHeader('X-Hub-Signature-256')[0];
5✔
158
        $signature_parts = explode('=', $signature);
5✔
159

160
        if (count($signature_parts) != 2 || $signature_parts[0] != 'sha256') {
5✔
161
            $this->logger->warning("Bad format for the github signature header");
1✔
162
            return $payload;
1✔
163
        }
164
        //All is well so far - get the request body and validate the signature
165
        $rawBody = (string) $this->request->getBody();
4✔
166

167
        if (!$this->githubService->validateSignature($rawBody, $signature_parts[1])) {
4✔
168
            $this->logger->warning("Github signature did not verify");
1✔
169
            return [];
1✔
170
        }
171

172
        $payload = json_decode($rawBody, true);
3✔
173

174
        //Lastly check that the request was for the correct repository
175
        if (!$this->githubService->isValidRepository($payload)) {
3✔
176
            $this->logger->warning("Repository Id in the request was not correct");
1✔
177
            return [];
1✔
178
        }
179

180
        //Handle the ping event here, just send a pong back
181
        if ($event === 'ping') {
2✔
182
            $this->logger->debug("Got a ping event, sent a pong back");
1✔
183
            return [];
1✔
184
        }
185

186
        //Return paylod, empty array if there were any errors
187
        return $payload;
1✔
188
    }
189
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc