fix git
This commit is contained in:
@@ -4,33 +4,43 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace Modules\Helpdesk\Models;
|
namespace Modules\Helpdesk\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||||
|
|
||||||
final class GitCommit extends Model
|
final class GitCommit extends Model
|
||||||
{
|
{
|
||||||
use HasFactory;
|
/**
|
||||||
|
* @var array<int,string>
|
||||||
protected $table = 'helpdesk_git_commits';
|
*/
|
||||||
|
|
||||||
/** @var list<string> */
|
|
||||||
protected $fillable = [
|
protected $fillable = [
|
||||||
'repository_id', 'ticket_id', 'hash', 'short_hash', 'branch', 'author_name', 'author_email', 'message', 'url', 'committed_at',
|
'repository_full_name',
|
||||||
|
'repository_name',
|
||||||
|
'ticket_id',
|
||||||
|
'sha',
|
||||||
|
'branch',
|
||||||
|
'author_name',
|
||||||
|
'author_email',
|
||||||
|
'message',
|
||||||
|
'commit_url',
|
||||||
|
'committed_at',
|
||||||
|
'payload',
|
||||||
];
|
];
|
||||||
|
|
||||||
/** @var array<string, string> */
|
/**
|
||||||
protected $casts = ['repository_id' => 'integer', 'ticket_id' => 'integer', 'committed_at' => 'datetime'];
|
* @var array<string,string>
|
||||||
|
*/
|
||||||
|
protected $casts = [
|
||||||
|
'payload' => 'array',
|
||||||
|
'committed_at' => 'datetime',
|
||||||
|
];
|
||||||
|
|
||||||
/** @return BelongsTo<GitRepository, GitCommit> */
|
/**
|
||||||
public function repository(): BelongsTo
|
* Related helpdesk ticket.
|
||||||
{
|
*/
|
||||||
return $this->belongsTo(GitRepository::class, 'repository_id');
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @return BelongsTo<Ticket, GitCommit> */
|
|
||||||
public function ticket(): BelongsTo
|
public function ticket(): BelongsTo
|
||||||
{
|
{
|
||||||
return $this->belongsTo(Ticket::class, 'ticket_id');
|
return $this->belongsTo(
|
||||||
|
Ticket::class,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace App\Http\Controllers\Git;
|
|||||||
use Illuminate\Http\JsonResponse;
|
use Illuminate\Http\JsonResponse;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Routing\Controller;
|
use Illuminate\Routing\Controller;
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Facades\Log;
|
use Illuminate\Support\Facades\Log;
|
||||||
|
|
||||||
final class GiteaWebhookController extends Controller
|
final class GiteaWebhookController extends Controller
|
||||||
@@ -18,21 +19,40 @@ final class GiteaWebhookController extends Controller
|
|||||||
{
|
{
|
||||||
Log::info('Gitea webhook endpoint hit.', [
|
Log::info('Gitea webhook endpoint hit.', [
|
||||||
'event' => $request->header('X-Gitea-Event'),
|
'event' => $request->header('X-Gitea-Event'),
|
||||||
|
'github_event' => $request->header('X-GitHub-Event'),
|
||||||
'delivery' => $request->header('X-Gitea-Delivery'),
|
'delivery' => $request->header('X-Gitea-Delivery'),
|
||||||
'authorization_present' => $request->headers->has('Authorization'),
|
'authorization_present' => $request->headers->has('Authorization'),
|
||||||
'signature_present' => $request->headers->has('X-Gitea-Signature'),
|
'signature_present' => $request->headers->has('X-Gitea-Signature'),
|
||||||
]);
|
]);
|
||||||
$event = (string) $request->header('X-Gitea-Event');
|
|
||||||
|
$authResponse = $this->validateAuthorization($request);
|
||||||
|
|
||||||
|
if ($authResponse instanceof JsonResponse) {
|
||||||
|
return $authResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
$signatureResponse = $this->validateSignature($request);
|
||||||
|
|
||||||
|
if ($signatureResponse instanceof JsonResponse) {
|
||||||
|
return $signatureResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
$event = (string) ($request->header('X-Gitea-Event') ?: $request->header('X-GitHub-Event'));
|
||||||
$payload = $request->json()->all();
|
$payload = $request->json()->all();
|
||||||
|
|
||||||
if ($event !== 'push') {
|
if ($event !== 'push') {
|
||||||
|
Log::info('Gitea webhook ignored.', [
|
||||||
|
'event' => $event,
|
||||||
|
'delivery' => $request->header('X-Gitea-Delivery'),
|
||||||
|
]);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'message' => 'Gitea webhook ignored.',
|
'message' => 'Gitea webhook ignored.',
|
||||||
'event' => $event,
|
'event' => $event,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
$commits = $request->input('commits', []);
|
$commits = data_get($payload, 'commits', []);
|
||||||
|
|
||||||
Log::info('Gitea push webhook received.', [
|
Log::info('Gitea push webhook received.', [
|
||||||
'repository' => data_get($payload, 'repository.full_name'),
|
'repository' => data_get($payload, 'repository.full_name'),
|
||||||
@@ -40,20 +60,36 @@ final class GiteaWebhookController extends Controller
|
|||||||
'branch' => str_replace('refs/heads/', '', (string) data_get($payload, 'ref')),
|
'branch' => str_replace('refs/heads/', '', (string) data_get($payload, 'ref')),
|
||||||
'before' => data_get($payload, 'before'),
|
'before' => data_get($payload, 'before'),
|
||||||
'after' => data_get($payload, 'after'),
|
'after' => data_get($payload, 'after'),
|
||||||
'commit_count' => count($commits),
|
'commit_count' => is_countable($commits) ? count($commits) : 0,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
foreach ($commits as $commit) {
|
foreach ($commits as $commit) {
|
||||||
Log::info('Gitea commit received.', [
|
GitCommit::updateOrCreate(
|
||||||
|
[
|
||||||
|
'sha' => data_get($commit, 'id'),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'repository_full_name' => data_get($payload, 'repository.full_name'),
|
||||||
|
'repository_name' => data_get($payload, 'repository.name'),
|
||||||
|
'branch' => str_replace(
|
||||||
|
'refs/heads/',
|
||||||
|
'',
|
||||||
|
(string) data_get($payload, 'ref')
|
||||||
|
),
|
||||||
|
'author_name' => data_get($commit, 'author.name'),
|
||||||
|
'author_email' => data_get($commit, 'author.email'),
|
||||||
|
'message' => data_get($commit, 'message'),
|
||||||
|
'commit_url' => data_get($commit, 'url'),
|
||||||
|
'committed_at' => data_get($commit, 'timestamp')
|
||||||
|
? Carbon::parse(data_get($commit, 'timestamp'))
|
||||||
|
: now(),
|
||||||
|
'payload' => $commit,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
Log::info('Git commit stored.', [
|
||||||
'sha' => data_get($commit, 'id'),
|
'sha' => data_get($commit, 'id'),
|
||||||
'message' => data_get($commit, 'message'),
|
'message' => data_get($commit, 'message'),
|
||||||
'url' => data_get($commit, 'url'),
|
|
||||||
'author_name' => data_get($commit, 'author.name'),
|
|
||||||
'author_email' => data_get($commit, 'author.email'),
|
|
||||||
'timestamp' => data_get($commit, 'timestamp'),
|
|
||||||
'added' => data_get($commit, 'added', []),
|
|
||||||
'modified' => data_get($commit, 'modified', []),
|
|
||||||
'removed' => data_get($commit, 'removed', []),
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +97,66 @@ final class GiteaWebhookController extends Controller
|
|||||||
'message' => 'Gitea push processed.',
|
'message' => 'Gitea push processed.',
|
||||||
'repository' => data_get($payload, 'repository.full_name'),
|
'repository' => data_get($payload, 'repository.full_name'),
|
||||||
'branch' => str_replace('refs/heads/', '', (string) data_get($payload, 'ref')),
|
'branch' => str_replace('refs/heads/', '', (string) data_get($payload, 'ref')),
|
||||||
'commit_count' => count($commits),
|
'commit_count' => is_countable($commits) ? count($commits) : 0,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the optional Gitea Authorization header.
|
||||||
|
*/
|
||||||
|
private function validateAuthorization(Request $request): ?JsonResponse
|
||||||
|
{
|
||||||
|
$expected = (string) config('services.gitea.webhook_authorization');
|
||||||
|
|
||||||
|
if ($expected === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$actual = (string) $request->header('Authorization');
|
||||||
|
|
||||||
|
if (hash_equals($expected, $actual)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::warning('Rejected Gitea webhook because Authorization header was invalid.', [
|
||||||
|
'delivery' => $request->header('X-Gitea-Delivery'),
|
||||||
|
'authorization_present' => $request->headers->has('Authorization'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Invalid authorization header.',
|
||||||
|
], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validate the optional Gitea HMAC SHA-256 signature.
|
||||||
|
*/
|
||||||
|
private function validateSignature(Request $request): ?JsonResponse
|
||||||
|
{
|
||||||
|
$secret = (string) config('services.gitea.webhook_secret');
|
||||||
|
|
||||||
|
if ($secret === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$payload = $request->getContent();
|
||||||
|
$signature = (string) $request->header('X-Gitea-Signature');
|
||||||
|
|
||||||
|
$expected = hash_hmac('sha256', $payload, $secret);
|
||||||
|
|
||||||
|
if ($signature !== '' && hash_equals($expected, $signature)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Log::warning('Rejected Gitea webhook because signature was invalid.', [
|
||||||
|
'delivery' => $request->header('X-Gitea-Delivery'),
|
||||||
|
'signature_present' => $request->headers->has('X-Gitea-Signature'),
|
||||||
|
'expected' => $expected,
|
||||||
|
'actual' => $signature,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Invalid Gitea signature.',
|
||||||
|
], 403);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('git_commits', function (Blueprint $table): void {
|
||||||
|
$table->id();
|
||||||
|
|
||||||
|
$table->string('repository_full_name')->index();
|
||||||
|
$table->string('repository_name')->nullable();
|
||||||
|
|
||||||
|
$table->unsignedBigInteger('ticket_id')->nullable();
|
||||||
|
|
||||||
|
$table->string('sha', 64)->unique();
|
||||||
|
$table->string('branch')->nullable();
|
||||||
|
|
||||||
|
$table->string('author_name')->nullable();
|
||||||
|
$table->string('author_email')->nullable();
|
||||||
|
|
||||||
|
$table->text('message');
|
||||||
|
|
||||||
|
$table->string('commit_url')->nullable();
|
||||||
|
|
||||||
|
$table->timestamp('committed_at')->nullable();
|
||||||
|
|
||||||
|
$table->json('payload')->nullable();
|
||||||
|
|
||||||
|
$table->timestamps();
|
||||||
|
|
||||||
|
$table->foreign('ticket_id')
|
||||||
|
->references('id')
|
||||||
|
->on('helpdesk_tickets')
|
||||||
|
->nullOnDelete();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('git_commits');
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user