diff --git a/docs/api/administration-service.yaml b/docs/api/administration-service.yaml index d3e6680572..c5c6fb7ef3 100644 --- a/docs/api/administration-service.yaml +++ b/docs/api/administration-service.yaml @@ -7655,6 +7655,9 @@ components: type: array items: $ref: '#/components/schemas/CompanyUniqueIdData' + holderDid: + type: string + nullable: true externalId: type: string userDetails: diff --git a/docs/api/registration-service.yaml b/docs/api/registration-service.yaml index 3514f90452..2300f0a434 100644 --- a/docs/api/registration-service.yaml +++ b/docs/api/registration-service.yaml @@ -3,6 +3,169 @@ info: title: Org.Eclipse.TractusX.Portal.Backend.Registration.Service version: v2.5.0 paths: + '/api/registration/bringYourOwnWallet/{did}/validateDid': + post: + tags: + - BringYourOwnWallet + summary: ' (Authorization required - Roles: submit_registration)' + parameters: + - name: did + in: path + required: true + schema: + type: string + responses: + '204': + description: No Content + content: + text/plain: + schema: + $ref: '#/components/schemas/NoContentResult' + application/json: + schema: + $ref: '#/components/schemas/NoContentResult' + text/json: + schema: + $ref: '#/components/schemas/NoContentResult' + '415': + description: Unsupported Media Type + content: + text/plain: + schema: + $ref: '#/components/schemas/ErrorResponse' + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + text/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found + content: + text/plain: + schema: + $ref: '#/components/schemas/ErrorResponse' + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + text/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error + '401': + description: The User is unauthorized + '/api/registration/bringYourOwnWallet/{companyId}/saveHolderDid/{did}': + post: + tags: + - BringYourOwnWallet + summary: ' (Authorization required - Roles: submit_registration)' + parameters: + - name: companyId + in: path + required: true + schema: + type: string + format: uuid + - name: did + in: path + required: true + schema: + type: string + responses: + '204': + description: No Content + content: + text/plain: + schema: + $ref: '#/components/schemas/NoContentResult' + application/json: + schema: + $ref: '#/components/schemas/NoContentResult' + text/json: + schema: + $ref: '#/components/schemas/NoContentResult' + '415': + description: Unsupported Media Type + content: + text/plain: + schema: + $ref: '#/components/schemas/ErrorResponse' + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + text/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found + content: + text/plain: + schema: + $ref: '#/components/schemas/ErrorResponse' + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + text/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error + '401': + description: The User is unauthorized + '/api/registration/bringYourOwnWallet/{companyId}/getHolderDid': + get: + tags: + - BringYourOwnWallet + summary: ' (Authorization required - Roles: submit_registration)' + parameters: + - name: companyId + in: path + required: true + schema: + type: string + format: uuid + responses: + '204': + description: No Content + content: + text/plain: + schema: + $ref: '#/components/schemas/NoContentResult' + application/json: + schema: + $ref: '#/components/schemas/NoContentResult' + text/json: + schema: + $ref: '#/components/schemas/NoContentResult' + '415': + description: Unsupported Media Type + content: + text/plain: + schema: + $ref: '#/components/schemas/ErrorResponse' + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + text/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '404': + description: Not Found + content: + text/plain: + schema: + $ref: '#/components/schemas/ErrorResponse' + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + text/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Internal Server Error + '401': + description: The User is unauthorized /api/registration/errormessage: get: tags: @@ -1333,6 +1496,9 @@ components: type: array items: $ref: '#/components/schemas/CompanyUniqueIdData' + holderDid: + type: string + nullable: true companyId: type: string format: uuid @@ -1589,6 +1755,13 @@ components: items: type: string additionalProperties: false + NoContentResult: + type: object + properties: + statusCode: + type: integer + format: int32 + additionalProperties: false OspDeclineReason: enum: - REGISTRATION_NOT_REQUESTED diff --git a/src/Portal.Backend.sln b/src/Portal.Backend.sln index 5504c5d307..9cd03ae85c 100644 --- a/src/Portal.Backend.sln +++ b/src/Portal.Backend.sln @@ -290,6 +290,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Framework.Processes.Worker. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Framework.Processes.Library", "framework\Framework.Processes.Library\Framework.Processes.Library.csproj", "{28007753-C8F8-43B0-A1C3-8FDC22727F44}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalDidResolver.Library", "externalsystems\UniversalDidResolver.Library\UniversalDidResolver.Library.csproj", "{1D666BBA-1F72-422D-B7D4-6355E2FB734A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "externalsystems", "externalsystems", "{6C41D4C8-B50D-D926-DC14-09C2931EC54F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UniversalDidResolver.Library.Tests", "..\tests\externalsystems\UniversalDidResolver.Library.Tests\UniversalDidResolver.Library.Tests.csproj", "{24D3F795-29AD-47B3-84B8-64644F7FE742}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -900,18 +906,6 @@ Global {B787DF92-23F7-410A-B592-95701E4B423D}.Release|x64.Build.0 = Release|Any CPU {B787DF92-23F7-410A-B592-95701E4B423D}.Release|x86.ActiveCfg = Release|Any CPU {B787DF92-23F7-410A-B592-95701E4B423D}.Release|x86.Build.0 = Release|Any CPU - {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Debug|x64.ActiveCfg = Debug|Any CPU - {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Debug|x64.Build.0 = Debug|Any CPU - {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Debug|x86.ActiveCfg = Debug|Any CPU - {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Debug|x86.Build.0 = Debug|Any CPU - {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Release|Any CPU.Build.0 = Release|Any CPU - {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Release|x64.ActiveCfg = Release|Any CPU - {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Release|x64.Build.0 = Release|Any CPU - {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Release|x86.ActiveCfg = Release|Any CPU - {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Release|x86.Build.0 = Release|Any CPU {7985B208-CE41-49DA-B749-B94B582612E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7985B208-CE41-49DA-B749-B94B582612E6}.Debug|Any CPU.Build.0 = Debug|Any CPU {7985B208-CE41-49DA-B749-B94B582612E6}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -924,6 +918,18 @@ Global {7985B208-CE41-49DA-B749-B94B582612E6}.Release|x64.Build.0 = Release|Any CPU {7985B208-CE41-49DA-B749-B94B582612E6}.Release|x86.ActiveCfg = Release|Any CPU {7985B208-CE41-49DA-B749-B94B582612E6}.Release|x86.Build.0 = Release|Any CPU + {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Debug|x64.ActiveCfg = Debug|Any CPU + {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Debug|x64.Build.0 = Debug|Any CPU + {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Debug|x86.ActiveCfg = Debug|Any CPU + {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Debug|x86.Build.0 = Debug|Any CPU + {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Release|Any CPU.Build.0 = Release|Any CPU + {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Release|x64.ActiveCfg = Release|Any CPU + {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Release|x64.Build.0 = Release|Any CPU + {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Release|x86.ActiveCfg = Release|Any CPU + {4C7E9EAC-222B-4C13-B8B1-5987406817A0}.Release|x86.Build.0 = Release|Any CPU {15BA8836-E9FE-4F64-AD97-261A524779A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {15BA8836-E9FE-4F64-AD97-261A524779A5}.Debug|Any CPU.Build.0 = Debug|Any CPU {15BA8836-E9FE-4F64-AD97-261A524779A5}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -1560,6 +1566,18 @@ Global {690B95C5-5BDD-472B-93E3-414F46C4EE94}.Release|x64.Build.0 = Release|Any CPU {690B95C5-5BDD-472B-93E3-414F46C4EE94}.Release|x86.ActiveCfg = Release|Any CPU {690B95C5-5BDD-472B-93E3-414F46C4EE94}.Release|x86.Build.0 = Release|Any CPU + {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Debug|x64.ActiveCfg = Debug|Any CPU + {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Debug|x64.Build.0 = Debug|Any CPU + {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Debug|x86.ActiveCfg = Debug|Any CPU + {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Debug|x86.Build.0 = Debug|Any CPU + {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Release|Any CPU.Build.0 = Release|Any CPU + {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Release|x64.ActiveCfg = Release|Any CPU + {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Release|x64.Build.0 = Release|Any CPU + {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Release|x86.ActiveCfg = Release|Any CPU + {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Release|x86.Build.0 = Release|Any CPU {0551F0AF-5373-4516-87AA-01D7FFD29C0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0551F0AF-5373-4516-87AA-01D7FFD29C0D}.Debug|Any CPU.Build.0 = Debug|Any CPU {0551F0AF-5373-4516-87AA-01D7FFD29C0D}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -1644,18 +1662,6 @@ Global {07BDC20D-23DD-4C0E-9C3D-B1D232AF17E3}.Release|x64.Build.0 = Release|Any CPU {07BDC20D-23DD-4C0E-9C3D-B1D232AF17E3}.Release|x86.ActiveCfg = Release|Any CPU {07BDC20D-23DD-4C0E-9C3D-B1D232AF17E3}.Release|x86.Build.0 = Release|Any CPU - {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Release|x86.ActiveCfg = Release|Any CPU - {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Release|x86.Build.0 = Release|Any CPU - {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Debug|x64.ActiveCfg = Debug|Any CPU - {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Debug|x64.Build.0 = Debug|Any CPU - {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Debug|x86.ActiveCfg = Debug|Any CPU - {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Debug|x86.Build.0 = Debug|Any CPU - {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Release|Any CPU.Build.0 = Release|Any CPU - {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Release|x64.ActiveCfg = Release|Any CPU - {D666EADA-770A-42FF-B891-5745F7A6BC2F}.Release|x64.Build.0 = Release|Any CPU {59E4B63B-BEA2-4CDA-98F0-13962146AEA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {59E4B63B-BEA2-4CDA-98F0-13962146AEA5}.Debug|Any CPU.Build.0 = Debug|Any CPU {59E4B63B-BEA2-4CDA-98F0-13962146AEA5}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -1860,31 +1866,35 @@ Global {28007753-C8F8-43B0-A1C3-8FDC22727F44}.Release|x64.Build.0 = Release|Any CPU {28007753-C8F8-43B0-A1C3-8FDC22727F44}.Release|x86.ActiveCfg = Release|Any CPU {28007753-C8F8-43B0-A1C3-8FDC22727F44}.Release|x86.Build.0 = Release|Any CPU + {1D666BBA-1F72-422D-B7D4-6355E2FB734A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D666BBA-1F72-422D-B7D4-6355E2FB734A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D666BBA-1F72-422D-B7D4-6355E2FB734A}.Debug|x64.ActiveCfg = Debug|Any CPU + {1D666BBA-1F72-422D-B7D4-6355E2FB734A}.Debug|x64.Build.0 = Debug|Any CPU + {1D666BBA-1F72-422D-B7D4-6355E2FB734A}.Debug|x86.ActiveCfg = Debug|Any CPU + {1D666BBA-1F72-422D-B7D4-6355E2FB734A}.Debug|x86.Build.0 = Debug|Any CPU + {1D666BBA-1F72-422D-B7D4-6355E2FB734A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D666BBA-1F72-422D-B7D4-6355E2FB734A}.Release|Any CPU.Build.0 = Release|Any CPU + {1D666BBA-1F72-422D-B7D4-6355E2FB734A}.Release|x64.ActiveCfg = Release|Any CPU + {1D666BBA-1F72-422D-B7D4-6355E2FB734A}.Release|x64.Build.0 = Release|Any CPU + {1D666BBA-1F72-422D-B7D4-6355E2FB734A}.Release|x86.ActiveCfg = Release|Any CPU + {1D666BBA-1F72-422D-B7D4-6355E2FB734A}.Release|x86.Build.0 = Release|Any CPU + {24D3F795-29AD-47B3-84B8-64644F7FE742}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24D3F795-29AD-47B3-84B8-64644F7FE742}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24D3F795-29AD-47B3-84B8-64644F7FE742}.Debug|x64.ActiveCfg = Debug|Any CPU + {24D3F795-29AD-47B3-84B8-64644F7FE742}.Debug|x64.Build.0 = Debug|Any CPU + {24D3F795-29AD-47B3-84B8-64644F7FE742}.Debug|x86.ActiveCfg = Debug|Any CPU + {24D3F795-29AD-47B3-84B8-64644F7FE742}.Debug|x86.Build.0 = Debug|Any CPU + {24D3F795-29AD-47B3-84B8-64644F7FE742}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24D3F795-29AD-47B3-84B8-64644F7FE742}.Release|Any CPU.Build.0 = Release|Any CPU + {24D3F795-29AD-47B3-84B8-64644F7FE742}.Release|x64.ActiveCfg = Release|Any CPU + {24D3F795-29AD-47B3-84B8-64644F7FE742}.Release|x64.Build.0 = Release|Any CPU + {24D3F795-29AD-47B3-84B8-64644F7FE742}.Release|x86.ActiveCfg = Release|Any CPU + {24D3F795-29AD-47B3-84B8-64644F7FE742}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {2EB6265F-323A-4BF3-969E-003D64A14B64} - EndGlobalSection GlobalSection(NestedProjects) = preSolution - {0CBCC851-99A1-4005-9BBA-E6A20A0AEDAA} = {323C198D-A8C6-4EB0-8B79-72624275E35F} - {CD76A7FF-D003-41DE-9442-F9AB223C6051} = {323C198D-A8C6-4EB0-8B79-72624275E35F} - {A43B5ACA-1209-46E9-84DB-A48553ED623E} = {323C198D-A8C6-4EB0-8B79-72624275E35F} - {A5BEDD89-7280-466E-8D14-EC5E177AAD07} = {323C198D-A8C6-4EB0-8B79-72624275E35F} - {5E80DEEA-B254-425C-8220-27EEF47C10BD} = {323C198D-A8C6-4EB0-8B79-72624275E35F} - {B787DF92-23F7-410A-B592-95701E4B423D} = {323C198D-A8C6-4EB0-8B79-72624275E35F} - {0C039C14-74CA-484C-B8D9-A307C97EC312} = {323C198D-A8C6-4EB0-8B79-72624275E35F} - {2B1218E4-E1A4-418E-A55F-16B7F30235A7} = {323C198D-A8C6-4EB0-8B79-72624275E35F} - {6C2F85D7-0443-4711-96DF-66EC46CA1A98} = {323C198D-A8C6-4EB0-8B79-72624275E35F} - {217FE6D7-0DE3-43CF-AFC4-7FA12700F447} = {323C198D-A8C6-4EB0-8B79-72624275E35F} - {B8E70529-7A8C-44F8-BE8C-1369754291AF} = {323C198D-A8C6-4EB0-8B79-72624275E35F} - {53E03A1A-E59F-4C67-80B4-5A232BF01A81} = {323C198D-A8C6-4EB0-8B79-72624275E35F} - {F995582E-729F-4EA0-831F-6CA5058114EF} = {323C198D-A8C6-4EB0-8B79-72624275E35F} - {12F16E1B-0275-4F41-8353-C2C9A79BA4E9} = {323C198D-A8C6-4EB0-8B79-72624275E35F} - {B6DE0855-385D-4A8C-BC22-04BE2105A2F4} = {323C198D-A8C6-4EB0-8B79-72624275E35F} - {F0EFB95B-39DD-4FDB-A044-0BB9302DDC41} = {323C198D-A8C6-4EB0-8B79-72624275E35F} - {1EFC9D98-C8EE-4399-9B2D-876CDDE8CFD3} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {06418D5E-5963-4D46-8F09-A0E132721C64} = {E0B99F7E-D108-4054-92C2-31304C5060DE} {69004CBA-5B0C-42C7-A4DA-4727F14AA20A} = {46383371-8252-4598-9350-A97692851408} {C482693F-A8D8-40FA-AD93-00B03CA6DC31} = {46383371-8252-4598-9350-A97692851408} @@ -1905,7 +1915,18 @@ Global {C512740F-48A2-4B5B-83F2-EB7753EAE028} = {AE4A5C54-72F3-4B55-BB86-09DFA3AA3D7B} {22DEE4A2-15ED-4176-B912-B357D474D2AC} = {AE4A5C54-72F3-4B55-BB86-09DFA3AA3D7B} {FBEA925C-EE3C-4D81-A492-0B2D386C161E} = {AB9C5AA2-DD5D-4A38-97C0-674A995C0AE0} + {1EFC9D98-C8EE-4399-9B2D-876CDDE8CFD3} = {323C198D-A8C6-4EB0-8B79-72624275E35F} + {F0EFB95B-39DD-4FDB-A044-0BB9302DDC41} = {323C198D-A8C6-4EB0-8B79-72624275E35F} + {F995582E-729F-4EA0-831F-6CA5058114EF} = {323C198D-A8C6-4EB0-8B79-72624275E35F} + {6C2F85D7-0443-4711-96DF-66EC46CA1A98} = {323C198D-A8C6-4EB0-8B79-72624275E35F} + {B6DE0855-385D-4A8C-BC22-04BE2105A2F4} = {323C198D-A8C6-4EB0-8B79-72624275E35F} + {12F16E1B-0275-4F41-8353-C2C9A79BA4E9} = {323C198D-A8C6-4EB0-8B79-72624275E35F} + {53E03A1A-E59F-4C67-80B4-5A232BF01A81} = {323C198D-A8C6-4EB0-8B79-72624275E35F} + {B8E70529-7A8C-44F8-BE8C-1369754291AF} = {323C198D-A8C6-4EB0-8B79-72624275E35F} + {217FE6D7-0DE3-43CF-AFC4-7FA12700F447} = {323C198D-A8C6-4EB0-8B79-72624275E35F} + {2B1218E4-E1A4-418E-A55F-16B7F30235A7} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {3F7A02D4-073C-40FE-B228-8E1BA96B1946} = {AE4A5C54-72F3-4B55-BB86-09DFA3AA3D7B} + {0C039C14-74CA-484C-B8D9-A307C97EC312} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {3828FF08-4CD7-4FF8-B94A-ED5D5FFA0382} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {8B7D8210-05A4-4C0A-AB19-D695E6E97281} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {CF80BBB1-C07D-425C-9A5B-54C5E2890CC4} = {282CEF03-292F-4A49-83C6-997567D0FF5F} @@ -1923,20 +1944,24 @@ Global {96D96CA7-35C0-40C6-A8C8-91E0C4456660} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {C3E5E7C8-69D3-4ECB-A4FA-53A9A780EFF0} = {282CEF03-292F-4A49-83C6-997567D0FF5F} {0221E83B-B26B-442F-ACAD-B1043DF9993A} = {282CEF03-292F-4A49-83C6-997567D0FF5F} - {4C7E9EAC-222B-4C13-B8B1-5987406817A0} = {282CEF03-292F-4A49-83C6-997567D0FF5F} + {B787DF92-23F7-410A-B592-95701E4B423D} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {7985B208-CE41-49DA-B749-B94B582612E6} = {323C198D-A8C6-4EB0-8B79-72624275E35F} + {4C7E9EAC-222B-4C13-B8B1-5987406817A0} = {282CEF03-292F-4A49-83C6-997567D0FF5F} {15BA8836-E9FE-4F64-AD97-261A524779A5} = {282CEF03-292F-4A49-83C6-997567D0FF5F} {1EE0ED29-2076-49B0-8110-FF43C9612888} = {C8957230-4203-452C-A085-34091C5E370B} {B682C5B9-AFAB-474D-95AD-B86099FC5EC7} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {C356AA93-5BEE-44CD-A905-EFD325ED7578} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {CC063A63-8282-4293-9A0E-2598ADCA747B} = {323C198D-A8C6-4EB0-8B79-72624275E35F} + {5E80DEEA-B254-425C-8220-27EEF47C10BD} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {EC493B36-9E14-4CAF-973F-FB96FDAF546F} = {A878BDF1-6DB6-4BA5-A724-92885A710856} {1694E75F-ABCE-4573-B805-18ED50F148FD} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {E1D41A07-F468-4D13-8185-35F127230B17} = {46383371-8252-4598-9350-A97692851408} + {A5BEDD89-7280-466E-8D14-EC5E177AAD07} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {19639645-A115-4824-865F-5559DA8B892A} = {282CEF03-292F-4A49-83C6-997567D0FF5F} {3B41408A-CDFE-4EEE-9660-FE6755FD2075} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {C53EAB34-1F66-48F8-88AB-226BE142D1CF} = {282CEF03-292F-4A49-83C6-997567D0FF5F} {B06A4F63-FE8C-40D2-B39F-8C15031A55CC} = {C8957230-4203-452C-A085-34091C5E370B} + {A43B5ACA-1209-46E9-84DB-A48553ED623E} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {F1A5A73C-2B8C-4959-A128-CC5A8DECCB1B} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {09EF5799-B375-49F1-B78F-0A94D8109F8B} = {AB9C5AA2-DD5D-4A38-97C0-674A995C0AE0} {C7ACF748-DEF4-4646-A791-F1DA437CC965} = {323C198D-A8C6-4EB0-8B79-72624275E35F} @@ -1975,6 +2000,7 @@ Global {AD0C9BAD-E68F-40CF-86B4-31AC61856658} = {B42CFF96-B8DB-48A6-A9CA-72BFB5F0117B} {C3F19E18-B8B9-4BCB-988D-8D1ADCE41D66} = {C8957230-4203-452C-A085-34091C5E370B} {690B95C5-5BDD-472B-93E3-414F46C4EE94} = {323C198D-A8C6-4EB0-8B79-72624275E35F} + {D666EADA-770A-42FF-B891-5745F7A6BC2F} = {282CEF03-292F-4A49-83C6-997567D0FF5F} {0551F0AF-5373-4516-87AA-01D7FFD29C0D} = {282CEF03-292F-4A49-83C6-997567D0FF5F} {4693D24A-E6AA-4C3D-9990-5F095EACBD0B} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {BF8EB6BB-E8CA-423C-AC50-01FB44D0D08B} = {323C198D-A8C6-4EB0-8B79-72624275E35F} @@ -1982,7 +2008,6 @@ Global {B6A2A753-5876-4C6C-8CC2-151B022C87F7} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {F2F9E057-668E-43BB-B3EB-10E8DE8E7BB4} = {282CEF03-292F-4A49-83C6-997567D0FF5F} {07BDC20D-23DD-4C0E-9C3D-B1D232AF17E3} = {323C198D-A8C6-4EB0-8B79-72624275E35F} - {D666EADA-770A-42FF-B891-5745F7A6BC2F} = {282CEF03-292F-4A49-83C6-997567D0FF5F} {59E4B63B-BEA2-4CDA-98F0-13962146AEA5} = {C8957230-4203-452C-A085-34091C5E370B} {C6DE5F4C-DB67-48B9-BE0E-0C557AB704AC} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {ED7073C2-1D64-4FB4-A363-1EC2CBCB1302} = {C8957230-4203-452C-A085-34091C5E370B} @@ -1991,6 +2016,8 @@ Global {143433B2-2792-4C5F-A3C2-E5C91D68E30D} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {9636BEC8-6929-4852-8DC8-8B41609630A3} = {282CEF03-292F-4A49-83C6-997567D0FF5F} {E5494227-BDFE-41F2-A12F-54292D76C29F} = {282CEF03-292F-4A49-83C6-997567D0FF5F} + {CD76A7FF-D003-41DE-9442-F9AB223C6051} = {323C198D-A8C6-4EB0-8B79-72624275E35F} + {0CBCC851-99A1-4005-9BBA-E6A20A0AEDAA} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {D8EBE555-F103-4D05-9697-5381E4DE1DFE} = {282CEF03-292F-4A49-83C6-997567D0FF5F} {AA14B842-6A65-40BB-818A-D450F66F4101} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {6ED01D72-BE48-45A6-A615-BFA83DF99EF4} = {B42CFF96-B8DB-48A6-A9CA-72BFB5F0117B} @@ -1998,5 +2025,11 @@ Global {96F978B7-71C1-4866-BFB5-8631F4743FA5} = {B42CFF96-B8DB-48A6-A9CA-72BFB5F0117B} {BD0268EB-65A9-4A0A-B724-214A6D2591B5} = {B42CFF96-B8DB-48A6-A9CA-72BFB5F0117B} {28007753-C8F8-43B0-A1C3-8FDC22727F44} = {B42CFF96-B8DB-48A6-A9CA-72BFB5F0117B} + {1D666BBA-1F72-422D-B7D4-6355E2FB734A} = {C8957230-4203-452C-A085-34091C5E370B} + {6C41D4C8-B50D-D926-DC14-09C2931EC54F} = {323C198D-A8C6-4EB0-8B79-72624275E35F} + {24D3F795-29AD-47B3-84B8-64644F7FE742} = {323C198D-A8C6-4EB0-8B79-72624275E35F} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2EB6265F-323A-4BF3-969E-003D64A14B64} EndGlobalSection EndGlobal diff --git a/src/administration/Administration.Service/BusinessLogic/BringYourOwnWalletBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/BringYourOwnWalletBusinessLogic.cs new file mode 100644 index 0000000000..97ab8cfac7 --- /dev/null +++ b/src/administration/Administration.Service/BusinessLogic/BringYourOwnWalletBusinessLogic.cs @@ -0,0 +1,43 @@ +/******************************************************************************** + * Copyright (c) 2025 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 Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories; + +namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.BusinessLogic; + +public class BringYourOwnWalletBusinessLogic( + IPortalRepositories portalRepositories, + IOptions options) : IBringYourOwnWalletBusinessLogic +{ + private readonly IEnumerable _excludedRoles = options.Value.NonApplicableUserRoles; + + public IEnumerable GetExcludedUserRoles() + { + return _excludedRoles; + } + + public async Task IsBringYourOwnWallet(Guid companyId) + { + var companyRepository = portalRepositories.GetInstance(); + var applicationId = await companyRepository.GetApplicationIdByCompanyId(companyId); + return await companyRepository.IsBringYourOwnWallet(applicationId); + } +} diff --git a/src/administration/Administration.Service/BusinessLogic/BringYourOwnWalletSettings.cs b/src/administration/Administration.Service/BusinessLogic/BringYourOwnWalletSettings.cs new file mode 100644 index 0000000000..799b2e1ecf --- /dev/null +++ b/src/administration/Administration.Service/BusinessLogic/BringYourOwnWalletSettings.cs @@ -0,0 +1,26 @@ + +/******************************************************************************** + * Copyright (c) 2025 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.BusinessLogic; + +public class BringYourOwnWalletSettings +{ + public IEnumerable NonApplicableUserRoles { get; set; } = Enumerable.Empty(); +} diff --git a/src/administration/Administration.Service/BusinessLogic/IBringYourOwnWalletBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/IBringYourOwnWalletBusinessLogic.cs new file mode 100644 index 0000000000..2c5debfd3d --- /dev/null +++ b/src/administration/Administration.Service/BusinessLogic/IBringYourOwnWalletBusinessLogic.cs @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (c) 2025 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 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.BusinessLogic; + +public interface IBringYourOwnWalletBusinessLogic +{ + IEnumerable GetExcludedUserRoles(); + + Task IsBringYourOwnWallet(Guid companyId); + +} diff --git a/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs index 76908fb009..5816b29e25 100644 --- a/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/RegistrationBusinessLogic.cs @@ -249,6 +249,7 @@ private async Task UpdateCompanyBpnInternal(Guid applicationId, string bpn) throw ConflictException.Create(AdministrationRegistrationErrors.REGISTRATION_CONFLICT_BPN_OF_COMPANY_SET, new ErrorParameter[] { new("companyId", applicationCompanyData.CompanyId.ToString()) }); } + var createWalletOrTransmitCustomerDidStep = await CreateWalletOrBpnCredentialStepAsync(applicationId); var context = await checklistService .VerifyChecklistEntryAndProcessSteps( applicationId, @@ -267,7 +268,7 @@ private async Task UpdateCompanyBpnInternal(Guid applicationId, string bpn) ProcessStepTypeId.CREATE_BUSINESS_PARTNER_NUMBER_PULL, ProcessStepTypeId.RETRIGGER_BUSINESS_PARTNER_NUMBER_PULL, ProcessStepTypeId.RETRIGGER_BUSINESS_PARTNER_NUMBER_PUSH, - ProcessStepTypeId.CREATE_IDENTITY_WALLET + createWalletOrTransmitCustomerDidStep ]) .ConfigureAwait(ConfigureAwaitOptions.None); @@ -291,13 +292,18 @@ private async Task UpdateCompanyBpnInternal(Guid applicationId, string bpn) entry => entry.ApplicationChecklistEntryStatusId = ApplicationChecklistEntryStatusId.DONE, registrationValidationFailed ? null - : new[] { CreateWalletStep() }); + : new[] { createWalletOrTransmitCustomerDidStep }); await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); } private ProcessStepTypeId CreateWalletStep() => _settings.UseDimWallet ? ProcessStepTypeId.CREATE_DIM_WALLET : ProcessStepTypeId.CREATE_IDENTITY_WALLET; + private async Task CreateWalletOrBpnCredentialStepAsync(Guid applicationId) + { + var isWalletCustomerProvider = await portalRepositories.GetInstance().IsBringYourOwnWallet(applicationId); + return isWalletCustomerProvider ? ProcessStepTypeId.TRANSMIT_BPN_DID : CreateWalletStep(); + } /// public async Task ProcessClearinghouseResponseAsync(ClearinghouseResponseData data, string bpn, CancellationToken cancellationToken) { @@ -428,6 +434,7 @@ public async Task ProcessClearinghouseSelfDescription(SelfDescriptionResponseDat /// public async Task ApproveRegistrationVerification(Guid applicationId) { + var createWalletOrTransmitCustomerDidStep = await CreateWalletOrBpnCredentialStepAsync(applicationId); var context = await checklistService .VerifyChecklistEntryAndProcessSteps( applicationId, @@ -435,7 +442,7 @@ public async Task ApproveRegistrationVerification(Guid applicationId) [ApplicationChecklistEntryStatusId.TO_DO], ProcessStepTypeId.MANUAL_VERIFY_REGISTRATION, [ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER], - [CreateWalletStep()]) + [createWalletOrTransmitCustomerDidStep]) .ConfigureAwait(ConfigureAwaitOptions.None); var businessPartnerSuccess = context.Checklist[ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER] == new ValueTuple(ApplicationChecklistEntryStatusId.DONE, null); @@ -448,7 +455,7 @@ public async Task ApproveRegistrationVerification(Guid applicationId) entry.ApplicationChecklistEntryStatusId = ApplicationChecklistEntryStatusId.DONE; }, businessPartnerSuccess - ? new[] { CreateWalletStep() } + ? new[] { createWalletOrTransmitCustomerDidStep } : null); await portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); diff --git a/src/administration/Administration.Service/BusinessLogic/TechnicalUserBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/TechnicalUserBusinessLogic.cs index a7568d21a8..0c39a6ed6a 100644 --- a/src/administration/Administration.Service/BusinessLogic/TechnicalUserBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/TechnicalUserBusinessLogic.cs @@ -46,7 +46,8 @@ public class TechnicalUserBusinessLogic( IOptions options, ITechnicalUserCreation technicalUserCreation, IIdentityService identityService, - IServiceAccountManagement serviceAccountManagement) + IServiceAccountManagement serviceAccountManagement, + IBringYourOwnWalletBusinessLogic bringYourOwnWalletBusinessLogic) : ITechnicalUserBusinessLogic { private readonly IIdentityData _identityData = identityService.IdentityData; @@ -81,6 +82,13 @@ public async Task> CreateOwnCompanyServiceAcc technicalUserCreationInfos.UserRoleIds.Except(result.TechnicalUserRoleIds) .IfAny(unassignable => throw ControllerArgumentException.Create(AdministrationServiceAccountErrors.SERVICE_ROLES_NOT_ASSIGN_ARGUMENT, parameters: [new("unassignable", string.Join(",", unassignable)), new("userRoleIds", string.Join(",", result.TechnicalUserRoleIds))])); + var filteredTechnicalUserRoles = technicalUserCreationInfos.UserRoleIds.Where(roleId => result.TechnicalUserRoleIds.Contains(roleId)).ToImmutableArray(); + var isBringYourOwnWallet = await bringYourOwnWalletBusinessLogic.IsBringYourOwnWallet(companyId).ConfigureAwait(ConfigureAwaitOptions.None); + if (isBringYourOwnWallet && filteredTechnicalUserRoles.Any(id => bringYourOwnWalletBusinessLogic.GetExcludedUserRoles().Contains(id))) + { + throw ControllerArgumentException.Create(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_USER_ROLES_NOT_ALLOWED, parameters: [new("userRoleIds", string.Join(",", bringYourOwnWalletBusinessLogic.GetExcludedUserRoles()))]); + } + const TechnicalUserTypeId TechnicalUserTypeId = TechnicalUserTypeId.OWN; var (_, _, serviceAccounts) = await technicalUserCreation.CreateTechnicalUsersAsync(technicalUserCreationInfos, companyId, [result.Bpn], TechnicalUserTypeId, false, true, new ServiceAccountCreationProcessData(ProcessTypeId.DIM_TECHNICAL_USER, null)).ConfigureAwait(ConfigureAwaitOptions.None); diff --git a/src/administration/Administration.Service/ErrorHandling/AdministrationServiceAccountErrorMessageContainer.cs b/src/administration/Administration.Service/ErrorHandling/AdministrationServiceAccountErrorMessageContainer.cs index dbd6887cda..e5bb4ab50b 100644 --- a/src/administration/Administration.Service/ErrorHandling/AdministrationServiceAccountErrorMessageContainer.cs +++ b/src/administration/Administration.Service/ErrorHandling/AdministrationServiceAccountErrorMessageContainer.cs @@ -41,6 +41,7 @@ public class AdministrationServiceAccountErrorMessageContainer : IErrorMessageCo new((int)AdministrationServiceAccountErrors.SERVICE_ACCOUNT_PENDING_PROCESS_STEPS, "Service Account {serviceAccountId} has pending process steps {processStepTypeIds}"), new((int)AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NOT_ACTIVE, "Service Account {serviceAccountId} is not status active"), new((int)AdministrationServiceAccountErrors.SERVICE_ACCOUNT_NO_PROVIDER_OR_OWNER, "Only provider or owner of the service account are allowed to delete it"), + new((int)AdministrationServiceAccountErrors.SERVICE_ACCOUNT_USER_ROLES_NOT_ALLOWED, "The roles {userRoleIds} are not allowed, clientId is BringYourOwnWallet"), new((int)AdministrationServiceAccountErrors.TECHNICAL_USER_CREATION_IN_PROGRESS, "Technical user can't be deleted because the creation progress is still running") ]); @@ -66,5 +67,6 @@ public enum AdministrationServiceAccountErrors SERVICE_ACCOUNT_PENDING_PROCESS_STEPS, TECHNICAL_USER_CREATION_IN_PROGRESS, SERVICE_ACCOUNT_NOT_ACTIVE, - SERVICE_ACCOUNT_NO_PROVIDER_OR_OWNER + SERVICE_ACCOUNT_NO_PROVIDER_OR_OWNER, + SERVICE_ACCOUNT_USER_ROLES_NOT_ALLOWED, } diff --git a/src/administration/Administration.Service/Program.cs b/src/administration/Administration.Service/Program.cs index 2e116630f3..c69a919265 100644 --- a/src/administration/Administration.Service/Program.cs +++ b/src/administration/Administration.Service/Program.cs @@ -64,6 +64,11 @@ await WebAppHelper builder.Services.AddTransient() .ConfigureRegistrationSettings(builder.Configuration.GetSection("Registration")); + builder.Services + .AddTransient() + .Configure( + builder.Configuration.GetSection("BringYourOwnWallet")); + builder.Services.AddTransient() .ConfigureServiceAccountSettings(builder.Configuration.GetSection("ServiceAccount")); diff --git a/src/administration/Administration.Service/appsettings.json b/src/administration/Administration.Service/appsettings.json index fa2bf198b1..15e07155dd 100644 --- a/src/administration/Administration.Service/appsettings.json +++ b/src/administration/Administration.Service/appsettings.json @@ -64,6 +64,11 @@ "ClockSkew": "00:05:00" } }, + "BringYourOwnWallet": { + "NonApplicableUserRoles": [ + "607818be-4978-41f4-bf63-fa8d2de51158" + ] + }, "Provisioning": { "CentralRealm": "", "CentralRealmId": "", diff --git a/src/externalsystems/Bpdm.Library/BusinessLogic/BpdmBusinessLogic.cs b/src/externalsystems/Bpdm.Library/BusinessLogic/BpdmBusinessLogic.cs index 6107a7313d..fff16d6079 100644 --- a/src/externalsystems/Bpdm.Library/BusinessLogic/BpdmBusinessLogic.cs +++ b/src/externalsystems/Bpdm.Library/BusinessLogic/BpdmBusinessLogic.cs @@ -151,17 +151,24 @@ await HandlePullLegalEntityInternal(context, result.CompanyId, result.BpdmData, }); var registrationValidationFailed = context.Checklist[ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION] == ApplicationChecklistEntryStatusId.FAILED; + var createWalletOrTransmitCustomerDidStep = await CreateWalletOrBpnCredentialStepAsync(context.ApplicationId); return new IApplicationChecklistService.WorkerChecklistProcessStepExecutionResult( ProcessStepStatusId.DONE, entry => entry.ApplicationChecklistEntryStatusId = ApplicationChecklistEntryStatusId.DONE, registrationValidationFailed ? null - : new[] { CreateWalletStep() }, + : new[] { createWalletOrTransmitCustomerDidStep }, new[] { ProcessStepTypeId.CREATE_BUSINESS_PARTNER_NUMBER_MANUAL }, true, null); } private ProcessStepTypeId CreateWalletStep() => _settings.UseDimWallet ? ProcessStepTypeId.CREATE_DIM_WALLET : ProcessStepTypeId.CREATE_IDENTITY_WALLET; + + private async Task CreateWalletOrBpnCredentialStepAsync(Guid applicationId) + { + var isWalletCustomerProvider = await portalRepositories.GetInstance().IsBringYourOwnWallet(applicationId); + return isWalletCustomerProvider ? ProcessStepTypeId.TRANSMIT_BPN_DID : CreateWalletStep(); + } } diff --git a/src/externalsystems/BpnDidResolver.Library/BusinessLogic/BpnDidResolverBusinessLogic.cs b/src/externalsystems/BpnDidResolver.Library/BusinessLogic/BpnDidResolverBusinessLogic.cs index 98c17a58e4..27c3f98d3b 100644 --- a/src/externalsystems/BpnDidResolver.Library/BusinessLogic/BpnDidResolverBusinessLogic.cs +++ b/src/externalsystems/BpnDidResolver.Library/BusinessLogic/BpnDidResolverBusinessLogic.cs @@ -39,7 +39,7 @@ public BpnDidResolverBusinessLogic(IPortalRepositories portalRepositories, IBpnD public async Task TransmitDidAndBpn(IApplicationChecklistService.WorkerChecklistProcessStepData context, CancellationToken cancellationToken) { - if (context.Checklist[ApplicationChecklistEntryTypeId.IDENTITY_WALLET] != ApplicationChecklistEntryStatusId.IN_PROGRESS) + if (context.Checklist[ApplicationChecklistEntryTypeId.IDENTITY_WALLET] != ApplicationChecklistEntryStatusId.IN_PROGRESS && !await IsBringYourOwnWallet(context.ApplicationId)) { return new IApplicationChecklistService.WorkerChecklistProcessStepExecutionResult( ProcessStepStatusId.FAILED, @@ -64,6 +64,9 @@ public BpnDidResolverBusinessLogic(IPortalRepositories portalRepositories, IBpnD null); } + private async Task IsBringYourOwnWallet(Guid applicationId) => + await _portalRepositories.GetInstance().IsBringYourOwnWallet(applicationId).ConfigureAwait(ConfigureAwaitOptions.None); + private async Task PostDidAndBpn(Guid applicationId, CancellationToken cancellationToken) { var (exists, did, bpn) = await _portalRepositories.GetInstance().GetDidAndBpnForApplicationId(applicationId).ConfigureAwait(ConfigureAwaitOptions.None); diff --git a/src/externalsystems/Dim.Library/BusinessLogic/DimBusinessLogic.cs b/src/externalsystems/Dim.Library/BusinessLogic/DimBusinessLogic.cs index 7ae3026cf1..56026b8002 100644 --- a/src/externalsystems/Dim.Library/BusinessLogic/DimBusinessLogic.cs +++ b/src/externalsystems/Dim.Library/BusinessLogic/DimBusinessLogic.cs @@ -247,7 +247,7 @@ private static async Task ValidateSchema(JsonDocument content, Cancellatio { var location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? throw new UnexpectedConditionException("Assembly location must be set"); - var path = Path.Combine(location, "Schemas", "DidDocument.schema.json"); + var path = Path.Join(location, "Schemas", "DidDocument.schema.json"); var schemaJson = await File.ReadAllTextAsync(path, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); var schema = JsonSchema.FromText(schemaJson); diff --git a/src/externalsystems/IssuerComponent.Library/BusinessLogic/IssuerComponentBusinessLogic.cs b/src/externalsystems/IssuerComponent.Library/BusinessLogic/IssuerComponentBusinessLogic.cs index d01a27a38d..d73907eb0b 100644 --- a/src/externalsystems/IssuerComponent.Library/BusinessLogic/IssuerComponentBusinessLogic.cs +++ b/src/externalsystems/IssuerComponent.Library/BusinessLogic/IssuerComponentBusinessLogic.cs @@ -44,6 +44,7 @@ public class IssuerComponentBusinessLogic( { var applicationId = context.ApplicationId; var (exists, holder, businessPartnerNumber, walletInformation) = await repositories.GetInstance().GetBpnlCredentialIformationByApplicationId(applicationId).ConfigureAwait(false); + var isBringYourOwnWallet = await repositories.GetInstance().IsBringYourOwnWallet(applicationId).ConfigureAwait(false); if (!exists) { throw new NotFoundException($"CompanyApplication {applicationId} does not exist"); @@ -64,11 +65,13 @@ public class IssuerComponentBusinessLogic( throw new ConflictException("The wallet information must be set"); } - var cryptoConfig = _settings.EncryptionConfigs.SingleOrDefault(x => x.Index == walletInformation.EncryptionMode) ?? throw new ConfigurationException($"EncryptionModeIndex {walletInformation.EncryptionMode} is not configured"); - var secret = CryptoHelper.Decrypt(walletInformation.ClientSecret, walletInformation.InitializationVector, Convert.FromHexString(cryptoConfig.EncryptionKey), cryptoConfig.CipherMode, cryptoConfig.PaddingMode); - + var secret = GetDecriptedSecret(walletInformation, isBringYourOwnWallet); var callbackUrl = $"{_settings.CallbackBaseUrl}/api/administration/registration/issuer/bpncredential"; - var data = new CreateBpnCredentialRequest(holder, businessPartnerNumber, new TechnicalUserDetails(walletInformation.WalletUrl, walletInformation.ClientId, secret), callbackUrl); + var data = new CreateBpnCredentialRequest(holder, businessPartnerNumber, + isBringYourOwnWallet + ? null + : new TechnicalUserDetails(walletInformation.WalletUrl, walletInformation.ClientId, secret), callbackUrl); + await service.CreateBpnlCredential(data, cancellationToken).ConfigureAwait(false); return new IApplicationChecklistService.WorkerChecklistProcessStepExecutionResult( ProcessStepStatusId.DONE, @@ -82,6 +85,18 @@ public class IssuerComponentBusinessLogic( null); } + private string GetDecriptedSecret(PortalBackend.DBAccess.Models.WalletInformation walletInformation, bool isBringYourOwnWallet) + { + if (isBringYourOwnWallet) + { + return string.Empty; + } + + var cryptoConfig = _settings.EncryptionConfigs.SingleOrDefault(x => x.Index == walletInformation.EncryptionMode) ?? throw new ConfigurationException($"EncryptionModeIndex {walletInformation.EncryptionMode} is not configured"); + return CryptoHelper.Decrypt(walletInformation.ClientSecret, walletInformation.InitializationVector, Convert.FromHexString(cryptoConfig.EncryptionKey), cryptoConfig.CipherMode, cryptoConfig.PaddingMode); + + } + public async Task StoreBpnlCredentialResponse(Guid applicationId, IssuerResponseData data) { var context = await checklistService @@ -112,6 +127,7 @@ public async Task StoreBpnlCredentialResponse(Guid applicationId, IssuerResponse { var applicationId = context.ApplicationId; var (exists, holder, businessPartnerNumber, walletInformation) = await repositories.GetInstance().GetBpnlCredentialIformationByApplicationId(applicationId).ConfigureAwait(false); + var isBringYourOwnWallet = await repositories.GetInstance().IsBringYourOwnWallet(applicationId).ConfigureAwait(false); if (!exists) { throw new NotFoundException($"CompanyApplication {applicationId} does not exist"); @@ -132,11 +148,14 @@ public async Task StoreBpnlCredentialResponse(Guid applicationId, IssuerResponse throw new ConflictException("The wallet information must be set"); } - var cryptoConfig = _settings.EncryptionConfigs.SingleOrDefault(x => x.Index == walletInformation.EncryptionMode) ?? throw new ConfigurationException($"EncryptionModeIndex {walletInformation.EncryptionMode} is not configured"); - var secret = CryptoHelper.Decrypt(walletInformation.ClientSecret, walletInformation.InitializationVector, Convert.FromHexString(cryptoConfig.EncryptionKey), cryptoConfig.CipherMode, cryptoConfig.PaddingMode); - + var secret = GetDecriptedSecret(walletInformation, isBringYourOwnWallet); var callbackUrl = $"{_settings.CallbackBaseUrl}/api/administration/registration/issuer/membershipcredential"; - var data = new CreateMembershipCredentialRequest(holder, businessPartnerNumber, "catena-x", new TechnicalUserDetails(walletInformation.WalletUrl, walletInformation.ClientId, secret), callbackUrl); + + var data = new CreateMembershipCredentialRequest(holder, businessPartnerNumber, "catena-x", + isBringYourOwnWallet + ? null + : new TechnicalUserDetails(walletInformation.WalletUrl, walletInformation.ClientId, secret), callbackUrl); + await service.CreateMembershipCredential(data, cancellationToken).ConfigureAwait(false); return new IApplicationChecklistService.WorkerChecklistProcessStepExecutionResult( ProcessStepStatusId.DONE, @@ -179,6 +198,7 @@ public async Task StoreMembershipCredentialResponse(Guid applicationId, IssuerRe public async Task CreateFrameworkCredentialData(Guid useCaseFrameworkVersionId, string frameworkId, Guid identityId, string token, CancellationToken cancellationToken) { var (holder, businessPartnerNumber, walletInformation) = await repositories.GetInstance().GetWalletData(identityId).ConfigureAwait(false); + var isBringYourOwnWallet = await repositories.GetInstance().IsBringYourOwnWallet(identityId).ConfigureAwait(false); if (holder is null) { throw new ConflictException("The holder must be set"); @@ -194,10 +214,12 @@ public async Task CreateFrameworkCredentialData(Guid useCaseFrameworkVersi throw new ConflictException("The wallet information must be set"); } - var cryptoConfig = _settings.EncryptionConfigs.SingleOrDefault(x => x.Index == walletInformation.EncryptionMode) ?? throw new ConfigurationException($"EncryptionModeIndex {walletInformation.EncryptionMode} is not configured"); - var secret = CryptoHelper.Decrypt(walletInformation.ClientSecret, walletInformation.InitializationVector, Convert.FromHexString(cryptoConfig.EncryptionKey), cryptoConfig.CipherMode, cryptoConfig.PaddingMode); + var secret = GetDecriptedSecret(walletInformation, isBringYourOwnWallet); - var data = new CreateFrameworkCredentialRequest(holder, businessPartnerNumber, frameworkId, useCaseFrameworkVersionId, new TechnicalUserDetails(walletInformation.WalletUrl, walletInformation.ClientId, secret), null); + var data = new CreateFrameworkCredentialRequest(holder, businessPartnerNumber, frameworkId, useCaseFrameworkVersionId, + isBringYourOwnWallet + ? null : + new TechnicalUserDetails(walletInformation.WalletUrl, walletInformation.ClientId, secret), null); return await service.CreateFrameworkCredential(data, token, cancellationToken).ConfigureAwait(false); } } diff --git a/src/externalsystems/UniversalDidResolver.Library/DependencyInjection/UniversalDidResolverServiceCollectionExtension.cs b/src/externalsystems/UniversalDidResolver.Library/DependencyInjection/UniversalDidResolverServiceCollectionExtension.cs new file mode 100644 index 0000000000..14740da1da --- /dev/null +++ b/src/externalsystems/UniversalDidResolver.Library/DependencyInjection/UniversalDidResolverServiceCollectionExtension.cs @@ -0,0 +1,59 @@ +/******************************************************************************** + * Copyright (c) 2025 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 Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; + +namespace Org.Eclipse.TractusX.Portal.Backend.UniversalDidResolver.Library.DependencyInjection; + +public static class UniversalDidResolverServiceCollectionExtension +{ + public static IServiceCollection AddUniversalDidResolverService(this IServiceCollection services, IConfigurationSection section) + { + services.AddOptions() + .Bind(section) + .Validate(settings => + !string.IsNullOrWhiteSpace(settings.UniversalResolverAddress), + "UniversalResolverAddress is required."); + + services.AddTransient>(); + + var sp = services.BuildServiceProvider(); + var settings = sp.GetRequiredService>(); + + RegisterUniversalResolver(settings.Value, services); + services + .AddTransient(); + + return services; + } + + private static void RegisterUniversalResolver(UniversalDidResolverSettings settings, IServiceCollection services) + { + var baseAddress = settings.UniversalResolverAddress.EndsWith('/') + ? settings.UniversalResolverAddress + : $"{settings.UniversalResolverAddress}/"; + services.AddHttpClient("universalResolver", c => + { + c.BaseAddress = new Uri(baseAddress); + }); + } +} diff --git a/src/externalsystems/UniversalDidResolver.Library/DependencyInjection/UniversalDidResolverSettings.cs b/src/externalsystems/UniversalDidResolver.Library/DependencyInjection/UniversalDidResolverSettings.cs new file mode 100644 index 0000000000..7722a66e25 --- /dev/null +++ b/src/externalsystems/UniversalDidResolver.Library/DependencyInjection/UniversalDidResolverSettings.cs @@ -0,0 +1,27 @@ +/******************************************************************************** + * Copyright (c) 2025 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.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.Portal.Backend.UniversalDidResolver.Library.DependencyInjection; + +public class UniversalDidResolverSettings +{ + [Required(AllowEmptyStrings = false)] + public string UniversalResolverAddress { get; set; } = null!; +} diff --git a/src/externalsystems/UniversalDidResolver.Library/IUniversalDidResolverService.cs b/src/externalsystems/UniversalDidResolver.Library/IUniversalDidResolverService.cs new file mode 100644 index 0000000000..cac4b2552d --- /dev/null +++ b/src/externalsystems/UniversalDidResolver.Library/IUniversalDidResolverService.cs @@ -0,0 +1,36 @@ +/******************************************************************************** + * Copyright (c) 2025 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.Portal.Backend.UniversalDidResolver.Library.Models; +using System.Text.Json; + +namespace Org.Eclipse.TractusX.Portal.Backend.UniversalDidResolver.Library; + +public interface IUniversalDidResolverService +{ + /// + /// Validates the did using the universal resolver + /// + /// The did that should be checked + /// The CancellationToken + /// true if the did is valid, otherwise false + Task ValidateDid(string did, CancellationToken cancellationToken); + + Task ValidateSchema(JsonDocument content, CancellationToken cancellationToken); +} diff --git a/src/externalsystems/UniversalDidResolver.Library/Models/DidValidationResult.cs b/src/externalsystems/UniversalDidResolver.Library/Models/DidValidationResult.cs new file mode 100644 index 0000000000..a292f463d0 --- /dev/null +++ b/src/externalsystems/UniversalDidResolver.Library/Models/DidValidationResult.cs @@ -0,0 +1,33 @@ +/******************************************************************************** + * Copyright (c) 2025 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; +using System.Text.Json.Serialization; + +namespace Org.Eclipse.TractusX.Portal.Backend.UniversalDidResolver.Library.Models; + +public record DidValidationResult( + [property: JsonPropertyName("didResolutionMetadata")] DidResolutionMetadata DidResolutionMetadata, + [property: JsonPropertyName("didDocument")] JsonDocument DidDocument + +); + +public record DidResolutionMetadata( + [property: JsonPropertyName("error")] string? Error +); diff --git a/src/externalsystems/UniversalDidResolver.Library/Schemas/DidDocument.schema.json b/src/externalsystems/UniversalDidResolver.Library/Schemas/DidDocument.schema.json new file mode 100644 index 0000000000..5675d8885d --- /dev/null +++ b/src/externalsystems/UniversalDidResolver.Library/Schemas/DidDocument.schema.json @@ -0,0 +1,89 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://eclipse-tractusx.github.io/DidDocument.schema.json", + "type": "object", + "required": ["@context", "id", "service", "verificationMethod"], + "properties": { + "@context": { + "type": "array", + "items": { + "type": "string" + } + }, + "id": { + "type": "string" + }, + "service": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "type"], + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "serviceEndpoint": { + "type": "string" + } + } + } + }, + "verificationMethod": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "type", "publicKeyJwk"], + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string" + }, + "controller": { + "type": "string" + }, + "publicKeyJwk": { + "type": "object", + "required": ["kty", "crv", "x", "y"], + "properties": { + "kty": { + "type": "string" + }, + "crv": { + "type": "string" + }, + "x": { + "type": "string" + }, + "y": { + "type": "string" + } + } + } + } + } + }, + "authentication": { + "type": "array", + "items": { + "type": "string" + } + }, + "assertionMethod": { + "type": "array", + "items": { + "type": "string" + } + }, + "keyAgreement": { + "type": "array", + "items": { + "type": "string" + } + } + } +} diff --git a/src/externalsystems/UniversalDidResolver.Library/UniversalDidResolver.Library.csproj b/src/externalsystems/UniversalDidResolver.Library/UniversalDidResolver.Library.csproj new file mode 100644 index 0000000000..d95863ce17 --- /dev/null +++ b/src/externalsystems/UniversalDidResolver.Library/UniversalDidResolver.Library.csproj @@ -0,0 +1,39 @@ + + + + + net9.0 + enable + enable + Org.Eclipse.TractusX.Portal.Backend.UniversalDidResolver.Library + Org.Eclipse.TractusX.Portal.Backend.UniversalDidResolver.Library + + + + + + + + + + + + + diff --git a/src/externalsystems/UniversalDidResolver.Library/UniversalDidResolverService.cs b/src/externalsystems/UniversalDidResolver.Library/UniversalDidResolverService.cs new file mode 100644 index 0000000000..abf33cf6a9 --- /dev/null +++ b/src/externalsystems/UniversalDidResolver.Library/UniversalDidResolverService.cs @@ -0,0 +1,82 @@ +/******************************************************************************** + * Copyright (c) 2025 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 Json.Schema; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; +using Org.Eclipse.TractusX.Portal.Backend.UniversalDidResolver.Library.Models; +using System.Net.Http.Json; +using System.Net.Sockets; +using System.Reflection; +using System.Text.Json; +namespace Org.Eclipse.TractusX.Portal.Backend.UniversalDidResolver.Library; + +public class UniversalDidResolverService(IHttpClientFactory httpClientFactory) : IUniversalDidResolverService +{ + private static readonly JsonSerializerOptions Options = new() { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }; + public async Task ValidateDid(string did, CancellationToken cancellationToken) + { + using var httpClient = httpClientFactory.CreateClient("universalResolver"); + HttpResponseMessage result; + try + { + result = await httpClient.GetAsync($"1.0/identifiers/{Uri.EscapeDataString(did)}", cancellationToken) + .CatchingIntoServiceExceptionFor("validate-did", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE).ConfigureAwait(false); + + } + catch (ServiceException ex) + { + if (ex.InnerException is SocketException) + { + throw new ControllerArgumentException("Universal resolver is not reachable"); + } + throw new ControllerArgumentException("DID URL could not be reached by the external resolver, 404 error"); + } + + if (!result.IsSuccessStatusCode) + { + throw new ControllerArgumentException($"Did validation failed with status code {result.StatusCode} and reason {result.ReasonPhrase}. Did: {did}"); + } + + var validationResult = await result.Content.ReadFromJsonAsync(Options, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + if (validationResult == null) + { + throw new ControllerArgumentException("DID validation failed: No result returned."); + } + + if (!string.IsNullOrWhiteSpace(validationResult.DidResolutionMetadata.Error)) + { + throw new ControllerArgumentException("DID validation failed during validation"); + } + + return validationResult; + } + + public async Task ValidateSchema(JsonDocument content, CancellationToken cancellationToken) + { + var location = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? throw new UnexpectedConditionException("Assembly location must be set"); + + var path = Path.Join(location, "Schemas", "DidDocument.schema.json"); + var schemaJson = await File.ReadAllTextAsync(path, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + + var schema = JsonSchema.FromText(schemaJson); + SchemaRegistry.Global.Register(schema); + var result = schema.Evaluate(content); + return result.IsValid; + } +} diff --git a/src/portalbackend/PortalBackend.DBAccess/Models/BringYourOwnWalletClientFields.cs b/src/portalbackend/PortalBackend.DBAccess/Models/BringYourOwnWalletClientFields.cs new file mode 100644 index 0000000000..eac8bddf86 --- /dev/null +++ b/src/portalbackend/PortalBackend.DBAccess/Models/BringYourOwnWalletClientFields.cs @@ -0,0 +1,8 @@ +namespace Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models; + +public static class BringYourOwnWalletClientFields +{ + public const string Identification = "BYOWCLIENT_ID"; + public const string NotUsed = "NOT_USED"; + +} diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs index 1befe0b478..fe5b085dec 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs @@ -67,6 +67,64 @@ Address ICompanyRepository.CreateAddress(string city, string streetname, string setOptionalParameters?.Invoke(address); return context.Addresses.Add(address).Entity; } + public async Task CreateCustomerWallet(Guid companyId, string did, JsonDocument didDocument) + { + var walletId = await context.CompanyWalletDatas + .Where(wallet => wallet.CompanyId == companyId) + .Select(wallet => wallet.Id) + .SingleOrDefaultAsync(); + + var wallet = new CompanyWalletData( + walletId == Guid.Empty ? Guid.NewGuid() : walletId, + companyId, + did, + didDocument, + BringYourOwnWalletClientFields.Identification, + new byte[1], + new byte[1], + default, + BringYourOwnWalletClientFields.NotUsed); + + if (walletId != Guid.Empty) + { + context.CompanyWalletDatas.Entry(wallet).State = EntityState.Modified; + } + else + { + context.CompanyWalletDatas.Add(wallet); + } + } + + public Task IsBringYourOwnWallet(Guid applicationId) + { + return context.CompanyApplications + .Where(app => app.Id == applicationId) + .Join(context.CompanyWalletDatas, + app => app.CompanyId, + wallet => wallet.CompanyId, + (app, wallet) => wallet) + .AnyAsync(wallet => wallet.ClientId == BringYourOwnWalletClientFields.Identification); + } + + public Task GetApplicationIdByCompanyId(Guid companyId) => + context.CompanyApplications + .AsNoTracking() + .Where(app => app.CompanyId == companyId) + .Select(app => app.Id) + .SingleOrDefaultAsync(); + + public Task IsDidInUse(string did) => + context.CompanyWalletDatas + .AsNoTracking() + .Where(wallet => wallet.Did == did) + .AnyAsync(); + + public Task GetCompanyHolderDidAsync(Guid companyId) => + context.Companies + .AsNoTracking() + .Where(company => company.Id == companyId) + .Select(company => company.DidDocumentLocation) + .SingleOrDefaultAsync(); public void AttachAndModifyAddress(Guid addressId, Action
? initialize, Action
modify) { diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/ICompanyRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/ICompanyRepository.cs index 34a6ea4846..ed95eaf1d0 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/ICompanyRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/ICompanyRepository.cs @@ -41,8 +41,17 @@ public interface ICompanyRepository void AttachAndModifyCompany(Guid companyId, Action? initialize, Action modify); - Address CreateAddress(string city, string streetname, string region, string countryAlpha2Code, Action
? setOptionalParameters = null); + Task CreateCustomerWallet(Guid companyId, string did, JsonDocument didDocument); + + Task IsBringYourOwnWallet(Guid applicationId); + + Task GetApplicationIdByCompanyId(Guid companyId); + Task IsDidInUse(string did); + + Task GetCompanyHolderDidAsync(Guid companyId); + + Address CreateAddress(string city, string streetname, string region, string countryAlpha2Code, Action
? setOptionalParameters = null); void AttachAndModifyAddress(Guid addressId, Action
? initialize, Action
modify); void CreateUpdateDeleteIdentifiers(Guid companyId, IEnumerable<(UniqueIdentifierId UniqueIdentifierId, string Value)> initialItems, IEnumerable<(UniqueIdentifierId UniqueIdentifierId, string Value)> modifiedItems); diff --git a/src/processes/ApplicationChecklist.Library/ApplicationChecklistCreationService.cs b/src/processes/ApplicationChecklist.Library/ApplicationChecklistCreationService.cs index 75049f8855..97845724be 100644 --- a/src/processes/ApplicationChecklist.Library/ApplicationChecklistCreationService.cs +++ b/src/processes/ApplicationChecklist.Library/ApplicationChecklistCreationService.cs @@ -51,7 +51,7 @@ public ApplicationChecklistCreationService(IPortalRepositories portalRepositorie private IEnumerable<(ApplicationChecklistEntryTypeId, ApplicationChecklistEntryStatusId)> CreateEntries(Guid applicationId, IEnumerable existingChecklistEntryTypeIds, string? bpn) { - var missingEntries = GetApplicationChecklistTypes() + var missingEntries = GetApplicationChecklistTypes(applicationId) .Except(existingChecklistEntryTypeIds); if (missingEntries.Any()) { @@ -64,9 +64,10 @@ public ApplicationChecklistCreationService(IPortalRepositories portalRepositorie return Enumerable.Empty<(ApplicationChecklistEntryTypeId, ApplicationChecklistEntryStatusId)>(); } - private IEnumerable GetApplicationChecklistTypes() + private IEnumerable GetApplicationChecklistTypes(Guid applicationId) { - if (_settings.UseDimWallet) + var isBringYourOwnWallet = _portalRepositories.GetInstance().IsBringYourOwnWallet(applicationId).GetAwaiter().GetResult(); + if (_settings.UseDimWallet || isBringYourOwnWallet) return Enum.GetValues(); return Enum.GetValues().Except(new[] diff --git a/src/registration/Registration.Common/RegistrationData.cs b/src/registration/Registration.Common/RegistrationData.cs index 83748f0012..393347d405 100644 --- a/src/registration/Registration.Common/RegistrationData.cs +++ b/src/registration/Registration.Common/RegistrationData.cs @@ -33,7 +33,8 @@ public record RegistrationData( string? StreetAdditional, string? StreetNumber, string? ZipCode, - IEnumerable UniqueIds + IEnumerable UniqueIds, + string? HolderDid = null ); public record CompanyUniqueIdData( diff --git a/src/registration/Registration.Service/BusinessLogic/BringYourOwnWalletBusinessLogic.cs b/src/registration/Registration.Service/BusinessLogic/BringYourOwnWalletBusinessLogic.cs new file mode 100644 index 0000000000..bab90d05e8 --- /dev/null +++ b/src/registration/Registration.Service/BusinessLogic/BringYourOwnWalletBusinessLogic.cs @@ -0,0 +1,148 @@ +/******************************************************************************** + * Copyright (c) 2025 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.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories; +using Org.Eclipse.TractusX.Portal.Backend.Registration.Service.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.UniversalDidResolver.Library; +using System.Text.Json; + +namespace Org.Eclipse.TractusX.Portal.Backend.Registration.Service.BusinessLogic +{ + public class BringYourOwnWalletBusinessLogic : IBringYourOwnWalletBusinessLogic + { + private readonly IUniversalDidResolverService _universalResolverService; + private readonly IPortalRepositories _portalRepositories; + + public BringYourOwnWalletBusinessLogic(IUniversalDidResolverService universalResolverService, IPortalRepositories portalRepositories) + { + _universalResolverService = universalResolverService; + _portalRepositories = portalRepositories; + + } + public async Task ValidateDid(string did, CancellationToken cancellationToken) + { + var validationResult = await _universalResolverService.ValidateDid(did, cancellationToken); + var isSchemaValid = await _universalResolverService.ValidateSchema( + validationResult.DidDocument, + cancellationToken + ).ConfigureAwait(false); + + if (!isSchemaValid) + { + throw ControllerArgumentException.Create(RegistrationErrors.REGISTRATION_INVALID_DID_DOCUMENT); + } + + var companyRepository = _portalRepositories.GetInstance(); + var didExists = await companyRepository.IsDidInUse(did).ConfigureAwait(ConfigureAwaitOptions.None); + if (didExists) + { + throw ConflictException.Create(RegistrationErrors.REGISTRATION_CONFLICT_DID_ALREADY_IN_USE); + } + + return validationResult.DidDocument; + } + + public async Task SaveCustomerWalletAsync(Guid companyId, string did) + { + var companyRepository = _portalRepositories.GetInstance(); + var companyExists = await companyRepository.IsExistingCompany(companyId).ConfigureAwait(ConfigureAwaitOptions.None); + if (!companyExists) + { + throw NotFoundException.Create(RegistrationErrors.REGISTRATION_CONFLICT_DID_ALREADY_IN_USE); + } + + if (string.IsNullOrEmpty(did)) + { + throw ControllerArgumentException.Create(RegistrationErrors.REGISTRATION_INVALID_DID_DOCUMENT); + } + + var didDocument = await ValidateDid(did, CancellationToken.None).ConfigureAwait(ConfigureAwaitOptions.None); + var didLocation = CreateDidLocation(didDocument); + + UpdateCompanyDidLocation(companyId, didLocation, companyRepository); + await companyRepository.CreateCustomerWallet(companyId, did, didDocument).ConfigureAwait(ConfigureAwaitOptions.None); + + await _portalRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); + } + + public async Task getCompanyWalletDidAsync(Guid companyId) + { + var companyRepository = _portalRepositories.GetInstance(); + var companyExists = await companyRepository.IsExistingCompany(companyId).ConfigureAwait(ConfigureAwaitOptions.None); + if (!companyExists) + { + throw NotFoundException.Create(RegistrationErrors.REGISTRATION_COMPANY_ID_NOT_FOUND, [new("companyId", companyId.ToString())]); + } + + var did = await companyRepository.GetCompanyHolderDidAsync(companyId).ConfigureAwait(ConfigureAwaitOptions.None); + if (string.IsNullOrEmpty(did)) + { + throw NotFoundException.Create(RegistrationErrors.REGISTRATION_COMPANY_ID_NOT_FOUND, [new("companyId", companyId.ToString())]); + } + return did; + } + private static void UpdateCompanyDidLocation(Guid companyId, string didLocation, ICompanyRepository companyRepository) => + companyRepository.AttachAndModifyCompany( + companyId, + _ => { }, + c => + { + c.DidDocumentLocation = didLocation; + } + ); + + private static string CreateDidLocation(JsonDocument didDocument) + { + if (!didDocument.RootElement.TryGetProperty("id", out var idProperty)) + { + throw ControllerArgumentException.Create(RegistrationErrors.REGISTRATION_INVALID_DID_DOCUMENT); + + } + + var did = idProperty.GetString(); + if (string.IsNullOrWhiteSpace(did) || !did.StartsWith("did:", StringComparison.OrdinalIgnoreCase)) + { + throw ControllerArgumentException.Create(RegistrationErrors.REGISTRATION_INVALID_DID_FORMAT); + } + + var didParts = did.Split(':', 3); + if (didParts.Length != 3) + { + throw ControllerArgumentException.Create(RegistrationErrors.REGISTRATION_INVALID_DID_FORMAT); + } + + var method = didParts[1]; + var identifier = didParts[2]; + + if (!method.Equals("web", StringComparison.OrdinalIgnoreCase)) + { + throw ControllerArgumentException.Create(RegistrationErrors.REGISTRATION_UNSUPPORTED_DID_METHOD, [new("method", method)]); + } + + var hostAndPath = identifier.Replace(":", "/"); + + var isBareDomain = !hostAndPath.Contains("/"); + var urlPath = isBareDomain ? "/.well-known/did.json" : "/did.json"; + + return $"https://{hostAndPath}{urlPath}"; + } + } +} diff --git a/src/registration/Registration.Service/BusinessLogic/IBringYourOwnWalletBusinessLogic.cs b/src/registration/Registration.Service/BusinessLogic/IBringYourOwnWalletBusinessLogic.cs new file mode 100644 index 0000000000..ef88d1a7fd --- /dev/null +++ b/src/registration/Registration.Service/BusinessLogic/IBringYourOwnWalletBusinessLogic.cs @@ -0,0 +1,30 @@ +/******************************************************************************** + * Copyright (c) 2025 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; + +namespace Org.Eclipse.TractusX.Portal.Backend.Registration.Service.BusinessLogic +{ + public interface IBringYourOwnWalletBusinessLogic + { + Task ValidateDid(string did, CancellationToken cancellationToken); + Task SaveCustomerWalletAsync(Guid companyId, string did); + Task getCompanyWalletDidAsync(Guid companyId); + } +} diff --git a/src/registration/Registration.Service/Controllers/BringYourOwnWalletController.cs b/src/registration/Registration.Service/Controllers/BringYourOwnWalletController.cs new file mode 100644 index 0000000000..c67a6b494d --- /dev/null +++ b/src/registration/Registration.Service/Controllers/BringYourOwnWalletController.cs @@ -0,0 +1,70 @@ +/******************************************************************************** + * Copyright (c) 2025 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 Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling.Web; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Web; +using Org.Eclipse.TractusX.Portal.Backend.Registration.Service.BusinessLogic; + +namespace Org.Eclipse.TractusX.Portal.Backend.Registration.Service.Controllers +{ + [ApiController] + [EnvironmentRoute("MVC_ROUTING_BASEPATH", "bringYourOwnWallet")] + public class BringYourOwnWalletController(IBringYourOwnWalletBusinessLogic logic) : ControllerBase + { + [HttpPost] + [Authorize(Roles = "submit_registration")] + [Route("{did}/validateDid")] + [ProducesResponseType(typeof(NoContentResult), StatusCodes.Status204NoContent)] + [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status415UnsupportedMediaType)] + [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)] + public async Task validateDid([FromRoute] string did, CancellationToken cancellationToken) + { + + await logic.ValidateDid(did, cancellationToken); + + return NoContent(); + } + + [HttpPost] + [Authorize(Roles = "submit_registration")] + [Route("{companyId}/saveHolderDid/{did}")] + [ProducesResponseType(typeof(NoContentResult), StatusCodes.Status204NoContent)] + [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status415UnsupportedMediaType)] + [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)] + public async Task SaveHolderDid([FromRoute] Guid companyId, [FromRoute] string did) + { + await logic.SaveCustomerWalletAsync(companyId, did); + + return NoContent(); + } + + [HttpGet] + [Authorize(Roles = "submit_registration")] + [Route("{companyId}/getHolderDid")] + [ProducesResponseType(typeof(NoContentResult), StatusCodes.Status204NoContent)] + [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status415UnsupportedMediaType)] + [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)] + public async Task GetHolderDid([FromRoute] Guid companyId) + { + return await logic.getCompanyWalletDidAsync(companyId); + } + } +} diff --git a/src/registration/Registration.Service/ErrorHandling/RegistrationErrorMessageContainer.cs b/src/registration/Registration.Service/ErrorHandling/RegistrationErrorMessageContainer.cs index e27cea4129..e350c285b1 100644 --- a/src/registration/Registration.Service/ErrorHandling/RegistrationErrorMessageContainer.cs +++ b/src/registration/Registration.Service/ErrorHandling/RegistrationErrorMessageContainer.cs @@ -37,6 +37,7 @@ public class RegistrationErrorMessageContainer : IErrorMessageContainer new((int)RegistrationErrors.REGISTRATION_FORBIDDEN_NOT_PERMITTED_DOCUMENT_ACCESS, "The user is not permitted to access document {documentId}."), new((int)RegistrationErrors.REGISTRATION_FORBIDDEN_DOCUMENT_ACCESSIBLE_AFTER_ONBOARDING_PROCESS, "Documents not accessible as onboarding process finished {documentId}."), new((int)RegistrationErrors.REGISTRATION_COMPANY_APPLICATION_NOT_FOUND, "CompanyApplication {applicationId} not found"), + new((int)RegistrationErrors.REGISTRATION_COMPANY_ID_NOT_FOUND, "Company {companyId} not found"), new((int)RegistrationErrors.REGISTRATION_COMPANY_APPLICATION_FOR_COMPANY_ID_NOT_FOUND, "CompanyApplication {applicationId} for CompanyId {companyId} not found"), new((int)RegistrationErrors.REGISTRATION_FORBIDDEN_USER_APPLICATION_NOT_ASSIGN_WITH_COMP_APPLICATION, "users company is not assigned with CompanyApplication {applicationId}"), new((int)RegistrationErrors.REGISTRATION_ARGUMENT_EMAIL_MUST_NOT_EMPTY, "email must not be empty"), @@ -71,7 +72,11 @@ public class RegistrationErrorMessageContainer : IErrorMessageContainer new((int)RegistrationErrors.REGISTRATION_NOT_INVALID_COUNTRY_CODE, "invalid country code {alpha2Code}"), new((int)RegistrationErrors.REGISTRATION_FORBIDDEN_USER_NOT_ALLOWED_TO_DECLINED_APP, "User is not allowed to decline this application"), new((int)RegistrationErrors.REGISTRATION_UNEXPECT_APP_DECLINED_DATA_NOT_NULL, "ApplicationDeclineData should never be null here"), - new((int)RegistrationErrors.REGISTRATION_UNEXPECT_IDENTITY_PROVIDER_TYPE_ID_SHARED_OR_OWN, "IdentityProviderTypeId should allways be shared or own here") + new((int)RegistrationErrors.REGISTRATION_UNEXPECT_IDENTITY_PROVIDER_TYPE_ID_SHARED_OR_OWN, "IdentityProviderTypeId should allways be shared or own here"), + new((int)RegistrationErrors.REGISTRATION_CONFLICT_DID_ALREADY_IN_USE, "DID is already in use."), + new((int)RegistrationErrors.REGISTRATION_INVALID_DID_DOCUMENT, "DID validation failed. DID connot be empty, NULL or data does not match with DID schema."), + new((int)RegistrationErrors.REGISTRATION_INVALID_DID_FORMAT, "Invalid DID format: must be in the form 'did::'."), + new((int)RegistrationErrors.REGISTRATION_UNSUPPORTED_DID_METHOD, "Unsupported DID method: '{method}'. Only 'did:web' is supported."), ]); @@ -127,6 +132,10 @@ public enum RegistrationErrors REGISTRATION_NOT_INVALID_COUNTRY_CODE, REGISTRATION_FORBIDDEN_USER_NOT_ALLOWED_TO_DECLINED_APP, REGISTRATION_UNEXPECT_APP_DECLINED_DATA_NOT_NULL, - REGISTRATION_UNEXPECT_IDENTITY_PROVIDER_TYPE_ID_SHARED_OR_OWN - + REGISTRATION_UNEXPECT_IDENTITY_PROVIDER_TYPE_ID_SHARED_OR_OWN, + REGISTRATION_INVALID_DID_DOCUMENT, + REGISTRATION_CONFLICT_DID_ALREADY_IN_USE, + REGISTRATION_COMPANY_ID_NOT_FOUND, + REGISTRATION_INVALID_DID_FORMAT, + REGISTRATION_UNSUPPORTED_DID_METHOD } diff --git a/src/registration/Registration.Service/Model/CompanyDetailData.cs b/src/registration/Registration.Service/Model/CompanyDetailData.cs index 023ed51067..831506bcb3 100644 --- a/src/registration/Registration.Service/Model/CompanyDetailData.cs +++ b/src/registration/Registration.Service/Model/CompanyDetailData.cs @@ -33,5 +33,6 @@ public record CompanyDetailData( string? StreetAdditional, string? StreetNumber, string? ZipCode, - IEnumerable UniqueIds -) : RegistrationData(Name, City, StreetName, CountryAlpha2Code, BusinessPartnerNumber, ShortName, Region, StreetAdditional, StreetNumber, ZipCode, UniqueIds); + IEnumerable UniqueIds, + string? HolderDid = null +) : RegistrationData(Name, City, StreetName, CountryAlpha2Code, BusinessPartnerNumber, ShortName, Region, StreetAdditional, StreetNumber, ZipCode, UniqueIds, HolderDid); diff --git a/src/registration/Registration.Service/Program.cs b/src/registration/Registration.Service/Program.cs index db4fbd2abd..0b47d2254e 100644 --- a/src/registration/Registration.Service/Program.cs +++ b/src/registration/Registration.Service/Program.cs @@ -19,17 +19,19 @@ ********************************************************************************/ using Org.Eclipse.TractusX.Portal.Backend.Bpdm.Library.DependencyInjection; +using Org.Eclipse.TractusX.Portal.Backend.Dim.Library.DependencyInjection; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling.Service; using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Extensions; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Token; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess; using Org.Eclipse.TractusX.Portal.Backend.Processes.ApplicationChecklist.Config; using Org.Eclipse.TractusX.Portal.Backend.Processes.Mailing.Library.DependencyInjection; using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library; using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library.Service; using Org.Eclipse.TractusX.Portal.Backend.Registration.Common.ErrorHandling; -using Org.Eclipse.TractusX.Portal.Backend.Registration.Service; using Org.Eclipse.TractusX.Portal.Backend.Registration.Service.BusinessLogic; using Org.Eclipse.TractusX.Portal.Backend.Registration.Service.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.UniversalDidResolver.Library.DependencyInjection; using Org.Eclipse.TractusX.Portal.Backend.Web.Initialization; using Org.Eclipse.TractusX.Portal.Backend.Web.PublicInfos.DependencyInjection; @@ -51,13 +53,14 @@ await WebAppHelper .AddTransient() .ConfigureRegistrationSettings(builder.Configuration.GetSection("Registration")) .AddTransient(); - builder.Services.AddApplicationChecklistCreation(builder.Configuration.GetSection("ApplicationCreation")); builder.Services .AddSingleton() .AddSingleton() .AddSingleton() .AddSingleton(); + builder.Services.AddTransient() + .AddUniversalDidResolverService(builder.Configuration.GetSection("UniversalDidResolver")); builder.Services.AddBpnAccess(builder.Configuration.GetSection("BpnAccess")); builder.Services.AddMailingProcessCreation(builder.Configuration.GetSection("MailingProcessCreation")); diff --git a/src/registration/Registration.Service/Registration.Service.csproj b/src/registration/Registration.Service/Registration.Service.csproj index aedcec3a55..c16887a2e0 100644 --- a/src/registration/Registration.Service/Registration.Service.csproj +++ b/src/registration/Registration.Service/Registration.Service.csproj @@ -53,6 +53,7 @@ + diff --git a/src/registration/Registration.Service/appsettings.json b/src/registration/Registration.Service/appsettings.json index 0c89fb26e4..2d7cadc091 100644 --- a/src/registration/Registration.Service/appsettings.json +++ b/src/registration/Registration.Service/appsettings.json @@ -36,6 +36,9 @@ "SubmitDocumentTypeIds": [], "PasswordResendAddress": "" }, + "UniversalDidResolver": { + "UniversalResolverAddress": "https://resolver.example.io/1.0/identifiers/" + }, "BpnAccess": { "BaseAddress": "" }, diff --git a/tests/administration/Administration.Service.Tests/BusinessLogic/BringYourOwnWalletBusinessLogicTests.cs b/tests/administration/Administration.Service.Tests/BusinessLogic/BringYourOwnWalletBusinessLogicTests.cs new file mode 100644 index 0000000000..2da1ab5f5e --- /dev/null +++ b/tests/administration/Administration.Service.Tests/BusinessLogic/BringYourOwnWalletBusinessLogicTests.cs @@ -0,0 +1,72 @@ +/******************************************************************************** + * Copyright (c) 2025 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 Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.BusinessLogic; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories; + +namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Tests.BusinessLogic; + +public class BringYourOwnWalletBusinessLogicTests +{ + private readonly IPortalRepositories _portalRepositories; + private readonly ICompanyRepository _companyRepository; + private readonly IOptions _options; + private readonly BringYourOwnWalletBusinessLogic _sut; + private readonly Guid _companyId = Guid.NewGuid(); + private readonly Guid _applicationId = Guid.NewGuid(); + private readonly Guid _excludedRoleId = Guid.NewGuid(); + + public BringYourOwnWalletBusinessLogicTests() + { + _portalRepositories = A.Fake(); + _companyRepository = A.Fake(); + _options = Options.Create(new BringYourOwnWalletSettings + { + NonApplicableUserRoles = new List { _excludedRoleId } + }); + A.CallTo(() => _portalRepositories.GetInstance()).Returns(_companyRepository); + _sut = new BringYourOwnWalletBusinessLogic(_portalRepositories, _options); + } + + [Fact] + public void GetExcludedUserRoles_ReturnsConfiguredRoles() + { + // Act + var result = _sut.GetExcludedUserRoles(); + + // Assert + result.Should().ContainSingle().Which.Should().Be(_excludedRoleId); + } + + [Fact] + public async Task IsBringYourOwnWallet_ReturnsExpectedValue() + { + // Arrange + A.CallTo(() => _companyRepository.GetApplicationIdByCompanyId(_companyId)).Returns(_applicationId); + A.CallTo(() => _companyRepository.IsBringYourOwnWallet(_applicationId)).Returns(true); + + // Act + var result = await _sut.IsBringYourOwnWallet(_companyId); + + // Assert + result.Should().BeTrue(); + } +} diff --git a/tests/administration/Administration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs b/tests/administration/Administration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs index acf817e0f4..8ee5f8766d 100644 --- a/tests/administration/Administration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs +++ b/tests/administration/Administration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs @@ -465,8 +465,59 @@ public async Task SetRegistrationVerification_WithApproval_CallsExpected(bool us .MustHaveHappenedOnceExactly(); } + [Theory] + [InlineData(true, ProcessStepTypeId.TRANSMIT_BPN_DID)] + [InlineData(false, ProcessStepTypeId.TRANSMIT_BPN_DID)] + public async Task SetRegistrationVerification_WithApproval_BYOW_CallsExpected(bool useDimWallet, ProcessStepTypeId expectedTypeId) + { + // Arrange + var options = Options.Create(new RegistrationSettings { UseDimWallet = useDimWallet }); + var logic = new RegistrationBusinessLogic(_portalRepositories, options, _checklistService, null!, null!, _dimBusinessLogic, null!, null!, null!, null!); + var entry = new ApplicationChecklistEntry(IdWithBpn, ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.TO_DO, DateTimeOffset.UtcNow); + SetupForApproveRegistrationVerification(entry); + A.CallTo(() => _companyRepository.IsBringYourOwnWallet(IdWithBpn)) + .Returns(true); + // Act + await logic.ApproveRegistrationVerification(IdWithBpn); + + // Assert + A.CallTo(() => _portalRepositories.SaveAsync()).MustHaveHappenedOnceExactly(); + entry.Comment.Should().BeNull(); + entry.ApplicationChecklistEntryStatusId.Should().Be(ApplicationChecklistEntryStatusId.DONE); + + A.CallTo(() => _mailingProcessCreation.CreateMailProcess(A._, A._, A>._)) + .MustNotHaveHappened(); + A.CallTo(() => _checklistService.FinalizeChecklistEntryAndProcessSteps( + A._, + null, + A>._, + A>.That.Matches(x => x.Count(y => y == expectedTypeId) == 1))) + .MustHaveHappenedOnceExactly(); + A.CallTo(() => _companyRepository.IsBringYourOwnWallet(IdWithBpn)).MustHaveHappenedOnceExactly(); + } + [Fact] public async Task SetRegistrationVerification_WithBpnNotDone_CallsExpected() + { + // Arrange + var entry = new ApplicationChecklistEntry(IdWithoutBpn, ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.TO_DO, DateTimeOffset.UtcNow); + SetupForApproveRegistrationVerification(entry); + A.CallTo(() => _companyRepository.IsBringYourOwnWallet(IdWithBpn)) + .Returns(true); + // Act + await _logic.ApproveRegistrationVerification(IdWithoutBpn); + + // Assert + A.CallTo(() => _portalRepositories.SaveAsync()).MustHaveHappenedOnceExactly(); + entry.Comment.Should().BeNull(); + entry.ApplicationChecklistEntryStatusId.Should().Be(ApplicationChecklistEntryStatusId.DONE); + A.CallTo(() => _mailingProcessCreation.CreateMailProcess(A._, A._, A>._)) + .MustNotHaveHappened(); + A.CallTo(() => _checklistService.FinalizeChecklistEntryAndProcessSteps(A._, null, A>._, null)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task SetRegistrationVerification_BYOW_WithBpnNotDone_CallsExpected() { // Arrange var entry = new ApplicationChecklistEntry(IdWithoutBpn, ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.TO_DO, DateTimeOffset.UtcNow); diff --git a/tests/administration/Administration.Service.Tests/BusinessLogic/TechnicalUserBusinessLogicTests.cs b/tests/administration/Administration.Service.Tests/BusinessLogic/TechnicalUserBusinessLogicTests.cs index 973b9d5386..fbb994c9e8 100644 --- a/tests/administration/Administration.Service.Tests/BusinessLogic/TechnicalUserBusinessLogicTests.cs +++ b/tests/administration/Administration.Service.Tests/BusinessLogic/TechnicalUserBusinessLogicTests.cs @@ -73,6 +73,7 @@ public class TechnicalUserBusinessLogicTests private readonly IFixture _fixture; private readonly IOptions _options; private readonly IIdentityService _identityService; + private readonly IBringYourOwnWalletBusinessLogic _bringYourOwnWalletBusinessLogic; public TechnicalUserBusinessLogicTests() { @@ -92,6 +93,8 @@ public TechnicalUserBusinessLogicTests() _processStepRepository = A.Fake(); _provisioningManager = A.Fake(); _portalRepositories = A.Fake(); + _bringYourOwnWalletBusinessLogic = A.Fake(); + A.CallTo(() => _portalRepositories.GetInstance()).Returns(_processStepRepository); A.CallTo(() => _portalRepositories.GetInstance()).Returns(_userRepository); A.CallTo(() => _portalRepositories.GetInstance()).Returns(_technicalUserRepository); @@ -104,6 +107,8 @@ public TechnicalUserBusinessLogicTests() A.CallTo(() => _identity.IdentityTypeId).Returns(IdentityTypeId.COMPANY_USER); A.CallTo(() => _identity.CompanyId).Returns(ValidCompanyId); A.CallTo(() => _identityService.IdentityData).Returns(_identity); + A.CallTo(() => _bringYourOwnWalletBusinessLogic.IsBringYourOwnWallet(A._)) + .Returns(false); var encryptionKey = _fixture.CreateMany(32).ToArray(); @@ -124,7 +129,7 @@ public async Task CreateOwnCompanyServiceAccountAsync_WithValidInput_ReturnsCrea // Arrange SetupCreateOwnCompanyServiceAccount(); var serviceAccountCreationInfos = new TechnicalUserCreationInfo("TheName", "Just a short description", IamClientAuthMethod.SECRET, Enumerable.Repeat(UserRoleId1, 1)); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act var result = await sut.CreateOwnCompanyServiceAccountAsync(serviceAccountCreationInfos); @@ -134,6 +139,26 @@ public async Task CreateOwnCompanyServiceAccountAsync_WithValidInput_ReturnsCrea x => x.IamClientAuthMethod == IamClientAuthMethod.SECRET); } + [Fact] + public async Task CreateOwnCompanyServiceAccountAsync_InvalieUserRole_ReturnsControllerException() + { + // Arrange + SetupCreateOwnCompanyServiceAccount(); + var serviceAccountCreationInfos = new TechnicalUserCreationInfo("TheName", "Just a short description", IamClientAuthMethod.SECRET, Enumerable.Repeat(UserRoleId1, 1)); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); + A.CallTo(() => _bringYourOwnWalletBusinessLogic.IsBringYourOwnWallet(A._)) + .Returns(true); + + A.CallTo(() => _bringYourOwnWalletBusinessLogic.GetExcludedUserRoles()) + .Returns([UserRoleId1]); + // Act + async Task Act() => await sut.CreateOwnCompanyServiceAccountAsync(serviceAccountCreationInfos); + + // Assert + var exception = await Assert.ThrowsAsync(Act); + exception.Message.Should().Be(AdministrationServiceAccountErrors.SERVICE_ACCOUNT_USER_ROLES_NOT_ALLOWED.ToString()); + } + [Fact] public async Task CreateOwnCompanyServiceAccountAsync_WithInvalidUser_NotFoundException() { @@ -142,7 +167,7 @@ public async Task CreateOwnCompanyServiceAccountAsync_WithInvalidUser_NotFoundEx A.CallTo(() => _identityService.IdentityData).Returns(identity); SetupCreateOwnCompanyServiceAccount(); var serviceAccountCreationInfos = new TechnicalUserCreationInfo("TheName", "Just a short description", IamClientAuthMethod.SECRET, Enumerable.Repeat(UserRoleId1, 1)); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act async Task Act() => await sut.CreateOwnCompanyServiceAccountAsync(serviceAccountCreationInfos); @@ -158,7 +183,7 @@ public async Task CreateOwnCompanyServiceAccountAsync_WithEmptyName_ThrowsContro // Arrange SetupCreateOwnCompanyServiceAccount(); var serviceAccountCreationInfos = new TechnicalUserCreationInfo(string.Empty, "Just a short description", IamClientAuthMethod.SECRET, Enumerable.Repeat(UserRoleId1, 1)); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act async Task Act() => await sut.CreateOwnCompanyServiceAccountAsync(serviceAccountCreationInfos); @@ -177,7 +202,7 @@ public async Task CreateOwnCompanyServiceAccountAsync_WithInvalidIamClientAuthMe // Arrange SetupCreateOwnCompanyServiceAccount(); var serviceAccountCreationInfos = new TechnicalUserCreationInfo("TheName", "Just a short description", IamClientAuthMethod.JWT, Enumerable.Repeat(UserRoleId1, 1)); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act async Task Act() => await sut.CreateOwnCompanyServiceAccountAsync(serviceAccountCreationInfos); @@ -197,7 +222,7 @@ public async Task CreateOwnCompanyServiceAccountAsync_WithInvalidUserRoleId_Thro var wrongUserRoleId = Guid.NewGuid(); SetupCreateOwnCompanyServiceAccount(); var serviceAccountCreationInfos = new TechnicalUserCreationInfo("TheName", "Just a short description", IamClientAuthMethod.SECRET, [UserRoleId1, wrongUserRoleId]); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act async Task Act() => await sut.CreateOwnCompanyServiceAccountAsync(serviceAccountCreationInfos); @@ -220,7 +245,7 @@ public async Task GetOwnCompanyServiceAccountDetailsAsync_WithValidInput_GetsAll { // Arrange SetupGetOwnCompanyServiceAccountDetails(); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act var result = await sut.GetOwnCompanyServiceAccountDetailsAsync(ValidServiceAccountId); @@ -240,7 +265,7 @@ public async Task GetOwnCompanyServiceAccountDetailsAsync_WithValidInputAndDimCo { // Arrange SetupGetOwnCompanyServiceAccountDetails(); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act var result = await sut.GetOwnCompanyServiceAccountDetailsAsync(ValidServiceAccountWithDimDataId); @@ -260,7 +285,7 @@ public async Task GetOwnCompanyServiceAccountDetailsAsync_WithValidInputAndDimCo { // Arrange SetupGetOwnCompanyServiceAccountDetails(); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act var result = await sut.GetOwnCompanyServiceAccountDetailsAsync(ValidServiceAccountWithDimDataIdWithPendingUserStatus); @@ -283,7 +308,7 @@ public async Task GetOwnCompanyServiceAccountDetailsAsync_WithInvalidCompany_Not SetupGetOwnCompanyServiceAccountDetails(); var invalidCompanyId = Guid.NewGuid(); A.CallTo(() => _identity.CompanyId).Returns(invalidCompanyId); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act async Task Act() => await sut.GetOwnCompanyServiceAccountDetailsAsync(ValidServiceAccountId); @@ -299,7 +324,7 @@ public async Task GetOwnCompanyServiceAccountDetailsAsync_WithInvalidServiceAcco // Arrange SetupGetOwnCompanyServiceAccountDetails(); var invalidServiceAccountId = Guid.NewGuid(); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act async Task Act() => await sut.GetOwnCompanyServiceAccountDetailsAsync(invalidServiceAccountId); @@ -318,7 +343,7 @@ public async Task ResetOwnCompanyServiceAccountSecretAsync_WithValidInput_GetsAl { // Arrange SetupResetOwnCompanyServiceAccountSecret(); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act var result = await sut.ResetOwnCompanyServiceAccountSecretAsync(ValidServiceAccountId); @@ -335,7 +360,7 @@ public async Task ResetOwnCompanyServiceAccountSecretAsync_WithInvalidUser_NotFo SetupResetOwnCompanyServiceAccountSecret(); var invalidUser = _fixture.Create(); A.CallTo(() => _identityService.IdentityData).Returns(invalidUser); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act async Task Act() => await sut.ResetOwnCompanyServiceAccountSecretAsync(ValidServiceAccountId); @@ -351,7 +376,7 @@ public async Task ResetOwnCompanyServiceAccountSecretAsync_WithInvalidServiceAcc // Arrange SetupResetOwnCompanyServiceAccountSecret(); var invalidServiceAccountId = Guid.NewGuid(); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act async Task Act() => await sut.ResetOwnCompanyServiceAccountSecretAsync(invalidServiceAccountId); @@ -371,7 +396,7 @@ public async Task UpdateOwnCompanyServiceAccountDetailsAsync_WithValidData_Retur // Arrange SetupUpdateOwnCompanyServiceAccountDetails(); var data = new ServiceAccountEditableDetails(ValidServiceAccountId, "new name", "changed description", IamClientAuthMethod.SECRET); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); TechnicalUser? initial = null; TechnicalUser? modified = null; @@ -406,7 +431,7 @@ public async Task UpdateOwnCompanyServiceAccountDetailsAsync_WithInvalidAuthMeth // Arrange SetupUpdateOwnCompanyServiceAccountDetails(); var data = new ServiceAccountEditableDetails(ValidServiceAccountId, "new name", "changed description", IamClientAuthMethod.JWT); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act async Task Act() => await sut.UpdateOwnCompanyServiceAccountDetailsAsync(ValidServiceAccountId, data); @@ -424,7 +449,7 @@ public async Task UpdateOwnCompanyServiceAccountDetailsAsync_WithDifferentServic // Arrange SetupUpdateOwnCompanyServiceAccountDetails(); var data = new ServiceAccountEditableDetails(ValidServiceAccountId, "new name", "changed description", IamClientAuthMethod.SECRET); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act async Task Act() => await sut.UpdateOwnCompanyServiceAccountDetailsAsync(Guid.NewGuid(), data); @@ -446,7 +471,7 @@ public async Task UpdateOwnCompanyServiceAccountDetailsAsync_WithNotExistingServ A.CallTo(() => _technicalUserRepository.GetTechnicalUserWithRoleDataClientIdAsync(invalidServiceAccountId, ValidCompanyId)) .Returns(null); var data = new ServiceAccountEditableDetails(invalidServiceAccountId, "new name", "changed description", IamClientAuthMethod.SECRET); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act async Task Act() => await sut.UpdateOwnCompanyServiceAccountDetailsAsync(invalidServiceAccountId, data); @@ -467,7 +492,7 @@ public async Task UpdateOwnCompanyServiceAccountDetailsAsync_WithInactiveService A.CallTo(() => _technicalUserRepository.GetTechnicalUserWithRoleDataClientIdAsync(InactiveServiceAccount, ValidCompanyId)) .Returns(inactive); var data = new ServiceAccountEditableDetails(InactiveServiceAccount, "new name", "changed description", IamClientAuthMethod.SECRET); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act async Task Act() => await sut.UpdateOwnCompanyServiceAccountDetailsAsync(InactiveServiceAccount, data); @@ -491,7 +516,7 @@ public async Task UpdateOwnCompanyServiceAccountDetailsAsync_WithExternalService A.CallTo(() => _technicalUserRepository.GetTechnicalUserWithRoleDataClientIdAsync(ExternalServiceAccount, ValidCompanyId)) .Returns(external); var data = new ServiceAccountEditableDetails(ExternalServiceAccount, "new name", "changed description", IamClientAuthMethod.SECRET); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act var result = await sut.UpdateOwnCompanyServiceAccountDetailsAsync(ExternalServiceAccount, data); @@ -536,7 +561,7 @@ public async Task GetOwnCompanyServiceAccountsDataAsync_GetsExpectedData(IEnumer .Returns((int skip, int take) => Task.FromResult?>(new(data.Count(), data.Skip(skip).Take(take)))); A.CallTo(() => _portalRepositories.GetInstance()).Returns(_technicalUserRepository); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act var result = await sut.GetOwnCompanyServiceAccountsDataAsync(1, 10, null, null, isUserInactive, userStatusIds); @@ -567,7 +592,7 @@ public async Task DeleteOwnCompanyServiceAccountAsync_WithNotExistingServiceAcco var serviceAccountId = Guid.NewGuid(); SetupDeleteOwnCompanyServiceAccount(); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act async Task Act() => await sut.DeleteOwnCompanyServiceAccountAsync(serviceAccountId); @@ -587,7 +612,7 @@ public async Task DeleteOwnCompanyServiceAccountAsync_WithExistingOfferSubscript A.CallTo(() => _technicalUserRepository.GetOwnTechnicalUserWithIamUserRolesAsync(A.That.Not.Matches(x => x == ValidServiceAccountId), A._, A>._)) .Returns(null); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act async Task Act() => await sut.DeleteOwnCompanyServiceAccountAsync(ValidServiceAccountId); @@ -607,7 +632,7 @@ public async Task DeleteOwnCompanyServiceAccountAsync_WithInvalidConnectorStatus A.CallTo(() => _technicalUserRepository.GetOwnTechnicalUserWithIamUserRolesAsync(A.That.Not.Matches(x => x == ValidServiceAccountId), A._, A>._)) .Returns(null); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act async Task Act() => await sut.DeleteOwnCompanyServiceAccountAsync(ValidServiceAccountId); @@ -628,7 +653,7 @@ public async Task DeleteOwnCompanyServiceAccountAsync_WithServiceAccountNotActiv A.CallTo(() => _technicalUserRepository.GetOwnTechnicalUserWithIamUserRolesAsync(ValidServiceAccountId, ValidCompanyId, A>._)) .Returns(new OwnTechnicalUserData(_userRoleIds, ValidServiceAccountId, userStatusId, true, Guid.NewGuid(), null, null, ConnectorStatusId.ACTIVE, OfferSubscriptionStatusId.PENDING, false, false, null)); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act async Task Act() => await sut.DeleteOwnCompanyServiceAccountAsync(ValidServiceAccountId); @@ -645,7 +670,7 @@ public async Task DeleteOwnCompanyServiceAccountAsync_WithUserNotOwnerOrProvider A.CallTo(() => _technicalUserRepository.GetOwnTechnicalUserWithIamUserRolesAsync(ValidServiceAccountId, ValidCompanyId, A>._)) .Returns(new OwnTechnicalUserData(_userRoleIds, ValidServiceAccountId, UserStatusId.ACTIVE, false, Guid.NewGuid(), null, null, ConnectorStatusId.ACTIVE, OfferSubscriptionStatusId.PENDING, false, false, null)); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act async Task Act() => await sut.DeleteOwnCompanyServiceAccountAsync(ValidServiceAccountId); @@ -668,7 +693,7 @@ public async Task DeleteOwnCompanyServiceAccountAsync_WithoutClient_CallsExpecte .Create(); var processId = Guid.NewGuid(); SetupDeleteOwnCompanyServiceAccount(connector, identity, processId); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, null!, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act await sut.DeleteOwnCompanyServiceAccountAsync(ValidServiceAccountId); @@ -705,7 +730,7 @@ public async Task GetServiceAccountRolesAsync_GetsExpectedData() A.CallTo(() => _portalRepositories.GetInstance()).Returns(_userRolesRepository); - var sut = new TechnicalUserBusinessLogic(null!, _portalRepositories, options, null!, _identityService, null!); + var sut = new TechnicalUserBusinessLogic(null!, _portalRepositories, options, null!, _identityService, null!, _bringYourOwnWalletBusinessLogic); // Act var result = await sut.GetServiceAccountRolesAsync(null).ToListAsync(); @@ -736,7 +761,7 @@ public async Task HandleServiceAccountCreationCallback_WithValidOfferSubscriptio A.CallTo(() => _technicalUserRepository.GetProcessDataForTechnicalUserCallback(A._, A>._)) .Returns((ProcessTypeId.OFFER_SUBSCRIPTION, context, technicalUserId, technicalUserVersionId)); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act await sut.HandleServiceAccountCreationCallback(process.Id, _fixture.Create()); @@ -767,7 +792,7 @@ public async Task HandleServiceAccountCreationCallback_WithLinkedUser_ExecutesEx A.CallTo(() => _offerSubscriptionsRepository.GetProcessDataForTechnicalUserCallback(A._, A>._)) .Returns(Enumerable.Repeat(new ValueTuple, Guid?, Guid?>(ProcessTypeId.OFFER_SUBSCRIPTION, context, technicalUserId, technicalUserVersionId), 1).ToAsyncEnumerable()); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); // Act await sut.HandleServiceAccountCreationCallback(process.Id, _fixture.Create()); @@ -797,7 +822,7 @@ public async Task HandleServiceAccountCreationCallback_WithMoreThanOneTechnicalU A.CallTo(() => _offerSubscriptionsRepository.GetProcessDataForTechnicalUserCallback(A._, A>._)) .Returns(Enumerable.Repeat(new ValueTuple, Guid?, Guid?>(ProcessTypeId.OFFER_SUBSCRIPTION, _fixture.Create>(), Guid.NewGuid(), Guid.NewGuid()), 2).ToAsyncEnumerable()); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); async Task Act() => await sut.HandleServiceAccountCreationCallback(process.Id, _fixture.Create()); @@ -825,7 +850,7 @@ public async Task HandleServiceAccountCreationCallback_WithNotExistingProcess_Th A.CallTo(() => _offerSubscriptionsRepository.GetProcessDataForTechnicalUserCallback(A._, A>._)) .Returns(Enumerable.Empty<(ProcessTypeId, VerifyProcessData, Guid?, Guid?)>().ToAsyncEnumerable()); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); async Task Act() => await sut.HandleServiceAccountCreationCallback(process.Id, _fixture.Create()); @@ -848,7 +873,7 @@ public async Task HandleServiceAccountCreationCallback_WithOfferSubscriptionIdNo A.CallTo(() => _technicalUserRepository.GetProcessDataForTechnicalUserCallback(A._, A>._)) .Returns((ProcessTypeId.OFFER_SUBSCRIPTION, context, null, null)); - var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement); + var sut = new TechnicalUserBusinessLogic(_provisioningManager, _portalRepositories, _options, _technicalUserCreation, _identityService, _serviceAccountManagement, _bringYourOwnWalletBusinessLogic); async Task Act() => await sut.HandleServiceAccountCreationCallback(process.Id, _fixture.Create()); diff --git a/tests/externalsystems/Bpdm.Library/BpdmBusinessLogicTests.cs b/tests/externalsystems/Bpdm.Library/BpdmBusinessLogicTests.cs index 1bb3afb299..2f07b99954 100644 --- a/tests/externalsystems/Bpdm.Library/BpdmBusinessLogicTests.cs +++ b/tests/externalsystems/Bpdm.Library/BpdmBusinessLogicTests.cs @@ -358,21 +358,10 @@ public async Task HandlePullLegalEntity_WithSharingStateError_ThrowsServiceExcep public async Task HandlePullLegalEntity_WithSharingProcessStartedNotSet_ReturnsExpected() { // Arrange - var company = new Company(Guid.NewGuid(), "Test Company", CompanyStatusId.ACTIVE, DateTimeOffset.UtcNow) - { - BusinessPartnerNumber = "1" - }; + var context = PreparePullLegalEntity(); var checklistEntry = _fixture.Build() .With(x => x.ApplicationChecklistEntryStatusId, ApplicationChecklistEntryStatusId.TO_DO) .Create(); - var checklist = new Dictionary - { - { ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE }, - { ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.TO_DO } - } - .ToImmutableDictionary(); - var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithoutSharingProcessStarted, default, checklist, Enumerable.Empty()); - SetupForHandlePullLegalEntity(company); // Act var result = await _logic.HandlePullLegalEntity(context, CancellationToken.None); @@ -386,6 +375,78 @@ public async Task HandlePullLegalEntity_WithSharingProcessStartedNotSet_ReturnsE result.ProcessMessage.Should().Be("SharingProcessStarted was not set"); } + [Fact] + public async Task HandlePullLegalEntity_BringYourOwnWallet_True_ReturnsExpected() + { + // Arrange + var context = PreparePullLegalEntity(); + + A.CallTo(() => _bpdmService.GetSharingState(A._, A._)) + .Returns(Task.FromResult(new BpdmSharingState( + Guid.NewGuid(), + BpdmSharingStateType.Success, + null, + null, + DateTimeOffset.UtcNow, + null + ))); + var bpdmLegalEntityOutputData = _fixture.Build() + .With(x => x.LegalEntity, + new BpdmLegelEntityData("CAXSDUMMYCATENAZZ", + null, + null, + null, + _fixture.Create(), Enumerable.Empty()) + ).Create(); + + A.CallTo(() => _bpdmService.FetchInputLegalEntity(context.ApplicationId.ToString(), A._)) + .Returns(bpdmLegalEntityOutputData); + + A.CallTo(() => _companyRepository.IsBringYourOwnWallet(A._)).Returns(true); + + // Act + var result = await _logic.HandlePullLegalEntity(context, CancellationToken.None); + + // Assert + result.ScheduleStepTypeIds.Should().ContainSingle(x => x == ProcessStepTypeId.TRANSMIT_BPN_DID); + } + + [Fact] + public async Task HandlePullLegalEntity_BringYourOwnWallet_False_ReturnsExpected() + { + // Arrange + var context = PreparePullLegalEntity(); + + A.CallTo(() => _bpdmService.GetSharingState(A._, A._)) + .Returns(Task.FromResult(new BpdmSharingState( + Guid.NewGuid(), + BpdmSharingStateType.Success, + null, + null, + DateTimeOffset.UtcNow, + null + ))); + var bpdmLegalEntityOutputData = _fixture.Build() + .With(x => x.LegalEntity, + new BpdmLegelEntityData("CAXSDUMMYCATENAZZ", + null, + null, + null, + _fixture.Create(), Enumerable.Empty()) + ).Create(); + + A.CallTo(() => _bpdmService.FetchInputLegalEntity(context.ApplicationId.ToString(), A._)) + .Returns(bpdmLegalEntityOutputData); + + A.CallTo(() => _companyRepository.IsBringYourOwnWallet(A._)).Returns(false); + + // Act + var result = await _logic.HandlePullLegalEntity(context, CancellationToken.None); + + // Assert + result.ScheduleStepTypeIds.Should().ContainSingle(x => x == ProcessStepTypeId.CREATE_IDENTITY_WALLET); + } + [Fact] public async Task HandlePullLegalEntity_WithSharingTypePending_ReturnsExpected() { @@ -576,4 +637,21 @@ private void SetupForHandlePullLegalEntity(Company? company = null) } #endregion + + private IApplicationChecklistService.WorkerChecklistProcessStepData PreparePullLegalEntity() + { + var company = new Company(Guid.NewGuid(), "Test Company", CompanyStatusId.ACTIVE, DateTimeOffset.UtcNow) + { + BusinessPartnerNumber = "1" + }; + var checklist = new Dictionary + { + { ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE }, + { ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.TO_DO } + } + .ToImmutableDictionary(); + var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithoutSharingProcessStarted, default, checklist, Enumerable.Empty()); + SetupForHandlePullLegalEntity(company); + return context; + } } diff --git a/tests/externalsystems/BpnDidResolver.Library.Tests/BpnDidResolverBusinessLogicTests.cs b/tests/externalsystems/BpnDidResolver.Library.Tests/BpnDidResolverBusinessLogicTests.cs index 11b319664c..9984a32bb7 100644 --- a/tests/externalsystems/BpnDidResolver.Library.Tests/BpnDidResolverBusinessLogicTests.cs +++ b/tests/externalsystems/BpnDidResolver.Library.Tests/BpnDidResolverBusinessLogicTests.cs @@ -38,6 +38,7 @@ public class BpnDidResolverBusinessLogicTests private readonly IFixture _fixture; private readonly IApplicationRepository _applicationRepository; + private readonly ICompanyRepository _companyRepository; private readonly IBpnDidResolverService _bpnDidResolverService; private readonly IBpnDidResolverBusinessLogic _logic; @@ -48,8 +49,10 @@ public BpnDidResolverBusinessLogicTests() var portalRepository = A.Fake(); _applicationRepository = A.Fake(); + _companyRepository = A.Fake(); _bpnDidResolverService = A.Fake(); A.CallTo(() => portalRepository.GetInstance()).Returns(_applicationRepository); + A.CallTo(() => portalRepository.GetInstance()).Returns(_companyRepository); _logic = new BpnDidResolverBusinessLogic(portalRepository, _bpnDidResolverService); } @@ -237,5 +240,30 @@ public async Task TransmitDidAndBpn_WithValid_CallsExpected() result.ScheduleStepTypeIds.Should().ContainSingle(x => x == ProcessStepTypeId.REQUEST_BPN_CREDENTIAL); } + [Fact] + public async Task TransmitDidAndBpn_WithBpnProcessInTodo_BringYourWalletTrue() + { + // Arrange + var checklist = new Dictionary + { + {ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE}, + {ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.TO_DO}, + {ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.TO_DO}, + } + .ToImmutableDictionary(); + var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(ApplicationId, default, checklist, Enumerable.Empty()); + var did = "did:web:test:1234"; + A.CallTo(() => _applicationRepository.GetDidAndBpnForApplicationId(ApplicationId)) + .Returns(new ValueTuple(true, did, BPN)); + A.CallTo(() => _companyRepository.IsBringYourOwnWallet(ApplicationId)).Returns(true); + + // Act + await _logic.TransmitDidAndBpn(context, CancellationToken.None); + + // Assert + A.CallTo(() => _bpnDidResolverService.TransmitDidAndBpn(did, BPN, A._)) + .MustHaveHappenedOnceExactly(); + } + #endregion } diff --git a/tests/externalsystems/IssuerComponent.Library.Tests/IssuerComponentBusinessLogicTests.cs b/tests/externalsystems/IssuerComponent.Library.Tests/IssuerComponentBusinessLogicTests.cs index 3f14bb93ef..dfc9720590 100644 --- a/tests/externalsystems/IssuerComponent.Library.Tests/IssuerComponentBusinessLogicTests.cs +++ b/tests/externalsystems/IssuerComponent.Library.Tests/IssuerComponentBusinessLogicTests.cs @@ -102,20 +102,8 @@ public IssuerComponentBusinessLogicTests() public async Task CreateBpnlCredential_WithValid_CallsExpected() { // Arrange - var cryptoConfig = _options.Value.EncryptionConfigs.First(); - var (secret, vector) = CryptoHelper.Encrypt("test123", Convert.FromHexString(cryptoConfig.EncryptionKey), cryptoConfig.CipherMode, cryptoConfig.PaddingMode); - var checklist = new Dictionary - { - {ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.BPNL_CREDENTIAL, ApplicationChecklistEntryStatusId.TO_DO} - } - .ToImmutableDictionary(); - var entry = new ApplicationChecklistEntry(IdWithBpn, ApplicationChecklistEntryTypeId.BPNL_CREDENTIAL, ApplicationChecklistEntryStatusId.TO_DO, DateTimeOffset.UtcNow); - var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithBpn, ProcessStepTypeId.REQUEST_BPN_CREDENTIAL, checklist, Enumerable.Empty()); - A.CallTo(() => _applicationRepository.GetBpnlCredentialIformationByApplicationId(A._)) - .Returns((true, "did:123:testabc", ValidBpn, new WalletInformation("cl1", secret, vector, 0, "https://example.com/wallet"))); + CreateContextForCredential(out var entry, out var checklist); + var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithBpn, ProcessStepTypeId.REQUEST_BPN_CREDENTIAL, checklist.ToImmutableDictionary(), Enumerable.Empty()); // Act var result = await _sut.CreateBpnlCredential(context, CancellationToken.None); @@ -144,6 +132,74 @@ public async Task CreateBpnlCredential_WithValid_CallsExpected() entry.ApplicationChecklistEntryStatusId.Should().Be(ApplicationChecklistEntryStatusId.IN_PROGRESS); } + [Fact] + public async Task CreateBpnlCredential_WithValid_BringYourOwnWallet_true_CallsExpected() + { + // Arrange + CreateContextForCredential(out var entry, out var checklist); + var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithBpn, ProcessStepTypeId.REQUEST_BPN_CREDENTIAL, checklist.ToImmutableDictionary(), Enumerable.Empty()); + + //bring your own wallet true + A.CallTo(() => _companyRepository.IsBringYourOwnWallet(A._)).Returns(true); + // Act + var result = await _sut.CreateBpnlCredential(context, CancellationToken.None); + + // Assert + A.CallTo(() => _issuerComponentService + .CreateBpnlCredential( + A.That.Matches(x => x.TechnicalUserDetails == null), + A._)) + .MustHaveHappenedOnceExactly(); + + //call should be 2 times now + A.CallTo(() => _applicationRepository.GetBpnlCredentialIformationByApplicationId(IdWithBpn)) + .MustHaveHappenedOnceExactly(); + result.StepStatusId.Should().Be(ProcessStepStatusId.DONE); + result.ScheduleStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.AWAIT_BPN_CREDENTIAL_RESPONSE); + result.Modified.Should().BeTrue(); + result.SkipStepTypeIds.Should().BeNull(); + result.ModifyChecklistEntry.Should().NotBeNull(); + result.ModifyChecklistEntry!(entry); + entry.ApplicationChecklistEntryStatusId.Should().Be(ApplicationChecklistEntryStatusId.IN_PROGRESS); + } + + [Fact] + public async Task CreateBpnlCredential_WithValid_BringYourOwnWallet_false_CallsExpected() + { + // Arrange + CreateContextForCredential(out var entry, out var checklist); + var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithBpn, ProcessStepTypeId.REQUEST_BPN_CREDENTIAL, checklist.ToImmutableDictionary(), Enumerable.Empty()); + + //bring your own wallet false + A.CallTo(() => _companyRepository.IsBringYourOwnWallet(A._)).Returns(false); + + var result = await _sut.CreateBpnlCredential(context, CancellationToken.None); + + A.CallTo(() => _issuerComponentService + .CreateBpnlCredential( + A.That.Matches(x => + x.Holder == "did:123:testabc" && + x.BusinessPartnerNumber == ValidBpn && + x.TechnicalUserDetails != null && + x.TechnicalUserDetails.WalletUrl == "https://example.com/wallet" && + x.TechnicalUserDetails.ClientId == "cl1" && + x.TechnicalUserDetails.ClientSecret == "test123" && + x.CallbackUrl == "https://example.org/callback/api/administration/registration/issuer/bpncredential"), + A._)) + .MustHaveHappenedOnceExactly(); + + //call should be 2 times now + A.CallTo(() => _applicationRepository.GetBpnlCredentialIformationByApplicationId(IdWithBpn)) + .MustHaveHappenedOnceExactly(); + result.StepStatusId.Should().Be(ProcessStepStatusId.DONE); + result.ScheduleStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.AWAIT_BPN_CREDENTIAL_RESPONSE); + result.Modified.Should().BeTrue(); + result.SkipStepTypeIds.Should().BeNull(); + result.ModifyChecklistEntry.Should().NotBeNull(); + result.ModifyChecklistEntry!(entry); + entry.ApplicationChecklistEntryStatusId.Should().Be(ApplicationChecklistEntryStatusId.IN_PROGRESS); + } + [Fact] public async Task CreateBpnlCredential_WithApplicationNotFound_ThrowsNotFoundException() { @@ -320,20 +376,8 @@ public async Task StoreBpnlCredential_WithUnsuccessful_UpdatesEntry() public async Task CreateMembershipCredential_WithValid_CallsExpected() { // Arrange - var cryptoConfig = _options.Value.EncryptionConfigs.First(); - var (secret, vector) = CryptoHelper.Encrypt("test123", Convert.FromHexString(cryptoConfig.EncryptionKey), cryptoConfig.CipherMode, cryptoConfig.PaddingMode); - var checklist = new Dictionary - { - {ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.DONE}, - {ApplicationChecklistEntryTypeId.BPNL_CREDENTIAL, ApplicationChecklistEntryStatusId.TO_DO} - } - .ToImmutableDictionary(); - var entry = new ApplicationChecklistEntry(IdWithBpn, ApplicationChecklistEntryTypeId.BPNL_CREDENTIAL, ApplicationChecklistEntryStatusId.TO_DO, DateTimeOffset.UtcNow); - var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithBpn, ProcessStepTypeId.REQUEST_BPN_CREDENTIAL, checklist, Enumerable.Empty()); - A.CallTo(() => _applicationRepository.GetBpnlCredentialIformationByApplicationId(A._)) - .Returns((true, "did:123:testabc", ValidBpn, new WalletInformation("cl1", secret, vector, 0, "https://example.com/wallet"))); + CreateContextForCredential(out var entry, out var checklist); + var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithBpn, ProcessStepTypeId.REQUEST_BPN_CREDENTIAL, checklist.ToImmutableDictionary(), Enumerable.Empty()); // Act var result = await _sut.CreateMembershipCredential(context, CancellationToken.None); @@ -352,6 +396,80 @@ public async Task CreateMembershipCredential_WithValid_CallsExpected() ), A._)) .MustHaveHappenedOnceExactly(); + + A.CallTo(() => _applicationRepository.GetBpnlCredentialIformationByApplicationId(IdWithBpn)) + .MustHaveHappenedOnceExactly(); + result.StepStatusId.Should().Be(ProcessStepStatusId.DONE); + result.ScheduleStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.AWAIT_MEMBERSHIP_CREDENTIAL_RESPONSE); + result.Modified.Should().BeTrue(); + result.SkipStepTypeIds.Should().BeNull(); + result.ModifyChecklistEntry.Should().NotBeNull(); + result.ModifyChecklistEntry!(entry); + entry.ApplicationChecklistEntryStatusId.Should().Be(ApplicationChecklistEntryStatusId.IN_PROGRESS); + } + + [Fact] + public async Task CreateMembershipCredential_WithValid_BringYourOwnWallet_True_CallsExpected() + { + // Arrange + CreateContextForCredential(out var entry, out var checklist); + var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithBpn, ProcessStepTypeId.REQUEST_BPN_CREDENTIAL, checklist.ToImmutableDictionary(), Enumerable.Empty()); + + //bring your own wallet true + A.CallTo(() => _companyRepository.IsBringYourOwnWallet(A._)).Returns(true); + + // Act + var result = await _sut.CreateMembershipCredential(context, CancellationToken.None); + + A.CallTo(() => _issuerComponentService + .CreateMembershipCredential( + A.That.Matches(x => + x.Holder == "did:123:testabc" && + x.HolderBpn == ValidBpn && + x.TechnicalUserDetails == null + ), + A._)) + .MustHaveHappenedOnceExactly(); + + A.CallTo(() => _applicationRepository.GetBpnlCredentialIformationByApplicationId(IdWithBpn)) + .MustHaveHappenedOnceExactly(); + result.StepStatusId.Should().Be(ProcessStepStatusId.DONE); + result.ScheduleStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.AWAIT_MEMBERSHIP_CREDENTIAL_RESPONSE); + result.Modified.Should().BeTrue(); + result.SkipStepTypeIds.Should().BeNull(); + result.ModifyChecklistEntry.Should().NotBeNull(); + result.ModifyChecklistEntry!(entry); + entry.ApplicationChecklistEntryStatusId.Should().Be(ApplicationChecklistEntryStatusId.IN_PROGRESS); + } + + [Fact] + public async Task CreateMembershipCredential_WithValid_BringYourOwnWallet_False_CallsExpected() + { + // Arrange + CreateContextForCredential(out var entry, out var checklist); + var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithBpn, ProcessStepTypeId.REQUEST_BPN_CREDENTIAL, checklist.ToImmutableDictionary(), Enumerable.Empty()); + + //bring your own wallet false + A.CallTo(() => _companyRepository.IsBringYourOwnWallet(A._)).Returns(false); + + // Act + var result = await _sut.CreateMembershipCredential(context, CancellationToken.None); + + // Assert + A.CallTo(() => _issuerComponentService + .CreateMembershipCredential( + A.That.Matches(x => + x.Holder == "did:123:testabc" && + x.HolderBpn == ValidBpn && + x.TechnicalUserDetails != null && + x.TechnicalUserDetails.ClientId == "cl1" && + x.TechnicalUserDetails.ClientSecret == "test123" && + x.TechnicalUserDetails.WalletUrl == "https://example.com/wallet" && + x.CallbackUrl == "https://example.org/callback/api/administration/registration/issuer/membershipcredential" + ), + A._)) + .MustHaveHappenedOnceExactly(); + A.CallTo(() => _applicationRepository.GetBpnlCredentialIformationByApplicationId(IdWithBpn)) .MustHaveHappenedOnceExactly(); result.StepStatusId.Should().Be(ProcessStepStatusId.DONE); @@ -549,6 +667,9 @@ public async Task CreateFrameworkCredential_WithValid_CallsExpected() A.CallTo(() => _issuerComponentService.CreateFrameworkCredential(A._, A._, A._)) .Returns(credentialId); + //bring your own wallet false + A.CallTo(() => _companyRepository.IsBringYourOwnWallet(A._)).Returns(false); + // Act var result = await _sut.CreateFrameworkCredentialData(useCaseFrameworkVersionId, "TRACEABILITY_FRAMEWORK", identityId, Token, CancellationToken.None); @@ -567,8 +688,29 @@ public async Task CreateFrameworkCredential_WithValid_CallsExpected() Token, A._)) .MustHaveHappenedOnceExactly(); - A.CallTo(() => _companyRepository.GetWalletData(identityId)) + + //bring your own wallet true + A.CallTo(() => _companyRepository.IsBringYourOwnWallet(A._)).Returns(true); + + // Act + result = await _sut.CreateFrameworkCredentialData(useCaseFrameworkVersionId, "TRACEABILITY_FRAMEWORK", identityId, Token, CancellationToken.None); + + //assert + A.CallTo(() => _issuerComponentService + .CreateFrameworkCredential( + A.That.Matches(x => + x.Holder == "did:123:testabc" && + x.HolderBpn == ValidBpn && + x.TechnicalUserDetails == null && + x.CallbackUrl == null + ), + Token, + A._)) .MustHaveHappenedOnceExactly(); + + //call is 2 times + A.CallTo(() => _companyRepository.GetWalletData(identityId)) + .MustHaveHappenedTwiceExactly(); result.Should().Be(credentialId); } @@ -644,4 +786,20 @@ private void SetupForProcessIssuerComponentResponse(ApplicationChecklistEntry ap } #endregion + + private void CreateContextForCredential(out ApplicationChecklistEntry entry, out Dictionary checklist) + { + var cryptoConfig = _options.Value.EncryptionConfigs.First(); + var (secret, vector) = CryptoHelper.Encrypt("test123", Convert.FromHexString(cryptoConfig.EncryptionKey), cryptoConfig.CipherMode, cryptoConfig.PaddingMode); + checklist = new Dictionary + { + {ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE}, + {ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.DONE}, + {ApplicationChecklistEntryTypeId.IDENTITY_WALLET, ApplicationChecklistEntryStatusId.DONE}, + {ApplicationChecklistEntryTypeId.BPNL_CREDENTIAL, ApplicationChecklistEntryStatusId.TO_DO} + }; + entry = new ApplicationChecklistEntry(IdWithBpn, ApplicationChecklistEntryTypeId.BPNL_CREDENTIAL, ApplicationChecklistEntryStatusId.TO_DO, DateTimeOffset.UtcNow); + A.CallTo(() => _applicationRepository.GetBpnlCredentialIformationByApplicationId(A._)) + .Returns((true, "did:123:testabc", ValidBpn, new WalletInformation("cl1", secret, vector, 0, "https://example.com/wallet"))); + } } diff --git a/tests/externalsystems/UniversalDidResolver.Library.Tests/UniversalDidResolver.Library.Tests.csproj b/tests/externalsystems/UniversalDidResolver.Library.Tests/UniversalDidResolver.Library.Tests.csproj new file mode 100644 index 0000000000..3576571aeb --- /dev/null +++ b/tests/externalsystems/UniversalDidResolver.Library.Tests/UniversalDidResolver.Library.Tests.csproj @@ -0,0 +1,43 @@ + + + + + + net9.0 + enable + enable + false + Org.Eclipse.TractusX.Portal.Backend.UniversalDidResolver.Library.Tests + Org.Eclipse.TractusX.Portal.Backend.UniversalDidResolver.Library.Tests + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/tests/externalsystems/UniversalDidResolver.Library.Tests/UniversalDidResolverServiceTest.cs b/tests/externalsystems/UniversalDidResolver.Library.Tests/UniversalDidResolverServiceTest.cs new file mode 100644 index 0000000000..0842e0b2e9 --- /dev/null +++ b/tests/externalsystems/UniversalDidResolver.Library.Tests/UniversalDidResolverServiceTest.cs @@ -0,0 +1,192 @@ +/******************************************************************************** + * Copyright (c) 2025 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 AutoFixture; +using AutoFixture.AutoFakeItEasy; +using FakeItEasy; +using FluentAssertions; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.Extensions; +using Org.Eclipse.TractusX.Portal.Backend.UniversalDidResolver.Library.DependencyInjection; +using Org.Eclipse.TractusX.Portal.Backend.UniversalDidResolver.Library.Models; +using System.Net; +using System.Text.Json; +using Xunit; + +namespace Org.Eclipse.TractusX.Portal.Backend.UniversalDidResolver.Library.Tests; + +public class UniversalDidResolverServiceTests +{ + private readonly IFixture _fixture; + private readonly UniversalDidResolverSettings _settings; + private readonly IUniversalDidResolverService _sut; + public UniversalDidResolverServiceTests() + { + _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + _fixture.ConfigureFixture(); + + _settings = new UniversalDidResolverSettings + { + UniversalResolverAddress = "https://dev.uniresolver.io/" + }; + _fixture.Inject(Options.Create(_settings)); + _sut = new UniversalDidResolverService(_fixture.Freeze()); + } + + [Fact] + public async Task ValidateDid_ReturnsTrue_WhenDidIsValid() + { + // Arrange + const string did = "did:web:123"; + HttpRequestMessage? request = null; + var didDocumentJson = "{\"id\":\"did:web:123\"}"; + using var doc = JsonDocument.Parse(didDocumentJson); + var didValidationResult = new DidValidationResult(new DidResolutionMetadata(null), doc); + using var responseMessage = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(JsonSerializer.Serialize(didValidationResult)) + }; + _fixture.ConfigureHttpClientFactoryFixture("universalResolver", responseMessage, requestMessage => request = requestMessage, _settings.UniversalResolverAddress); + + // Mock ValidateSchema to return true + var universalDidResolverService = _fixture.Freeze(); + A.CallTo(() => universalDidResolverService.ValidateSchema(A._, A._)).Returns(true); + + // Act + await _sut.ValidateDid(did, CancellationToken.None); + + // Assert + request.Should().NotBeNull(); + request!.RequestUri.Should().NotBeNull(); + request.RequestUri!.AbsoluteUri.Should().Be($"https://dev.uniresolver.io/1.0/identifiers/{Uri.EscapeDataString(did)}"); + } + + [Fact] + public async Task ValidateDid_ReturnsFalse_WhenDidHasError() + { + // Arrange + const string did = "did:web:123"; + HttpRequestMessage? request = null; + var didDocumentJson = "{\"id\":\"did:web:123\"}"; + using var doc = JsonDocument.Parse(didDocumentJson); + var didValidationResult = new DidValidationResult( + new DidResolutionMetadata("notFound"), + doc + ); + using var responseMessage = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent(JsonSerializer.Serialize(didValidationResult)) + }; + _fixture.ConfigureHttpClientFactoryFixture("universalResolver", responseMessage, requestMessage => request = requestMessage, _settings.UniversalResolverAddress); + + // Act + async Task Act() => await _sut.ValidateDid(did, CancellationToken.None); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Contain("DID validation failed during validation"); + } + + [Fact] + public async Task ValidateDid_ThrowNotFoundException_WhenDidNotResolved() + { + // Arrange + const string did = "did:web:123"; + HttpRequestMessage? request = null; + using var emptyDoc = JsonDocument.Parse("{}"); + var didValidationResult = new DidValidationResult(new DidResolutionMetadata(null), emptyDoc); + using var responseMessage = new HttpResponseMessage + { + StatusCode = HttpStatusCode.BadRequest, + Content = new StringContent(JsonSerializer.Serialize(didValidationResult)) + }; + _fixture.ConfigureHttpClientFactoryFixture( + "universalResolver", + responseMessage, + requestMessage => request = requestMessage, + _settings.UniversalResolverAddress + ); + + // Act + async Task Act() => await _sut.ValidateDid(did, CancellationToken.None); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Be("DID URL could not be reached by the external resolver, 404 error"); + request.Should().NotBeNull(); + request!.RequestUri.Should().NotBeNull(); + request.RequestUri!.AbsoluteUri.Should().Be($"https://dev.uniresolver.io/1.0/identifiers/{Uri.EscapeDataString(did)}"); + } + + [Fact] + public async Task ValidateDid_ReturnsFalse_WhenDeserializationReturnsNull() + { + // Arrange + const string did = "did:web:123"; + HttpRequestMessage? request = null; + using var responseMessage = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = null // Simulating an empty response + }; + _fixture.ConfigureHttpClientFactoryFixture("universalResolver", responseMessage, requestMessage => request = requestMessage, _settings.UniversalResolverAddress); + + // Act + async Task Act() => await _sut.ValidateDid(did, CancellationToken.None); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Contain("The input does not contain any JSON tokens"); + request.Should().NotBeNull(); + request!.RequestUri.Should().NotBeNull(); + request.RequestUri!.AbsoluteUri.Should().Be($"https://dev.uniresolver.io/1.0/identifiers/{Uri.EscapeDataString(did)}"); + } + + [Fact] + public async Task ValidateDid_ThrowsException_WhenValidationResultIsNull() + { + // Arrange + const string did = "did:web:123"; + HttpRequestMessage? request = null; + using var responseMessage = new HttpResponseMessage + { + StatusCode = HttpStatusCode.OK, + Content = new StringContent("null", System.Text.Encoding.UTF8, "application/json") + }; + _fixture.ConfigureHttpClientFactoryFixture( + "universalResolver", + responseMessage, + requestMessage => request = requestMessage, + _settings.UniversalResolverAddress + ); + + // Act + async Task Act() => await _sut.ValidateDid(did, CancellationToken.None); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Contain("DID validation failed: No result returned."); + request.Should().NotBeNull(); + request!.RequestUri.Should().NotBeNull(); + request.RequestUri!.AbsoluteUri.Should().Be($"https://dev.uniresolver.io/1.0/identifiers/{Uri.EscapeDataString(did)}"); + } +} diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/CompanyRepositoryTests.cs b/tests/portalbackend/PortalBackend.DBAccess.Tests/CompanyRepositoryTests.cs index eda817deff..ade9186fc0 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/CompanyRepositoryTests.cs +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/CompanyRepositoryTests.cs @@ -27,6 +27,7 @@ using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; using System.Collections.Immutable; +using System.Text.Json; using Xunit.Extensions.AssemblyFixture; namespace Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Tests; @@ -988,9 +989,10 @@ public async Task GetAllMemberCompaniesBPN_withNull_ReturnsExpected() var result = await sut.GetAllMemberCompaniesBPNAsync(null).ToListAsync(); // Assert - result.Should().NotBeNull().And.HaveCount(5).And.Satisfy( + result.Should().NotBeNull().And.HaveCount(6).And.Satisfy( x => x == "BPNL07800HZ01643", x => x == "BPNL00000003AYRE", + x => x == "BPNL00000000BYOW", x => x == "BPNL00000003CRHK", x => x == "BPNL00000003CRHL", x => x == "BPNL00000001TEST"); @@ -1104,4 +1106,63 @@ public async Task IsExistingCompany_WithNotExistingCompany_ReturnsFalse() } #endregion + + #region BringYourOwnWallet + + [Fact] + public async Task IsBringYourOwnWallet_ReturnsTrue() + { + // Arrange + var (sut, _) = await CreateSut(); + + // Act + var result = await sut.IsBringYourOwnWallet(Guid.Parse("c20ba2f7-ac9d-48d4-9498-a46be50c9271")).ConfigureAwait(ConfigureAwaitOptions.None); + + // Assert + result.Should().BeTrue(); + } + + [Fact] + public async Task IsBringYourOwnWallet_NoMatchingClientId_ReturnsFalse() + { + // Arrange + var (sut, _) = await CreateSut(); + + // Act + var result = await sut.IsBringYourOwnWallet(Guid.NewGuid()).ConfigureAwait(ConfigureAwaitOptions.None); + + // Assert + result.Should().BeFalse(); + } + + [Fact] + public async Task CreateCompanyWallet_ReturnsExpectedResult() + { + // Arrange + var (sut, context) = await CreateSut(); + var didDocument = JsonDocument.Parse("{}"); // Provide a valid JSON string or object as needed + + // Act + await sut.CreateCustomerWallet(Guid.NewGuid(), "did:web:example.com", didDocument); + + // Assert + context.CompanyWalletDatas.Should().NotBeEmpty(); + context.CompanyWalletDatas.Should().HaveCount(2); + } + + [Fact] + public async Task CreateCompanyWallet_ExistingCustomerWallet_ReturnsExpectedResult() + { + // Arrange + var (sut, context) = await CreateSut(); + var didDocument = JsonDocument.Parse("{}"); // Provide a valid JSON string or object as needed + + // Act + await sut.CreateCustomerWallet(Guid.Parse("554f23d5-669e-48ed-b06b-9a84dfa017c5"), "did:web:example.com", didDocument); + + // Assert + context.CompanyWalletDatas.Should().NotBeEmpty(); + context.CompanyWalletDatas.Where(wallet => wallet.ClientId.Equals(BringYourOwnWalletClientFields.Identification)).Should().HaveCount(1); + } + #endregion } diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/companies.unittest.json b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/companies.unittest.json index 79959e6335..e8847e211f 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/companies.unittest.json +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/companies.unittest.json @@ -89,5 +89,15 @@ "company_status_id": 1, "address_id": "27418031-d6b5-442b-8802-3f653c6679f7", "self_description_document_id": null + }, + { + "id": "554f23d5-669e-48ed-b06b-9a84dfa017c5", + "date_created": "2022-03-24 18:01:33.306000 +00:00", + "business_partner_number": "BPNL00000000BYOW", + "name": "Bring Your Own Wallet Company", + "shortname": "CX-Operator", + "company_status_id": 2, + "address_id": "b4db3945-19a7-4a50-97d6-e66e8dfd04fb", + "self_description_document_id": "00000000-0000-0000-0000-000000000009" } ] diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/company_applications.unittest.json b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/company_applications.unittest.json index 919554ba9d..60e8bd4bb5 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/company_applications.unittest.json +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/company_applications.unittest.json @@ -48,5 +48,15 @@ "checklist_process_id": "0187aba6-a66e-4fe5-9519-c4254a4a63d8", "company_application_type_id": 1, "last_editor_id": null + }, + { + "id": "c20ba2f7-ac9d-48d4-9498-a46be50c9271", + "date_created": "2023-11-06 10:01:33.439000 +00:00", + "date_last_changed": "2023-11-06 10:01:33.439000 +00:00", + "application_status_id": 1, + "company_id": "554f23d5-669e-48ed-b06b-9a84dfa017c5", + "checklist_process_id": null, + "company_application_type_id": 1, + "last_editor_id": null } ] diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/company_wallet_data.unittest.json b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/company_wallet_data.unittest.json index 9d8a55daa8..7dc848ab3b 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/company_wallet_data.unittest.json +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/company_wallet_data.unittest.json @@ -9,5 +9,17 @@ "initialization_vector": null, "encryption_mode": 0, "authentication_service_url": "https://example.org/auth" + }, + { + "id": "84571142-db6b-4b86-b7af-1039dc0ffb5d", + "company_id": "554f23d5-669e-48ed-b06b-9a84dfa017c5", + "did": "did:web:test", + "did_document": {}, + "client_id": "BYOWCLIENT_ID", + "client_secret": "", + "initialization_vector": null, + "encryption_mode": 0, + "authentication_service_url": "https://example.org/auth" } + ] \ No newline at end of file diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/process_steps.unittest.json b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/process_steps.unittest.json index 76f2d6de1b..1976553b74 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/process_steps.unittest.json +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/process_steps.unittest.json @@ -270,5 +270,21 @@ "process_id": "d91ac968-ffe8-466f-b34a-ae2517b7c6ab", "date_created": "2023-10-11 08:15:20.479000 +00:00", "date_last_changed": null + }, + { + "id": "dada25f9-dcef-4c18-9eaf-479d9a855e17", + "process_step_type_id": 500, + "process_step_status_id": 2, + "process_id": "d6f8e638-c8f2-4803-b910-6d379241905a", + "date_created": "2023-10-11 08:15:20.479000 +00:00", + "date_last_changed": null + }, + { + "id": "3dbf0030-cec1-4471-9647-8dbb06350ebb", + "process_step_type_id": 502, + "process_step_status_id": 1, + "process_id": "d6f8e638-c8f2-4803-b910-6d379241905a", + "date_created": "2023-10-11 08:15:20.479000 +00:00", + "date_last_changed": null } ] diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/processes.unittest.json b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/processes.unittest.json index ca72277ac5..56930654af 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/processes.unittest.json +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/processes.unittest.json @@ -106,5 +106,11 @@ "process_type_id" : 3, "lock_expiry_date" : "2023-03-01 00:00:00.000000 +00:00", "version" : "deadbeef-dead-beef-dead-beefdeadbeef" + }, + { + "id": "d6f8e638-c8f2-4803-b910-6d379241905a", + "process_type_id" : 3, + "lock_expiry_date" : "2023-03-01 00:00:00.000000 +00:00", + "version" : "deadbeef-dead-beef-dead-beefdeadbeef" } ] diff --git a/tests/registration/Registration.Service.Tests/BusinessLogic/BringYourOwnWalletBuisinessLogicTests.cs b/tests/registration/Registration.Service.Tests/BusinessLogic/BringYourOwnWalletBuisinessLogicTests.cs new file mode 100644 index 0000000000..29ee6d72f8 --- /dev/null +++ b/tests/registration/Registration.Service.Tests/BusinessLogic/BringYourOwnWalletBuisinessLogicTests.cs @@ -0,0 +1,417 @@ +/******************************************************************************** + * Copyright (c) 2025 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 FakeItEasy; +using FluentAssertions; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories; +using Org.Eclipse.TractusX.Portal.Backend.Registration.Service.BusinessLogic; +using Org.Eclipse.TractusX.Portal.Backend.Registration.Service.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.UniversalDidResolver.Library; +using Org.Eclipse.TractusX.Portal.Backend.UniversalDidResolver.Library.Models; +using System.Text.Json; +using Xunit; + +namespace Org.Eclipse.TractusX.Portal.Backend.Registration.Service.Tests.BusinessLogic; + +public class BringYourOwnWalletBuisinessLogicTests +{ + private readonly IUniversalDidResolverService _universalDidResolverService; + private readonly IBringYourOwnWalletBusinessLogic _sut; + private readonly IPortalRepositories _portalRepositories = A.Fake(); + public BringYourOwnWalletBuisinessLogicTests() + { + _universalDidResolverService = A.Fake(); + _portalRepositories = A.Fake(); + _sut = new BringYourOwnWalletBusinessLogic(_universalDidResolverService, _portalRepositories); + } + + [Fact] + public async Task ValidateDid_Completes_WhenDidIsValid() + { + // Arrange + const string did = "did:web:123"; + var didDocument = JsonDocument.Parse("{\"id\":\"did:web:123\"}"); + var validationResult = new DidValidationResult(new DidResolutionMetadata(null), didDocument); + A.CallTo(() => _universalDidResolverService.ValidateDid(did, A._)) + .ReturnsLazily(() => Task.FromResult(validationResult)); + A.CallTo(() => _universalDidResolverService.ValidateSchema(didDocument, A._)) + .ReturnsLazily(() => true); + + // Act & Assert + await _sut.Invoking(s => s.ValidateDid(did, CancellationToken.None)) + .Should().NotThrowAsync(); + } + + [Fact] + public async Task ValidateDid_ThrowsSericeException_WhenDidHasError() + { + // Arrange + const string did = "did:web:123"; + var didDocument = JsonDocument.Parse("{\"id\":\"did:web:123\"}"); + var validationResult = new DidValidationResult(new DidResolutionMetadata("notFound"), didDocument); + A.CallTo(() => _universalDidResolverService.ValidateDid(did, A._)) + .ReturnsLazily(() => Task.FromResult(validationResult)); + + // Act + var act = () => _sut.ValidateDid(did, CancellationToken.None); + + // Assert + await act.Should().ThrowAsync() + .WithMessage(RegistrationErrors.REGISTRATION_INVALID_DID_DOCUMENT.ToString()); + } + + [Fact] + public async Task ValidateDid_ThrowsConflictException_WhenDidExists() + { + // Arrange + const string did = "did:web:123"; + var didDocument = JsonDocument.Parse("{\"id\":\"did:web:123\"}"); + var validationResult = new DidValidationResult(new DidResolutionMetadata(null), didDocument); + var companyRepository = _portalRepositories.GetInstance(); + + A.CallTo(() => _portalRepositories.GetInstance()).Returns(companyRepository); + A.CallTo(() => _universalDidResolverService.ValidateDid(did, A._)) + .ReturnsLazily(() => Task.FromResult(validationResult)); + A.CallTo(() => _universalDidResolverService.ValidateSchema(didDocument, A._)) + .ReturnsLazily(() => true); + A.CallTo(() => companyRepository.IsDidInUse(did)).ReturnsLazily(() => true); + + // Act + var act = () => _sut.ValidateDid(did, CancellationToken.None); + + // Assert + await act.Should().ThrowAsync() + .WithMessage(RegistrationErrors.REGISTRATION_CONFLICT_DID_ALREADY_IN_USE.ToString()); + } + + [Fact] + public async Task ValidateDid_ThrowsServiceException_WhenSchemaIsInvalid() + { + // Arrange + const string did = "did:web:123"; + var didDocument = JsonDocument.Parse("{\"id\":\"did:web:123\"}"); + var validationResult = new DidValidationResult(new DidResolutionMetadata(null), didDocument); + + A.CallTo(() => _universalDidResolverService.ValidateDid(did, A._)) + .ReturnsLazily(() => Task.FromResult(validationResult)); + A.CallTo(() => _universalDidResolverService.ValidateSchema(didDocument, A._)) + .ReturnsLazily(() => false); + + // Act + var act = () => _sut.ValidateDid(did, CancellationToken.None); + + // Assert + await act.Should().ThrowAsync() + .WithMessage(RegistrationErrors.REGISTRATION_INVALID_DID_DOCUMENT.ToString()); + } + + [Fact] + public async Task ValidateDid_ThrowsServiceException_WhenValidationResultIsNull() + { + // Arrange + const string did = "did:web:empty"; + var didDocument = JsonDocument.Parse("{}"); + var validationResult = new DidValidationResult( + new DidResolutionMetadata(Error: null), + didDocument + ); + A.CallTo(() => _universalDidResolverService.ValidateDid(did, A._)) + .ReturnsLazily(_ => Task.FromResult(validationResult)); + + // Act + var act = () => _sut.ValidateDid(did, CancellationToken.None); + + // Assert + await act.Should().ThrowAsync() + .WithMessage(RegistrationErrors.REGISTRATION_INVALID_DID_DOCUMENT.ToString()); + } + + [Fact] + public async Task SaveCustomerWalletAsync_SavesWallet_WhenDidIsValid() + { + // Arrange + var companyId = Guid.NewGuid(); + var did = "did:web:example.com"; + var didDocument = JsonDocument.Parse("{\"id\":\"did:web:example.com\"}"); + var validationResult = new DidValidationResult(new DidResolutionMetadata(null), didDocument); + var companyRepository = A.Fake(); + A.CallTo(() => _portalRepositories.GetInstance().CreateCustomerWallet(companyId, did, didDocument)) + .DoesNothing(); + A.CallTo(() => _portalRepositories.GetInstance()) + .Returns(companyRepository); + A.CallTo(() => _universalDidResolverService.ValidateDid(did, A._)) + .ReturnsLazily(() => Task.FromResult(validationResult)); + A.CallTo(() => _universalDidResolverService.ValidateSchema(didDocument, A._)) + .ReturnsLazily(() => true); + A.CallTo(() => companyRepository.IsExistingCompany(companyId)).ReturnsLazily(() => true); + // Act + await _sut.SaveCustomerWalletAsync(companyId, did); + + // Assert + A.CallTo(() => _portalRepositories.GetInstance().CreateCustomerWallet(companyId, did, didDocument)) + .MustHaveHappenedOnceExactly(); + A.CallTo(() => _portalRepositories.SaveAsync()).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task SaveCustomerWalletAsync_ThrowsException_WhenDidIsEmpty() + { + // Arrange + var companyId = Guid.NewGuid(); + var did = ""; + var didDocument = JsonDocument.Parse("{\"id\":\"did:web:example.com\"}"); + var validationResult = new DidValidationResult(new DidResolutionMetadata(null), didDocument); + var companyRepository = A.Fake(); + + A.CallTo(() => _portalRepositories.GetInstance()) + .Returns(companyRepository); + A.CallTo(() => companyRepository.IsExistingCompany(companyId)).ReturnsLazily(() => true); + A.CallTo(() => _universalDidResolverService.ValidateDid(did, A._)) + .ReturnsLazily(() => Task.FromResult(validationResult)); + A.CallTo(() => _universalDidResolverService.ValidateSchema(didDocument, A._)) + .ReturnsLazily(() => true); + // Act + Func act = async () => await _sut.SaveCustomerWalletAsync(companyId, did); + + // Assert + await act.Should().ThrowAsync() + .WithMessage(RegistrationErrors.REGISTRATION_INVALID_DID_DOCUMENT.ToString()); + } + + [Fact] + public async Task SaveCustomerWalletAsync_ThrowsException_WhenDidIsInvalid() + { + // Arrange + var companyId = Guid.NewGuid(); + var did = "did:web:INVALID"; + var didDocument = JsonDocument.Parse("{\"id\":\"did:web:example.com\"}"); + + A.CallTo(() => _portalRepositories.GetInstance()) + .Returns(A.Fake()); + A.CallTo(() => _universalDidResolverService.ValidateDid(did, A._)).Throws(); + A.CallTo(() => _universalDidResolverService.ValidateSchema(didDocument, A._)) + .ReturnsLazily(() => true); + // Act + Func act = async () => await _sut.SaveCustomerWalletAsync(companyId, did); + + // Assert + await act.Should().ThrowAsync(); + } + + [Fact] + public async Task SaveCustomerWalletAsync_ThrowsException_WhenCompanyIdInvalid() + { + // Arrange + var companyId = Guid.NewGuid(); + var did = "did:web:example.com"; + var didDocument = JsonDocument.Parse("{\"id\":\"did:web:example.com\"}"); + var companyRepository = A.Fake(); + + A.CallTo(() => _portalRepositories.GetInstance()) + .Returns(companyRepository); + A.CallTo(() => _universalDidResolverService.ValidateDid(did, A._)).Throws(); + A.CallTo(() => _universalDidResolverService.ValidateSchema(didDocument, A._)) + .ReturnsLazily(() => true); + A.CallTo(() => companyRepository.IsExistingCompany(companyId)).ReturnsLazily(() => false); + // Act + Func act = async () => await _sut.SaveCustomerWalletAsync(companyId, did); + + // Assert + await act.Should().ThrowAsync() + .WithMessage(RegistrationErrors.REGISTRATION_CONFLICT_DID_ALREADY_IN_USE.ToString()); + } + + [Fact] + public async Task GetCompanyWalletDidAsync_ReturnsDid_WhenWalletExists() + { + // Arrange + var companyId = Guid.NewGuid(); + var expectedDid = "did:web:example.com"; + var companyRepository = A.Fake(); + A.CallTo(() => _portalRepositories.GetInstance()) + .Returns(companyRepository); + A.CallTo(() => companyRepository.GetCompanyHolderDidAsync(companyId)) + .Returns(Task.FromResult(expectedDid)); + A.CallTo(() => companyRepository.IsExistingCompany(companyId)).ReturnsLazily(() => true); + + // Act + var result = await _sut.getCompanyWalletDidAsync(companyId); + + // Assert + result.Should().Be(expectedDid); + } + + [Fact] + public async Task GetCompanyWalletDidAsync_ThrowException_WhenWalletDoesNotExist() + { + // Arrange + var companyId = Guid.NewGuid(); + var companyRepository = A.Fake(); + A.CallTo(() => _portalRepositories.GetInstance()) + .Returns(companyRepository); + A.CallTo(() => companyRepository.IsExistingCompany(companyId)).ReturnsLazily(() => true); + A.CallTo(() => companyRepository.GetCompanyHolderDidAsync(companyId)) + .Returns(Task.FromResult(null)); + // Act + Func act = async () => await _sut.getCompanyWalletDidAsync(companyId); + + // Assert + await act.Should().ThrowAsync() + .WithMessage(RegistrationErrors.REGISTRATION_COMPANY_ID_NOT_FOUND.ToString()); + } + + [Fact] + public async Task GetCompanyWalletDidAsync_ThrowException_WhenCompanyIdDoesNotExist() + { + // Arrange + var companyId = Guid.NewGuid(); + var companyRepository = A.Fake(); + A.CallTo(() => _portalRepositories.GetInstance()) + .Returns(companyRepository); + A.CallTo(() => companyRepository.IsExistingCompany(companyId)).ReturnsLazily(() => false); + A.CallTo(() => companyRepository.GetCompanyHolderDidAsync(companyId)) + .Returns(Task.FromResult(null)); + // Act + Func act = async () => await _sut.getCompanyWalletDidAsync(companyId); + + // Assert + await act.Should().ThrowAsync() + .WithMessage(RegistrationErrors.REGISTRATION_COMPANY_ID_NOT_FOUND.ToString()); + } + + [Fact] + public async Task SaveCustomerWallet_InvalidDocumentId_ThrowException() + { + // Arrange + const string did = "did:web:123"; + var companyId = Guid.NewGuid(); + var didDocument = JsonDocument.Parse("{}"); + var validationResult = new DidValidationResult(new DidResolutionMetadata(null), didDocument); + var companyRepository = A.Fake(); + + A.CallTo(() => _portalRepositories.GetInstance().CreateCustomerWallet(companyId, did, didDocument)) + .DoesNothing(); + A.CallTo(() => _portalRepositories.GetInstance()) + .Returns(companyRepository); + A.CallTo(() => _universalDidResolverService.ValidateDid(did, A._)) + .ReturnsLazily(() => Task.FromResult(validationResult)); + A.CallTo(() => _universalDidResolverService.ValidateSchema(didDocument, A._)) + .ReturnsLazily(() => true); + A.CallTo(() => companyRepository.IsExistingCompany(companyId)).ReturnsLazily(() => true); + + // Act + + Func act = async () => + await _sut.SaveCustomerWalletAsync(companyId, did); + + // Assert + await act.Should().ThrowAsync() + .WithMessage(RegistrationErrors.REGISTRATION_INVALID_DID_DOCUMENT.ToString()); + } + + [Fact] + public async Task SaveCustomerWallet_InvalidDidFormat_ThrowException() + { + // Arrange + const string did = "did:web:123"; + var companyId = Guid.NewGuid(); + var didDocument = JsonDocument.Parse("{\"id\":\":web:123\"}"); + var validationResult = new DidValidationResult(new DidResolutionMetadata(null), didDocument); + var companyRepository = A.Fake(); + A.CallTo(() => _portalRepositories.GetInstance().CreateCustomerWallet(companyId, did, didDocument)) + .DoesNothing(); + A.CallTo(() => _portalRepositories.GetInstance()) + .Returns(companyRepository); + A.CallTo(() => companyRepository.IsExistingCompany(companyId)).ReturnsLazily(() => true); + A.CallTo(() => _universalDidResolverService.ValidateDid(did, A._)) + .ReturnsLazily(() => Task.FromResult(validationResult)); + A.CallTo(() => _universalDidResolverService.ValidateSchema(didDocument, A._)) + .ReturnsLazily(() => true); + + // Act + + Func act = async () => + await _sut.SaveCustomerWalletAsync(companyId, did); + + // Assert + await act.Should().ThrowAsync() + .WithMessage(RegistrationErrors.REGISTRATION_INVALID_DID_FORMAT.ToString()); + } + + [Fact] + public async Task SaveCustomerWallet_InvalidDidForm_ThrowException() + { + // Arrange + const string did = "did:web:123"; + var companyId = Guid.NewGuid(); + var didDocument = JsonDocument.Parse("{\"id\":\"did:web\"}"); + var validationResult = new DidValidationResult(new DidResolutionMetadata(null), didDocument); + var companyRepository = A.Fake(); + + A.CallTo(() => _portalRepositories.GetInstance().CreateCustomerWallet(companyId, did, didDocument)) + .DoesNothing(); + A.CallTo(() => _portalRepositories.GetInstance()) + .Returns(companyRepository); + A.CallTo(() => companyRepository.IsExistingCompany(companyId)).ReturnsLazily(() => true); + A.CallTo(() => _universalDidResolverService.ValidateDid(did, A._)) + .ReturnsLazily(() => Task.FromResult(validationResult)); + A.CallTo(() => _universalDidResolverService.ValidateSchema(didDocument, A._)) + .ReturnsLazily(() => true); + + // Act + + Func act = async () => + await _sut.SaveCustomerWalletAsync(companyId, did); + + // Assert + await act.Should().ThrowAsync() + .WithMessage(RegistrationErrors.REGISTRATION_INVALID_DID_FORMAT.ToString()); + } + + [Fact] + public async Task SaveCustomerWallet_InvalidDidMethod_ThrowException() + { + // Arrange + const string did = "did:web:123"; + var companyId = Guid.NewGuid(); + var didDocument = JsonDocument.Parse("{\"id\":\"did:error:123\"}"); + var validationResult = new DidValidationResult(new DidResolutionMetadata(null), didDocument); + var companyRepository = A.Fake(); + A.CallTo(() => _portalRepositories.GetInstance().CreateCustomerWallet(companyId, did, didDocument)) + .DoesNothing(); + A.CallTo(() => _portalRepositories.GetInstance()) + .Returns(companyRepository); + A.CallTo(() => companyRepository.IsExistingCompany(companyId)).ReturnsLazily(() => true); + A.CallTo(() => _universalDidResolverService.ValidateDid(did, A._)) + .ReturnsLazily(() => Task.FromResult(validationResult)); + A.CallTo(() => _universalDidResolverService.ValidateSchema(didDocument, A._)) + .ReturnsLazily(() => true); + + // Act + + Func act = async () => + await _sut.SaveCustomerWalletAsync(companyId, did); + + // Assert + await act.Should().ThrowAsync() + .WithMessage(RegistrationErrors.REGISTRATION_UNSUPPORTED_DID_METHOD.ToString()); + } +} diff --git a/tests/registration/Registration.Service.Tests/Controller/BringYourOwnWalletControllerTests.cs b/tests/registration/Registration.Service.Tests/Controller/BringYourOwnWalletControllerTests.cs new file mode 100644 index 0000000000..58f3b0d55f --- /dev/null +++ b/tests/registration/Registration.Service.Tests/Controller/BringYourOwnWalletControllerTests.cs @@ -0,0 +1,146 @@ +/******************************************************************************** + * Copyright (c) 2025 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 FakeItEasy; +using FluentAssertions; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Identity; +using Org.Eclipse.TractusX.Portal.Backend.Registration.Service.BusinessLogic; +using Org.Eclipse.TractusX.Portal.Backend.Registration.Service.Controllers; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.Extensions; +using System.Net; +using Xunit; + +namespace Org.Eclipse.TractusX.Portal.Backend.Registration.Service.Tests; + +public class BringYourOwnWalletControllerTests +{ + private readonly IIdentityData _identity; + private readonly BringYourOwnWalletController _controller; + private readonly IBringYourOwnWalletBusinessLogic _bringYourOwnWalletBusinessLogicFake; + + public BringYourOwnWalletControllerTests() + { + _identity = A.Fake(); + A.CallTo(() => _identity.IdentityId).Returns(Guid.NewGuid()); + A.CallTo(() => _identity.IdentityTypeId).Returns(IdentityTypeId.COMPANY_USER); + A.CallTo(() => _identity.CompanyId).Returns(Guid.NewGuid()); + _bringYourOwnWalletBusinessLogicFake = A.Fake(); + _controller = new BringYourOwnWalletController(_bringYourOwnWalletBusinessLogicFake); + _controller.AddControllerContextWithClaimAndBearer("ac-token", _identity); + } + + [Fact] + public async Task ValidateDid_ShouldBeValid() + { + // Arrange + var did = "did:web:example.com"; + A.CallTo(() => _bringYourOwnWalletBusinessLogicFake.ValidateDid(did, A._)) + .Returns(Task.FromResult(System.Text.Json.JsonDocument.Parse("{}"))); + + // Act + await _controller.validateDid(did, CancellationToken.None); + + // Assert + A.CallTo(() => _bringYourOwnWalletBusinessLogicFake.ValidateDid(did, CancellationToken.None)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task ValidateDid_WhenDidIsInvalid_ShouldThrowServiceException() + { + // Arrange + var did = "did:web:invalid"; + var exception = new ServiceException("DID validation failed", HttpStatusCode.BadRequest); + A.CallTo(() => _bringYourOwnWalletBusinessLogicFake.ValidateDid(did, A._)).Throws(exception); + + // Act + Func act = async () => await _controller.validateDid(did, CancellationToken.None); + + // Assert + await act.Should().ThrowAsync() + .WithMessage("DID validation failed"); + } + + [Fact] + public async Task SaveHolderDid_WhenDidIsInvalid_ShouldThrowServiceException() + { + // Arrange + var companyId = Guid.NewGuid(); + var did = "did:web:example.com"; + var exception = new ServiceException("DID validation failed", HttpStatusCode.BadRequest); + + A.CallTo(() => _bringYourOwnWalletBusinessLogicFake.SaveCustomerWalletAsync(companyId, did)) + .Throws(exception); + + // Act + Func act = async () => await _controller.SaveHolderDid(companyId, did); + + // Assert + await act.Should().ThrowAsync() + .WithMessage("DID validation failed"); + } + + [Fact] + public async Task SaveHolderDid_ShouldBeValid() + { + // Arrange + var companyId = Guid.NewGuid(); + var did = "did:web:example.com"; + A.CallTo(() => _bringYourOwnWalletBusinessLogicFake.ValidateDid(did, A._)) + .Returns(Task.FromResult(System.Text.Json.JsonDocument.Parse("{}"))); + + // Act + await _controller.SaveHolderDid(companyId, did); + + // Assert + A.CallTo(() => _bringYourOwnWalletBusinessLogicFake.SaveCustomerWalletAsync(companyId, did)).MustHaveHappenedOnceExactly(); + } + [Fact] + public async Task GetHolderDid_ShouldReturnDid_WhenDidExists() + { + // Arrange + var companyId = Guid.NewGuid(); + var expectedDid = "did:web:example.com"; + A.CallTo(() => _bringYourOwnWalletBusinessLogicFake.getCompanyWalletDidAsync(companyId)) + .Returns(Task.FromResult(expectedDid)); + + // Act + var result = await _controller.GetHolderDid(companyId); + + // Assert + result.Should().Be(expectedDid); + A.CallTo(() => _bringYourOwnWalletBusinessLogicFake.getCompanyWalletDidAsync(companyId)).MustHaveHappenedOnceExactly(); + } + + [Fact] + public async Task GetHolderDid_ShouldReturnNull_WhenDidDoesNotExist() + { + // Arrange + var companyId = Guid.NewGuid(); + A.CallTo(() => _bringYourOwnWalletBusinessLogicFake.getCompanyWalletDidAsync(companyId)) + .Throws(new NotFoundException("Company wallet DID not found for the given company ID.")); + + // Act + Func act = async () => await _controller.GetHolderDid(companyId); + + // Assert + await act.Should().ThrowAsync() + .WithMessage("Company wallet DID not found for the given company ID."); + } +}