修复相关漏洞

This commit is contained in:
fuenmao
2026-09-17 09:53:50 +08:00
parent 20ea1bc70c
commit e9894538e8
28 changed files with 757 additions and 51 deletions
@@ -48,6 +48,12 @@ public class OnboardingElectronicSignService : IOnboardingElectronicSignService
/// <inheritdoc />
public async Task<OnboardingElectronicSignDetailOutput> Initiate(
OnboardingElectronicSignTaskInput input)
{
return await OnboardingConcurrency.RunTask(_db, input.TaskId, () => InitiateCore(input));
}
private async Task<OnboardingElectronicSignDetailOutput> InitiateCore(
OnboardingElectronicSignTaskInput input)
{
var task = await GetAuthorizedTask(input.TaskId);
ValidateTaskCanSign(task);
@@ -99,6 +105,12 @@ public class OnboardingElectronicSignService : IOnboardingElectronicSignService
/// <inheritdoc />
public async Task<OnboardingElectronicSignDetailOutput> Reinitiate(
OnboardingElectronicSignReinitiateInput input)
{
return await OnboardingConcurrency.RunTask(_db, input.TaskId, () => ReinitiateCore(input));
}
private async Task<OnboardingElectronicSignDetailOutput> ReinitiateCore(
OnboardingElectronicSignReinitiateInput input)
{
var task = await GetAuthorizedTask(input.TaskId);
ValidateTaskCanSign(task);
@@ -256,6 +268,12 @@ public class OnboardingElectronicSignService : IOnboardingElectronicSignService
/// <inheritdoc />
public async Task<OnboardingElectronicSignDetailOutput> Sync(
OnboardingElectronicSignTaskInput input)
{
return await OnboardingConcurrency.RunTask(_db, input.TaskId, () => SyncCore(input));
}
private async Task<OnboardingElectronicSignDetailOutput> SyncCore(
OnboardingElectronicSignTaskInput input)
{
var task = await GetAuthorizedTask(input.TaskId);
var process = await GetRequiredProcess(task.Id);
@@ -266,6 +284,12 @@ public class OnboardingElectronicSignService : IOnboardingElectronicSignService
/// <inheritdoc />
public async Task<OnboardingElectronicSignDetailOutput> GetSignLink(
OnboardingElectronicSignTaskInput input)
{
return await OnboardingConcurrency.RunTask(_db, input.TaskId, () => GetSignLinkCore(input));
}
private async Task<OnboardingElectronicSignDetailOutput> GetSignLinkCore(
OnboardingElectronicSignTaskInput input)
{
var task = await GetAuthorizedTask(input.TaskId);
var process = await GetRequiredProcess(task.Id);
@@ -289,6 +313,12 @@ public class OnboardingElectronicSignService : IOnboardingElectronicSignService
/// <inheritdoc />
public async Task<OnboardingElectronicSignPreviewOutput> PreviewEmployeeSignedDocument(
OnboardingElectronicSignPreviewInput input)
{
return await OnboardingConcurrency.RunTask(_db, input.TaskId, () => PreviewEmployeeSignedDocumentCore(input));
}
private async Task<OnboardingElectronicSignPreviewOutput> PreviewEmployeeSignedDocumentCore(
OnboardingElectronicSignPreviewInput input)
{
var task = await GetTask(input.TaskId);
var process = await GetRequiredProcess(task.Id);
@@ -346,6 +376,12 @@ public class OnboardingElectronicSignService : IOnboardingElectronicSignService
/// <inheritdoc />
public async Task<OnboardingElectronicSignDetailOutput> CompanySeal(
OnboardingElectronicSignTaskInput input)
{
return await OnboardingConcurrency.RunTask(_db, input.TaskId, () => CompanySealCore(input));
}
private async Task<OnboardingElectronicSignDetailOutput> CompanySealCore(
OnboardingElectronicSignTaskInput input)
{
var task = await GetAuthorizedTask(input.TaskId);
var process = await GetRequiredProcess(task.Id);
@@ -378,7 +414,29 @@ public class OnboardingElectronicSignService : IOnboardingElectronicSignService
$"请先逐份查看员工已签文件:{string.Join("、", notPreviewed)}");
}
// 发出前持久化占用。进程退出/响应丢失也不能自动重发可能已送达的盖章命令。
var alreadySubmitted = await _db.Queryable<ObElectronicSignEvent>()
.AnyAsync(item => item.SignProcessId == process.Id
&& item.EventSource == AdminCompanySealEventSource
&& (item.EventType == "company_seal_submitted"
|| item.EventType == "company_seal_dispatch_started"));
if (alreadySubmitted)
throw Oops.Bah("企业盖章命令已发送或结果待确认,请先刷新签署状态并核实契约锁平台,系统不会重复盖章");
var companyName = await GetCompanyLegalName(task);
await RecordAdminOperation(
process,
AdminCompanySealEventSource,
"company_seal_dispatch_started",
JsonSerializer.Serialize(new
{
process.ProviderContractId,
companyName,
process.SealId,
operatorUserId = UserManager.UserId,
operatorName = UserManager.Name
}),
"已占用盖章请求;发送结果待确认,禁止自动重复发送");
try
{
await _qiyuesuoClient.CompanySign(
@@ -406,10 +464,10 @@ public class OnboardingElectronicSignService : IOnboardingElectronicSignService
return await BuildDetail(task, process);
}
process.LastError = $"企业盖章失败:{LimitError(signException.Message)}";
process.LastError = $"企业盖章结果待核实,已阻止重复发送:{LimitError(signException.Message)}";
process.UpdateTime = DateTime.Now;
await _db.Updateable(process).ExecuteCommandAsync();
throw;
throw Oops.Bah("企业盖章未确认成功,请刷新状态并核实契约锁平台;为避免重复盖章,不能直接重发。原因:" + LimitError(signException.Message));
}
await RecordAdminOperation(
@@ -441,6 +499,11 @@ public class OnboardingElectronicSignService : IOnboardingElectronicSignService
/// <inheritdoc />
public async Task Notice(OnboardingElectronicSignTaskInput input)
{
await OnboardingConcurrency.RunTask(_db, input.TaskId, () => NoticeCore(input));
}
private async Task NoticeCore(OnboardingElectronicSignTaskInput input)
{
var task = await GetAuthorizedTask(input.TaskId);
var process = await GetRequiredProcess(task.Id);
@@ -455,6 +518,11 @@ public class OnboardingElectronicSignService : IOnboardingElectronicSignService
/// <inheritdoc />
public async Task Invalid(OnboardingElectronicSignInvalidInput input)
{
await OnboardingConcurrency.RunTask(_db, input.TaskId, () => InvalidCore(input));
}
private async Task InvalidCore(OnboardingElectronicSignInvalidInput input)
{
var task = await GetAuthorizedTask(input.TaskId);
var process = await GetRequiredProcess(task.Id);
@@ -478,6 +546,21 @@ public class OnboardingElectronicSignService : IOnboardingElectronicSignService
/// <inheritdoc />
public async Task HandleCallback(string payloadJson)
{
if (string.IsNullOrWhiteSpace(payloadJson))
throw Oops.Bah("契约锁回调内容为空");
using var json = JsonDocument.Parse(payloadJson);
var process = await FindProcess(FindString(json.RootElement, "contractId"), FindString(json.RootElement, "bizId"));
if (process == null)
{
await HandleCallbackCore(payloadJson);
return;
}
// 只在锁外定位任务;锁内重新读取事件和流程,重复回调不会同时执行归档。
await OnboardingConcurrency.RunTask(_db, process.TaskId, () => HandleCallbackCore(payloadJson, taskLockHeld: true));
}
private async Task HandleCallbackCore(string payloadJson, bool taskLockHeld = false)
{
if (string.IsNullOrWhiteSpace(payloadJson))
throw Oops.Bah("契约锁回调内容为空");
@@ -504,6 +587,13 @@ public class OnboardingElectronicSignService : IOnboardingElectronicSignService
"contractStatus",
"status");
var process = await FindProcess(providerContractId, bizId);
// 回调首次定位时流程可能还在创建;若此刻才匹配到,先取得任务锁再重新执行。
if (process != null && !taskLockHeld)
{
await OnboardingConcurrency.RunTask(_db, process.TaskId,
() => HandleCallbackCore(payloadJson, taskLockHeld: true));
return;
}
if (callbackEvent == null)
{
callbackEvent = new ObElectronicSignEvent
@@ -549,7 +639,7 @@ public class OnboardingElectronicSignService : IOnboardingElectronicSignService
try
{
process.LastCallbackAt = DateTime.Now;
await _db.Updateable(process).ExecuteCommandAsync();
await _db.Updateable(process).UpdateColumns(item => item.LastCallbackAt).ExecuteCommandAsync();
// 明文回调只作为刷新触发器,最终状态重新向契约锁查询,伪造回调不能改变业务状态。
await SyncProcess(process, "callback");
callbackEvent.ProviderStatus = process.ProviderStatus;
@@ -939,7 +1029,7 @@ public class OnboardingElectronicSignService : IOnboardingElectronicSignService
ObElectronicSignProcess process,
QiyuesuoContractResult providerContract)
{
var localStatus = ResolveSignStatus(providerContract);
var localStatus = OnboardingSignStateGuard.Resolve(process.SignStatus, ResolveSignStatus(providerContract));
process.ProviderStatus = providerContract.Status;
process.SignStatus = localStatus;
if (localStatus == ElectronicSignStatus.Completed
@@ -950,7 +1040,8 @@ public class OnboardingElectronicSignService : IOnboardingElectronicSignService
process.SignUrlExpireAt = null;
await _db.Updateable<ObTaskContract>()
.SetColumns(item => item.ContractStatus == TaskContractStatus.ElectronicSigned)
.Where(item => item.TaskId == process.TaskId)
.Where(item => item.TaskId == process.TaskId && process.IsCurrent == true
&& item.ContractStatus != TaskContractStatus.Archived)
.ExecuteCommandAsync();
}
else if (IsTerminal(localStatus))
@@ -974,24 +1065,25 @@ public class OnboardingElectronicSignService : IOnboardingElectronicSignService
foreach (var document in documents)
{
if (document.SignedPdfFileId.HasValue)
continue;
var signedPdf = await _qiyuesuoClient.DownloadDocument(
document.ProviderDocumentId);
var pdfFileId = await _fileService.StoreGeneratedFile(
SysDictConst.FILE_ENGINE_ALIYUN_OSS,
$"{SafeFileName(process.BizId)}_{document.DocumentSort:D2}_{SafeFileName(document.DocumentName)}_已签署.pdf",
signedPdf);
document.SignedPdfFileId = pdfFileId;
document.UpdateTime = DateTime.Now;
await _db.Updateable(document).ExecuteCommandAsync();
if (!document.SignedPdfFileId.HasValue)
{
var signedPdf = await _qiyuesuoClient.DownloadDocument(document.ProviderDocumentId);
document.SignedPdfFileId = await _fileService.StoreGeneratedFile(
SysDictConst.FILE_ENGINE_ALIYUN_OSS,
$"{SafeFileName(process.BizId)}_{document.DocumentSort:D2}_{SafeFileName(document.DocumentName)}_已签署.pdf",
signedPdf);
document.UpdateTime = DateTime.Now;
await _db.Updateable(document).ExecuteCommandAsync();
}
// 中断重试时仍回填合同指针,不能因文档已落库而跳过未完成的关联更新。
var pdfFileId = document.SignedPdfFileId.Value;
await _db.Updateable<ObTaskContract>()
.SetColumns(item => new ObTaskContract
{
GeneratedPdfFileId = pdfFileId,
ContractStatus = TaskContractStatus.Archived
})
.Where(item => item.Id == document.TaskContractId)
.Where(item => item.Id == document.TaskContractId && process.IsCurrent == true)
.ExecuteCommandAsync();
}
@@ -0,0 +1,23 @@
namespace SimpleAdmin.Application;
/// <summary>同步第三方状态时的单向迁移保护;未知/陈旧结果不能覆盖已确认的签署进度。</summary>
internal static class OnboardingSignStateGuard
{
internal static string Resolve(string current, string incoming)
{
if (current == ElectronicSignStatus.Archived && incoming == ElectronicSignStatus.Completed)
return ElectronicSignStatus.Archived;
if (IsTerminal(current) && current != incoming)
throw Oops.Bah("契约锁返回状态与本地终态不一致,已保留本地状态,请核实后重试");
if (current == ElectronicSignStatus.PendingCompanySeal
&& incoming is not (ElectronicSignStatus.PendingCompanySeal
or ElectronicSignStatus.Completed or ElectronicSignStatus.Archived
or ElectronicSignStatus.Rejected or ElectronicSignStatus.Invalid or ElectronicSignStatus.Expired))
throw Oops.Bah("契约锁状态暂未同步,已保留员工签署结果,请稍后重试");
return incoming;
}
private static bool IsTerminal(string status)
=> status is ElectronicSignStatus.Completed or ElectronicSignStatus.Archived
or ElectronicSignStatus.Rejected or ElectronicSignStatus.Invalid or ElectronicSignStatus.Expired;
}