Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Records migration #258

Merged
merged 10 commits into from
Jan 31, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading