Skip to content

Commit

Permalink
feat(revocation): add endpoints to revoke credentials
Browse files Browse the repository at this point in the history
* add endpoint for issuer to revoke a credential
* add endpoint for holder to revoke a credential
* add logic to revoke credentials when they are expired

Refs: #14 #15 #16
  • Loading branch information
Phil91 committed Apr 24, 2024
1 parent 6ff37be commit 1c333d1
Show file tree
Hide file tree
Showing 66 changed files with 3,742 additions and 233 deletions.
1 change: 0 additions & 1 deletion DEPENDENCIES
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ nuget/nuget/-/JsonSchema.Net/6.0.3, MIT AND OFL-1.1 AND CC-BY-SA-4.0, approved,
nuget/nuget/-/Laraue.EfCoreTriggers.Common/7.1.0, MIT, approved, #10247
nuget/nuget/-/Laraue.EfCoreTriggers.PostgreSql/7.1.0, MIT, approved, #10248
nuget/nuget/-/Mono.TextTemplating/2.2.1, MIT, approved, clearlydefined
nuget/nuget/-/Newtonsoft.Json/12.0.2, MIT AND BSD-3-Clause, approved, #11114
nuget/nuget/-/Newtonsoft.Json/13.0.1, MIT AND BSD-3-Clause, approved, #3266
nuget/nuget/-/Newtonsoft.Json/13.0.3, MIT AND BSD-3-Clause, approved, #3266
nuget/nuget/-/Npgsql.EntityFrameworkCore.PostgreSQL/7.0.11, PostgreSQL AND MIT AND Apache-2.0, approved, #10081
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,15 @@ public async Task ExecuteAsync(CancellationToken stoppingToken)

var now = dateTimeProvider.OffsetNow;
var companySsiDetailsRepository = repositories.GetInstance<ICompanySsiDetailsRepository>();
var processStepRepository = repositories.GetInstance<IProcessStepRepository>();
var inactiveVcsToDelete = now.AddDays(-(_settings.InactiveVcsToDeleteInWeeks * 7));
var expiredVcsToDelete = now.AddMonths(-_settings.ExpiredVcsToDeleteInMonth);

var credentials = outerLoopRepositories.GetInstance<ICompanySsiDetailsRepository>()
.GetExpiryData(now, inactiveVcsToDelete, expiredVcsToDelete);
await foreach (var credential in credentials.WithCancellation(stoppingToken).ConfigureAwait(false))
{
await ProcessCredentials(credential, companySsiDetailsRepository, repositories, portalService,
stoppingToken).ConfigureAwait(false);
await ProcessCredentials(credential, companySsiDetailsRepository, repositories, portalService, processStepRepository, stoppingToken).ConfigureAwait(false);
}
}
catch (Exception ex)
Expand All @@ -104,6 +104,7 @@ private static async Task ProcessCredentials(
ICompanySsiDetailsRepository companySsiDetailsRepository,
IIssuerRepositories repositories,
IPortalService portalService,
IProcessStepRepository processStepRepository,
CancellationToken cancellationToken)
{
if (data.ScheduleData.IsVcToDelete)
Expand All @@ -112,7 +113,7 @@ private static async Task ProcessCredentials(
}
else if (data.ScheduleData.IsVcToDecline)
{
await HandleDecline(data, companySsiDetailsRepository, portalService, cancellationToken).ConfigureAwait(false);
HandleDecline(data.Id, companySsiDetailsRepository, processStepRepository);
}
else
{
Expand All @@ -123,34 +124,21 @@ private static async Task ProcessCredentials(
await repositories.SaveAsync().ConfigureAwait(false);
}

private static async ValueTask HandleDecline(
CredentialExpiryData data,
private static void HandleDecline(
Guid credentialId,
ICompanySsiDetailsRepository companySsiDetailsRepository,
IPortalService portalService,
CancellationToken cancellationToken)
IProcessStepRepository processStepRepository)
{
companySsiDetailsRepository.AttachAndModifyCompanySsiDetails(data.Id, c =>
var processId = processStepRepository.CreateProcess(ProcessTypeId.DECLINE_CREDENTIAL).Id;
processStepRepository.CreateProcessStep(ProcessStepTypeId.REVOKE_CREDENTIAL, ProcessStepStatusId.TODO, processId);
companySsiDetailsRepository.AttachAndModifyCompanySsiDetails(credentialId, c =>
{
c.CompanySsiDetailStatusId = data.CompanySsiDetailStatusId;
c.ProcessId = null;
},
c =>
{
c.CompanySsiDetailStatusId = CompanySsiDetailStatusId.INACTIVE;
c.ProcessId = processId;
});

if (Guid.TryParse(data.RequesterId, out var requesterId))
{
var content = JsonSerializer.Serialize(new { Type = data.VerifiedCredentialTypeId, CredentialId = data.Id }, Options);
await portalService.AddNotification(content, requesterId, NotificationTypeId.CREDENTIAL_REJECTED, cancellationToken).ConfigureAwait(false);

var typeValue = data.VerifiedCredentialTypeId.GetEnumValue() ?? throw new UnexpectedConditionException($"VerifiedCredentialType {data.VerifiedCredentialTypeId} does not exists");
var mailParameters = new Dictionary<string, string>
{
{ "requestName", typeValue },
{ "reason", "The credential is already expired" }
};
await portalService.TriggerMail("CredentialRejected", requesterId, mailParameters, cancellationToken).ConfigureAwait(false);
}
}

private static async ValueTask HandleNotification(
Expand Down Expand Up @@ -189,15 +177,18 @@ private static async ValueTask HandleNotification(

if (Guid.TryParse(data.RequesterId, out var requesterId))
{
await portalService.AddNotification(content, requesterId, NotificationTypeId.CREDENTIAL_EXPIRY, cancellationToken).ConfigureAwait(false);
var typeValue = data.VerifiedCredentialTypeId.GetEnumValue() ?? throw new UnexpectedConditionException($"VerifiedCredentialType {data.VerifiedCredentialTypeId} does not exists");
var mailParameters = new Dictionary<string, string>
await portalService.AddNotification(content, requesterId, NotificationTypeId.CREDENTIAL_EXPIRY,
cancellationToken);
var typeValue = data.VerifiedCredentialTypeId.GetEnumValue() ??
throw new UnexpectedConditionException(
$"VerifiedCredentialType {data.VerifiedCredentialTypeId} does not exists");
var mailParameters = new MailParameter[]
{
{ "typeId", typeValue },
{ "version", data.DetailVersion ?? "no version" },
{ "expiryDate", data.ExpiryDate?.ToString("dd MMMM yyyy") ?? throw new ConflictException("Expiry Date must be set here") }
new("typeId", typeValue), new("version", data.DetailVersion ?? "no version"),
new("expiryDate",
data.ExpiryDate?.ToString("dd MMMM yyyy") ??
throw new ConflictException("Expiry Date must be set here"))
};

await portalService.TriggerMail("CredentialExpiry", requesterId, mailParameters, cancellationToken).ConfigureAwait(false);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
********************************************************************************/

using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums;
using System.Text.Json;

namespace Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models;

Expand All @@ -27,6 +28,7 @@ public record SsiApprovalData(
Guid? ProcessId,
VerifiedCredentialTypeKindId? Kind,
string? Bpn,
JsonDocument? Schema,
DetailData? DetailData
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,16 +144,16 @@ public Task<bool> CheckSsiDetailsExistsForCompany(string bpnl, VerifiedCredentia
(verifiedCredentialExternalTypeUseCaseDetailId == null || x.VerifiedCredentialExternalTypeDetailVersionId == verifiedCredentialExternalTypeUseCaseDetailId));

/// <inheritdoc />
public Task<(bool Exists, string? Version, string? Template, IEnumerable<string> UseCase, DateTimeOffset Expiry)> CheckCredentialTypeIdExistsForExternalTypeDetailVersionId(Guid verifiedCredentialExternalTypeUseCaseDetailId, VerifiedCredentialTypeId verifiedCredentialTypeId) =>
public Task<(bool Exists, string? Version, string? Template, IEnumerable<VerifiedCredentialExternalTypeId> ExternalTypeIds, DateTimeOffset Expiry)> CheckCredentialTypeIdExistsForExternalTypeDetailVersionId(Guid verifiedCredentialExternalTypeUseCaseDetailId, VerifiedCredentialTypeId verifiedCredentialTypeId) =>
_context.VerifiedCredentialExternalTypeDetailVersions
.Where(x =>
x.Id == verifiedCredentialExternalTypeUseCaseDetailId &&
x.VerifiedCredentialExternalType!.VerifiedCredentialTypeAssignedExternalTypes.Any(y => y.VerifiedCredentialTypeId == verifiedCredentialTypeId))
.Select(x => new ValueTuple<bool, string?, string?, IEnumerable<string>, DateTimeOffset>(
.Select(x => new ValueTuple<bool, string?, string?, IEnumerable<VerifiedCredentialExternalTypeId>, DateTimeOffset>(
true,
x.Version,
x.Template,
x.VerifiedCredentialExternalType!.VerifiedCredentialTypeAssignedExternalTypes.Select(y => y.VerifiedCredentialType!.VerifiedCredentialTypeAssignedUseCase!.UseCase!.Shortname),
x.VerifiedCredentialExternalType!.VerifiedCredentialTypeAssignedExternalTypes.Select(y => y.VerifiedCredentialExternalTypeId),
x.Expiry))
.SingleOrDefaultAsync();

Expand Down Expand Up @@ -188,6 +188,7 @@ public IQueryable<CompanySsiDetail> GetAllCredentialDetails(CompanySsiDetailStat
x.ProcessId,
x.VerifiedCredentialType!.VerifiedCredentialTypeAssignedKind == null ? null : x.VerifiedCredentialType!.VerifiedCredentialTypeAssignedKind!.VerifiedCredentialTypeKindId,
x.Bpnl,
x.CompanySsiProcessData!.Schema,
x.VerifiedCredentialExternalTypeDetailVersion == null ?
null :
new DetailData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using Microsoft.EntityFrameworkCore;
using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models;
using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities;
using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities;
using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums;
using System.Text.Json;

Expand Down Expand Up @@ -76,4 +77,28 @@ public CredentialRepository(IssuerDbContext dbContext)
.Where(x => x.CompanySsiDetailId == credentialId)
.Select(x => new ValueTuple<string, string?>(x.CompanySsiDetail!.Bpnl, x.CallbackUrl))
.SingleOrDefaultAsync();

public Task<(bool Exists, Guid? ExternalCredentialId, CompanySsiDetailStatusId StatusId, IEnumerable<(Guid DocumentId, DocumentStatusId DocumentStatusId)> Documents)> GetRevocationDataById(Guid credentialId) =>
_dbContext.CompanySsiDetails
.Where(x => x.Id == credentialId)
.Select(x => new ValueTuple<bool, Guid?, CompanySsiDetailStatusId, IEnumerable<(Guid, DocumentStatusId)>>(
true,
x.ExternalCredentialId,
x.CompanySsiDetailStatusId,
x.Documents.Select(d => new ValueTuple<Guid, DocumentStatusId>(d.Id, d.DocumentStatusId))))
.SingleOrDefaultAsync();

public void AttachAndModifyCredential(Guid credentialId, Action<CompanySsiDetail>? initialize, Action<CompanySsiDetail> modify)
{
var entity = new CompanySsiDetail(credentialId, string.Empty, default!, default!, null!, null!, default!);
initialize?.Invoke(entity);
_dbContext.CompanySsiDetails.Attach(entity);
modify(entity);
}

public Task<(VerifiedCredentialTypeId TypeId, string RequesterId)> GetCredentialNotificationData(Guid credentialId) =>
_dbContext.CompanySsiDetails
.Where(x => x.Id == credentialId)
.Select(x => new ValueTuple<VerifiedCredentialTypeId, string>(x.VerifiedCredentialTypeId, x.CreatorUserId))
.SingleOrDefaultAsync();
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,17 @@ public void AssignDocumentToCompanySsiDetails(Guid documentId, Guid companySsiDe
var document = new CompanySsiDetailAssignedDocument(documentId, companySsiDetailId);
_dbContext.CompanySsiDetailAssignedDocuments.Add(document);
}

public void AttachAndModifyDocuments(IEnumerable<(Guid DocumentId, Action<Document>? Initialize, Action<Document> Modify)> documentData)
{
var initial = documentData.Select(x =>
{
var document = new Document(x.DocumentId, null!, null!, null!, default, default, default, default);
x.Initialize?.Invoke(document);
return (Document: document, x.Modify);
}
).ToList();
_dbContext.AttachRange(initial.Select(x => x.Document));
initial.ForEach(x => x.Modify(x.Document));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public interface ICompanySsiDetailsRepository
/// <param name="verifiedCredentialExternalTypeUseCaseDetailId">Id of vc external type use case detail id</param>
/// <param name="verifiedCredentialTypeId">Id of the vc type</param>
/// <returns>Returns a valueTuple with identifiers if the externalTypeUseCaseDetailId exists and the corresponding credentialTypeId</returns>
Task<(bool Exists, string? Version, string? Template, IEnumerable<string> UseCase, DateTimeOffset Expiry)> CheckCredentialTypeIdExistsForExternalTypeDetailVersionId(Guid verifiedCredentialExternalTypeUseCaseDetailId, VerifiedCredentialTypeId verifiedCredentialTypeId);
Task<(bool Exists, string? Version, string? Template, IEnumerable<VerifiedCredentialExternalTypeId> ExternalTypeIds, DateTimeOffset Expiry)> CheckCredentialTypeIdExistsForExternalTypeDetailVersionId(Guid verifiedCredentialExternalTypeUseCaseDetailId, VerifiedCredentialTypeId verifiedCredentialTypeId);

/// <summary>
/// Checks whether the given credentialTypeId is a <see cref="VerifiedCredentialTypeKindId"/> Certificate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
********************************************************************************/

using Org.Eclipse.TractusX.SsiCredentialIssuer.DBAccess.Models;
using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Entities;
using Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums;
using System.Text.Json;

Expand All @@ -31,4 +32,7 @@ public interface ICredentialRepository
Task<(VerifiedCredentialTypeKindId CredentialTypeKindId, JsonDocument Schema)> GetCredentialStorageInformationById(Guid credentialId);
Task<(Guid? ExternalCredentialId, VerifiedCredentialTypeKindId KindId, bool HasEncryptionInformation, string? CallbackUrl)> GetExternalCredentialAndKindId(Guid credentialId);
Task<(string Bpn, string? CallbackUrl)> GetCallbackUrl(Guid credentialId);
Task<(bool Exists, Guid? ExternalCredentialId, CompanySsiDetailStatusId StatusId, IEnumerable<(Guid DocumentId, DocumentStatusId DocumentStatusId)> Documents)> GetRevocationDataById(Guid credentialId);
void AttachAndModifyCredential(Guid credentialId, Action<CompanySsiDetail>? initialize, Action<CompanySsiDetail> modify);
Task<(VerifiedCredentialTypeId TypeId, string RequesterId)> GetCredentialNotificationData(Guid credentialId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,5 @@ public interface IDocumentRepository
Document CreateDocument(string documentName, byte[] documentContent, byte[] hash, MediaTypeId mediaTypeId, DocumentTypeId documentTypeId, Action<Document>? setupOptionalFields);

void AssignDocumentToCompanySsiDetails(Guid documentId, Guid companySsiDetailId);
void AttachAndModifyDocuments(IEnumerable<(Guid DocumentId, Action<Document>? Initialize, Action<Document> Modify)> documentData);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@ namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums;

public enum ProcessStepTypeId
{
// Issuer Process
// CREATE CREDENTIAL PROCESS
CREATE_CREDENTIAL = 1,
SIGN_CREDENTIAL = 2,
SAVE_CREDENTIAL_DOCUMENT = 3,
CREATE_CREDENTIAL_FOR_HOLDER = 4,
TRIGGER_CALLBACK = 5,

// DECLINE PROCESS
REVOKE_CREDENTIAL = 100,
TRIGGER_NOTIFICATION = 101,
TRIGGER_MAIL = 102
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Entities.Enums;
public enum ProcessTypeId
{
CREATE_CREDENTIAL = 1,
DECLINE_CREDENTIAL = 2
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,21 @@ public enum VerifiedCredentialExternalTypeId
[EnumMember(Value = "vehicleDismantle")]
VEHICLE_DISMANTLE = 4,

[EnumMember(Value = "SustainabilityCredential")]
SUSTAINABILITY_CREDENTIAL = 5,
[EnumMember(Value = "CircularEconomyCredential")]
CIRCULAR_ECONOMY = 5,

[EnumMember(Value = "QualityCredential")]
QUALITY_CREDENTIAL = 6,

[EnumMember(Value = "BusinessPartnerCredential")]
BUSINESS_PARTNER_NUMBER = 7
BUSINESS_PARTNER_NUMBER = 7,

[EnumMember(Value = "DemandCapacityCredential")]
DEMAND_AND_CAPACITY_MANAGEMENT = 8,

[EnumMember(Value = "DemandCapacityCredential")]
DEMAND_AND_CAPACITY_MANAGEMENT_PURIS = 9,

[EnumMember(Value = "BusinessPartnerCredential")]
BUSINESS_PARTNER_DATA_MANAGEMENT = 10
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,21 @@ public enum VerifiedCredentialTypeId
[EnumMember(Value = "Dismantler Certificate")]
DISMANTLER_CERTIFICATE = 4,

[EnumMember(Value = "Sustainability Framework")]
SUSTAINABILITY_FRAMEWORK = 5,
[EnumMember(Value = "Circular Economy")]
CIRCULAR_ECONOMY = 5,

[EnumMember(Value = "frameworkAgreement.quality")]
FRAMEWORK_AGREEMENT_QUALITY = 6,

[EnumMember(Value = "BusinessPartnerCredential")]
BUSINESS_PARTNER_NUMBER = 7
BUSINESS_PARTNER_NUMBER = 7,

[EnumMember(Value = "Demand and Capacity Management")]
DEMAND_AND_CAPACITY_MANAGEMENT = 8,

[EnumMember(Value = "Demand and Capacity Management")]
DEMAND_AND_CAPACITY_MANAGEMENT_PURIS = 9,

[EnumMember(Value = "Business Partner Data Management")]
BUSINESS_PARTNER_DATA_MANAGEMENT = 10
}
Loading

0 comments on commit 1c333d1

Please sign in to comment.