diff --git a/packages/consumption/src/modules/requests/itemProcessors/transferFileOwnership/TransferFileOwnershipRequestItemProcessor.ts b/packages/consumption/src/modules/requests/itemProcessors/transferFileOwnership/TransferFileOwnershipRequestItemProcessor.ts index 2f3b7db87..20c42e7c5 100644 --- a/packages/consumption/src/modules/requests/itemProcessors/transferFileOwnership/TransferFileOwnershipRequestItemProcessor.ts +++ b/packages/consumption/src/modules/requests/itemProcessors/transferFileOwnership/TransferFileOwnershipRequestItemProcessor.ts @@ -8,7 +8,7 @@ import { TransferFileOwnershipAcceptResponseItem, TransferFileOwnershipRequestItem } from "@nmshd/content"; -import { CoreAddress, CoreDate, CoreId } from "@nmshd/core-types"; +import { CoreAddress, CoreId } from "@nmshd/core-types"; import { File } from "@nmshd/transport"; import { ConsumptionCoreErrors } from "../../../../consumption/ConsumptionCoreErrors"; import { ValidationResult } from "../../../common/ValidationResult"; @@ -53,15 +53,11 @@ export class TransferFileOwnershipRequestItemProcessor extends GenericRequestIte } } - if (requestItem.ownershipToken) { - const validationResult = await this.accountController.files.validateFileOwnershipToken(foundFile.id, requestItem.ownershipToken); - if (!validationResult.isValid) { - return ValidationResult.error( - ConsumptionCoreErrors.requests.invalidRequestItem( - `The specified ownershipToken is not valid for the File with ID '${requestItem.fileReference.id.toString()}'.` - ) - ); - } + const validationResult = await this.accountController.files.validateFileOwnershipToken(foundFile.id, requestItem.ownershipToken); + if (!validationResult.isValid) { + return ValidationResult.error( + ConsumptionCoreErrors.requests.invalidRequestItem(`The specified ownershipToken is not valid for the File with ID '${requestItem.fileReference.id.toString()}'.`) + ); } return ValidationResult.success(); @@ -118,15 +114,13 @@ export class TransferFileOwnershipRequestItemProcessor extends GenericRequestIte } } - if (requestItem.ownershipToken) { - const validationResult = await this.accountController.files.validateFileOwnershipToken(file.id, requestItem.ownershipToken); - if (!validationResult.isValid) { - return ValidationResult.error( - ConsumptionCoreErrors.requests.invalidRequestItem( - `You cannot accept this RequestItem since the specified ownershipToken is not valid for the File with ID '${requestItem.fileReference.id.toString()}'.` - ) - ); - } + const validationResult = await this.accountController.files.validateFileOwnershipToken(file.id, requestItem.ownershipToken); + if (!validationResult.isValid) { + return ValidationResult.error( + ConsumptionCoreErrors.requests.invalidRequestItem( + `You cannot accept this RequestItem since the specified ownershipToken is not valid for the File with ID '${requestItem.fileReference.id.toString()}'.` + ) + ); } return ValidationResult.success(); @@ -139,9 +133,7 @@ export class TransferFileOwnershipRequestItemProcessor extends GenericRequestIte ): Promise { const peerFile = await this.accountController.files.getOrLoadFileByReference(requestItem.fileReference); - const ownFile = requestItem.ownershipToken - ? await this.accountController.files.claimFileOwnership(peerFile.id, requestItem.ownershipToken) - : await this.transferFileOwnershipByReupload(peerFile); + const ownFile = await this.accountController.files.claimFileOwnership(peerFile.id, requestItem.ownershipToken); const repositoryAttribute = await this.consumptionController.attributes.createRepositoryAttribute({ content: IdentityAttribute.from({ @@ -166,19 +158,6 @@ export class TransferFileOwnershipRequestItemProcessor extends GenericRequestIte }); } - private async transferFileOwnershipByReupload(peerFile: File) { - const fileContent = await this.accountController.files.downloadFileContent(peerFile); - return await this.accountController.files.sendFile({ - buffer: fileContent, - title: peerFile.cache!.title, - description: peerFile.cache!.description, - filename: peerFile.cache!.filename, - mimetype: peerFile.cache!.mimetype, - expiresAt: CoreDate.from("9999-12-31T00:00:00.000Z"), - tags: peerFile.cache!.tags - }); - } - public override async applyIncomingResponseItem( responseItem: TransferFileOwnershipAcceptResponseItem | AcceptResponseItem | RejectResponseItem, _requestItem: TransferFileOwnershipRequestItem, diff --git a/packages/consumption/test/modules/requests/itemProcessors/transferFileOwnership/TransferFileOwnershipRequestItemProcessor.test.ts b/packages/consumption/test/modules/requests/itemProcessors/transferFileOwnership/TransferFileOwnershipRequestItemProcessor.test.ts index 31799f3f4..f0bc155de 100644 --- a/packages/consumption/test/modules/requests/itemProcessors/transferFileOwnership/TransferFileOwnershipRequestItemProcessor.test.ts +++ b/packages/consumption/test/modules/requests/itemProcessors/transferFileOwnership/TransferFileOwnershipRequestItemProcessor.test.ts @@ -1,7 +1,7 @@ import { IDatabaseConnection } from "@js-soft/docdb-access-abstractions"; import { sleep } from "@js-soft/ts-utils"; import { IdentityAttribute, IdentityFileReference, Request, ResponseItemResult, TransferFileOwnershipAcceptResponseItem, TransferFileOwnershipRequestItem } from "@nmshd/content"; -import { CoreAddress, CoreDate, FileReference } from "@nmshd/core-types"; +import { CoreAddress, CoreDate } from "@nmshd/core-types"; import { AccountController } from "@nmshd/transport"; import { ConsumptionController, ConsumptionIds, LocalRequest, LocalRequestStatus, TransferFileOwnershipRequestItemProcessor } from "../../../../../src"; import { TestUtil } from "../../../../core/TestUtil"; @@ -51,7 +51,8 @@ describe("TransferFileOwnershipRequestItemProcessor", function () { const requestItem = TransferFileOwnershipRequestItem.from({ mustBeAccepted: false, - fileReference: senderFile.toFileReference(senderAccountController.config.baseUrl).truncate() + fileReference: senderFile.toFileReference(senderAccountController.config.baseUrl).truncate(), + ownershipToken: senderFile.ownershipToken! }); const request = Request.from({ items: [requestItem] }); @@ -62,23 +63,10 @@ describe("TransferFileOwnershipRequestItemProcessor", function () { test("returns success if the ownership of an own File should be transferred entering a FileReference", async function () { const senderFile = await TestUtil.uploadFile(senderAccountController, { tags: ["x:tag"] }); - const requestItem = TransferFileOwnershipRequestItem.from({ - mustBeAccepted: false, - fileReference: senderFile.toFileReference(senderAccountController.config.baseUrl) - }); - const request = Request.from({ items: [requestItem] }); - - const result = await senderProcessor.canCreateOutgoingRequestItem(requestItem, request, recipient); - expect(result).successfulValidationResult(); - }); - - test("returns success if the ownership of an own File should be transferred using an ownershipToken", async function () { - const senderFile = await TestUtil.uploadFile(senderAccountController, { tags: ["x:tag"] }); - const requestItem = TransferFileOwnershipRequestItem.from({ mustBeAccepted: false, fileReference: senderFile.toFileReference(senderAccountController.config.baseUrl), - ownershipToken: senderFile.ownershipToken + ownershipToken: senderFile.ownershipToken! }); const request = Request.from({ items: [requestItem] }); @@ -91,7 +79,8 @@ describe("TransferFileOwnershipRequestItemProcessor", function () { const requestItem = TransferFileOwnershipRequestItem.from({ mustBeAccepted: false, - fileReference: thirdPartyFile.toFileReference(thirdPartyAccountController.config.baseUrl) + fileReference: thirdPartyFile.toFileReference(thirdPartyAccountController.config.baseUrl), + ownershipToken: thirdPartyFile.ownershipToken! }); const request = Request.from({ items: [requestItem] }); @@ -109,7 +98,8 @@ describe("TransferFileOwnershipRequestItemProcessor", function () { const requestItem = TransferFileOwnershipRequestItem.from({ mustBeAccepted: false, - fileReference: thirdPartyFileReference + fileReference: thirdPartyFileReference, + ownershipToken: thirdPartyFile.ownershipToken! }); const request = Request.from({ items: [requestItem] }); @@ -126,7 +116,8 @@ describe("TransferFileOwnershipRequestItemProcessor", function () { const requestItem = TransferFileOwnershipRequestItem.from({ mustBeAccepted: false, - fileReference: senderExpiredFile.toFileReference(senderAccountController.config.baseUrl) + fileReference: senderExpiredFile.toFileReference(senderAccountController.config.baseUrl), + ownershipToken: senderExpiredFile.ownershipToken! }); const request = Request.from({ items: [requestItem] }); @@ -159,32 +150,10 @@ describe("TransferFileOwnershipRequestItemProcessor", function () { test("returns success when checking if the transfer of ownership of a File can be accepted that is owned by the sender", async function () { const senderFile = await TestUtil.uploadFile(senderAccountController, { tags: ["x:tag"] }); - const requestItem = TransferFileOwnershipRequestItem.from({ - mustBeAccepted: false, - fileReference: senderFile.toFileReference(senderAccountController.config.baseUrl) - }); - - const incomingRequest = LocalRequest.from({ - id: await ConsumptionIds.request.generate(), - createdAt: CoreDate.utc(), - isOwn: false, - peer: sender, - status: LocalRequestStatus.DecisionRequired, - content: Request.from({ items: [requestItem] }), - statusLog: [] - }); - - const result = await recipientProcessor.canAccept(requestItem, { accept: true }, incomingRequest); - expect(result).successfulValidationResult(); - }); - - test("returns success when checking if the transfer of ownership of a File can be accepted with an ownershipToken", async function () { - const senderFile = await TestUtil.uploadFile(senderAccountController, { tags: ["x:tag"] }); - const requestItem = TransferFileOwnershipRequestItem.from({ mustBeAccepted: false, fileReference: senderFile.toFileReference(senderAccountController.config.baseUrl), - ownershipToken: senderFile.ownershipToken + ownershipToken: senderFile.ownershipToken! }); const incomingRequest = LocalRequest.from({ @@ -207,7 +176,8 @@ describe("TransferFileOwnershipRequestItemProcessor", function () { const requestItem = TransferFileOwnershipRequestItem.from({ mustBeAccepted: false, - fileReference: senderExpiredFile.toFileReference(senderAccountController.config.baseUrl) + fileReference: senderExpiredFile.toFileReference(senderAccountController.config.baseUrl), + ownershipToken: senderExpiredFile.ownershipToken! }); const incomingRequest = LocalRequest.from({ @@ -232,7 +202,8 @@ describe("TransferFileOwnershipRequestItemProcessor", function () { const requestItem = TransferFileOwnershipRequestItem.from({ mustBeAccepted: false, - fileReference: recipientFile.toFileReference(recipientAccountController.config.baseUrl) + fileReference: recipientFile.toFileReference(recipientAccountController.config.baseUrl), + ownershipToken: recipientFile.ownershipToken! }); const incomingRequest = LocalRequest.from({ @@ -257,7 +228,8 @@ describe("TransferFileOwnershipRequestItemProcessor", function () { const requestItem = TransferFileOwnershipRequestItem.from({ mustBeAccepted: false, - fileReference: thirdPartyFile.toFileReference(thirdPartyAccountController.config.baseUrl) + fileReference: thirdPartyFile.toFileReference(thirdPartyAccountController.config.baseUrl), + ownershipToken: thirdPartyFile.ownershipToken! }); const incomingRequest = LocalRequest.from({ @@ -305,52 +277,14 @@ describe("TransferFileOwnershipRequestItemProcessor", function () { }); describe("accept", function () { - test("creates a RepositoryAttribute with tags and an own shared IdentityAttribute and responds with a TransferFileOwnershipAcceptResponseItem", async function () { - const senderFile = await TestUtil.uploadFile(senderAccountController, { tags: ["x:tag"] }); - - const requestItem = TransferFileOwnershipRequestItem.from({ - mustBeAccepted: false, - fileReference: senderFile.toFileReference(senderAccountController.config.baseUrl) - }); - - const incomingRequest = LocalRequest.from({ - id: await ConsumptionIds.request.generate(), - createdAt: CoreDate.utc(), - isOwn: false, - peer: sender, - status: LocalRequestStatus.DecisionRequired, - content: Request.from({ items: [requestItem] }), - statusLog: [] - }); - - const responseItem = await recipientProcessor.accept(requestItem, { accept: true }, incomingRequest); - expect(responseItem).toBeInstanceOf(TransferFileOwnershipAcceptResponseItem); - - const ownSharedIdentityAttribute = await recipientConsumptionController.attributes.getLocalAttribute(responseItem.attributeId); - expect(ownSharedIdentityAttribute!.shareInfo!.peer).toStrictEqual(sender); - expect(ownSharedIdentityAttribute!.shareInfo!.requestReference).toStrictEqual(incomingRequest.id); - expect(ownSharedIdentityAttribute!.content.value).toBeInstanceOf(IdentityFileReference); - expect((ownSharedIdentityAttribute!.content as IdentityAttribute).tags).toStrictEqual(["x:tag"]); - - const repositoryAttribute = await recipientConsumptionController.attributes.getLocalAttribute(ownSharedIdentityAttribute!.shareInfo!.sourceAttribute!); - expect(repositoryAttribute!.shareInfo).toBeUndefined(); - expect(repositoryAttribute!.content.value).toBeInstanceOf(IdentityFileReference); - expect((repositoryAttribute!.content as IdentityAttribute).tags).toStrictEqual(["x:tag"]); - - const fileReference = FileReference.from((repositoryAttribute!.content.value as IdentityFileReference).value); - const file = await recipientAccountController.files.getFile(fileReference.id); - expect(file!.isOwn).toBe(true); - expect(file!.cache!.tags).toStrictEqual(["x:tag"]); - }); - - test("claims the ownership of the sender's file", async function () { + test("should accept a TransferFileOwnershipRequestItem", async function () { const senderFile = await TestUtil.uploadFile(senderAccountController, { tags: ["x:tag"] }); const senderFileReference = senderFile.toFileReference(senderAccountController.config.baseUrl); const requestItem = TransferFileOwnershipRequestItem.from({ mustBeAccepted: false, fileReference: senderFileReference, - ownershipToken: senderFile.ownershipToken + ownershipToken: senderFile.ownershipToken! }); const incomingRequest = LocalRequest.from({ @@ -387,53 +321,14 @@ describe("TransferFileOwnershipRequestItemProcessor", function () { }); describe("applyIncomingResponseItem", function () { - test("creates peer shared IdentityAttribute in case of a TransferFileOwnershipAcceptResponseItem", async function () { - const senderFile = await TestUtil.uploadFile(senderAccountController, { tags: ["x:tag"] }); - - const requestItem = TransferFileOwnershipRequestItem.from({ - mustBeAccepted: false, - fileReference: senderFile.toFileReference(senderAccountController.config.baseUrl) - }); - - const requestInfo = { - id: await ConsumptionIds.request.generate(), - peer: recipient - }; - - const recipientFile = await TestUtil.uploadFile(recipientAccountController, { tags: ["x:tag"] }); - const responseItem = TransferFileOwnershipAcceptResponseItem.from({ - result: ResponseItemResult.Accepted, - attributeId: await ConsumptionIds.attribute.generate(), - attribute: IdentityAttribute.from({ - value: IdentityFileReference.from({ - value: recipientFile.toFileReference(recipientAccountController.config.baseUrl).truncate() - }), - owner: recipient, - tags: ["x:tag"] - }) - }); - - await senderProcessor.applyIncomingResponseItem(responseItem, requestItem, requestInfo); - - const peerSharedIdentityAttribute = await senderConsumptionController.attributes.getLocalAttribute(responseItem.attributeId); - expect(peerSharedIdentityAttribute!.shareInfo!.peer).toStrictEqual(recipient); - expect(peerSharedIdentityAttribute!.shareInfo!.requestReference).toStrictEqual(requestInfo.id); - expect(peerSharedIdentityAttribute!.shareInfo!.sourceAttribute).toBeUndefined(); - - const truncatedFileReference = (peerSharedIdentityAttribute!.content.value as IdentityFileReference).value; - const file = await senderAccountController.files.getOrLoadFileByReference(FileReference.from(truncatedFileReference)); - expect(file.isOwn).toBe(false); - expect(file.cache!.tags).toStrictEqual(["x:tag"]); - }); - - test("updates the File whose ownership was claimed", async function () { + test("should update the File whose ownership was claimed", async function () { const senderFile = await TestUtil.uploadFile(senderAccountController, { tags: ["x:tag"] }); const senderTruncatedFileReference = senderFile.toFileReference(senderAccountController.config.baseUrl).truncate(); const requestItem = TransferFileOwnershipRequestItem.from({ mustBeAccepted: false, fileReference: senderTruncatedFileReference, - ownershipToken: senderFile.ownershipToken + ownershipToken: senderFile.ownershipToken! }); const requestInfo = { @@ -467,6 +362,7 @@ describe("TransferFileOwnershipRequestItemProcessor", function () { expect(peerSharedIdentityAttribute!.shareInfo!.peer).toStrictEqual(recipient); expect(peerSharedIdentityAttribute!.shareInfo!.requestReference).toStrictEqual(requestInfo.id); expect(peerSharedIdentityAttribute!.shareInfo!.sourceAttribute).toBeUndefined(); + expect((peerSharedIdentityAttribute!.content as IdentityAttribute).tags).toStrictEqual(["x:tag"]); const peerTruncatedFileReference = (peerSharedIdentityAttribute!.content.value as IdentityFileReference).value; expect(peerTruncatedFileReference).toStrictEqual(senderTruncatedFileReference); diff --git a/packages/content/src/requests/items/transferFileOwnership/TransferFileOwnershipRequestItem.ts b/packages/content/src/requests/items/transferFileOwnership/TransferFileOwnershipRequestItem.ts index c2eff8f69..a87105349 100644 --- a/packages/content/src/requests/items/transferFileOwnership/TransferFileOwnershipRequestItem.ts +++ b/packages/content/src/requests/items/transferFileOwnership/TransferFileOwnershipRequestItem.ts @@ -6,12 +6,12 @@ import { IRequestItem, RequestItem } from "../../RequestItem"; export interface TransferFileOwnershipRequestItemJSON extends RequestItemJSON { "@type": "TransferFileOwnershipRequestItem"; fileReference: string; - ownershipToken?: string; + ownershipToken: string; } export interface ITransferFileOwnershipRequestItem extends IRequestItem { fileReference: IFileReference; - ownershipToken?: string; + ownershipToken: string; } @type("TransferFileOwnershipRequestItem") @@ -21,8 +21,8 @@ export class TransferFileOwnershipRequestItem extends RequestItem implements ITr public fileReference: FileReference; @serialize() - @validate({ nullable: true }) - public ownershipToken?: string; + @validate() + public ownershipToken: string; public static from( value: ITransferFileOwnershipRequestItem | Omit | TransferFileOwnershipRequestItemJSON diff --git a/packages/content/test/requests/items/TransferFileOwnershipRequestItem.test.ts b/packages/content/test/requests/items/TransferFileOwnershipRequestItem.test.ts index f9c86e436..3b519d263 100644 --- a/packages/content/test/requests/items/TransferFileOwnershipRequestItem.test.ts +++ b/packages/content/test/requests/items/TransferFileOwnershipRequestItem.test.ts @@ -20,7 +20,8 @@ describe("TransferFileOwnershipRequestItem", () => { test("should serialize the fileReference of the RequestItem to a truncatedFileReference", function () { const requestItem = TransferFileOwnershipRequestItem.from({ mustBeAccepted: true, - fileReference: fileReference + fileReference, + ownershipToken: "anOwnershipToken" }); const requestItemJson = requestItem.toJSON(); @@ -31,7 +32,8 @@ describe("TransferFileOwnershipRequestItem", () => { const requestItemJson = { "@type": "TransferFileOwnershipRequestItemJSON", mustBeAccepted: true, - fileReference: fileReference.truncate() + fileReference: fileReference.truncate(), + ownershipToken: "anOwnershipToken" }; const requestItem = TransferFileOwnershipRequestItem.from(requestItemJson); diff --git a/packages/runtime/src/dataViews/content/RequestItemDVOs.ts b/packages/runtime/src/dataViews/content/RequestItemDVOs.ts index efefa2426..7479e8e14 100644 --- a/packages/runtime/src/dataViews/content/RequestItemDVOs.ts +++ b/packages/runtime/src/dataViews/content/RequestItemDVOs.ts @@ -79,5 +79,5 @@ export interface TransferFileOwnershipRequestItemDVO extends RequestItemDVO { type: "TransferFileOwnershipRequestItemDVO"; fileReference: string; file: FileDVO; - ownershipToken?: string; + ownershipToken: string; } diff --git a/packages/runtime/src/useCases/common/Schemas.ts b/packages/runtime/src/useCases/common/Schemas.ts index 2d1156657..8584d3432 100644 --- a/packages/runtime/src/useCases/common/Schemas.ts +++ b/packages/runtime/src/useCases/common/Schemas.ts @@ -3210,7 +3210,8 @@ export const CanCreateOutgoingRequestRequest: any = { "required": [ "@type", "fileReference", - "mustBeAccepted" + "mustBeAccepted", + "ownershipToken" ], "additionalProperties": false }, @@ -11308,7 +11309,8 @@ export const CreateOutgoingRequestRequest: any = { "required": [ "@type", "fileReference", - "mustBeAccepted" + "mustBeAccepted", + "ownershipToken" ], "additionalProperties": false }, @@ -14843,7 +14845,8 @@ export const ReceivedIncomingRequestRequest: any = { "required": [ "@type", "fileReference", - "mustBeAccepted" + "mustBeAccepted", + "ownershipToken" ], "additionalProperties": false }, diff --git a/packages/runtime/test/modules/DeciderModule.test.ts b/packages/runtime/test/modules/DeciderModule.test.ts index 891c79773..8edbfce99 100644 --- a/packages/runtime/test/modules/DeciderModule.test.ts +++ b/packages/runtime/test/modules/DeciderModule.test.ts @@ -2111,7 +2111,8 @@ describe("DeciderModule", () => { { "@type": "TransferFileOwnershipRequestItem", mustBeAccepted: true, - fileReference: file.reference.truncated + fileReference: file.reference.truncated, + ownershipToken: file.ownershipToken } ] },