GrypeSpdx.php
1 <?php 2 3 namespace App\Jobs; 4 5 use App\Http\Services\VulnerabilityService; 6 use App\Models\TOE; 7 use App\Models\TOEVulnerability; 8 use App\Models\Vulnerability; 9 use App\Notifications\VulnerabilityCreated; 10 use Exception; 11 use Illuminate\Bus\Queueable; 12 use Illuminate\Contracts\Queue\ShouldBeUnique; 13 use Illuminate\Contracts\Queue\ShouldQueue; 14 use Illuminate\Database\Eloquent\Casts\AsCollection; 15 use Illuminate\Foundation\Bus\Dispatchable; 16 use Illuminate\Queue\InteractsWithQueue; 17 use Illuminate\Queue\SerializesModels; 18 use Illuminate\Support\Collection; 19 use Illuminate\Support\Facades\Artisan; 20 use Illuminate\Support\Facades\Notification; 21 use Illuminate\Support\Facades\Storage; 22 use Symfony\Component\Process\Process; 23 24 class GrypeSpdx implements ShouldQueue 25 { 26 use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; 27 28 public Collection $toes; 29 30 /** 31 * Create a new job instance. 32 * 33 * @return void 34 */ 35 public function __construct(Collection $toes) 36 { 37 $this->toes = $toes; 38 } 39 40 private function vulnerability_from_match($match) 41 { 42 $vulnerability = $match['vulnerability']; 43 $severity = $vulnerability['severity']; 44 $artifact = $match['artifact']; 45 46 if (count($match['relatedVulnerabilities']) > 0) { 47 $vulnerability = $match['relatedVulnerabilities'][0]; 48 } 49 50 return [ 51 'vid' => $vulnerability['id'], 52 'severity' => $severity, 53 'state' => $match['vulnerability']['fix']['state'], 54 'description' => $vulnerability['description'] ?? 'N/A', 55 'artifact_name' => $artifact['name'], 56 'artifact_version' => $artifact['version'], 57 'artifact_type' => $artifact['type'], 58 'source' => $vulnerability['dataSource'], 59 ]; 60 } 61 62 private function get_vulnerabilities($toe): Collection 63 { 64 $vulnerabilities = collect([]); 65 $count = 0; 66 67 foreach ($this->getMatches($toe) as $match) { 68 $data = $this->vulnerability_from_match($match); 69 $vulnerability = Vulnerability::where($data)->first(); 70 71 if (!$vulnerability) { 72 $count++; 73 $vulnerability = Vulnerability::create($data); 74 } 75 76 $found = TOEVulnerability 77 ::where('toe_id', $toe->id) 78 ->where('vulnerability_id', $vulnerability->id) 79 ->exists(); 80 81 if (!$found) { 82 $vulnerabilities->push($vulnerability); 83 $toe->vulnerabilities()->attach($vulnerability); 84 } 85 } 86 87 return $vulnerabilities; 88 } 89 90 private function create_notifications(Collection $vulnerabilities, TOE $toe): void 91 { 92 foreach ($toe->getNotifiableUsers() as $user) { 93 $user->notify(new VulnerabilityCreated($vulnerabilities, $toe)); 94 } 95 } 96 97 private function getMatches($toe): array 98 { 99 $process = new Process(['grype', Storage::path($toe->spdx_path), '-o', 'json']); 100 $process->mustRun(); 101 $data = json_decode($process->getOutput(), true); 102 return $data['matches']; 103 } 104 105 /** 106 * Execute the job. 107 * 108 * @return void 109 */ 110 public function handle() 111 { 112 foreach ($this->toes as $toe) { 113 $vulnerabilities = $this->get_vulnerabilities($toe); 114 115 if ($vulnerabilities->count() > 0) { 116 $this->create_notifications($vulnerabilities, $toe); 117 } 118 } 119 } 120 }