Skip to content

Commit

Permalink
Merge pull request vtsykun#158 from vtsykun/feat/show-webhook-log
Browse files Browse the repository at this point in the history
Show all received webhooks for oauth2 integrations
  • Loading branch information
vtsykun authored Aug 31, 2023
2 parents 707fe05 + 65a5f42 commit 1e95294
Show file tree
Hide file tree
Showing 10 changed files with 151 additions and 16 deletions.
31 changes: 24 additions & 7 deletions src/Controller/Api/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@
use Doctrine\Persistence\ManagerRegistry;
use Packeton\Attribute\Vars;
use Packeton\Controller\ControllerTrait;
use Packeton\Entity\Job;
use Packeton\Entity\OAuthIntegration;
use Packeton\Entity\Package;
use Packeton\Entity\User;
use Packeton\Entity\Webhook;
use Packeton\Integrations\IntegrationRegistry;
use Packeton\Integrations\Model\AppUtils;
use Packeton\Model\AutoHookUser;
use Packeton\Model\DownloadManager;
use Packeton\Model\PackageManager;
use Packeton\Security\Provider\AuditSessionProvider;
use Packeton\Service\JobPersister;
use Packeton\Service\Scheduler;
use Packeton\Util\PacketonUtils;
use Packeton\Webhook\HookBus;
Expand Down Expand Up @@ -244,7 +247,7 @@ public function trackDownloadsAction(Request $request): Response
}

if ($failed) {
return new JsonResponse(['status' => 'partial', 'message' => 'Packages '.json_encode($failed).' not found'], 200);
return new JsonResponse(['status' => 'partial', 'message' => 'Packages '. json_encode($failed).' not found'], 200);
}

return new JsonResponse(['status' => 'success'], 201);
Expand Down Expand Up @@ -311,19 +314,32 @@ protected function receiveIntegrationHook(Request $request, OAuthIntegration $oa
$user = $this->getUser();
if (null === $oauth && $user instanceof AutoHookUser) {
$oauth = $this->registry->getRepository(OAuthIntegration::class)->find((int) $user->getHookIdentifier());
if (null === $oauth) {
return null;
}
}

if (null === $oauth) {
return null;
}

$job = AppUtils::createLogJob($request, $oauth);
$response = $app = null;
try {
$app = $this->integrations->findApp($oauth->getAlias());
return $app->receiveHooks($oauth, $request, $this->getJsonPayload($request));
$response = $app->receiveHooks($oauth, $request, $this->getJsonPayload($request));
$job->setStatus(Job::STATUS_COMPLETED);
} catch (\Throwable $e) {
$this->logger->error($e->getMessage(), ['e' => $e]);
$error = AppUtils::castError($e, $app, true);
$this->logger->error($error, ['e' => $e]);

$job->setStatus(Job::STATUS_ERRORED);
$job->addResult('error', $error);
}

return null;
$job->addResult('response', $response);
try {
$this->container->get(JobPersister::class)->persist($job, false);
} catch (\Throwable $e) {}

return $response;
}

/**
Expand Down Expand Up @@ -364,6 +380,7 @@ public static function getSubscribedServices(): array
PackageManager::class,
Scheduler::class,
HookBus::class,
JobPersister::class,
]
);
}
Expand Down
4 changes: 4 additions & 0 deletions src/Controller/OAuth/IntegrationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Doctrine\Persistence\ManagerRegistry;
use Packeton\Attribute\Vars;
use Packeton\Composer\JsonResponse;
use Packeton\Entity\Job;
use Packeton\Entity\OAuthIntegration;
use Packeton\Entity\Package;
use Packeton\Exception\SkipLoggerExceptionInterface;
Expand Down Expand Up @@ -62,6 +63,8 @@ public function index(string $alias, #[Vars] OAuthIntegration $oauth): Response
$e instanceof SkipLoggerExceptionInterface ? $this->logger->info($errorMsg, ['e' => $e]) : $this->logger->error($errorMsg, ['e' => $e]);
}

$jobs = $this->registry->getRepository(Job::class)->findJobsByType('webhook:integration', $oauth->getId());

$config = $client->getConfig($oauth);

return $this->render('integration/index.html.twig', [
Expand All @@ -73,6 +76,7 @@ public function index(string $alias, #[Vars] OAuthIntegration $oauth): Response
'config' => $config,
'oauth' => $oauth,
'errorMsg' => $errorMsg,
'jobs' => $jobs,
]);
}

Expand Down
2 changes: 1 addition & 1 deletion src/Controller/WebhookController.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public function jobAction(#[Vars] Job $entity): Response

$result = $entity->getResult() ?: [];
try {
$response = array_map(HookResponse::class.'::fromArray', $result['response'] ?? []);
$response = array_map(HookResponse::fromArray(...), $result['response'] ?? []);
} catch (\Throwable $e) {
$response = null;
}
Expand Down
25 changes: 24 additions & 1 deletion src/Entity/Job.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@

use Doctrine\ORM\Mapping as ORM;
use DateTimeInterface;
use Packeton\Repository\JobRepository;

#[ORM\Entity(repositoryClass: 'Packeton\Repository\JobRepository')]
#[ORM\Entity(repositoryClass: JobRepository::class)]
#[ORM\Table(name: 'job')]
#[ORM\Index(columns: ['type'], name: 'type_idx')]
#[ORM\Index(columns: ['status'], name: 'status_idx')]
Expand Down Expand Up @@ -57,6 +58,11 @@ class Job
#[ORM\Column(name: 'packageid', type: 'integer', nullable: true)]
private ?int $packageId = null;

public function __construct()
{
$this->createdAt = new \DateTime('now', new \DateTimeZone('UTC'));
}

public function start()
{
$this->startedAt = new \DateTime();
Expand Down Expand Up @@ -137,6 +143,23 @@ public function getResult(?string $property = null)
return $property ? ($this->result[$property] ?? null) : $this->result;
}

public function setResult(array $result): void
{
$this->result = $result;
}

public function addResult(array|string $keyOfValue, mixed $value = null): void
{
$this->result ??= [];

if (is_array($keyOfValue)) {
$this->result = array_merge($this->result, $keyOfValue);
return;
}

$this->result[$keyOfValue] = $value;
}

public function setCreatedAt(DateTimeInterface $createdAt)
{
$this->createdAt = $createdAt;
Expand Down
31 changes: 31 additions & 0 deletions src/Integrations/Model/AppUtils.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@

namespace Packeton\Integrations\Model;

use Packeton\Entity\Job;
use Packeton\Entity\OAuthIntegration as App;
use Packeton\Integrations\AppInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;

class AppUtils
Expand All @@ -26,6 +28,35 @@ public static function findUrl(string $externalId, App $app, AppInterface $clien
return self::clonePref($client->getConfig(), $app) === 'clone_ssh' && isset($repo['ssh_url']) ? $repo['ssh_url'] : $repo['url'];
}

public static function createLogJob(Request $request, App $app): Job
{
$job = new Job();
$job->setType('webhook:integration');
$job->setPackageId($app->getId());
$headers = [];

foreach ($request->headers->all() as $name => $value) {
if (is_array($value)) {
$value = reset($value);
}

if (is_scalar($value)) {
$headers[] = "$name: $value";
}
}

$result = [
'request_headers' => implode("\n", $headers),
'request_body' => substr($request->getContent(),0, 65536),
'status' => Job::STATUS_COMPLETED,
];

$job->start();
$job->complete($result);
$job->setResult($result);
return $job;
}

public static function castError(\Throwable $e, App|array $app = null, bool $moreInfo = false): string
{
$msg = '';
Expand Down
11 changes: 9 additions & 2 deletions src/Security/Provider/AuditSessionProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,22 @@ private function log(array $session, string $identity): void
$sessions = $this->redis->hGet("user_session", $identity);
$sessions = $sessions ? json_decode($sessions, true) : [];

if (is_string($session['ua'] ?? null)) {
$session['ua'] = substr($session['ua'], 512);
}
if (is_string($session['error'] ?? null)) {
$session['error'] = substr($session['error'], 512);
}

$unix = time();
$len = count($sessions);
$probe = $session;
unset($probe['usage'], $probe['last_usage']);
unset($probe['usage'], $probe['last_usage'], $probe['error'], $probe['ua']);

$exists = false;
for ($i = $len - 4; $i < $len; $i++) {
$probe1 = $sessions[$i] ?? null;
unset($probe1['usage'], $probe1['last_usage']);
unset($probe1['usage'], $probe1['last_usage'], $probe1['error'], $probe1['ua']);

if ($probe1 === $probe) {
$probe = $sessions[$i];
Expand Down
10 changes: 10 additions & 0 deletions src/Twig/PackagistExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ public function getFunctions(): array
new TwigFunction('get_group_data', [$this, 'getGroupData']),
new TwigFunction('get_packages_choice_form_data', [$this, 'buildPackagesExpandedList']),
new TwigFunction('get_api_token', [$this, 'getApiToken']),
new TwigFunction('cast_string', [$this, 'castString']),
new TwigFunction('get_sub_repos_data', [$this, 'getSubReposData']),
new TwigFunction('get_free_zipballs', [$this, 'getZipballs']),
new TwigFunction('show_api_token', [$this, 'showApiTokenButton'], ['is_safe' => ['html']]),
Expand All @@ -64,6 +65,15 @@ public function getFilters(): array
];
}

public function castString(mixed $value)
{
if (is_array($value)) {
return json_encode($value, 448);
}

return $value;
}

public function getSubReposData(UserInterface $user = null)
{
return $this->subRepositoryHelper->getTwigData($user);
Expand Down
2 changes: 1 addition & 1 deletion src/Webhook/HookRequestExecutor.php
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ private function getRequestHeaders($info, array $baseHeaders = [])
$headers[strtolower($name)] = $value;
}

list($debug) = explode("\r\n\r\n", $info['debug']);
[$debug] = explode("\r\n\r\n", $info['debug']);
$debug = explode("\r\n", $debug);
array_shift($debug);
foreach ($debug as $header) {
Expand Down
5 changes: 1 addition & 4 deletions src/Webhook/SenderWorker.php
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,7 @@ public function __invoke(Job $job): array
$parentJob = $this->registry->getRepository(Job::class)->find($payload['parentJob']);
if ($parentJob instanceof Job) {
try {
$response = array_map(
HookResponse::class . '::fromArray',
$parentJob->getResult()['response'] ?? []
);
$response = array_map(HookResponse::fromArray(...), $parentJob->getResult()['response'] ?? []);
$context['parentResponse'] = reset($response);
} catch (\Throwable $e) {}
}
Expand Down
46 changes: 46 additions & 0 deletions templates/integration/index.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,52 @@ If a different account is used, you may lose access to the current organization
</div>
</div>
{% endif %}

{% if jobs is defined and jobs|length > 0 %}
<section class="row">
<div class="col-md-8" style="padding-top: 35px">
<h3 class="title" style="font-size: 1.8em">Webhooks Deliveries</h3>
{% for job in jobs %}
<div class="panel panel-default">
<div class="panel-body">
{% if job.status == 'completed' %}
<i class="fa fa-check" style="color: #28a745" title="Success"></i>
{% else %}
<i class="fa fa-times" style="color: #ba140d" title="Failed"></i>
{% endif %}
<span class="hook-delivery-guid"
data-toggle="collapse"
aria-expanded="true"
href="#collapse-{{ job.id }}"
aria-controls="collapse-{{ job.id }}"
>
<i class="fa fa-cube"></i> {{ job.id }}
</span>
<span style="float: right;font-size: 0.85em; color: #7b7b7d">{{ job.completedAt|date('Y-m-d H:i:s') ~ ' UTC' }}</span>
</div>

<div class="hook-delivery-content collapse" id="collapse-{{ job.id }}">
<div style="margin: 8px">
{% if job.getResult('error') %}
<b>Error</b>
<pre class="github">{{ cast_string(job.getResult('error')) }}</pre>
{% endif %}
<b>Result:</b>
<pre class="github">{{ cast_string(job.getResult('response')) }}</pre>
<b>Headers:</b>
<pre class="github">{{ cast_string(job.getResult('request_headers')) }}</pre>
<b>Body:</b>
<pre class="github">{{ cast_string(job.getResult('request_body')) }}</pre>
</div>
</div>

</div>
{% endfor %}

</div>
</section>
{% endif %}

</div>
</div>
{% endblock %}
Expand Down

0 comments on commit 1e95294

Please sign in to comment.