Add PHP Analyzer Plugin and Composer Lock Data Handling
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
Some checks failed
Docs CI / lint-and-preview (push) Has been cancelled
- Implemented the PhpAnalyzerPlugin to analyze PHP projects. - Created ComposerLockData class to represent data from composer.lock files. - Developed ComposerLockReader to load and parse composer.lock files asynchronously. - Introduced ComposerPackage class to encapsulate package details. - Added PhpPackage class to represent PHP packages with metadata and evidence. - Implemented PhpPackageCollector to gather packages from ComposerLockData. - Created PhpLanguageAnalyzer to perform analysis and emit results. - Added capability signals for known PHP frameworks and CMS. - Developed unit tests for the PHP language analyzer and its components. - Included sample composer.lock and expected output for testing. - Updated project files for the new PHP analyzer library and tests.
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Findings.Ledger.WebService.Contracts;
|
||||
|
||||
public sealed record AirgapImportRequest
|
||||
{
|
||||
[JsonPropertyName("bundleId")]
|
||||
public required string BundleId { get; init; }
|
||||
|
||||
[JsonPropertyName("mirrorGeneration")]
|
||||
public string? MirrorGeneration { get; init; }
|
||||
|
||||
[JsonPropertyName("merkleRoot")]
|
||||
public required string MerkleRoot { get; init; }
|
||||
|
||||
[JsonPropertyName("timeAnchor")]
|
||||
public required DateTimeOffset TimeAnchor { get; init; }
|
||||
|
||||
[JsonPropertyName("publisher")]
|
||||
public string? Publisher { get; init; }
|
||||
|
||||
[JsonPropertyName("hashAlgorithm")]
|
||||
public string? HashAlgorithm { get; init; }
|
||||
|
||||
[JsonPropertyName("contents")]
|
||||
public string[] Contents { get; init; } = Array.Empty<string>();
|
||||
|
||||
[JsonPropertyName("importOperator")]
|
||||
public string? ImportOperator { get; init; }
|
||||
}
|
||||
|
||||
public sealed record AirgapImportResponse(
|
||||
Guid ChainId,
|
||||
long? Sequence,
|
||||
Guid? LedgerEventId,
|
||||
string Status,
|
||||
string? Error);
|
||||
@@ -0,0 +1,37 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace StellaOps.Findings.Ledger.WebService.Contracts;
|
||||
|
||||
public sealed record OrchestratorExportRequest
|
||||
{
|
||||
[JsonPropertyName("runId")]
|
||||
public required Guid RunId { get; init; }
|
||||
|
||||
[JsonPropertyName("jobType")]
|
||||
public required string JobType { get; init; }
|
||||
|
||||
[JsonPropertyName("artifactHash")]
|
||||
public required string ArtifactHash { get; init; }
|
||||
|
||||
[JsonPropertyName("policyHash")]
|
||||
public required string PolicyHash { get; init; }
|
||||
|
||||
[JsonPropertyName("startedAt")]
|
||||
public required DateTimeOffset StartedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("completedAt")]
|
||||
public DateTimeOffset? CompletedAt { get; init; }
|
||||
|
||||
[JsonPropertyName("status")]
|
||||
public required string Status { get; init; }
|
||||
|
||||
[JsonPropertyName("manifestPath")]
|
||||
public string? ManifestPath { get; init; }
|
||||
|
||||
[JsonPropertyName("logsPath")]
|
||||
public string? LogsPath { get; init; }
|
||||
}
|
||||
|
||||
public sealed record OrchestratorExportResponse(
|
||||
Guid RunId,
|
||||
string MerkleRoot);
|
||||
@@ -12,6 +12,7 @@ using StellaOps.Configuration;
|
||||
using StellaOps.DependencyInjection;
|
||||
using StellaOps.Findings.Ledger.Domain;
|
||||
using StellaOps.Findings.Ledger.Infrastructure;
|
||||
using StellaOps.Findings.Ledger.Infrastructure.AirGap;
|
||||
using StellaOps.Findings.Ledger.Infrastructure.Merkle;
|
||||
using StellaOps.Findings.Ledger.Infrastructure.Postgres;
|
||||
using StellaOps.Findings.Ledger.Infrastructure.Projection;
|
||||
@@ -140,6 +141,10 @@ builder.Services.AddSingleton<PolicyEngineEvaluationService>();
|
||||
builder.Services.AddSingleton<IPolicyEvaluationService>(sp => sp.GetRequiredService<PolicyEngineEvaluationService>());
|
||||
builder.Services.AddSingleton<ILedgerEventWriteService, LedgerEventWriteService>();
|
||||
builder.Services.AddSingleton<IFindingWorkflowService, FindingWorkflowService>();
|
||||
builder.Services.AddSingleton<IOrchestratorExportRepository, PostgresOrchestratorExportRepository>();
|
||||
builder.Services.AddSingleton<OrchestratorExportService>();
|
||||
builder.Services.AddSingleton<IAirgapImportRepository, PostgresAirgapImportRepository>();
|
||||
builder.Services.AddSingleton<AirgapImportService>();
|
||||
builder.Services.AddSingleton<IAttachmentEncryptionService, AttachmentEncryptionService>();
|
||||
builder.Services.AddSingleton<IAttachmentUrlSigner, AttachmentUrlSigner>();
|
||||
builder.Services.AddSingleton<IConsoleCsrfValidator, ConsoleCsrfValidator>();
|
||||
@@ -300,6 +305,95 @@ app.MapGet("/ledger/export/sboms", () => TypedResults.Json(new ExportPage<SbomEx
|
||||
.RequireAuthorization(LedgerExportPolicy)
|
||||
.Produces(StatusCodes.Status200OK);
|
||||
|
||||
app.MapPost("/internal/ledger/orchestrator-export", async Task<Results<Accepted<OrchestratorExportResponse>, ProblemHttpResult>> (
|
||||
HttpContext httpContext,
|
||||
OrchestratorExportRequest request,
|
||||
OrchestratorExportService service,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (!httpContext.Request.Headers.TryGetValue("X-Stella-Tenant", out var tenantValues) || string.IsNullOrWhiteSpace(tenantValues))
|
||||
{
|
||||
return TypedResults.Problem(statusCode: StatusCodes.Status400BadRequest, title: "missing_tenant");
|
||||
}
|
||||
|
||||
var tenantId = tenantValues.ToString();
|
||||
var input = new OrchestratorExportInput(
|
||||
tenantId,
|
||||
request.RunId,
|
||||
request.JobType,
|
||||
request.ArtifactHash,
|
||||
request.PolicyHash,
|
||||
request.StartedAt,
|
||||
request.CompletedAt,
|
||||
request.Status,
|
||||
request.ManifestPath,
|
||||
request.LogsPath);
|
||||
|
||||
var record = await service.RecordAsync(input, cancellationToken).ConfigureAwait(false);
|
||||
var response = new OrchestratorExportResponse(record.RunId, record.MerkleRoot);
|
||||
return TypedResults.Accepted($"/internal/ledger/orchestrator-export/{record.RunId}", response);
|
||||
})
|
||||
.WithName("OrchestratorExportRecord")
|
||||
.RequireAuthorization(LedgerWritePolicy)
|
||||
.Produces(StatusCodes.Status202Accepted)
|
||||
.ProducesProblem(StatusCodes.Status400BadRequest);
|
||||
|
||||
app.MapGet("/internal/ledger/orchestrator-export/{artifactHash}", async Task<Results<JsonHttpResult<IReadOnlyList<OrchestratorExportRecord>>, ProblemHttpResult>> (
|
||||
HttpContext httpContext,
|
||||
string artifactHash,
|
||||
OrchestratorExportService service,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (!httpContext.Request.Headers.TryGetValue("X-Stella-Tenant", out var tenantValues) || string.IsNullOrWhiteSpace(tenantValues))
|
||||
{
|
||||
return TypedResults.Problem(statusCode: StatusCodes.Status400BadRequest, title: "missing_tenant");
|
||||
}
|
||||
|
||||
var records = await service.GetByArtifactAsync(tenantValues.ToString(), artifactHash, cancellationToken).ConfigureAwait(false);
|
||||
return TypedResults.Json(records);
|
||||
})
|
||||
.WithName("OrchestratorExportQuery")
|
||||
.RequireAuthorization(LedgerExportPolicy)
|
||||
.Produces(StatusCodes.Status200OK)
|
||||
.ProducesProblem(StatusCodes.Status400BadRequest);
|
||||
|
||||
app.MapPost("/internal/ledger/airgap-import", async Task<Results<Accepted<AirgapImportResponse>, ProblemHttpResult>> (
|
||||
HttpContext httpContext,
|
||||
AirgapImportRequest request,
|
||||
AirgapImportService service,
|
||||
CancellationToken cancellationToken) =>
|
||||
{
|
||||
if (!httpContext.Request.Headers.TryGetValue("X-Stella-Tenant", out var tenantValues) || string.IsNullOrWhiteSpace(tenantValues))
|
||||
{
|
||||
return TypedResults.Problem(statusCode: StatusCodes.Status400BadRequest, title: "missing_tenant");
|
||||
}
|
||||
|
||||
var input = new AirgapImportInput(
|
||||
tenantValues.ToString(),
|
||||
request.BundleId,
|
||||
request.MirrorGeneration,
|
||||
request.MerkleRoot,
|
||||
request.TimeAnchor,
|
||||
request.Publisher,
|
||||
request.HashAlgorithm,
|
||||
request.Contents ?? Array.Empty<string>(),
|
||||
request.ImportOperator);
|
||||
|
||||
var result = await service.RecordAsync(input, cancellationToken).ConfigureAwait(false);
|
||||
if (!result.Success)
|
||||
{
|
||||
return TypedResults.Problem(statusCode: StatusCodes.Status409Conflict, title: "airgap_import_failed", detail: result.Error ?? "Failed to record air-gap import.");
|
||||
}
|
||||
|
||||
var response = new AirgapImportResponse(result.ChainId, result.SequenceNumber, result.LedgerEventId, "accepted", null);
|
||||
return TypedResults.Accepted($"/internal/ledger/airgap-import/{request.BundleId}", response);
|
||||
})
|
||||
.WithName("AirgapImportRecord")
|
||||
.RequireAuthorization(LedgerWritePolicy)
|
||||
.Produces(StatusCodes.Status202Accepted)
|
||||
.ProducesProblem(StatusCodes.Status400BadRequest)
|
||||
.ProducesProblem(StatusCodes.Status409Conflict);
|
||||
|
||||
app.Run();
|
||||
|
||||
static Created<LedgerEventResponse> CreateCreatedResponse(LedgerEventRecord record)
|
||||
|
||||
@@ -214,7 +214,7 @@ public sealed class AttestationQueryService
|
||||
sqlBuilder.Append(" LIMIT @take");
|
||||
parameters.Add(new NpgsqlParameter<int>("take", request.Limit + 1) { NpgsqlDbType = NpgsqlDbType.Integer });
|
||||
|
||||
await using var connection = await _dataSource.OpenConnectionAsync(request.TenantId, cancellationToken).ConfigureAwait(false);
|
||||
await using var connection = await _dataSource.OpenConnectionAsync(request.TenantId, "attestation", cancellationToken).ConfigureAwait(false);
|
||||
await using var command = new NpgsqlCommand(sqlBuilder.ToString(), connection)
|
||||
{
|
||||
CommandTimeout = _dataSource.CommandTimeoutSeconds
|
||||
|
||||
@@ -168,7 +168,7 @@ public sealed class ExportQueryService
|
||||
NpgsqlDbType = NpgsqlDbType.Integer
|
||||
});
|
||||
|
||||
await using var connection = await _dataSource.OpenConnectionAsync(request.TenantId, cancellationToken).ConfigureAwait(false);
|
||||
await using var connection = await _dataSource.OpenConnectionAsync(request.TenantId, "export", cancellationToken).ConfigureAwait(false);
|
||||
await using var command = new NpgsqlCommand(sqlBuilder.ToString(), connection)
|
||||
{
|
||||
CommandTimeout = _dataSource.CommandTimeoutSeconds
|
||||
|
||||
Reference in New Issue
Block a user