feat(validation): add custom validation rules for Git repository URLs and branches

- Introduced `ValidGitRepositoryUrl` and `ValidGitBranch` validation rules to ensure safe and valid input for Git repository URLs and branch names.
- Updated relevant Livewire components and API controllers to utilize the new validation rules, enhancing security against command injection and invalid inputs.
- Refactored existing validation logic to improve consistency and maintainability across the application.
This commit is contained in:
Andras Bacsai
2025-08-22 14:38:21 +02:00
parent 841e33bac0
commit 8408205955
9 changed files with 393 additions and 28 deletions

View File

@@ -7,6 +7,7 @@ use App\Models\GithubApp;
use App\Models\Project;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use App\Rules\ValidGitBranch;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Route;
use Livewire\Component;
@@ -155,6 +156,21 @@ class GithubPrivateRepository extends Component
public function submit()
{
try {
// Validate git repository parts and branch
$validator = validator([
'selected_repository_owner' => $this->selected_repository_owner,
'selected_repository_repo' => $this->selected_repository_repo,
'selected_branch_name' => $this->selected_branch_name,
], [
'selected_repository_owner' => 'required|string|regex:/^[a-zA-Z0-9\-_]+$/',
'selected_repository_repo' => 'required|string|regex:/^[a-zA-Z0-9\-_\.]+$/',
'selected_branch_name' => ['required', 'string', new ValidGitBranch],
]);
if ($validator->fails()) {
throw new \RuntimeException('Invalid repository data: '.$validator->errors()->first());
}
$destination_uuid = $this->query['destination'];
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
@@ -171,8 +187,8 @@ class GithubPrivateRepository extends Component
$application = Application::create([
'name' => generate_application_name($this->selected_repository_owner.'/'.$this->selected_repository_repo, $this->selected_branch_name),
'repository_project_id' => $this->selected_repository_id,
'git_repository' => "{$this->selected_repository_owner}/{$this->selected_repository_repo}",
'git_branch' => $this->selected_branch_name,
'git_repository' => str($this->selected_repository_owner)->trim()->toString().'/'.str($this->selected_repository_repo)->trim()->toString(),
'git_branch' => str($this->selected_branch_name)->trim()->toString(),
'build_pack' => $this->build_pack,
'ports_exposes' => $this->port,
'publish_directory' => $this->publish_directory,

View File

@@ -9,6 +9,8 @@ use App\Models\PrivateKey;
use App\Models\Project;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use App\Rules\ValidGitBranch;
use App\Rules\ValidGitRepositoryUrl;
use Illuminate\Support\Str;
use Livewire\Component;
use Spatie\Url\Url;
@@ -53,17 +55,29 @@ class GithubPrivateRepositoryDeployKey extends Component
private ?string $git_host = null;
private string $git_repository;
private ?string $git_repository = null;
protected $rules = [
'repository_url' => 'required',
'branch' => 'required|string',
'repository_url' => ['required', 'string'],
'branch' => ['required', 'string'],
'port' => 'required|numeric',
'is_static' => 'required|boolean',
'publish_directory' => 'nullable|string',
'build_pack' => 'required|string',
];
protected function rules()
{
return [
'repository_url' => ['required', 'string', new ValidGitRepositoryUrl],
'branch' => ['required', 'string', new ValidGitBranch],
'port' => 'required|numeric',
'is_static' => 'required|boolean',
'publish_directory' => 'nullable|string',
'build_pack' => 'required|string',
];
}
protected $validationAttributes = [
'repository_url' => 'Repository',
'branch' => 'Branch',
@@ -135,6 +149,9 @@ class GithubPrivateRepositoryDeployKey extends Component
$this->get_git_source();
// Note: git_repository has already been validated and transformed in get_git_source()
// It may now be in SSH format (git@host:repo.git) which is valid for deploy keys
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
if ($this->git_source === 'other') {
@@ -194,6 +211,15 @@ class GithubPrivateRepositoryDeployKey extends Component
private function get_git_source()
{
// Validate repository URL before parsing
$validator = validator(['repository_url' => $this->repository_url], [
'repository_url' => ['required', 'string', new ValidGitRepositoryUrl],
]);
if ($validator->fails()) {
throw new \RuntimeException('Invalid repository URL: '.$validator->errors()->first('repository_url'));
}
$this->repository_url_parsed = Url::fromString($this->repository_url);
$this->git_host = $this->repository_url_parsed->getHost();
$this->git_repository = $this->repository_url_parsed->getSegment(1).'/'.$this->repository_url_parsed->getSegment(2);
@@ -206,8 +232,10 @@ class GithubPrivateRepositoryDeployKey extends Component
if (str($this->repository_url)->startsWith('http')) {
$this->git_host = $this->repository_url_parsed->getHost();
$this->git_repository = $this->repository_url_parsed->getSegment(1).'/'.$this->repository_url_parsed->getSegment(2);
// Convert to SSH format for deploy key usage
$this->git_repository = Str::finish("git@$this->git_host:$this->git_repository", '.git');
} else {
// If it's already in SSH format, just use it as-is
$this->git_repository = $this->repository_url;
}
$this->git_source = 'other';

View File

@@ -9,6 +9,8 @@ use App\Models\Project;
use App\Models\Service;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use App\Rules\ValidGitBranch;
use App\Rules\ValidGitRepositoryUrl;
use Carbon\Carbon;
use Livewire\Component;
use Spatie\Url\Url;
@@ -62,7 +64,7 @@ class PublicGitRepository extends Component
public bool $new_compose_services = false;
protected $rules = [
'repository_url' => 'required|url',
'repository_url' => ['required', 'string'],
'port' => 'required|numeric',
'isStatic' => 'required|boolean',
'publish_directory' => 'nullable|string',
@@ -71,6 +73,20 @@ class PublicGitRepository extends Component
'docker_compose_location' => 'nullable|string',
];
protected function rules()
{
return [
'repository_url' => ['required', 'string', new ValidGitRepositoryUrl],
'port' => 'required|numeric',
'isStatic' => 'required|boolean',
'publish_directory' => 'nullable|string',
'build_pack' => 'required|string',
'base_directory' => 'nullable|string',
'docker_compose_location' => 'nullable|string',
'git_branch' => ['required', 'string', new ValidGitBranch],
];
}
protected $validationAttributes = [
'repository_url' => 'repository',
'port' => 'port',
@@ -141,6 +157,15 @@ class PublicGitRepository extends Component
public function loadBranch()
{
try {
// Validate repository URL
$validator = validator(['repository_url' => $this->repository_url], [
'repository_url' => ['required', 'string', new ValidGitRepositoryUrl],
]);
if ($validator->fails()) {
throw new \RuntimeException('Invalid repository URL: '.$validator->errors()->first('repository_url'));
}
if (str($this->repository_url)->startsWith('git@')) {
$github_instance = str($this->repository_url)->after('git@')->before(':');
$repository = str($this->repository_url)->after(':')->before('.git');
@@ -191,6 +216,15 @@ class PublicGitRepository extends Component
$this->git_branch = 'main';
$this->base_directory = '/';
// Validate repository URL before parsing
$validator = validator(['repository_url' => $this->repository_url], [
'repository_url' => ['required', 'string', new ValidGitRepositoryUrl],
]);
if ($validator->fails()) {
throw new \RuntimeException('Invalid repository URL: '.$validator->errors()->first('repository_url'));
}
$this->repository_url_parsed = Url::fromString($this->repository_url);
$this->git_host = $this->repository_url_parsed->getHost();
$this->git_repository = $this->repository_url_parsed->getSegment(1).'/'.$this->repository_url_parsed->getSegment(2);
@@ -234,6 +268,27 @@ class PublicGitRepository extends Component
{
try {
$this->validate();
// Additional validation for git repository and branch
if ($this->git_source === 'other') {
// For 'other' sources, git_repository contains the full URL
$validator = validator(['git_repository' => $this->git_repository], [
'git_repository' => ['required', 'string', new ValidGitRepositoryUrl],
]);
if ($validator->fails()) {
throw new \RuntimeException('Invalid repository URL: '.$validator->errors()->first('git_repository'));
}
}
$branchValidator = validator(['git_branch' => $this->git_branch], [
'git_branch' => ['required', 'string', new ValidGitBranch],
]);
if ($branchValidator->fails()) {
throw new \RuntimeException('Invalid branch: '.$branchValidator->errors()->first('git_branch'));
}
$destination_uuid = $this->query['destination'];
$project_uuid = $this->parameters['project_uuid'];
$environment_uuid = $this->parameters['environment_uuid'];