Skip to content

Commit

Permalink
Records migration (#258)
Browse files Browse the repository at this point in the history
* refactor credential sets

Signed-off-by: Kevin <[email protected]>

* records migration

Signed-off-by: Kevin <[email protected]>

* records migration 2

Signed-off-by: Kevin <[email protected]>

* records migration 3

Signed-off-by: Kevin <[email protected]>

* improve mDocRecord  migration

Signed-off-by: kenkosmowski <[email protected]>

* fix mdoc migration

Signed-off-by: kenkosmowski <[email protected]>

* migration for OidPresentationRecord

Signed-off-by: kenkosmowski <[email protected]>

* fix version update for migrated records

Signed-off-by: kenkosmowski <[email protected]>

* remove debug code

Signed-off-by: kenkosmowski <[email protected]>

---------

Signed-off-by: Kevin <[email protected]>
Signed-off-by: kenkosmowski <[email protected]>
Co-authored-by: kenkosmowski <[email protected]>
  • Loading branch information
Dindexx and kenkosmowski authored Jan 31, 2025
1 parent e33bdd4 commit 7df514a
Show file tree
Hide file tree
Showing 34 changed files with 698 additions and 265 deletions.
2 changes: 1 addition & 1 deletion src/Hyperledger.Aries/Storage/Records/RecordBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public DateTime? UpdatedAtUtc
/// Get and set the schema version of a wallet record
/// </summary>
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Populate)]
protected internal int RecordVersion
public int RecordVersion
{
get
{
Expand Down
38 changes: 24 additions & 14 deletions src/WalletFramework.MdocVc/MdocRecord.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,16 @@ namespace WalletFramework.MdocVc;
[JsonConverter(typeof(MdocRecordJsonConverter))]
public sealed class MdocRecord : RecordBase, ICredential
{
public const int CurrentVersion = 2;

public CredentialId CredentialId
{
get => CredentialId
.ValidCredentialId(Id)
.UnwrapOrThrow(new InvalidOperationException("The Id is corrupt"));
private set => Id = value;
}

[RecordTag]
public DocType DocType => Mdoc.DocType;

Expand All @@ -42,11 +44,10 @@ public CredentialId CredentialId

public Option<DateTime> ExpiresAt { get; }

[JsonIgnore]
public CredentialSetId CredentialSetId
//TODO: Use CredentialSetId Type instead fo string
public string CredentialSetId
{
get => CredentialSetId.ValidCredentialSetId(Get())
.UnwrapOrThrow(new InvalidOperationException("The CredentialSetId is corrupt"));
get => Get();
set => Set(value, false);
}

Expand All @@ -56,7 +57,7 @@ public MdocRecord(
Mdoc mdoc,
Option<List<MdocDisplay>> displays,
KeyId keyId,
CredentialSetId credentialSetId,
string credentialSetId,
CredentialState credentialState,
Option<DateTime> expiresAt,
bool isOneTimeUse = false)
Expand All @@ -69,6 +70,7 @@ public MdocRecord(
CredentialState = credentialState;
ExpiresAt = expiresAt;
OneTimeUse = isOneTimeUse;
RecordVersion = CurrentVersion;
}

#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
Expand All @@ -78,8 +80,10 @@ public MdocRecord()
#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.

public CredentialId GetId() => CredentialId;

public CredentialSetId GetCredentialSetId() => CredentialSetId;

public CredentialSetId GetCredentialSetId() => Core.Credentials.CredentialSetId
.ValidCredentialSetId(CredentialSetId)
.UnwrapOrThrow();

public static implicit operator Mdoc(MdocRecord record) => record.Mdoc;
}
Expand Down Expand Up @@ -112,11 +116,15 @@ public static class MdocRecordFun
private const string CredentialSetIdJsonKey = "credentialSetId";
private const string CredentialStateJsonKey = "credentialState";
private const string ExpiresAtJsonKey = "expiresAt";
private const string RecordVersionJsonKey = "recordVersion";
private const string OneTimeUseJsonKey = "oneTimeUse";

public static MdocRecord DecodeFromJson(JObject json)
{
var id = json[nameof(RecordBase.Id)]!.ToString();
var recordVersion = json.GetByKey(RecordVersionJsonKey).ToOption().Match(
value => value.ToObject<int>(),
() => 1);

var mdocStr = json[MdocJsonKey]!.ToString();
var mdoc = Mdoc
Expand All @@ -133,22 +141,24 @@ from mdocDisplays in MdocDisplayFun.DecodeFromJson(jArray)
.ValidKeyId(json[KeyIdJsonKey]!.ToString())
.UnwrapOrThrow();

var credentialSetId = CredentialSetId.ValidCredentialSetId(json[CredentialSetIdJsonKey]!.ToString()).UnwrapOrThrow();
var credentialSetId = recordVersion >= 2 ? json[CredentialSetIdJsonKey]!.ToObject<string>()! : string.Empty;

var expiresAt =
from expires in json.GetByKey(ExpiresAtJsonKey).ToOption()
select expires.ToObject<DateTime>();

var credentialState = Enum.Parse<CredentialState>(json[CredentialStateJsonKey]!.ToString());
var credentialState = recordVersion >= 2
? Enum.Parse<CredentialState>(json[CredentialStateJsonKey]!.ToString())
: CredentialState.Active;

var oneTimeUse = json.GetByKey(OneTimeUseJsonKey).ToOption().Match(
Some: value => value.ToObject<bool>(),
None: () => false
);
None: () => false);

var result = new MdocRecord(mdoc, displays, keyId, credentialSetId, credentialState, expiresAt, oneTimeUse)
{
Id = id
Id = id,
RecordVersion = recordVersion
};

return result;
Expand All @@ -161,7 +171,7 @@ public static JObject EncodeToJson(this MdocRecord record)
{ nameof(RecordBase.Id), record.Id },
{ MdocJsonKey, record.Mdoc.Encode() },
{ KeyIdJsonKey, record.KeyId.ToString() },
{ CredentialSetIdJsonKey, record.CredentialSetId.ToString() },
{ CredentialSetIdJsonKey, record.CredentialSetId },
{ CredentialStateJsonKey, record.CredentialState.ToString() },
{ OneTimeUseJsonKey, record.OneTimeUse }
};
Expand Down
127 changes: 10 additions & 117 deletions src/WalletFramework.Oid4Vc/CredentialSet/CredentialSetService.cs
Original file line number Diff line number Diff line change
@@ -1,136 +1,29 @@
using Hyperledger.Aries;
using Hyperledger.Aries.Agents;
using Hyperledger.Aries.Storage;
using LanguageExt;
using WalletFramework.Core.Credentials;
using WalletFramework.Core.Functional;
using WalletFramework.MdocVc;
using WalletFramework.Oid4Vc.CredentialSet.Models;
using WalletFramework.Oid4Vc.Oid4Vci.Abstractions;
using WalletFramework.SdJwtVc.Models.Records;
using WalletFramework.SdJwtVc.Services;
using WalletFramework.SdJwtVc.Services.SdJwtVcHolderService;

namespace WalletFramework.Oid4Vc.CredentialSet;

public class CredentialSetService(
IAgentProvider agentProvider,
ISdJwtVcHolderService sdJwtVcHolderService,
IMdocStorage mDocStorage,
IStatusListService statusListService,
IWalletRecordService walletRecordService)
: ICredentialSetService
ICredentialSetStorage storage,
IStatusListService statusListService) : ICredentialSetService
{
public async Task<Option<IEnumerable<SdJwtRecord>>> GetAssociatedSdJwtRecords(CredentialSetId credentialSetId)
{
var context = await agentProvider.GetContextAsync();

var sdJwtQuery = SearchQuery.Equal(
"~" + nameof(SdJwtRecord.CredentialSetId),
credentialSetId);

var sdJwtRecords = await sdJwtVcHolderService.ListAsync(
context,
sdJwtQuery);

return sdJwtRecords.Any()
? sdJwtRecords
: Option<IEnumerable<SdJwtRecord>>.None;
}

public async Task<Option<IEnumerable<MdocRecord>>> GetAssociatedMDocRecords(CredentialSetId credentialSetId)
{
var mDocQuery = SearchQuery.Equal(
"~" + nameof(MdocRecord.CredentialSetId),
credentialSetId);

return await mDocStorage.List(
Option<ISearchQuery>.Some(mDocQuery));
}

public virtual async Task DeleteAsync(CredentialSetId credentialSetId)
{
var context = await agentProvider.GetContextAsync();
var credentialSetRecord = await walletRecordService.GetAsync<CredentialSetRecord>(context.Wallet, credentialSetId);
if (credentialSetRecord == null)
throw new AriesFrameworkException(ErrorCode.RecordNotFound, "CredentialSet record not found");

var sdJwtRecords = await GetAssociatedSdJwtRecords(credentialSetId);
await sdJwtRecords.Match(
Some: async records =>
{
foreach (var record in records)
await sdJwtVcHolderService.DeleteAsync(context, record.Id);
},
None: () => Task.CompletedTask);

var mDocRecords = await GetAssociatedMDocRecords(credentialSetId);
await mDocRecords.Match(
Some: async records =>
{
foreach (var record in records)
await mDocStorage.Delete(record);
},
None: () => Task.CompletedTask);

credentialSetRecord.State = CredentialState.Deleted;
credentialSetRecord.DeletedAt = DateTime.UtcNow;
await walletRecordService.UpdateAsync(context.Wallet, credentialSetRecord);
}

public async Task AddAsync(CredentialSetRecord credentialSetRecord)
{
var context = await agentProvider.GetContextAsync();
await walletRecordService.AddAsync(context.Wallet, credentialSetRecord);
}

public async Task<Option<IEnumerable<CredentialSetRecord>>> ListAsync(
Option<ISearchQuery> query,
int count = 100,
int skip = 0)
{
var context = await agentProvider.GetContextAsync();
var records = await walletRecordService.SearchAsync<CredentialSetRecord>(
context.Wallet,
query.ToNullable(),
null,
count,
skip);

if (records.Count == 0)
return Option<IEnumerable<CredentialSetRecord>>.None;

return records;
}

public async Task<Option<CredentialSetRecord>> GetAsync(CredentialSetId credentialSetId)
{
var context = await agentProvider.GetContextAsync();
var record = await walletRecordService.GetAsync<CredentialSetRecord>(context.Wallet, credentialSetId);

return record;
}

public virtual async Task UpdateAsync(CredentialSetRecord credentialSetRecord)
{
var context = await agentProvider.GetContextAsync();
await walletRecordService.UpdateAsync(context.Wallet, credentialSetRecord);
}

public async Task<CredentialSetRecord> RefreshCredentialSetState(CredentialSetRecord credentialSetRecord)
{
var oldState = credentialSetRecord.State;

if (credentialSetRecord.IsDeleted())
return credentialSetRecord;

credentialSetRecord.ExpiresAt.IfSome(expiresAt =>
{
if (expiresAt < DateTime.UtcNow)
credentialSetRecord.State = CredentialState.Expired;
});

await credentialSetRecord.StatusList.IfSomeAsync(
await credentialSetRecord.StatusListEntry.IfSomeAsync(
async statusList =>
{
await statusListService.GetState(statusList).IfSomeAsync(
Expand All @@ -140,16 +33,16 @@ await statusListService.GetState(statusList).IfSomeAsync(
credentialSetRecord.State = CredentialState.Revoked;
});
});
if (oldState != credentialSetRecord.State)
await UpdateAsync(credentialSetRecord);

if (oldState != credentialSetRecord.State)
await storage.Update(credentialSetRecord);

return credentialSetRecord;
}

public async Task RefreshCredentialSetStates()
{
var credentialSetRecords = await ListAsync(Option<ISearchQuery>.None);
var credentialSetRecords = await storage.List(Option<ISearchQuery>.None);

await credentialSetRecords.IfSomeAsync(
async records =>
Expand Down
89 changes: 89 additions & 0 deletions src/WalletFramework.Oid4Vc/CredentialSet/CredentialSetStorage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using Hyperledger.Aries;
using Hyperledger.Aries.Agents;
using Hyperledger.Aries.Storage;
using LanguageExt;
using WalletFramework.Core.Credentials;
using WalletFramework.Oid4Vc.CredentialSet.Models;
using WalletFramework.Oid4Vc.Oid4Vci.Abstractions;
using WalletFramework.SdJwtVc.Services.SdJwtVcHolderService;
using WalletFramework.Core.Functional;

namespace WalletFramework.Oid4Vc.CredentialSet;

public class CredentialSetStorage(
IAgentProvider agentProvider,
ISdJwtVcHolderService sdJwtVcHolderService,
IMdocStorage mDocStorage,
IWalletRecordService walletRecordService) : ICredentialSetStorage
{
public async Task Add(CredentialSetRecord credentialSetRecord)
{
var context = await agentProvider.GetContextAsync();
await walletRecordService.AddAsync(context.Wallet, credentialSetRecord);
}

public virtual async Task Delete(CredentialSetId credentialSetId)
{
var context = await agentProvider.GetContextAsync();
var credentialSetRecord =
await walletRecordService.GetAsync<CredentialSetRecord>(context.Wallet, credentialSetId);
if (credentialSetRecord == null)
throw new AriesFrameworkException(ErrorCode.RecordNotFound, "CredentialSet record not found");

var sdJwtRecords = await sdJwtVcHolderService.ListAsync(context, credentialSetId);
await sdJwtRecords.Match(
Some: async records =>
{
foreach (var record in records)
await sdJwtVcHolderService.DeleteAsync(context, record.Id);
},
None: () => Task.CompletedTask);

var mDocRecords = await mDocStorage.List(credentialSetId);
await mDocRecords.Match(
Some: async records =>
{
foreach (var record in records)
await mDocStorage.Delete(record);
},
None: () => Task.CompletedTask);

credentialSetRecord.State = CredentialState.Deleted;
credentialSetRecord.DeletedAt = DateTime.UtcNow;
await walletRecordService.UpdateAsync(context.Wallet, credentialSetRecord);
}

public async Task<Option<CredentialSetRecord>> Get(CredentialSetId credentialSetId)
{
var context = await agentProvider.GetContextAsync();
var record = await walletRecordService.GetAsync<CredentialSetRecord>(context.Wallet, credentialSetId);

return record;
}

public async Task<Option<IEnumerable<CredentialSetRecord>>> List(
Option<ISearchQuery> query,
int count = 100,
int skip = 0)
{
var context = await agentProvider.GetContextAsync();
var records = await walletRecordService.SearchAsync<CredentialSetRecord>(
context.Wallet,
query.ToNullable(),
null,
count,
skip);

if (records.Count == 0)
return Option<IEnumerable<CredentialSetRecord>>.None;

return records;
}


public virtual async Task Update(CredentialSetRecord credentialSetRecord)
{
var context = await agentProvider.GetContextAsync();
await walletRecordService.UpdateAsync(context.Wallet, credentialSetRecord);
}
}
Loading

0 comments on commit 7df514a

Please sign in to comment.