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 Mar 27, 2024
1 parent 97e70b6 commit ef464dc
Show file tree
Hide file tree
Showing 41 changed files with 1,306 additions and 101 deletions.
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);
await ProcessCredentials(credential, companySsiDetailsRepository, repositories, portalService, processStepRepository, stoppingToken);
}
}
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,30 +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)
{
var content = JsonSerializer.Serialize(new { Type = data.VerifiedCredentialTypeId, CredentialId = data.Id }, Options);
await portalService.AddNotification(content, data.RequesterId, NotificationTypeId.CREDENTIAL_REJECTED, cancellationToken);
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;
});

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", data.RequesterId, mailParameters, cancellationToken);
}

private static async ValueTask HandleNotification(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@

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

public record DocumentData
(
public record DocumentData(
Guid DocumentId,
string DocumentName,
DocumentTypeId argDocumentTypeId);
DocumentTypeId DocumentTypeId);
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!, Guid.Empty, default!);
initialize?.Invoke(entity);
_dbContext.CompanySsiDetails.Attach(entity);
modify(entity);
}

public Task<(VerifiedCredentialTypeId TypeId, Guid RequesterId)> GetCredentialNotificationData(Guid credentialId) =>
_dbContext.CompanySsiDetails
.Where(x => x.Id == credentialId)
.Select(x => new ValueTuple<VerifiedCredentialTypeId, Guid>(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 @@ -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, Guid 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 @@ -2,31 +2,31 @@
{
"id": "1268a76a-ca19-4dd8-b932-01f24071d562",
"verified_credential_external_type_id": 3,
"version": "1.0.0",
"version": "1.0",
"template": null,
"valid_from": "2023-06-01 00:00:00.000000 +00:00",
"expiry": "2023-09-30 00:00:00.000000 +00:00"
},
{
"id": "1268a76a-ca19-4dd8-b932-01f24071d563",
"verified_credential_external_type_id": 1,
"version": "2.0.0",
"version": "2.0",
"template": null,
"valid_from": "2023-06-01 00:00:00.000000 +00:00",
"expiry": "2023-12-23 00:00:00.000000 +00:00"
},
{
"id": "1268a76a-ca19-4dd8-b932-01f24071d564",
"verified_credential_external_type_id": 1,
"version": "3.0.0",
"version": "3.0",
"template": null,
"valid_from": "2024-01-01 00:00:00.000000 +00:00",
"expiry": "2024-12-31 00:00:00.000000 +00:00"
},
{
"id": "1268a76a-ca19-4dd8-b932-01f24071d565",
"verified_credential_external_type_id": 5,
"version": "1.0.0",
"version": "1.0",
"template": null,
"valid_from": "2024-01-01 00:00:00.000000 +00:00",
"expiry": "2024-12-31 00:00:00.000000 +00:00"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@
{
"id": "1268a76a-ca19-4dd8-b932-01f24071d560",
"verified_credential_external_type_id": 1,
"version": "1.0.0",
"version": "1.0",
"template": "https://catena-x.net/fileadmin/user_upload/04_Einfuehren_und_umsetzen/Governance_Framework/231016_Catena-X_Use_Case_Framework_Traceability.pdf",
"valid_from": "2023-06-01 00:00:00.000000 +00:00",
"expiry": "2023-09-30 00:00:00.000000 +00:00"
},
{
"id": "1268a76a-ca19-4dd8-b932-01f24071d561",
"verified_credential_external_type_id": 2,
"version": "1.0.0",
"version": "1.0",
"template": "https://catena-x.net/fileadmin/user_upload/04_Einfuehren_und_umsetzen/Governance_Framework/231016_Catena-X_Use_Case_Framework_PCF.pdf",
"valid_from": "2023-06-01 00:00:00.000000 +00:00",
"expiry": "2023-09-30 00:00:00.000000 +00:00"
},
{
"id": "37aa6259-b452-4d50-b09e-827929dcfa15",
"verified_credential_external_type_id": 6,
"version": "1.0.0",
"version": "1.0",
"template": "https://catena-x.net/fileadmin/user_upload/04_Einfuehren_und_umsetzen/Governance_Framework/231016_Catena-X_Use_Case_Framework_PCF.pdf",
"valid_from": "2023-10-16 00:00:00.000000 +00:00",
"expiry": "2023-10-16 00:00:00.000000 +00:00"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/********************************************************************************
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

using System.Text.Json.Serialization;

namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Models;

public record RevokeCredentialRequest(
[property: JsonPropertyName("payload")] RevokePayload Payload
);

public record RevokePayload(
[property: JsonPropertyName("revoke")] bool Revoke
);
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Wallet.Service.Services;

public interface IBasicAuthTokenService
{
Task<HttpClient> GetBasicAuthorizedClient<T>(BasicAuthSettings settings, CancellationToken cancellationToken);
Task<HttpClient> GetBasicAuthorizedClient<T>(BasicAuthSettings settings, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,6 @@ public interface IWalletService
Task<string> SignCredential(Guid credentialId, CancellationToken cancellationToken);
Task<Guid> CreateCredentialForHolder(string holderWalletUrl, string clientId, string clientSecret, string credential, CancellationToken cancellationToken);
Task<JsonDocument> GetCredential(Guid externalCredentialId, CancellationToken cancellationToken);
Task RevokeCredentialForIssuer(Guid externalCredentialId, CancellationToken cancellationToken);
Task RevokeCredentialForHolder(string holderWalletUrl, string clientId, string clientSecret, Guid externalCredentialId, CancellationToken cancellationToken);
}
26 changes: 26 additions & 0 deletions src/externalservices/Wallet.Service/Services/WalletService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,30 @@ public async Task<Guid> CreateCredentialForHolder(string holderWalletUrl, string

return response.Id;
}

public async Task RevokeCredentialForIssuer(Guid externalCredentialId, CancellationToken cancellationToken)
{
var client = await _basicAuthTokenService.GetBasicAuthorizedClient<WalletService>(_settings, cancellationToken);
var data = new RevokeCredentialRequest(new RevokePayload(true));
await client.PatchAsJsonAsync($"/api/v2.0.0/credentials/{externalCredentialId}", data, Options, cancellationToken)
.CatchingIntoServiceExceptionFor("revoke-credential", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE,
async x => (false, await x.Content.ReadAsStringAsync().ConfigureAwait(false)))
.ConfigureAwait(false);
}

public async Task RevokeCredentialForHolder(string holderWalletUrl, string clientId, string clientSecret, Guid externalCredentialId, CancellationToken cancellationToken)
{
var authSettings = new BasicAuthSettings
{
ClientId = clientId,
ClientSecret = clientSecret,
TokenAddress = $"{holderWalletUrl}/oauth/token"
};
var client = await _basicAuthTokenService.GetBasicAuthorizedClient<WalletService>(authSettings, cancellationToken);
var data = new RevokeCredentialRequest(new RevokePayload(true));
await client.PatchAsJsonAsync($"/api/v2.0.0/credentials/{externalCredentialId}", data, Options, cancellationToken)
.CatchingIntoServiceExceptionFor("revoke-credential", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE,
async x => (false, await x.Content.ReadAsStringAsync().ConfigureAwait(false)))
.ConfigureAwait(false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/********************************************************************************
* Copyright (c) 2024 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
* SPDX-License-Identifier: Apache-2.0
********************************************************************************/

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

namespace Org.Eclipse.TractusX.SsiCredentialIssuer.Service.BusinessLogic;

public interface IRevocationBusinessLogic
{
Task RevokeIssuerCredential(Guid credentialId, CancellationToken cancellationToken);
Task RevokeHolderCredential(Guid credentialId, TechnicalUserDetails walletInformation, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -197,15 +197,17 @@ public async Task ApproveCredential(Guid credentialId, CancellationToken cancell
c.ProcessId = processId;
});
var typeValue = data.Type.GetEnumValue() ?? throw UnexpectedConditionException.Create(CredentialErrors.CREDENTIAL_TYPE_NOT_FOUND, new ErrorParameter[] { new("verifiedCredentialType", data.Type.ToString()) });
var content = JsonSerializer.Serialize(new { data.Type, CredentialId = credentialId }, Options);
await _portalService.AddNotification(content, _identity.IdentityId, NotificationTypeId.CREDENTIAL_APPROVAL, cancellationToken).ConfigureAwait(false);
var mailParameters = new Dictionary<string, string>
{
{ "requestName", typeValue },
{ "credentialType", typeValue },
{ "expiryDate", expiry.ToString("o", CultureInfo.InvariantCulture) }
};
await _portalService.TriggerMail("CredentialApproval", _identity.IdentityId, mailParameters, cancellationToken).ConfigureAwait(false);

var content = JsonSerializer.Serialize(new { data.Type, CredentialId = credentialId }, Options);
await _portalService.AddNotification(content, _identity.IdentityId, NotificationTypeId.CREDENTIAL_APPROVAL, cancellationToken).ConfigureAwait(false);

await _repositories.SaveAsync().ConfigureAwait(false);
}

Expand Down Expand Up @@ -505,8 +507,9 @@ private async Task<Guid> HandleCredentialProcessCreation(
c.ClientId = technicalUserDetails.ClientId;
c.ClientSecret = secret;
c.InitializationVector = initializationVector;
c.HolderWalletUrl = technicalUserDetails.WalletUrl;
c.EncryptionMode = cryptoConfig.Index;
c.InitializationVector = initializationVector;
c.CallbackUrl = callbackUrl;
});

Expand Down
Loading

0 comments on commit ef464dc

Please sign in to comment.