## Summary Implements Spec 284 for provider-neutral artifact source taxonomy. - add shared artifact source descriptor, resolver, taxonomy, and provider-detail support - update findings, evidence snapshots, stored reports, inventory items, and tenant review surfaces to disclose descriptor-first artifact summaries - add bounded Pest unit, feature, guard, and browser coverage for the taxonomy slice - include the completed Spec 284 package artifacts under `specs/284-provider-neutral-artifact-source-taxonomy/` ## Notes - branch: `284-provider-neutral-artifact-source-taxonomy` - commit: `bf8d59e0` - this PR was created as part of the requested commit/push/PR flow against `platform-dev` Co-authored-by: Ahmed Darrazi <ahmed.darrazi@live.de> Reviewed-on: #343
541 lines
23 KiB
PHP
541 lines
23 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Support\Artifacts;
|
|
|
|
use App\Models\EvidenceSnapshotItem;
|
|
use App\Models\Finding;
|
|
use App\Models\InventoryItem;
|
|
use App\Models\ManagedEnvironment;
|
|
use App\Models\OperationRun;
|
|
use App\Models\ProviderConnection;
|
|
use App\Models\StoredReport;
|
|
use App\Support\Governance\Controls\CanonicalControlResolutionRequest;
|
|
use App\Support\Governance\Controls\CanonicalControlResolver;
|
|
use App\Support\Inventory\InventoryPolicyTypeMeta;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Support\Arr;
|
|
use Illuminate\Support\Str;
|
|
|
|
final readonly class ArtifactSourceResolver
|
|
{
|
|
public function __construct(
|
|
private CanonicalControlResolver $canonicalControlResolver,
|
|
) {}
|
|
|
|
public function forFinding(Finding $finding): ArtifactSourceDescriptor
|
|
{
|
|
$controlResolution = $this->canonicalControlResolutionForFinding($finding);
|
|
$evidence = is_array($finding->evidence_jsonb) ? $finding->evidence_jsonb : [];
|
|
$targetIdentifier = $this->firstString([
|
|
$finding->subject_external_id,
|
|
Arr::get($evidence, 'policy_id'),
|
|
Arr::get($evidence, 'policy_external_id'),
|
|
Arr::get($evidence, 'external_id'),
|
|
Arr::get($evidence, 'id'),
|
|
]);
|
|
|
|
return new ArtifactSourceDescriptor(
|
|
workspaceId: (int) $finding->workspace_id,
|
|
tenantId: (int) $finding->managed_environment_id,
|
|
managedEnvironmentId: (int) $finding->managed_environment_id,
|
|
sourceFamily: ArtifactSourceTaxonomy::SOURCE_FAMILY_FINDING,
|
|
sourceKind: ArtifactSourceTaxonomy::SOURCE_KIND_MODEL_SUMMARY,
|
|
providerKey: $this->providerKey($evidence),
|
|
providerConnectionId: $this->providerConnectionId($finding->tenant, $evidence),
|
|
sourceTargetKind: $targetIdentifier !== null
|
|
? ArtifactSourceTaxonomy::SOURCE_TARGET_GOVERNED_SUBJECT
|
|
: ArtifactSourceTaxonomy::SOURCE_TARGET_MANAGED_ENVIRONMENT,
|
|
sourceTargetIdentifier: $targetIdentifier ?? (string) $finding->managed_environment_id,
|
|
detectorKey: $this->findingDetectorKey($finding),
|
|
controlKey: $this->controlKeyFromResolution($controlResolution),
|
|
);
|
|
}
|
|
|
|
public function providerDetailForFinding(Finding $finding): ArtifactProviderDetail
|
|
{
|
|
$evidence = is_array($finding->evidence_jsonb) ? $finding->evidence_jsonb : [];
|
|
$policyType = $this->firstString([
|
|
Arr::get($evidence, 'policy_type'),
|
|
Arr::get($evidence, 'provider_object_type'),
|
|
]);
|
|
$typeDescriptor = InventoryPolicyTypeMeta::typeDescriptorFor($policyType, $evidence);
|
|
|
|
return new ArtifactProviderDetail(
|
|
legacyFindingType: $this->nullableString($finding->finding_type),
|
|
legacyPolicyType: $policyType,
|
|
providerObjectType: $typeDescriptor['provider_object_type'] ?? $policyType ?? $this->nullableString($finding->finding_type),
|
|
providerDisplayType: $typeDescriptor['provider_display_type'] ?? Str::headline((string) $finding->finding_type),
|
|
detectorDetail: $this->findingDetectorKey($finding),
|
|
);
|
|
}
|
|
|
|
public function forStoredReport(StoredReport $report): ArtifactSourceDescriptor
|
|
{
|
|
$payload = is_array($report->payload) ? $report->payload : [];
|
|
$detectorKey = $this->storedReportDetectorKey((string) $report->report_type);
|
|
|
|
return new ArtifactSourceDescriptor(
|
|
workspaceId: (int) $report->workspace_id,
|
|
tenantId: (int) $report->managed_environment_id,
|
|
managedEnvironmentId: (int) $report->managed_environment_id,
|
|
sourceFamily: ArtifactSourceTaxonomy::SOURCE_FAMILY_STORED_REPORT,
|
|
sourceKind: ArtifactSourceTaxonomy::SOURCE_KIND_STORED_REPORT,
|
|
providerKey: $this->providerKey($payload),
|
|
providerConnectionId: $this->providerConnectionId($report->tenant, $payload),
|
|
sourceTargetKind: ArtifactSourceTaxonomy::SOURCE_TARGET_MANAGED_ENVIRONMENT,
|
|
sourceTargetIdentifier: (string) $report->managed_environment_id,
|
|
detectorKey: $detectorKey,
|
|
controlKey: $this->controlKeyForReport((string) $report->report_type),
|
|
);
|
|
}
|
|
|
|
public function providerDetailForStoredReport(StoredReport $report): ArtifactProviderDetail
|
|
{
|
|
return new ArtifactProviderDetail(
|
|
legacyReportType: $this->nullableString($report->report_type),
|
|
providerObjectType: $this->nullableString($report->report_type),
|
|
providerDisplayType: Str::headline(str_replace('.', '_', (string) $report->report_type)),
|
|
detectorDetail: $this->storedReportDetectorKey((string) $report->report_type),
|
|
);
|
|
}
|
|
|
|
public function forInventoryItem(InventoryItem $item): ArtifactSourceDescriptor
|
|
{
|
|
$metadata = is_array($item->meta_jsonb) ? $item->meta_jsonb : [];
|
|
$typeDescriptor = InventoryPolicyTypeMeta::typeDescriptorFor($this->nullableString($item->policy_type), $metadata);
|
|
$detectorKey = 'inventory.'.($typeDescriptor['provider_object_type'] ?? $item->policy_type ?? 'unknown');
|
|
|
|
return new ArtifactSourceDescriptor(
|
|
workspaceId: (int) $item->workspace_id,
|
|
tenantId: (int) $item->managed_environment_id,
|
|
managedEnvironmentId: (int) $item->managed_environment_id,
|
|
sourceFamily: ArtifactSourceTaxonomy::SOURCE_FAMILY_INVENTORY,
|
|
sourceKind: ArtifactSourceTaxonomy::SOURCE_KIND_INVENTORY_PROJECTION,
|
|
providerKey: $this->providerKey($metadata),
|
|
providerConnectionId: $this->providerConnectionId($item->tenant, $metadata),
|
|
sourceTargetKind: ArtifactSourceTaxonomy::SOURCE_TARGET_GOVERNED_SUBJECT,
|
|
sourceTargetIdentifier: $this->firstString([$item->external_id, $item->getKey() !== null ? (string) $item->getKey() : null]),
|
|
detectorKey: $detectorKey,
|
|
controlKey: $this->controlKeyForInventoryPolicyType($this->nullableString($item->policy_type)),
|
|
);
|
|
}
|
|
|
|
public function providerDetailForInventoryItem(InventoryItem $item): ArtifactProviderDetail
|
|
{
|
|
$metadata = is_array($item->meta_jsonb) ? $item->meta_jsonb : [];
|
|
$typeDescriptor = InventoryPolicyTypeMeta::typeDescriptorFor($this->nullableString($item->policy_type), $metadata);
|
|
|
|
return new ArtifactProviderDetail(
|
|
legacyPolicyType: $typeDescriptor['legacy_policy_type'] ?? null,
|
|
providerObjectType: $typeDescriptor['provider_object_type'] ?? null,
|
|
providerDisplayType: $typeDescriptor['provider_display_type'] ?? null,
|
|
detectorDetail: 'inventory.'.($typeDescriptor['provider_object_type'] ?? 'unknown'),
|
|
);
|
|
}
|
|
|
|
public function forEvidenceSnapshotItem(EvidenceSnapshotItem $item): ArtifactSourceDescriptor
|
|
{
|
|
$payload = is_array($item->summary_payload) ? $item->summary_payload : [];
|
|
$descriptor = $payload['source_descriptor'] ?? null;
|
|
|
|
if (is_array($descriptor)) {
|
|
return ArtifactSourceDescriptor::fromArray($descriptor);
|
|
}
|
|
|
|
$tenant = $item->tenant;
|
|
|
|
return $this->forEvidenceProviderPayload(
|
|
$tenant instanceof ManagedEnvironment ? $tenant : null,
|
|
[
|
|
'dimension_key' => $item->dimension_key,
|
|
'source_kind' => $item->source_kind,
|
|
'source_record_type' => $item->source_record_type,
|
|
'source_record_id' => $item->source_record_id,
|
|
'summary_payload' => $payload,
|
|
],
|
|
workspaceId: (int) $item->workspace_id,
|
|
managedEnvironmentId: (int) $item->managed_environment_id,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $item
|
|
*/
|
|
public function forEvidenceProviderPayload(?ManagedEnvironment $tenant, array $item, ?int $workspaceId = null, ?int $managedEnvironmentId = null): ArtifactSourceDescriptor
|
|
{
|
|
$sourceKind = $this->sourceKind($item['source_kind'] ?? null);
|
|
$dimensionKey = $this->nullableString($item['dimension_key'] ?? null);
|
|
$sourceRecordType = $this->nullableString($item['source_record_type'] ?? null);
|
|
$sourceRecordId = $this->nullableString($item['source_record_id'] ?? null);
|
|
$summaryPayload = is_array($item['summary_payload'] ?? null) ? $item['summary_payload'] : [];
|
|
$sourceTargetKind = $this->sourceTargetKindForEvidenceItem($sourceKind, $sourceRecordType);
|
|
|
|
$workspaceId ??= (int) $tenant?->workspace_id;
|
|
$managedEnvironmentId ??= (int) $tenant?->getKey();
|
|
|
|
return new ArtifactSourceDescriptor(
|
|
workspaceId: $workspaceId,
|
|
tenantId: $managedEnvironmentId,
|
|
managedEnvironmentId: $managedEnvironmentId,
|
|
sourceFamily: $this->sourceFamilyForEvidenceItem($sourceKind, $sourceRecordType, $dimensionKey),
|
|
sourceKind: $sourceKind,
|
|
providerKey: $this->providerKey($summaryPayload),
|
|
providerConnectionId: $this->providerConnectionId($tenant, $summaryPayload),
|
|
sourceTargetKind: $sourceTargetKind,
|
|
sourceTargetIdentifier: $this->sourceTargetIdentifier($sourceTargetKind, $sourceRecordId, $managedEnvironmentId),
|
|
detectorKey: $dimensionKey,
|
|
controlKey: $this->controlKeyForEvidenceDimension($dimensionKey, $summaryPayload),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $item
|
|
*/
|
|
public function providerDetailForEvidenceProviderPayload(array $item): ArtifactProviderDetail
|
|
{
|
|
$sourceKind = $this->sourceKind($item['source_kind'] ?? null);
|
|
$dimensionKey = $this->nullableString($item['dimension_key'] ?? null);
|
|
$sourceRecordType = $this->nullableString($item['source_record_type'] ?? null);
|
|
$sourceRecordId = $this->nullableString($item['source_record_id'] ?? null);
|
|
|
|
if (($sourceKind === ArtifactSourceTaxonomy::SOURCE_KIND_STORED_REPORT || $sourceRecordType === StoredReport::class) && is_numeric($sourceRecordId)) {
|
|
$report = StoredReport::query()->find((int) $sourceRecordId);
|
|
|
|
if ($report instanceof StoredReport) {
|
|
return $this->providerDetailForStoredReport($report);
|
|
}
|
|
}
|
|
|
|
return match ($this->sourceFamilyForEvidenceItem($sourceKind, $sourceRecordType, $dimensionKey)) {
|
|
ArtifactSourceTaxonomy::SOURCE_FAMILY_FINDING => new ArtifactProviderDetail(
|
|
legacyFindingType: $dimensionKey,
|
|
providerObjectType: $sourceRecordType,
|
|
providerDisplayType: 'Findings summary',
|
|
detectorDetail: $dimensionKey,
|
|
),
|
|
ArtifactSourceTaxonomy::SOURCE_FAMILY_OPERATION_RUN => new ArtifactProviderDetail(
|
|
providerObjectType: OperationRun::class,
|
|
providerDisplayType: 'Operation run',
|
|
detectorDetail: $dimensionKey,
|
|
),
|
|
ArtifactSourceTaxonomy::SOURCE_FAMILY_INVENTORY => new ArtifactProviderDetail(
|
|
providerObjectType: InventoryItem::class,
|
|
providerDisplayType: 'Inventory projection',
|
|
detectorDetail: $dimensionKey,
|
|
),
|
|
default => new ArtifactProviderDetail(
|
|
providerObjectType: $sourceRecordType,
|
|
providerDisplayType: $dimensionKey !== null ? Str::headline($dimensionKey) : null,
|
|
detectorDetail: $dimensionKey,
|
|
),
|
|
};
|
|
}
|
|
|
|
public function canonicalControlResolutionForFinding(Finding $finding): array
|
|
{
|
|
return $this->canonicalControlResolver
|
|
->resolve($this->resolutionRequestForFinding($finding))
|
|
->toArray();
|
|
}
|
|
|
|
public function resolutionRequestForFinding(Finding $finding): CanonicalControlResolutionRequest
|
|
{
|
|
$evidence = is_array($finding->evidence_jsonb) ? $finding->evidence_jsonb : [];
|
|
$findingType = (string) $finding->finding_type;
|
|
|
|
if ($findingType === Finding::FINDING_TYPE_PERMISSION_POSTURE) {
|
|
return new CanonicalControlResolutionRequest(
|
|
provider: $this->providerKey($evidence),
|
|
consumerContext: 'evidence',
|
|
subjectFamilyKey: 'permission_posture',
|
|
workload: 'entra',
|
|
signalKey: 'permission_posture.required_graph_permission',
|
|
);
|
|
}
|
|
|
|
if ($findingType === Finding::FINDING_TYPE_ENTRA_ADMIN_ROLES) {
|
|
$roleTemplateId = (string) ($evidence['role_template_id'] ?? '');
|
|
|
|
return new CanonicalControlResolutionRequest(
|
|
provider: $this->providerKey($evidence),
|
|
consumerContext: 'evidence',
|
|
subjectFamilyKey: 'entra_admin_roles',
|
|
workload: 'entra',
|
|
signalKey: $roleTemplateId === '62e90394-69f5-4237-9190-012177145e10'
|
|
? 'entra_admin_roles.global_admin_assignment'
|
|
: 'entra_admin_roles.privileged_role_assignment',
|
|
);
|
|
}
|
|
|
|
if ($findingType === Finding::FINDING_TYPE_DRIFT) {
|
|
$policyType = $this->firstString([
|
|
$evidence['policy_type'] ?? null,
|
|
'drift',
|
|
]) ?? 'drift';
|
|
|
|
return new CanonicalControlResolutionRequest(
|
|
provider: $this->providerKey($evidence),
|
|
consumerContext: 'evidence',
|
|
subjectFamilyKey: $policyType,
|
|
workload: 'intune',
|
|
signalKey: match ($policyType) {
|
|
'deviceCompliancePolicy' => 'intune.device_compliance_policy',
|
|
'drift' => 'finding.drift',
|
|
default => 'intune.device_configuration_drift',
|
|
},
|
|
);
|
|
}
|
|
|
|
return new CanonicalControlResolutionRequest(
|
|
provider: $this->providerKey($evidence),
|
|
consumerContext: 'evidence',
|
|
subjectFamilyKey: $findingType,
|
|
);
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $resolution
|
|
*/
|
|
private function controlKeyFromResolution(array $resolution): ?string
|
|
{
|
|
return $this->nullableString(Arr::get($resolution, 'control.control_key'));
|
|
}
|
|
|
|
private function findingDetectorKey(Finding $finding): ?string
|
|
{
|
|
$evidence = is_array($finding->evidence_jsonb) ? $finding->evidence_jsonb : [];
|
|
$findingType = (string) $finding->finding_type;
|
|
|
|
if ($findingType === Finding::FINDING_TYPE_PERMISSION_POSTURE) {
|
|
return 'permission_posture.required_graph_permission';
|
|
}
|
|
|
|
if ($findingType === Finding::FINDING_TYPE_ENTRA_ADMIN_ROLES) {
|
|
$roleTemplateId = (string) ($evidence['role_template_id'] ?? '');
|
|
|
|
return $roleTemplateId === '62e90394-69f5-4237-9190-012177145e10'
|
|
? 'entra_admin_roles.global_admin_assignment'
|
|
: 'entra_admin_roles.privileged_role_assignment';
|
|
}
|
|
|
|
if ($findingType === Finding::FINDING_TYPE_DRIFT) {
|
|
$policyType = $this->nullableString($evidence['policy_type'] ?? null) ?? 'drift';
|
|
|
|
return match ($policyType) {
|
|
'deviceCompliancePolicy' => 'intune.device_compliance_policy',
|
|
'drift' => 'finding.drift',
|
|
default => 'intune.device_configuration_drift',
|
|
};
|
|
}
|
|
|
|
return $findingType !== '' ? $findingType : null;
|
|
}
|
|
|
|
private function storedReportDetectorKey(string $reportType): ?string
|
|
{
|
|
return match ($reportType) {
|
|
StoredReport::REPORT_TYPE_PERMISSION_POSTURE => 'permission_posture.required_graph_permission',
|
|
StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES => 'entra_admin_roles.privileged_role_assignment',
|
|
default => $this->nullableString($reportType),
|
|
};
|
|
}
|
|
|
|
private function controlKeyForReport(string $reportType): ?string
|
|
{
|
|
$request = match ($reportType) {
|
|
StoredReport::REPORT_TYPE_PERMISSION_POSTURE => new CanonicalControlResolutionRequest(
|
|
provider: 'microsoft',
|
|
consumerContext: 'report',
|
|
subjectFamilyKey: 'permission_posture',
|
|
workload: 'entra',
|
|
signalKey: 'permission_posture.required_graph_permission',
|
|
),
|
|
StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES => new CanonicalControlResolutionRequest(
|
|
provider: 'microsoft',
|
|
consumerContext: 'report',
|
|
subjectFamilyKey: 'entra_admin_roles',
|
|
workload: 'entra',
|
|
signalKey: 'entra_admin_roles.privileged_role_assignment',
|
|
),
|
|
default => new CanonicalControlResolutionRequest(
|
|
provider: 'microsoft',
|
|
consumerContext: 'report',
|
|
subjectFamilyKey: $reportType,
|
|
),
|
|
};
|
|
|
|
return $this->controlKeyFromResolution($this->canonicalControlResolver->resolve($request)->toArray());
|
|
}
|
|
|
|
private function controlKeyForInventoryPolicyType(?string $policyType): ?string
|
|
{
|
|
if ($policyType === null) {
|
|
return null;
|
|
}
|
|
|
|
$request = new CanonicalControlResolutionRequest(
|
|
provider: 'microsoft',
|
|
consumerContext: 'evidence',
|
|
subjectFamilyKey: $policyType,
|
|
workload: str_contains($policyType, 'conditionalAccess') ? 'entra' : 'intune',
|
|
signalKey: match ($policyType) {
|
|
'deviceCompliancePolicy' => 'intune.device_compliance_policy',
|
|
'conditionalAccessPolicy' => 'conditional_access.policy_state',
|
|
default => 'intune.device_configuration_drift',
|
|
},
|
|
);
|
|
|
|
return $this->controlKeyFromResolution($this->canonicalControlResolver->resolve($request)->toArray());
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $summaryPayload
|
|
*/
|
|
private function controlKeyForEvidenceDimension(?string $dimensionKey, array $summaryPayload): ?string
|
|
{
|
|
$direct = $this->nullableString(Arr::get($summaryPayload, 'source_descriptor.control_key'))
|
|
?? $this->nullableString(Arr::get($summaryPayload, 'control_key'));
|
|
|
|
if ($direct !== null) {
|
|
return $direct;
|
|
}
|
|
|
|
$control = collect(Arr::wrap($summaryPayload['canonical_controls'] ?? []))
|
|
->first(static fn (mixed $item): bool => is_array($item) && filled($item['control_key'] ?? null));
|
|
|
|
if (is_array($control)) {
|
|
return $this->nullableString($control['control_key'] ?? null);
|
|
}
|
|
|
|
return match ($dimensionKey) {
|
|
'permission_posture' => $this->controlKeyForReport(StoredReport::REPORT_TYPE_PERMISSION_POSTURE),
|
|
'entra_admin_roles' => $this->controlKeyForReport(StoredReport::REPORT_TYPE_ENTRA_ADMIN_ROLES),
|
|
default => null,
|
|
};
|
|
}
|
|
|
|
private function sourceKind(mixed $value): string
|
|
{
|
|
if (is_string($value) && ArtifactSourceTaxonomy::isSourceKind($value)) {
|
|
return $value;
|
|
}
|
|
|
|
return ArtifactSourceTaxonomy::SOURCE_KIND_MODEL_SUMMARY;
|
|
}
|
|
|
|
private function sourceFamilyForEvidenceItem(string $sourceKind, ?string $sourceRecordType, ?string $dimensionKey): string
|
|
{
|
|
if ($sourceKind === ArtifactSourceTaxonomy::SOURCE_KIND_STORED_REPORT || $sourceRecordType === StoredReport::class || $sourceRecordType === 'stored_report') {
|
|
return ArtifactSourceTaxonomy::SOURCE_FAMILY_STORED_REPORT;
|
|
}
|
|
|
|
if ($sourceKind === ArtifactSourceTaxonomy::SOURCE_KIND_OPERATION_ROLLUP || $sourceRecordType === OperationRun::class || $sourceRecordType === 'operation_run') {
|
|
return ArtifactSourceTaxonomy::SOURCE_FAMILY_OPERATION_RUN;
|
|
}
|
|
|
|
if ($sourceKind === ArtifactSourceTaxonomy::SOURCE_KIND_INVENTORY_PROJECTION || $sourceRecordType === InventoryItem::class || $sourceRecordType === 'inventory_item') {
|
|
return ArtifactSourceTaxonomy::SOURCE_FAMILY_INVENTORY;
|
|
}
|
|
|
|
if ($sourceRecordType === Finding::class || $sourceRecordType === 'finding' || in_array($dimensionKey, ['findings_summary', 'baseline_drift_posture'], true)) {
|
|
return ArtifactSourceTaxonomy::SOURCE_FAMILY_FINDING;
|
|
}
|
|
|
|
return ArtifactSourceTaxonomy::SOURCE_FAMILY_EVIDENCE_SNAPSHOT;
|
|
}
|
|
|
|
private function sourceTargetKindForEvidenceItem(string $sourceKind, ?string $sourceRecordType): string
|
|
{
|
|
if ($sourceKind === ArtifactSourceTaxonomy::SOURCE_KIND_OPERATION_ROLLUP || $sourceRecordType === OperationRun::class || $sourceRecordType === 'operation_run') {
|
|
return ArtifactSourceTaxonomy::SOURCE_TARGET_OPERATION_RUN;
|
|
}
|
|
|
|
if ($sourceRecordType === ProviderConnection::class || $sourceRecordType === 'provider_connection') {
|
|
return ArtifactSourceTaxonomy::SOURCE_TARGET_PROVIDER_CONNECTION;
|
|
}
|
|
|
|
return ArtifactSourceTaxonomy::SOURCE_TARGET_MANAGED_ENVIRONMENT;
|
|
}
|
|
|
|
private function sourceTargetIdentifier(string $sourceTargetKind, ?string $sourceRecordId, int $managedEnvironmentId): ?string
|
|
{
|
|
return match ($sourceTargetKind) {
|
|
ArtifactSourceTaxonomy::SOURCE_TARGET_OPERATION_RUN,
|
|
ArtifactSourceTaxonomy::SOURCE_TARGET_PROVIDER_CONNECTION,
|
|
ArtifactSourceTaxonomy::SOURCE_TARGET_GOVERNED_SUBJECT => $sourceRecordId,
|
|
default => (string) $managedEnvironmentId,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $payload
|
|
*/
|
|
private function providerKey(array $payload): string
|
|
{
|
|
return $this->firstString([
|
|
Arr::get($payload, 'source_descriptor.provider_key'),
|
|
Arr::get($payload, 'provider_key'),
|
|
Arr::get($payload, 'provider'),
|
|
]) ?? 'microsoft';
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $payload
|
|
*/
|
|
private function providerConnectionId(?ManagedEnvironment $tenant, array $payload): ?int
|
|
{
|
|
$value = Arr::get($payload, 'source_descriptor.provider_connection_id')
|
|
?? Arr::get($payload, 'provider_connection_id')
|
|
?? Arr::get($payload, 'providerConnectionId')
|
|
?? Arr::get($payload, 'provider_connection.id');
|
|
|
|
if (is_numeric($value)) {
|
|
return (int) $value;
|
|
}
|
|
|
|
if (! $tenant instanceof ManagedEnvironment || ! $tenant->exists) {
|
|
return null;
|
|
}
|
|
|
|
$connectionId = $tenant->providerConnections()
|
|
->where('provider', $this->providerKey($payload))
|
|
->where('is_default', true)
|
|
->value('id');
|
|
|
|
return is_numeric($connectionId) ? (int) $connectionId : null;
|
|
}
|
|
|
|
/**
|
|
* @param list<mixed> $values
|
|
*/
|
|
private function firstString(array $values): ?string
|
|
{
|
|
foreach ($values as $value) {
|
|
$string = $this->nullableString($value);
|
|
|
|
if ($string !== null) {
|
|
return $string;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private function nullableString(mixed $value): ?string
|
|
{
|
|
if ($value instanceof Model && $value->getKey() !== null) {
|
|
return (string) $value->getKey();
|
|
}
|
|
|
|
if (! is_scalar($value)) {
|
|
return null;
|
|
}
|
|
|
|
$value = trim((string) $value);
|
|
|
|
return $value === '' ? null : $value;
|
|
}
|
|
}
|