diff --git a/charts/dim/README.md b/charts/dim/README.md index c0f771c..548ce5e 100644 --- a/charts/dim/README.md +++ b/charts/dim/README.md @@ -50,7 +50,6 @@ dependencies: | dim.healthChecks.liveness.path | string | `"/healthz"` | | | dim.healthChecks.readyness.path | string | `"/ready"` | | | dim.swaggerEnabled | bool | `false` | | -| dim.rootDirectoryId | string | `"00000000-0000-0000-0000-000000000000"` | | | dim.operatorId | string | `"00000000-0000-0000-0000-000000000000"` | | | migrations.name | string | `"migrations"` | | | migrations.image.name | string | `"ghcr.io/sap/ssi-dim-middle-layer_dim-migrations"` | | @@ -65,17 +64,17 @@ dependencies: | processesworker.image.tag | string | `""` | | | processesworker.imagePullPolicy | string | `"IfNotPresent"` | | | processesworker.resources | object | `{"limits":{"cpu":"45m","memory":"300M"},"requests":{"cpu":"15m","memory":"300M"}}` | We recommend to review the default resource limits as this should a conscious choice. | -| processesworker.dim.adminMail | string | `"mail@example.org"` | | -| processesworker.dim.clientIdCisCentral | string | `""` | | -| processesworker.dim.clientSecretCisCentral | string | `""` | | -| processesworker.dim.authUrl | string | `""` | | -| processesworker.subaccount.baseUrl | string | `""` | Url to the subaccount service api | -| processesworker.entitlement.baseUrl | string | `""` | Url to the entitlement service api | -| processesworker.cf.clientId | string | `""` | | -| processesworker.cf.clientSecret | string | `""` | | -| processesworker.cf.tokenAddress | string | `""` | | -| processesworker.cf.baseUrl | string | `""` | Url to the cf service api | -| processesworker.cf.grantType | string | `"client_credentials"` | | +| processesworker.dim.applicationName | string | `""` | | +| processesworker.provisioning.clientId | string | `""` | | +| processesworker.provisioning.clientSecret | string | `""` | | +| processesworker.provisioning.tokenAddress | string | `""` | | +| processesworker.provisioning.baseUrl | string | `""` | Url to the cf service api | +| processesworker.provisioning.grantType | string | `"client_credentials"` | | +| processesworker.provisioning.encryptionConfigIndex | int | `0` | | +| processesworker.provisioning.encryptionConfigs.index0.index | int | `0` | | +| processesworker.provisioning.encryptionConfigs.index0.cipherMode | string | `"CBC"` | | +| processesworker.provisioning.encryptionConfigs.index0.paddingMode | string | `"PKCS7"` | | +| processesworker.provisioning.encryptionConfigs.index0.encryptionKey | string | `""` | EncryptionKey to encrypt the provisioning client-secret. Secret-key 'provisioning-encryption-key0'. Expected format is 256 bit (64 digits) hex. | | processesworker.callback.scope | string | `"openid"` | | | processesworker.callback.grantType | string | `"client_credentials"` | | | processesworker.callback.clientId | string | `""` | Provide client-id for callback. | diff --git a/charts/dim/templates/cronjob-processes.yaml b/charts/dim/templates/cronjob-processes.yaml index 64f2588..79e2c32 100644 --- a/charts/dim/templates/cronjob-processes.yaml +++ b/charts/dim/templates/cronjob-processes.yaml @@ -67,36 +67,34 @@ spec: - name: "CONNECTIONSTRINGS__DIMDB" value: "Server={{ .Values.externalDatabase.host }};Database={{ .Values.externalDatabase.database }};Port={{ .Values.externalDatabase.port }};User Id={{ .Values.externalDatabase.username }};Password=$(DIM_PASSWORD);Ssl Mode={{ .Values.dbConnection.sslMode }};" {{- end }} - - name: "DIM__ADMINMAIL" - value: "{{ .Values.processesworker.dim.adminMail }}" - - name: "DIM__ROOTDIRECTORYID" - value: "{{ .Values.dim.rootDirectoryId }}" - - name: "DIM__CLIENTIDCISCENTRAL" - value: "{{ .Values.processesworker.dim.clientIdCisCentral }}" - - name: "DIM__CLIENTSECRETCISCENTRAL" + - name: "DIM__APPLICATIONNAME" + value: "{{ .Values.processesworker.dim.applicationName }}" + - name: "DIM__ENCRYPTIONCONFIGINDEX" + value: "{{ .Values.processesworker.provisioning.encryptionConfigIndex }}" + - name: "DIM__ENCRYPTIONCONFIGS__0__INDEX" + value: "{{ .Values.processesworker.provisioning.encryptionConfigs.index0.index }}" + - name: "DIM__ENCRYPTIONCONFIGS__0__ENCRYPTIONKEY" valueFrom: secretKeyRef: name: "{{ template "dim.secretName" . }}" - key: "client-secret-cis-central" - - name: "DIM__AUTHURL" - value: "{{ .Values.processesworker.dim.authUrl }}" - - name: "SUBACCOUNT__BASEURL" - value: "{{ .Values.processesworker.subaccount.baseUrl }}" - - name: "ENTITLEMENT__BASEURL" - value: "{{ .Values.processesworker.entitlement.baseUrl }}" - - name: "CF__CLIENTID" - value: "{{ .Values.processesworker.cf.clientId }}" - - name: "CF__CLIENTSECRET" + key: "provisioning-encryption-key0" + - name: "DIM__ENCRYPTIONCONFIGS__0__CIPHERMODE" + value: "{{ .Values.processesworker.provisioning.encryptionConfigs.index0.cipherMode }}" + - name: "DIM__ENCRYPTIONCONFIGS__0__PADDINGMODE" + value: "{{ .Values.processesworker.provisioning.encryptionConfigs.index0.paddingMode }}" + - name: "PROVISIONING__CLIENTID" + value: "{{ .Values.processesworker.provisioning.clientId }}" + - name: "PROVISIONING__CLIENTSECRET" valueFrom: secretKeyRef: name: "{{ template "dim.secretName" . }}" - key: "client-secret-cf" - - name: "CF__TOKENADDRESS" - value: "{{ .Values.processesworker.cf.tokenAddress }}" - - name: "CF__BASEURL" - value: "{{ .Values.processesworker.cf.baseUrl }}" - - name: "CF__GRANTTYPE" - value: "{{ .Values.processesworker.cf.grantType }}" + key: "client-secret-provisioning" + - name: "PROVISIONING__TOKENADDRESS" + value: "{{ .Values.processesworker.provisioning.tokenAddress }}" + - name: "PROVISIONING__BASEURL" + value: "{{ .Values.processesworker.provisioning.baseUrl }}" + - name: "PROVISIONING__GRANTTYPE" + value: "{{ .Values.processesworker.provisioning.grantType }}" - name: "CALLBACK__USERNAME" value: "empty" - name: "CALLBACK__PASSWORD" diff --git a/charts/dim/templates/deployment.yaml b/charts/dim/templates/deployment.yaml index de8d07e..cc54afe 100644 --- a/charts/dim/templates/deployment.yaml +++ b/charts/dim/templates/deployment.yaml @@ -80,10 +80,21 @@ spec: value: "{{ .Values.dim.healthChecks.liveness.path}}" - name: "SWAGGERENABLED" value: "{{ .Values.dim.swaggerEnabled }}" - - name: "DIM__ROOTDIRECTORYID" - value: "{{ .Values.dim.rootDirectoryId }}" - name: "DIM__OPERATORID" value: "{{ .Values.dim.operatorId }}" + - name: "DIM__ENCRYPTIONCONFIGINDEX" + value: "{{ .Values.processesworker.provisioning.encryptionConfigIndex }}" + - name: "DIM__ENCRYPTIONCONFIGS__0__INDEX" + value: "{{ .Values.processesworker.provisioning.encryptionConfigs.index0.index }}" + - name: "DIM__ENCRYPTIONCONFIGS__0__ENCRYPTIONKEY" + valueFrom: + secretKeyRef: + name: "{{ template "dim.secretName" . }}" + key: "provisioning-encryption-key0" + - name: "DIM__ENCRYPTIONCONFIGS__0__CIPHERMODE" + value: "{{ .Values.processesworker.provisioning.encryptionConfigs.index0.cipherMode }}" + - name: "DIM__ENCRYPTIONCONFIGS__0__PADDINGMODE" + value: "{{ .Values.processesworker.provisioning.encryptionConfigs.index0.paddingMode }}" - name: "JWTBEAREROPTIONS__METADATAADDRESS" value: "{{ .Values.idp.address }}{{ .Values.idp.jwtBearerOptions.metadataPath }}" - name: "JWTBEAREROPTIONS__REQUIREHTTPSMETADATA" @@ -94,19 +105,6 @@ spec: value: "{{ .Values.idp.address }}{{ .Values.idp.jwtBearerOptions.tokenValidationParameters.validIssuerPath }}" - name: "JWTBEAREROPTIONS__REFRESHINTERVAL" value: "{{ .Values.idp.jwtBearerOptions.refreshInterval }}" - - name: "CF__CLIENTID" - value: "{{ .Values.processesworker.cf.clientId }}" - - name: "CF__CLIENTSECRET" - valueFrom: - secretKeyRef: - name: "{{ template "dim.secretName" . }}" - key: "client-secret-cf" - - name: "CF__TOKENADDRESS" - value: "{{ .Values.processesworker.cf.tokenAddress }}" - - name: "CF__BASEURL" - value: "{{ .Values.processesworker.cf.baseUrl }}" - - name: "CF__GRANTTYPE" - value: "{{ .Values.processesworker.cf.grantType }}" ports: - name: http containerPort: {{ .Values.portContainer }} diff --git a/charts/dim/templates/secret.yaml b/charts/dim/templates/secret.yaml index 3fdece7..472091e 100644 --- a/charts/dim/templates/secret.yaml +++ b/charts/dim/templates/secret.yaml @@ -36,15 +36,17 @@ data: # use data map instead of stringData to prevent base64 encoding of already base64-encoded existing value from secret # use index function for secret keys with hyphen otherwise '$secret.data.secretKey' works too client-secret-cis-central: {{ coalesce ( .Values.processesworker.dim.clientSecretCisCentral | b64enc ) ( index $secret.data "client-secret-cis-central" ) | default ( randAlphaNum 32 ) | quote }} - client-secret-cf: {{ coalesce ( .Values.processesworker.cf.clientSecret | b64enc ) ( index $secret.data "client-secret-cf" ) | default ( randAlphaNum 32 ) | quote }} + client-secret-provisioning: {{ coalesce ( .Values.processesworker.provisioning.clientSecret | b64enc ) ( index $secret.data "client-secret-provisioning" ) | default ( randAlphaNum 32 ) | quote }} client-secret-callback: {{ coalesce ( .Values.processesworker.callback.clientSecret | b64enc ) ( index $secret.data "client-secret-callback" ) | default ( randAlphaNum 32 ) | quote }} + provisioning-encryption-key0: {{ coalesce ( .Values.processesworker.provisioning.encryptionConfigs.index0.encryptionKey | b64enc ) ( index $secret.data "provisioning-encryption-key0" ) | default ( randAlphaNum 32 ) | quote }} technicalusercreation-encryption-key0: {{ coalesce ( .Values.processesworker.technicalUserCreation.encryptionConfigs.index0.encryptionKey | b64enc ) ( index $secret.data "technicalusercreation-encryption-key0" ) | default ( randAlphaNum 32 ) | quote }} {{ else -}} stringData: # if secret doesn't exist, use provided value from values file or generate a random one client-secret-cis-central: {{ .Values.processesworker.dim.clientSecretCisCentral | default ( randAlphaNum 32 ) | quote }} - client-secret-cf: {{ .Values.processesworker.cf.clientSecret | default ( randAlphaNum 32 ) | quote }} + client-secret-provisioning: {{ .Values.processesworker.provisioning.clientSecret | default ( randAlphaNum 32 ) | quote }} client-secret-callback: {{ .Values.processesworker.callback.clientSecret | default ( randAlphaNum 32 ) | quote }} + provisioning-encryption-key0: {{ .Values.processesworker.provisioning.encryptionConfigs.index0.encryptionKey | default ( randAlphaNum 32 ) | quote }} technicalusercreation-encryption-key0: {{ .Values.processesworker.technicalUserCreation.encryptionConfigs.index0.encryptionKey | default ( randAlphaNum 32 ) | quote }} {{ end }} {{- end -}} diff --git a/charts/dim/values.yaml b/charts/dim/values.yaml index 60a68c1..195089f 100644 --- a/charts/dim/values.yaml +++ b/charts/dim/values.yaml @@ -42,7 +42,6 @@ dim: readyness: path: "/ready" swaggerEnabled: false - rootDirectoryId: "00000000-0000-0000-0000-000000000000" operatorId: "00000000-0000-0000-0000-000000000000" migrations: @@ -80,23 +79,23 @@ processesworker: cpu: 45m memory: 300M dim: - adminMail: "mail@example.org" - clientIdCisCentral: "" - clientSecretCisCentral: "" - authUrl: "" - subaccount: - # -- Url to the subaccount service api - baseUrl: "" - entitlement: - # -- Url to the entitlement service api - baseUrl: "" - cf: + applicationName: "" + provisioning: clientId: "" clientSecret: "" tokenAddress: "" # -- Url to the cf service api baseUrl: "" grantType: "client_credentials" + encryptionConfigIndex: 0 + encryptionConfigs: + index0: + index: 0 + cipherMode: "CBC" + paddingMode: "PKCS7" + # -- EncryptionKey to encrypt the provisioning client-secret. Secret-key 'provisioning-encryption-key0'. + # Expected format is 256 bit (64 digits) hex. + encryptionKey: "" callback: scope: "openid" grantType: "client_credentials" diff --git a/docs/admin/database/db-view.md b/docs/admin/database/db-view.md index 881a520..2719f8b 100644 --- a/docs/admin/database/db-view.md +++ b/docs/admin/database/db-view.md @@ -59,6 +59,8 @@ erDiagram bytea initialization_vector integer encryption_mode uuid process_id FK + uuid operation_id + uuid service_key_id } TENANTS { uuid id PK @@ -67,17 +69,18 @@ erDiagram text did_document_location bool is_issuer uuid process_id FK - uuid sub_account_id - text service_instance_id - text service_binding_name - uuid space_id - uuid dim_instance_id + uuid wallet_id + text token_address + text client_id + uuid operation_id text did_download_url text did - text application_id uuid company_id - text application_key + text base_url uuid operator_id + bytea client_secret + int encryption_mode + bytea initialization_vector } ``` @@ -105,29 +108,30 @@ label (TEXT): The label of the process step type. #### Possible Values -- `CREATE_SUBACCOUNT`: Creates the sub account in sap -- `CREATE_SERVICEMANAGER_BINDINGS`: Creates the service manager binding for the created subaccount -- `ASSIGN_ENTITLEMENTS`: Assigns the entitlements -- `CREATE_SERVICE_INSTANCE`: Creates the service instance -- `CREATE_SERVICE_BINDING`: Creates the service binding for the created service instance -- `SUBSCRIBE_APPLICATION`: Subscribes to the `decentralized-identity-management-app` application -- `CREATE_CLOUD_FOUNDRY_ENVIRONMENT`: Creates the cloud foundry environment -- `CREATE_CLOUD_FOUNDRY_SPACE`: Creates the cloud foundry space for the created environment -- `ADD_SPACE_MANAGER_ROLE`: Adds the space manager role for the created subaccount -- `ADD_SPACE_DEVELOPER_ROLE`: Adds the space developer role for the created subaccount -- `CREATE_DIM_SERVICE_INSTANCE`: Creates the dim instance -- `CREATE_SERVICE_INSTANCE_BINDING`: Creates the binding for to the created dim instance -- `GET_DIM_DETAILS`: Retrieves the dim details from SAP Dim -- `CREATE_APPLICATION`: Creates the application in the wallet -- `CREATE_COMPANY_IDENTITY`: Creates a company identity for the wallet -- `ASSIGN_COMPANY_APPLICATION`: Assigns the company identity to the application -- `CREATE_STATUS_LIST`: Creates a statuslist for a company -- `SEND_CALLBACK`: Sends the callback to the portal to transmit the data of the created wallet and did -- `CREATE_TECHNICAL_USER`: Creates a new technical user for a wallet -- `GET_TECHNICAL_USER_DATA`: Retrieves the technical user data from the SAP Dim -- `SEND_TECHNICAL_USER_CREATION_CALLBACK`: Sends the technical user data back to the portal -- `DELETE_TECHNICAL_USER`: Deletes the technical user from the database and from the SAP Dim -- `SEND_TECHNICAL_USER_DELETION_CALLBACK`: Sends a status to the portal if the deletion was successful +- `CREATE_WALLET`: Sends the wallet creation process to the SAP Dim +- `CHECK_OPERATION`: Checks the wallet creation operation to be completed +- `GET_COMPANY`: Gets the company and wallet information +- `GET_DID_DOCUMENT`: Gets the did document and the did for the wallet +- `CREATE_STATUS_LIST`: Only if the tenant is an issuer - Creates the status list +- `SEND_CALLBACK`: Sends the wallet data back to the portal backend +- `RETRIGGER_CREATE_WALLET`: Retriggers the `CREATE_WALLET` step +- `RETRIGGER_CHECK_OPERATION`: Retriggers the `CHECK_OPERATION` step +- `RETRIGGER_GET_COMPANY`: Retriggers the `GET_COMPANY` step +- `RETRIGGER_GET_DID_DOCUMENT`: Retriggers the `GET_DID_DOCUMENT` step +- `RETRIGGER_CREATE_STATUS_LIST`: Retriggers the `CREATE_STATUS_LIST` step +- `RETRIGGER_SEND_CALLBACK`: Retriggers the `SEND_CALLBACK` step +- `CREATE_TECHNICAL_USER`: Sends a technical user creation request to the SAP Dim +- `GET_TECHNICAL_USER_DATA`: Gets the technical user data (clientId, clientSecret and tokenUrl) +- `GET_TECHNICAL_USER_SERVICE_KEY`: Gets the service key id which is needed to delete the technical user later on +- `SEND_TECHNICAL_USER_CREATION_CALLBACK`: Sends all information of the technical user to the portal backend +- `RETRIGGER_CREATE_TECHNICAL_USER`: Retriggers the `CREATE_TECHNICAL_USER` step +- `RETRIGGER_GET_TECHNICAL_USER_DATA`: Retriggers the `GET_TECHNICAL_USER_DATA` step +- `RETRIGGER_GET_TECHNICAL_USER_SERVICE_KEY`: Retriggers the `GET_TECHNICAL_USER_SERVICE_KEY` step +- `RETRIGGER_SEND_TECHNICAL_USER_CREATION_CALLBACK`: Retriggers the `SEND_TECHNICAL_USER_CREATION_CALLBACK` step +- `DELETE_TECHNICAL_USER`: Deletes the technical user from the SAP Dim +- `SEND_TECHNICAL_USER_DELETION_CALLBACK`: Sends a status of whether the deletion was successful to the portal and deletes the technical user from the database +- `RETRIGGER_DELETE_TECHNICAL_USER`: Retriggers the `DELETE_TECHNICAL_USER` step +- `RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK`: Retriggers the `SEND_TECHNICAL_USER_DELETION_CALLBACK` step ### PROCESS_STEPS @@ -160,7 +164,7 @@ version (UUID): The version of the process. id (UUID): A unique identifier for the technical user. This is the primary key of the table tenant_id (UUID): A unique identifier for the tenant. This is a foreign key referencing id in the TENANT table -external_id (UUID): the id of the technical user in the dim +external_id (UUID): the id of the technical user in the DIM technical_user_name (TEXT): The name of the technical user token_address (TEXT): The address for the authentication of the technical user client_id (TEXT): The client id which is needed for authentication @@ -168,6 +172,8 @@ client_secret (BYTEA): The encrypted client secret initialization_vector (BYTEA): The used initialization vector which is needed for decrypting the secret encryption_mode (INTEGER): The used encryption mode for the secret process_id (UUID): A unique identifier for the process. This is a foreign key referencing id in the PROCESS table +operation_id (UUID): A unique identifier of the operation which is created on SAP DIM side +service_key_id (UUID): A unique identifier of the technical user on SAP DIM side ### TENANTS @@ -177,17 +183,18 @@ bpn (TEXT): Bpn of the company must be unique in combination with the name did_document_location (TEXT): The location of the did document (url) is_issuer (BOOL): Defines if the requesting tenant is an issuer process_id (UUID): A unique identifier for the process. This is a foreign key referencing id in the PROCESS table -sub_account_id (UUID): A unique identifier of the sub account in the SAP DIM -service_instance_id (TEXT): A unique identifier of the service instance id in the SAP DIM -service_binding_name (TEXT): The service binding name in the SAP DIM -space_id (UUID): A unique identifier of the space id in the SAP DIM -dim_instance_id (UUID): A unique identifier of the dim instance in the SAP DIM +operator_id (UUID): A unique identifier of the operator which is used for the wallet creation did_download_url (TEXT): The url of the did document. did (TEXT): The did of the wallet -application_id (TEXT): A unique identifier of the application in the SAP DIM +base_url (TEXT): The address of the wallet +token_address (TEXT): The address for the authentication of the wallet +client_id (TEXT): The client id which is needed for authentication +client_secret (BYTEA): The encrypted client secret +initialization_vector (BYTEA): The used initialization vector which is needed for decrypting the secret +encryption_mode (INTEGER): The used encryption mode for the secret company_id (UUID): A unique identifier of the company in the SAP DIM -application_key (TEXT): The key of the application in the SAP DIM -operator_id (UUID): A unique identifier of the operator which is used for the wallet creation +operation_id (UUID): A unique identifier of the operation which is created in the SAP DIM +wallet_id (UUID): A unique identifier of the wallet in the SAP DIM ### Enum Value Tables diff --git a/docs/admin/dev-process/Enumeration Handling.md b/docs/admin/dev-process/Enumeration Handling.md index 2207eb2..b6dd649 100644 --- a/docs/admin/dev-process/Enumeration Handling.md +++ b/docs/admin/dev-process/Enumeration Handling.md @@ -2,7 +2,7 @@ Enum or enumeration are used for data type consisting of named values like elements, status workflow, types, etc., that represent integral constants. Enums are non-transactional (so called static data) which can only get changed in a new application version. Changes in the operation mode of an application are not allowed since this will result into possible system breaks. -List of used enums in the ssi dim middle layer application +List of used enums in the SSI DIM Middle Layer application - process_step_statuses - process_step_types diff --git a/docs/admin/iam/identity-access-management.md b/docs/admin/iam/identity-access-management.md index 4d48705..0da75a1 100644 --- a/docs/admin/iam/identity-access-management.md +++ b/docs/admin/iam/identity-access-management.md @@ -2,7 +2,7 @@ ## Role concept -The endpoints of the SSI Dim Middle Layer can only be used with an authenticated and authorized user. To be able to authorize the (technical) user, he needs to have the configured audience which is set in the appsettings under `JWTBEAREROPTIONS__TOKENVALIDATIONPARAMETERS__VALIDAUDIENCE`. The configured audience **must** be added as a client in the IAM system. +The endpoints of the SSI DIM Middle Layer can only be used with an authenticated and authorized user. To be able to authorize the (technical) user, he needs to have the configured audience which is set in the appsettings under `JWTBEAREROPTIONS__TOKENVALIDATIONPARAMETERS__VALIDAUDIENCE`. The configured audience **must** be added as a client in the IAM system. The following roles need to exist in the client that is configured as the valid audience and need to be assigned to the (technical) user: diff --git a/docs/admin/known-knowns/known-issues-and-limitations.md b/docs/admin/known-knowns/known-issues-and-limitations.md index 153d012..3fc991d 100644 --- a/docs/admin/known-knowns/known-issues-and-limitations.md +++ b/docs/admin/known-knowns/known-issues-and-limitations.md @@ -1,6 +1,6 @@ # Known Issues and Limitations -- The creation of the CF Space currently is only possible by using a personal SAP account. This will likely be adjusted to a technical user in the future. For now, the configuration needs to be made with an personal user. +There are currently no known issues or limitations. ## NOTICE diff --git a/docs/admin/processes/01. create_wallet.md b/docs/admin/processes/01. create_wallet.md new file mode 100644 index 0000000..18fc5cf --- /dev/null +++ b/docs/admin/processes/01. create_wallet.md @@ -0,0 +1,149 @@ +# Create Wallet Process + +## Summary + +The create wallet process handles the creation of a wallet. The process steps are the following: + +```mermaid +flowchart TD + A((Start wallet creation process)) + B(CREATE_WALLET) + C(CHECK_OPERATION) + D(GET_COMPANY) + E(GET_DID_DOCUMENT) + F(CREATE_STATUS_LIST) + G(SEND_CALLBACK) + H((Process completed)) + I(RETRIGGER_CREATE_WALLET) + J(RETRIGGER_CHECK_OPERATION) + K(RETRIGGER_GET_COMPANY) + L(RETRIGGER_GET_DID_DOCUMENT) + M(RETRIGGER_CREATE_STATUS_LIST) + N(RETRIGGER_SEND_CALLBACK) + A--> B + B -->|Success| C + C -->|Success| D + D -->|Success| E + E -->|Success, if not issuer| G + E -->|Success, if issuer| F + F -->|Success| G + G --> H + B-->|Error| I + C-->|Error| J + D-->|Error| K + E-->|Error| L + F-->|Error| M + G-->|Error| N + I--> B + J--> C + K--> D + L--> E + M--> F + N--> G +``` + +## External dependencies + +The process worker communicates with the SAP DIM to create the wallet. It further more communicates with the portal backend to return the wallet data. + +## Process Steps + +### CREATE_WALLET + +The process step `CREATE_WALLET` is automatically triggered from the process worker. It sends a request to create the wallet to the SAP DIM. + +### CHECK_OPERATION + +The process step `CHECK_OPERATION` is automatically triggered from the process worker. It requests the status of the operation which is created in the `CREATE_WALLET` step. If the status is `completed` it takes the data of the wallet and saves it in the database. + +### GET_COMPANY + +The process step `GET_COMPANY` is automatically triggered from the process worker. It retrieves Did download url and company id and saves it to the database. + +Example did document: + +```json +{ + "@context": + [ + "https://www.w3.org/ns/did/v1", + "https://w3id.org/security/suites/jws-2020/v1", + ], + "id": "did:web:example.org:api:did:BPNL0000001TEST", + "service": + [ + { + "type": "CredentialService", + "serviceEndpoint": "https://dis-agent-prod.eu10.dim.cloud.sap/api/v1.0.0/iatp", + "id": "did:web:example.org:api:did:BPNL0000001TEST#CredentialService", + }, + ], + "verificationMethod": + [ + { + "id": "did:web:example.org:api:did:BPNL0000001TEST#keys-1c55236c-f974-4c7e-bdd3-b667d0c2e1a4", + "type": "JsonWebKey2020", + "controller": "did:web:example.org:api:did:BPNL0000001TEST", + "publicKeyJwk": + { + "kty": "EC", + "crv": "secp256k1", + "x": "kxZJ_7xWDmRm6VX11Oi7JYYzicfme53whduBhpJSM_c", + "y": "p47wM71fs0rBLM6la9eat6f1sIMtzbqOON0PG393rso", + }, + }, + { + "id": "did:web:example.org:api:did:BPNL0000001TEST#keys-2a5052ac-dac7-47b4-842a-786efe60e8cb", + "type": "JsonWebKey2020", + "controller": "did:web:example.org:api:staticdata:did:BPNL0000001TEST", + "publicKeyJwk": + { + "kty": "EC", + "crv": "secp256k1", + "x": "AUHunkMYUecQfgrJUbksH83mRpcqHdhAdMEBP-Z0lTQ", + "y": "68HOmlhB1gdfggV9AvMlbe4yfI-Ce6brV-ge6z-7shg", + }, + }, + ], + "authentication": + [ + "did:web:example.org:api:did:BPNL0000001TEST#keys-1c55236c-f974-4c7e-bdd3-b667d0c2e1a4", + ], + "assertionMethod": + [ + "did:web:example.org:api:did:BPNL0000001TEST#keys-2a5052ac-dac7-47b4-842a-786efe60e8cb", + ], + "keyAgreement": [], +} +``` + +### GET_DID_DOCUMENT + +The process step `GET_DID_DOCUMENT` is automatically triggered from the process worker. It retrieves the did and saves it to the database. + +### CREATE_STATUS_LIST + +The process step `CREATE_STATUS_LIST` is automatically triggered from the process worker if the wallet is created for the issuer. It creates a status list. + +### SEND_CALLBACK + +The process step `SEND_CALLBACK` is automatically triggered from the process worker. It sends the wallet data, did and the did document to the portal backend. + +## Retrigger + +| Step Name | Retrigger Step | Retrigger Endpoint | +| ------------------ | ---------------------------- | ------------------------------------------------------------------------------------------- | +| CREATE_WALLET | RETRIGGER_CREATE_WALLET | api/dim/process/wallet/{processId}/retrigger?processStepTypeId=RETRIGGER_CREATE_WALLET | +| CHECK_OPERATION | RETRIGGER_CHECK_OPERATION | api/dim/process/wallet/{processId}/retrigger?processStepTypeId=RETRIGGER_CHECK_OPERATION | +| GET_COMPANY | RETRIGGER_GET_COMPANY | api/dim/process/wallet/{processId}/retrigger?processStepTypeId=RETRIGGER_GET_COMPANY | +| GET_DID_DOCUMENT | RETRIGGER_GET_DID_DOCUMENT | api/dim/process/wallet/{processId}/retrigger?processStepTypeId=RETRIGGER_GET_DID_DOCUMENT | +| CREATE_STATUS_LIST | RETRIGGER_CREATE_STATUS_LIST | api/dim/process/wallet/{processId}/retrigger?processStepTypeId=RETRIGGER_CREATE_STATUS_LIST | +| SEND_CALLBACK | RETRIGGER_SEND_CALLBACK | api/dim/process/wallet/{processId}/retrigger?processStepTypeId=RETRIGGER_SEND_CALLBACK | + +## NOTICE + +This work is licensed under the [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0). + +- SPDX-License-Identifier: Apache-2.0 +- SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company, BMW Group AG and ssi-dim-middle-layer contributors +- Source URL: https://github.com/SAP/ssi-dim-middle-layer diff --git a/docs/admin/processes/02. create_technical_user.md b/docs/admin/processes/02. create_technical_user.md new file mode 100644 index 0000000..f3eff8b --- /dev/null +++ b/docs/admin/processes/02. create_technical_user.md @@ -0,0 +1,71 @@ +# Create Technical User Process + +## Summary + +The create technical user process handles the creation of a technical user. The process steps are the following: + +```mermaid +flowchart TD + A((Start technical user creation process)) + B(CREATE_TECHNICAL_USER) + C(GET_TECHNICAL_USER_DATA) + D(GET_TECHNICAL_USER_SERVICE_KEY) + E(SEND_TECHNICAL_USER_CREATION_CALLBACK) + F(RETRIGGER_CREATE_TECHNICAL_USER) + G(RETRIGGER_GET_TECHNICAL_USER_DATA) + H(RETRIGGER_GET_TECHNICAL_USER_SERVICE_KEY) + I(RETRIGGER_SEND_TECHNICAL_USER_CREATION_CALLBACK) + J((Process completed)) + A --> B + B -->|Success| C + C -->|Success| D + D -->|Success| E + E -->|Success| J + B-->|Error| F + C-->|Error| G + D-->|Error| H + E-->|Error| I + F--> B + G--> C + H--> D + I--> E +``` + +## External dependencies + +The process worker communicates with the SAP DIM to create the technical user. It further more communicates with the portal backend to return the technical user data. + +## Process Steps + +### CREATE_TECHNICAL_USER + +The process step `CREATE_TECHNICAL_USER` is automatically triggered from the process worker. It sends a request to create the technical user to the SAP DIM. + +### GET_TECHNICAL_USER_DATA + +The process step `GET_TECHNICAL_USER_DATA` is automatically triggered from the process worker. It requests the status of the operation which is created in the `CREATE_TECHNICAL_USER` step. If the status is `completed` it takes the data of the technical user and saves it in the database. + +### GET_TECHNICAL_USER_SERVICE_KEY + +The process step `GET_TECHNICAL_USER_SERVICE_KEY` is automatically triggered from the process worker. It retrieves the service key id which is the unique identifier of the technical user in the SAP DIM. + +### SEND_TECHNICAL_USER_CREATION_CALLBACK + +The process step `SEND_TECHNICAL_USER_CREATION_CALLBACK` is automatically triggered from the process worker. It sends the technical user details to the portal backend. + +## Retrigger + +| Step Name | Retrigger Step | Retrigger Endpoint | +| ------------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| CREATE_TECHNICAL_USER | RETRIGGER_CREATE_TECHNICAL_USER | api/dim/process/technicalUser/{processId}/retrigger?processStepTypeId=RETRIGGER_CREATE_TECHNICAL_USER | +| GET_TECHNICAL_USER_DATA | RETRIGGER_GET_TECHNICAL_USER_DATA | api/dim/process/technicalUser/{processId}/retrigger?processStepTypeId=RETRIGGER_GET_TECHNICAL_USER_DATA | +| GET_TECHNICAL_USER_SERVICE_KEY | RETRIGGER_GET_TECHNICAL_USER_SERVICE_KEY | api/dim/process/technicalUser/{processId}/retrigger?processStepTypeId=RETRIGGER_GET_TECHNICAL_USER_SERVICE_KEY | +| SEND_TECHNICAL_USER_CREATION_CALLBACK | RETRIGGER_SEND_TECHNICAL_USER_CREATION_CALLBACK | api/dim/process/technicalUser/{processId}/retrigger?processStepTypeId=RETRIGGER_SEND_TECHNICAL_USER_CREATION_CALLBACK | + +## NOTICE + +This work is licensed under the [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0). + +- SPDX-License-Identifier: Apache-2.0 +- SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company, BMW Group AG and ssi-dim-middle-layer contributors +- Source URL: https://github.com/SAP/ssi-dim-middle-layer diff --git a/docs/admin/processes/03. delete_technical_user.md b/docs/admin/processes/03. delete_technical_user.md new file mode 100644 index 0000000..59783f1 --- /dev/null +++ b/docs/admin/processes/03. delete_technical_user.md @@ -0,0 +1,51 @@ +# Delete Technical User Process + +## Summary + +The delete technical user process handles the deletion of an technical user. The process steps are the following: + +```mermaid +flowchart TD + A((Start technical user deletion process)) + B(DELETE_TECHNICAL_USER) + C(SEND_TECHNICAL_USER_DELETION_CALLBACK) + D(RETRIGGER_DELETE_TECHNICAL_USER) + E(RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK) + F((Process completed)) + A --> B + B -->|Success| C + C -->|Success| F + B-->|Error| D(RETRIGGER_DELETE_TECHNICAL_USER) + C-->|Error| E(RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK) + D--> B + E--> C +``` + +## External dependencies + +The process worker communicates with the SAP DIM to delete the technical user. It further more communicates with the portal backend to return the status of the deletion. + +## Process Steps + +### DELETE_TECHNICAL_USER + +The process step `DELETE_TECHNICAL_USER` is automatically triggered from the process worker. It sends a request to delete the technical user from the SAP DIM. + +### SEND_TECHNICAL_USER_DELETION_CALLBACK + +The process step `SEND_TECHNICAL_USER_DELETION_CALLBACK` is automatically triggered from the process worker. It deletes the technical user from the DIM database and sends a status update to the portal backend. + +## Retrigger + +| Step Name | Retrigger Step | Retrigger Endpoint | +| ------------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | +| DELETE_TECHNICAL_USER | RETRIGGER_DELETE_TECHNICAL_USER | api/dim/process/technicalUser/{processId}/retrigger?processStepTypeId=RETRIGGER_DELETE_TECHNICAL_USER | +| SEND_TECHNICAL_USER_DELETION_CALLBACK | RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK | api/dim/process/technicalUser/{processId}/retrigger?processStepTypeId=RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK | + +## NOTICE + +This work is licensed under the [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0). + +- SPDX-License-Identifier: Apache-2.0 +- SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company, BMW Group AG and ssi-dim-middle-layer contributors +- Source URL: https://github.com/SAP/ssi-dim-middle-layer diff --git a/docs/admin/processes/index.md b/docs/admin/processes/index.md new file mode 100644 index 0000000..9dd85ab --- /dev/null +++ b/docs/admin/processes/index.md @@ -0,0 +1,27 @@ +# Summary + +The main process worker project is the `Processes.Worker` which runs all the processes. It therefor looks up `process_steps` in status `TODO` and their respective `processes` and executes those. + +## Processes + +The process worker supports the following processes: + +- [CreateWallet](../processes/01.%20create_wallet.md) - handles the creation of wallets +- [CreateTechnicalUser](../processes/02.%20create_technical_user.md) - handles the creation of technical user +- [DeleteTechnicalUser](../processes/03.%20delete_technical_user.md) - handles the deletion of technical user + +## Retriggering + +The process has a logic to retrigger failing steps. For this a retrigger step is created which can be triggered via an api call to retrigger the step. This logic is implemented separately for each process. In general the retriggering of a step is possible if for example external services are not available. The retrigger logic for each process can be found in the process file. + +## Configuration + +The configuration of the process worker such as the interval of execution or encryption configuration is made via the helm chart. + +## NOTICE + +This work is licensed under the [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0). + +- SPDX-License-Identifier: Apache-2.0 +- SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company, BMW Group AG and ssi-dim-middle-layer contributors +- Source URL: https://github.com/SAP/ssi-dim-middle-layer diff --git a/docs/architecture/Architecture Constraints.md b/docs/architecture/Architecture Constraints.md index 138bd6d..5ec96b1 100644 --- a/docs/architecture/Architecture Constraints.md +++ b/docs/architecture/Architecture Constraints.md @@ -2,7 +2,7 @@ ## Overview -The following document outlines the architecture constraints for the SSI Dim Middle Layer App. This App serves as a central point for wallet as well as technical user creation. The constraints outlined in this document are intended to guide the development and deployment of the system to ensure it meets the specified requirements and adheres to the defined standards. +The following document outlines the architecture constraints for the SSI DIM Middle Layer App. This App serves as a central point for wallet as well as technical user creation. The constraints outlined in this document are intended to guide the development and deployment of the system to ensure it meets the specified requirements and adheres to the defined standards. ## General Constraints diff --git a/docs/architecture/Context and scope.md b/docs/architecture/Context and scope.md index a2b5940..4564670 100644 --- a/docs/architecture/Context and scope.md +++ b/docs/architecture/Context and scope.md @@ -6,9 +6,9 @@ The Self-Sovereign Identity (SSI) DIM Middle Layer purpose is to facilitate seam ## Technical Context -From a technical standpoint, the SSI dim middle layer is built on a foundation that promotes interaction, monitoring and a host of other functionalities that are crucial for maintaining a secure and reliable identity management system. +From a technical standpoint, the SSI DIM middle layer is built on a foundation that promotes interaction, monitoring and a host of other functionalities that are crucial for maintaining a secure and reliable identity management system. -A key aspect of the technical context is the commitment to open-source principles. The SSI dim middle layer is constructed with open-source components to the greatest extent possible, fostering a collaborative and transparent development environment. Moreover, the entire codebase of the SSI dim middle layer is open-sourced, reflecting a 100% commitment to the open-source community. +A key aspect of the technical context is the commitment to open-source principles. The SSI DIM middle layer is constructed with open-source components to the greatest extent possible, fostering a collaborative and transparent development environment. Moreover, the entire codebase of the SSI DIM middle layer is open-sourced, reflecting a 100% commitment to the open-source community. ## NOTICE diff --git a/docs/architecture/Development Concept.md b/docs/architecture/Development Concept.md index 8d3e710..cb8bd0e 100644 --- a/docs/architecture/Development Concept.md +++ b/docs/architecture/Development Concept.md @@ -6,7 +6,7 @@ Details to the build, test and deploy process can get found under the following ## Development Guidelines -The SSI dim middle layer is using following key frameworks: +The SSI DIM middle layer is using following key frameworks: - .Net - Entity Framework @@ -127,12 +127,12 @@ An API manager or gateway tool will handle or help address the API security guid ## Migration -To run the SSI dim middle layer, migrations are needed to load the initial data inside the SSI dim middle layer db to enable the SSI dim middle layer to work. +To run the SSI DIM middle layer, migrations are needed to load the initial data inside the SSI DIM middle layer db to enable the SSI DIM middle layer to work. The migration will consist of an initial migration as well as delta migration files with future releases. As part of a new release, a migration file (if applicable) will get released and can get loaded via a delta load. ## Configurability -SSI Dim Middle Layer configuration is mainly possible via the appsettings files as well as the static data migration files. +SSI DIM Middle Layer configuration is mainly possible via the appsettings files as well as the static data migration files. ## NOTICE diff --git a/docs/architecture/Operational Concept.md b/docs/architecture/Operational Concept.md index a48e9b9..19740fa 100644 --- a/docs/architecture/Operational Concept.md +++ b/docs/architecture/Operational Concept.md @@ -1,18 +1,18 @@ # Operational concepts -## SSI Dim Middle Layer Services +## SSI DIM Middle Layer Services ### Configuration -The SSI Dim Middle Layer services can be configured using two methods: +The SSI DIM Middle Layer services can be configured using two methods: ### appsettings.json -If you build the SSI Dim Middle Layer, you can modify the appsettings.json for each backend service, to individually configure to a certain extend. This file contains all possible config entries for the application. +If you build the SSI DIM Middle Layer, you can modify the appsettings.json for each backend service, to individually configure to a certain extend. This file contains all possible config entries for the application. ### Helm Chart -The most relevant config properties are exposed as environment variables and must be set in the Helm chart so the application can run at all. Check the SSI Dim Middle Layer Helm chart in Git for all available variables. +The most relevant config properties are exposed as environment variables and must be set in the Helm chart so the application can run at all. Check the SSI DIM Middle Layer Helm chart in Git for all available variables. ### DB Migration File diff --git a/docs/architecture/Whitebox Overall System.md b/docs/architecture/Whitebox Overall System.md index fe6a002..43ef8f3 100644 --- a/docs/architecture/Whitebox Overall System.md +++ b/docs/architecture/Whitebox Overall System.md @@ -2,7 +2,7 @@ ## Summary -In the following image you see the overall system overview of the SSI Dim Middle Layer +In the following image you see the overall system overview of the SSI DIM Middle Layer ```mermaid flowchart LR @@ -15,7 +15,7 @@ flowchart LR SD(SAP DIM) PHD[("Postgres Database \n \n (Base data created with \n application seeding)")] - subgraph SSI Dim Middle Layer Product + subgraph SSI DIM Middle Layer Product ING PHD DS diff --git a/environments/argocd-app-templates/appsetup-int.yaml b/environments/argocd-app-templates/appsetup-int.yaml index 6b31dc6..c862deb 100644 --- a/environments/argocd-app-templates/appsetup-int.yaml +++ b/environments/argocd-app-templates/appsetup-int.yaml @@ -21,7 +21,7 @@ apiVersion: argoproj.io/v1alpha1 kind: Application metadata: - name: dim-middle-layer + name: dim-middle-layer-v2 spec: destination: namespace: product-portal @@ -29,7 +29,7 @@ spec: source: path: charts/dim repoURL: 'https://github.com/SAP/ssi-dim-middle-layer.git' - targetRevision: dim-1.2.1 + targetRevision: feature/79-div-provisioning plugin: env: - name: AVP_SECRET diff --git a/environments/helm-values/values-int.yaml b/environments/helm-values/values-int.yaml index 1bf1a6b..fa7307d 100644 --- a/environments/helm-values/values-int.yaml +++ b/environments/helm-values/values-int.yaml @@ -28,11 +28,11 @@ ingress: nginx.ingress.kubernetes.io/proxy-body-size: "8m" nginx.ingress.kubernetes.io/cors-allow-origin: "https://*.int.catena-x.net" tls: - - secretName: "dim.int.catena-x.net-tls" + - secretName: "dim-v2.int.catena-x.net-tls" hosts: - - "dim.int.catena-x.net" + - "dim-v2.int.catena-x.net" hosts: - - host: "dim.int.catena-x.net" + - host: "dim-v2.int.catena-x.net" paths: - path: "/api/dim" pathType: "Prefix" @@ -41,7 +41,6 @@ ingress: dim: swaggerEnabled: true - rootDirectoryId: "d6cd4e2e-1053-4ba6-888e-1cd56509958a" operatorId: "d6cd4e2e-1053-4ba6-888e-1cd56509958a" migrations: @@ -52,23 +51,16 @@ processesworker: logging: default: "Debug" dim: - adminMail: "" - clientIdCisCentral: "" - clientSecretCisCentral: "" - authUrl: "https://catena-x-int-div.authentication.eu10.hana.ondemand.com" - subaccount: - # -- Url to the subaccount service api - baseUrl: "https://accounts-service.cfapps.eu10.hana.ondemand.com" - entitlement: - # -- Url to the entitlement service api - baseUrl: "https://entitlements-service.cfapps.eu10.hana.ondemand.com" - cf: - clientId: "" - clientSecret: "" - tokenAddress: "https://login.cf.eu10.hana.ondemand.com/oauth/token" - # -- Url to the cf service api - baseUrl: "https://api.cf.eu10.hana.ondemand.com" + applicationName: "catena-x-portal" + provisioning: + clientId: "" + clientSecret: "" + tokenAddress: "https://provisioning-prod-uvm2enuw.authentication.eu10.hana.ondemand.com/oauth/token" + baseUrl: "https://div-provisioning-prod.eu10.div.cloud.sap" grantType: "client_credentials" + encryptionConfigs: + index0: + encryptionKey: "" callback: scope: "openid" grantType: "client_credentials" diff --git a/src/Dim.sln b/src/Dim.sln index ee2abba..f52c3a4 100644 --- a/src/Dim.sln +++ b/src/Dim.sln @@ -42,6 +42,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DimProcess.Library.Tests", EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dim.Web.Tests", "..\tests\web\Dim.Web.Tests\Dim.Web.Tests.csproj", "{CE87E424-36CF-4597-9E08-2D687E67F259}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dim.DbAccess.Tests", "..\tests\database\Dim.DbAccess.Tests\Dim.DbAccess.Tests.csproj", "{5A8971D7-D0FB-4886-8982-48DC312F6262}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dim.Clients.Tests", "..\tests\clients\Dim.Clients.Tests\Dim.Clients.Tests.csproj", "{256D56CC-5A19-45D5-8E98-06419ADBC275}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -112,6 +116,14 @@ Global {CE87E424-36CF-4597-9E08-2D687E67F259}.Debug|Any CPU.Build.0 = Debug|Any CPU {CE87E424-36CF-4597-9E08-2D687E67F259}.Release|Any CPU.ActiveCfg = Release|Any CPU {CE87E424-36CF-4597-9E08-2D687E67F259}.Release|Any CPU.Build.0 = Release|Any CPU + {5A8971D7-D0FB-4886-8982-48DC312F6262}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A8971D7-D0FB-4886-8982-48DC312F6262}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A8971D7-D0FB-4886-8982-48DC312F6262}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A8971D7-D0FB-4886-8982-48DC312F6262}.Release|Any CPU.Build.0 = Release|Any CPU + {256D56CC-5A19-45D5-8E98-06419ADBC275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {256D56CC-5A19-45D5-8E98-06419ADBC275}.Debug|Any CPU.Build.0 = Debug|Any CPU + {256D56CC-5A19-45D5-8E98-06419ADBC275}.Release|Any CPU.ActiveCfg = Release|Any CPU + {256D56CC-5A19-45D5-8E98-06419ADBC275}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {8356C7AF-6F88-4A62-B3E9-5656634A6FEA} = {B84A3CAB-AC86-4B2D-A490-79E1002350FF} @@ -130,5 +142,7 @@ Global {A44447B0-794D-451A-A571-E3B761174B48} = {CB1B7D43-9AFC-47EF-8915-A547F7F553AB} {85D316A0-17BE-4983-AB06-5C72365ABD9B} = {CB1B7D43-9AFC-47EF-8915-A547F7F553AB} {CE87E424-36CF-4597-9E08-2D687E67F259} = {CB1B7D43-9AFC-47EF-8915-A547F7F553AB} + {5A8971D7-D0FB-4886-8982-48DC312F6262} = {CB1B7D43-9AFC-47EF-8915-A547F7F553AB} + {256D56CC-5A19-45D5-8E98-06419ADBC275} = {CB1B7D43-9AFC-47EF-8915-A547F7F553AB} EndGlobalSection EndGlobal diff --git a/src/clients/Dim.Clients/Api/Cf/CfClient.cs b/src/clients/Dim.Clients/Api/Cf/CfClient.cs deleted file mode 100644 index 956bf51..0000000 --- a/src/clients/Dim.Clients/Api/Cf/CfClient.cs +++ /dev/null @@ -1,287 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Api.Cf.DependencyInjection; -using Dim.Clients.Extensions; -using Dim.Clients.Token; -using Microsoft.Extensions.Options; -using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; -using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; -using System.Net.Http.Json; -using System.Text.Json; -using System.Web; - -namespace Dim.Clients.Api.Cf; - -public class CfClient : ICfClient -{ - private readonly CfSettings _settings; - private readonly IBasicAuthTokenService _basicAuthTokenService; - - public CfClient(IBasicAuthTokenService basicAuthTokenService, IOptions settings) - { - _basicAuthTokenService = basicAuthTokenService; - _settings = settings.Value; - } - - public async Task CreateCloudFoundrySpace(string tenantName, CancellationToken cancellationToken) - { - var client = await _basicAuthTokenService.GetBasicAuthorizedLegacyClient(_settings, cancellationToken).ConfigureAwait(false); - var cfEnvironmentId = await GetEnvironmentId(tenantName, cancellationToken, client).ConfigureAwait(false); - var data = new CreateSpaceRequest( - $"{tenantName}-space", - new SpaceRelationship(new SpaceOrganization(new SpaceRelationshipData(cfEnvironmentId))) - ); - - var result = await client.PostAsJsonAsync("/v3/spaces", data, JsonSerializerExtensions.Options, cancellationToken) - .CatchingIntoServiceExceptionFor("create-cfe", HttpAsyncResponseMessageExtension.RecoverOptions.ALLWAYS).ConfigureAwait(false); - try - { - var response = await result.Content - .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); - - if (response == null) - { - throw new ServiceException("Response must not be null"); - } - - return response.Id; - } - catch (JsonException je) - { - throw new ServiceException(je.Message); - } - } - - private static async Task GetEnvironmentId(string tenantName, CancellationToken cancellationToken, HttpClient client) - { - var environmentsResponse = await client.GetAsync($"/v3/organizations?names={HttpUtility.UrlEncode(tenantName)}", cancellationToken) - .CatchingIntoServiceExceptionFor("get-organizations", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE); - var environments = await environmentsResponse.Content - .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); - - var tenantEnvironment = environments?.Resources.Where(x => x.Name == tenantName); - if (tenantEnvironment == null || tenantEnvironment.Count() != 1) - { - throw new ServiceException($"There should only be one cf environment for tenant {tenantName}", true); - } - - return tenantEnvironment.Single().EnvironmentId; - } - - public async Task AddSpaceRoleToUser(string type, string user, Guid spaceId, CancellationToken cancellationToken) - { - var client = await _basicAuthTokenService.GetBasicAuthorizedLegacyClient(_settings, cancellationToken).ConfigureAwait(false); - var data = new AddSpaceRoleToUserRequest( - type, - new SpaceRoleRelationship( - new RelationshipUser(new UserData(user, "sap.ids")), - new SpaceRoleSpace(new SpaceRoleData(spaceId)) - ) - ); - - await client.PostAsJsonAsync("/v3/roles", data, JsonSerializerExtensions.Options, cancellationToken) - .CatchingIntoServiceExceptionFor("add-space-roles", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE).ConfigureAwait(false); - } - - public async Task GetServicePlan(string servicePlanName, string servicePlanType, CancellationToken cancellationToken) - { - var client = await _basicAuthTokenService.GetBasicAuthorizedLegacyClient(_settings, cancellationToken).ConfigureAwait(false); - var result = await client.GetAsync("/v3/service_plans", cancellationToken) - .CatchingIntoServiceExceptionFor("get-service-plan", HttpAsyncResponseMessageExtension.RecoverOptions.ALLWAYS).ConfigureAwait(false); - try - { - var response = await result.Content - .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); - - if (response == null) - { - throw new ServiceException("response should never be null here"); - } - - var servicePlans = response.Resources.Where(x => x.Name == servicePlanType && - x.BrokerCatalog?.BrokerCatalogMetadata?.AutoSubscription?.AppName == servicePlanName); - if (servicePlans.Count() != 1) - { - throw new ServiceException($"There must be exactly one service plan with name {servicePlanName} and type {servicePlanType}", isRecoverable: !servicePlans.Any()); - } - - return servicePlans.Single().Id; - } - catch (JsonException je) - { - throw new ServiceException(je.Message); - } - } - - public async Task GetSpace(string tenantName, CancellationToken cancellationToken) - { - var spaceName = $"{tenantName}-space"; - var client = await _basicAuthTokenService.GetBasicAuthorizedLegacyClient(_settings, cancellationToken).ConfigureAwait(false); - var result = await client.GetAsync($"/v3/spaces?names={HttpUtility.UrlEncode(spaceName)}", cancellationToken) - .CatchingIntoServiceExceptionFor("get-space", HttpAsyncResponseMessageExtension.RecoverOptions.ALLWAYS).ConfigureAwait(false); - try - { - var response = await result.Content - .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); - - if (response == null) - { - throw new ServiceException("response should never be null here"); - } - - var spaces = response.Resources.Where(x => x.Name == spaceName); - if (spaces.Count() != 1) - { - throw new ServiceException($"There must be exactly one space with name {spaceName}", isRecoverable: !spaces.Any()); - } - - return spaces.Single().Id; - } - catch (JsonException je) - { - throw new ServiceException(je.Message); - } - } - - public async Task CreateDimServiceInstance(string tenantName, Guid spaceId, Guid servicePlanId, CancellationToken cancellationToken) - { - var client = await _basicAuthTokenService.GetBasicAuthorizedLegacyClient(_settings, cancellationToken).ConfigureAwait(false); - var data = new CreateDimServiceInstance( - "managed", - $"{tenantName}-dim-instance", - new DimRelationships( - new DimSpace(new DimData(spaceId)), - new DimServicePlan(new DimData(servicePlanId))) - ); - - await client.PostAsJsonAsync("/v3/service_instances", data, JsonSerializerExtensions.Options, cancellationToken) - .CatchingIntoServiceExceptionFor("create-dim-si", HttpAsyncResponseMessageExtension.RecoverOptions.ALLWAYS).ConfigureAwait(false); - } - - private async Task GetServiceInstances(string tenantName, Guid? spaceId, CancellationToken cancellationToken) - { - var name = $"{tenantName}-dim-instance"; - var client = await _basicAuthTokenService.GetBasicAuthorizedLegacyClient(_settings, cancellationToken).ConfigureAwait(false); - var result = await client.GetAsync($"/v3/service_instances?names={HttpUtility.UrlEncode(name)}", cancellationToken) - .CatchingIntoServiceExceptionFor("get-si", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE).ConfigureAwait(false); - try - { - var response = await result.Content - .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); - if (response == null) - { - throw new ServiceException("Response must not be null"); - } - - var resources = response.Resources.Where(x => x.Name == name && x.Type == "managed" && (spaceId == null || x.Relationships.Space.Data.Id == spaceId.Value) && x.LastOperation.State == "succeeded"); - if (resources.Count() != 1) - { - throw new ServiceException("There must be exactly one service instance", isRecoverable: !resources.Any()); - } - - return resources.Single().Id; - } - catch (JsonException je) - { - throw new ServiceException(je.Message); - } - } - - public async Task CreateServiceInstanceBindings(string tenantName, string? keyName, Guid spaceId, CancellationToken cancellationToken) - { - var serviceInstanceId = await GetServiceInstances(tenantName, spaceId, cancellationToken).ConfigureAwait(false); - var client = await _basicAuthTokenService.GetBasicAuthorizedLegacyClient(_settings, cancellationToken).ConfigureAwait(false); - var data = new CreateServiceCredentialBindingRequest( - "key", - $"{keyName ?? tenantName}-dim-key01", - new ServiceCredentialRelationships( - new DimServiceInstance(new DimData(serviceInstanceId))) - ); - await client.PostAsJsonAsync("/v3/service_credential_bindings", data, JsonSerializerOptions.Default, cancellationToken) - .CatchingIntoServiceExceptionFor("create-si-bindings", HttpAsyncResponseMessageExtension.RecoverOptions.ALLWAYS); - } - - public async Task GetServiceBinding(string tenantName, Guid spaceId, string bindingName, CancellationToken cancellationToken) - { - var client = await _basicAuthTokenService.GetBasicAuthorizedLegacyClient(_settings, cancellationToken).ConfigureAwait(false); - var serviceInstanceId = await GetServiceInstances(tenantName, spaceId, cancellationToken).ConfigureAwait(false); - var result = await client.GetAsync($"/v3/service_credential_bindings?names={HttpUtility.UrlEncode(bindingName)}", cancellationToken) - .CatchingIntoServiceExceptionFor("get-credential-binding", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE).ConfigureAwait(false); - try - { - var response = await result.Content - .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); - if (response == null) - { - throw new ServiceException("Response must not be null"); - } - - var resources = response.Resources.Where(x => x.Relationships.ServiceInstance.Data.Id == serviceInstanceId); - if (resources.Count() != 1) - { - throw new ServiceException("There must be exactly one service credential binding", isRecoverable: !resources.Any()); - } - - return resources.Single().Id; - } - catch (JsonException je) - { - throw new ServiceException(je.Message); - } - } - - public async Task GetServiceBindingDetails(Guid id, CancellationToken cancellationToken) - { - var client = await _basicAuthTokenService.GetBasicAuthorizedLegacyClient(_settings, cancellationToken).ConfigureAwait(false); - var result = await client.GetAsync($"/v3/service_credential_bindings/{id}/details", cancellationToken) - .CatchingIntoServiceExceptionFor("get-credential-binding-name", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE).ConfigureAwait(false); - try - { - var response = await result.Content - .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); - - if (response == null) - { - throw new ServiceException("There must be exactly one service instance", isRecoverable: true); - } - - return response; - } - catch (JsonException je) - { - throw new ServiceException(je.Message); - } - } - - public async Task DeleteServiceInstanceBindings(Guid serviceBindingId, CancellationToken cancellationToken) - { - var client = await _basicAuthTokenService.GetBasicAuthorizedLegacyClient(_settings, cancellationToken).ConfigureAwait(false); - await client.DeleteAsync($"/v3/service_credential_bindings/{serviceBindingId}", cancellationToken) - .CatchingIntoServiceExceptionFor("delete-si-bindings", HttpAsyncResponseMessageExtension.RecoverOptions.ALLWAYS); - } -} diff --git a/src/clients/Dim.Clients/Api/Cf/DependencyInjection/CfClientServiceExtensions.cs b/src/clients/Dim.Clients/Api/Cf/DependencyInjection/CfClientServiceExtensions.cs deleted file mode 100644 index dba6419..0000000 --- a/src/clients/Dim.Clients/Api/Cf/DependencyInjection/CfClientServiceExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Extensions; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace Dim.Clients.Api.Cf.DependencyInjection; - -public static class CfClientServiceExtensions -{ - public static IServiceCollection AddCfClient(this IServiceCollection services, IConfigurationSection section) - { - services.AddOptions() - .Bind(section) - .ValidateOnStart(); - - var sp = services.BuildServiceProvider(); - var settings = sp.GetRequiredService>(); - services - .AddCustomHttpClientWithAuthentication(settings.Value.BaseUrl, settings.Value.TokenAddress) - .AddTransient(); - - return services; - } -} diff --git a/src/clients/Dim.Clients/Api/Cf/DependencyInjection/CfSettings.cs b/src/clients/Dim.Clients/Api/Cf/DependencyInjection/CfSettings.cs deleted file mode 100644 index ebe728d..0000000 --- a/src/clients/Dim.Clients/Api/Cf/DependencyInjection/CfSettings.cs +++ /dev/null @@ -1,30 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Token; -using System.ComponentModel.DataAnnotations; - -namespace Dim.Clients.Api.Cf.DependencyInjection; - -public class CfSettings : BasicAuthSettings -{ - [Required] - public string BaseUrl { get; set; } = null!; -} diff --git a/src/clients/Dim.Clients/Api/Cf/ICfClient.cs b/src/clients/Dim.Clients/Api/Cf/ICfClient.cs deleted file mode 100644 index 1e34fc6..0000000 --- a/src/clients/Dim.Clients/Api/Cf/ICfClient.cs +++ /dev/null @@ -1,34 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Api.Cf; - -public interface ICfClient -{ - Task CreateCloudFoundrySpace(string tenantName, CancellationToken cancellationToken); - Task AddSpaceRoleToUser(string type, string user, Guid spaceId, CancellationToken cancellationToken); - Task GetServicePlan(string servicePlanName, string servicePlanType, CancellationToken cancellationToken); - Task GetSpace(string tenantName, CancellationToken cancellationToken); - Task CreateDimServiceInstance(string tenantName, Guid spaceId, Guid servicePlanId, CancellationToken cancellationToken); - Task CreateServiceInstanceBindings(string tenantName, string? keyName, Guid spaceId, CancellationToken cancellationToken); - Task GetServiceBinding(string tenantName, Guid spaceId, string bindingName, CancellationToken cancellationToken); - Task GetServiceBindingDetails(Guid id, CancellationToken cancellationToken); - Task DeleteServiceInstanceBindings(Guid serviceBindingId, CancellationToken cancellationToken); -} diff --git a/src/clients/Dim.Clients/Api/Cf/ServicePlanResponse.cs b/src/clients/Dim.Clients/Api/Cf/ServicePlanResponse.cs deleted file mode 100644 index 956834b..0000000 --- a/src/clients/Dim.Clients/Api/Cf/ServicePlanResponse.cs +++ /dev/null @@ -1,140 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -using System.Text.Json.Serialization; - -namespace Dim.Clients.Api.Cf; - -public record ServicePlanResponse( - [property: JsonPropertyName("resources")] IEnumerable Resources -); - -public record ServicePlanResources( - [property: JsonPropertyName("guid")] Guid Id, - [property: JsonPropertyName("name")] string Name, - [property: JsonPropertyName("broker_catalog")] BrokerCatalog? BrokerCatalog -); - -public record BrokerCatalog( - [property: JsonPropertyName("id")] string Id, - [property: JsonPropertyName("metadata")] BrokerCatalogMetadata? BrokerCatalogMetadata -); - -public record BrokerCatalogMetadata( - [property: JsonPropertyName("auto_subscription")] AutoSupscription? AutoSubscription -); - -public record AutoSupscription( - [property: JsonPropertyName("app_name")] string? AppName -); - -public record ServiceInstanceResponse( - [property: JsonPropertyName("resources")] IEnumerable Resources -); - -public record ServiceInstanceResource( - [property: JsonPropertyName("guid")] Guid Id, - [property: JsonPropertyName("name")] string Name, - [property: JsonPropertyName("type")] string Type, - [property: JsonPropertyName("relationships")] ServiceInstanceRelationship Relationships, - [property: JsonPropertyName("last_operation")] LastOperation LastOperation -); - -public record ServiceInstanceRelationship( - [property: JsonPropertyName("space")] ServiceInstanceRelationshipSpace Space -); - -public record LastOperation( - [property: JsonPropertyName("state")] string State -); - -public record ServiceInstanceRelationshipSpace( - [property: JsonPropertyName("data")] DimData Data -); - -public record CreateServiceCredentialBindingRequest( - [property: JsonPropertyName("type")] string Type, - [property: JsonPropertyName("name")] string Name, - [property: JsonPropertyName("relationships")] ServiceCredentialRelationships Relationships -); - -public record ServiceCredentialRelationships( - [property: JsonPropertyName("service_instance")] DimServiceInstance ServiceInstance -); - -public record DimServiceInstance( - [property: JsonPropertyName("data")] DimData Data -); - -public record CreateDimServiceInstance( - [property: JsonPropertyName("type")] string Type, - [property: JsonPropertyName("name")] string Name, - [property: JsonPropertyName("relationships")] DimRelationships Relationships -); - -public record DimRelationships( - [property: JsonPropertyName("space")] DimSpace Space, - [property: JsonPropertyName("service_plan")] DimServicePlan ServicePlan -); - -public record DimServicePlan( - [property: JsonPropertyName("data")] DimData Data -); - -public record DimSpace( - [property: JsonPropertyName("data")] DimData Data -); - -public record DimData( - [property: JsonPropertyName("guid")] Guid Id -); - -public record ServiceCredentialBindingResponse( - [property: JsonPropertyName("resources")] IEnumerable Resources -); - -public record ServiceCredentialBindingRelationships( - [property: JsonPropertyName("service_instance")] ScbServiceInstnace ServiceInstance -); - -public record ScbServiceInstnace( - [property: JsonPropertyName("data")] DimData Data -); - -public record ServiceCredentialBindingResource( - [property: JsonPropertyName("guid")] Guid Id, - [property: JsonPropertyName("relationships")] ServiceCredentialBindingRelationships Relationships -); - -public record ServiceCredentialBindingDetailResponse( - [property: JsonPropertyName("credentials")] Credentials Credentials -); - -public record Credentials( - [property: JsonPropertyName("url")] string Url, - [property: JsonPropertyName("uaa")] Uaa Uaa -); - -public record Uaa( - [property: JsonPropertyName("clientid")] string ClientId, - [property: JsonPropertyName("clientsecret")] string ClientSecret, - [property: JsonPropertyName("url")] string Url, - [property: JsonPropertyName("apiurl")] string ApiUrl -); diff --git a/src/clients/Dim.Clients/Api/Cf/SpaceResponse.cs b/src/clients/Dim.Clients/Api/Cf/SpaceResponse.cs deleted file mode 100644 index 21f9234..0000000 --- a/src/clients/Dim.Clients/Api/Cf/SpaceResponse.cs +++ /dev/null @@ -1,31 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 Contributors to the Eclipse Foundation - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -using System.Text.Json.Serialization; - -namespace Dim.Clients.Api.Cf; - -public record SpaceResponse( - [property: JsonPropertyName("resources")] IEnumerable Resources -); - -public record Space( - [property: JsonPropertyName("guid")] Guid Id, - [property: JsonPropertyName("name")] string Name -); diff --git a/src/clients/Dim.Clients/Api/Dim/ApplicationResponse.cs b/src/clients/Dim.Clients/Api/Dim/ApplicationResponse.cs deleted file mode 100644 index 9db87fc..0000000 --- a/src/clients/Dim.Clients/Api/Dim/ApplicationResponse.cs +++ /dev/null @@ -1,31 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -using System.Text.Json.Serialization; - -namespace Dim.Clients.Api.Dim; - -public record ApplicationResponse( - [property: JsonPropertyName("id")] string Id, - [property: JsonPropertyName("application")] string Application, - [property: JsonPropertyName("applicationKey")] string ApplicaitonKey, - [property: JsonPropertyName("description")] string Description, - [property: JsonPropertyName("colorAccent")] int ColorAccent -); diff --git a/src/clients/Dim.Clients/Api/Dim/CreateApplicationRequest.cs b/src/clients/Dim.Clients/Api/Dim/CreateApplicationRequest.cs deleted file mode 100644 index 5ffbecc..0000000 --- a/src/clients/Dim.Clients/Api/Dim/CreateApplicationRequest.cs +++ /dev/null @@ -1,37 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -using System.Text.Json.Serialization; - -namespace Dim.Clients.Api.Dim; - -public record CreateApplicationRequest( - [property: JsonPropertyName("payload")] ApplicationPayload Payload -); - -public record ApplicationPayload( - [property: JsonPropertyName("application")] string Application, - [property: JsonPropertyName("description")] string Description, - [property: JsonPropertyName("colorAccent")] int ColorAccent -); - -public record CreateApplicationResponse( - [property: JsonPropertyName("id")] string Id -); diff --git a/src/clients/Dim.Clients/Api/Dim/CreateCompanyIdentityRequest.cs b/src/clients/Dim.Clients/Api/Dim/CreateCompanyIdentityRequest.cs deleted file mode 100644 index e135e0d..0000000 --- a/src/clients/Dim.Clients/Api/Dim/CreateCompanyIdentityRequest.cs +++ /dev/null @@ -1,65 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -using System.Text.Json.Serialization; - -namespace Dim.Clients.Api.Dim; - -public record CreateCompanyIdentityRequest( - [property: JsonPropertyName("payload")] Payload Payload -); - -public record Payload( - [property: JsonPropertyName("hostingURL")] string HostingUrl, - [property: JsonPropertyName("network")] Network Network, - [property: JsonPropertyName("services")] IEnumerable Services, - [property: JsonPropertyName("keys")] IEnumerable Keys, - [property: JsonPropertyName("name")] string Name -); - -public record Service( - [property: JsonPropertyName("id")] string Id, - [property: JsonPropertyName("type")] string Type, - [property: JsonPropertyName("serviceEndpoint")] string ServiceEndpoint -); - -public record Network( - [property: JsonPropertyName("didMethod")] string DidMethod, - [property: JsonPropertyName("type")] string Type -); - -public record Key( - [property: JsonPropertyName("type")] string Type -); - -public record CreateCompanyIdentityResponse( - [property: JsonPropertyName("did")] string Did, - [property: JsonPropertyName("companyId")] Guid CompanyId, - [property: JsonPropertyName("downloadURL")] string DownloadUrl -); -// -// public record UpdateCompanyIdentityRequest( -// [property: JsonPropertyName("didDocUpdates")] DidDocUpdates DidDocUpdates -// ); -// -// public record DidDocUpdates( -// [property: JsonPropertyName("removeServices")] IEnumerable RemoveServices, -// [property: JsonPropertyName("addServices")] IEnumerable AddServices -// ); diff --git a/src/clients/Dim.Clients/Api/Dim/DimClient.cs b/src/clients/Dim.Clients/Api/Dim/DimClient.cs index 5ec830e..52352ee 100644 --- a/src/clients/Dim.Clients/Api/Dim/DimClient.cs +++ b/src/clients/Dim.Clients/Api/Dim/DimClient.cs @@ -18,93 +18,42 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Dim.Clients.Api.Dim.Models; using Dim.Clients.Extensions; using Dim.Clients.Token; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; using System.Net.Http.Json; using System.Text.Json; +using System.Web; namespace Dim.Clients.Api.Dim; -public class DimClient(IBasicAuthTokenService basicAuthTokenService, IHttpClientFactory clientFactory) +public class DimClient(IBasicAuthTokenService basicAuthTokenService) : IDimClient { - public async Task CreateCompanyIdentity(BasicAuthSettings dimBasicAuth, Guid tenantId, string hostingUrl, string baseUrl, bool isIssuer, CancellationToken cancellationToken) + public async Task GetCompanyData(BasicAuthSettings dimAuth, string dimBaseUrl, string tenantName, string application, CancellationToken cancellationToken) { - var client = await basicAuthTokenService.GetBasicAuthorizedClient(dimBasicAuth, cancellationToken).ConfigureAwait(false); - var data = new CreateCompanyIdentityRequest(new Payload( - hostingUrl, - new Network("web", "production"), - [new Service($"dim:web:{tenantId}", "CredentialService", "https://dis-agent-prod.eu10.dim.cloud.sap/api/v1.0.0/iatp")], - isIssuer ? - [ - new("SIGNING"), - new("SIGNING_VC") - ] : - new Key[] - { - new("SIGNING") - }, - "holder iatp")); - var result = await client.PostAsJsonAsync($"{baseUrl}/api/v2.0.0/companyIdentities", data, JsonSerializerExtensions.Options, cancellationToken) - .CatchingIntoServiceExceptionFor("create-company-identity", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, - async m => - { - var message = await m.Content.ReadAsStringAsync().ConfigureAwait(false); - return (false, message); - }).ConfigureAwait(false); - try - { - var response = await result.Content - .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); - if (response == null) - { - throw new ServiceException("Response was empty", true); - } - - return response; - } - catch (JsonException je) - { - throw new ServiceException(je.Message); - } - } - - public async Task GetDidDocument(string url, CancellationToken cancellationToken) - { - var client = clientFactory.CreateClient("didDocumentDownload"); - using var result = await client.GetStreamAsync(url, cancellationToken).ConfigureAwait(false); - var document = await JsonDocument.ParseAsync(result, cancellationToken: cancellationToken).ConfigureAwait(false); - return document; - } - - public async Task CreateApplication(BasicAuthSettings dimAuth, string dimBaseUrl, string tenantName, CancellationToken cancellationToken) - { - var client = await basicAuthTokenService.GetBasicAuthorizedClient(dimAuth, cancellationToken).ConfigureAwait(false); - var data = new CreateApplicationRequest(new ApplicationPayload( - "catena-x-portal", - $"Catena-X Portal MIW for {tenantName}", - 6)); - var result = await client.PostAsJsonAsync($"{dimBaseUrl}/api/v2.0.0/applications", data, JsonSerializerExtensions.Options, cancellationToken) - .CatchingIntoServiceExceptionFor("create-application", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, + var client = await basicAuthTokenService.GetBasicAuthorizedClient(dimAuth, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + var filterString = $"name eq {tenantName} and application eq {application}"; + var result = await client.GetAsync($"{dimBaseUrl}/api/v2.0.0/companyIdentities?$filter={HttpUtility.UrlEncode(filterString)}", cancellationToken) + .CatchingIntoServiceExceptionFor("get-company-data", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, async m => { - var message = await m.Content.ReadAsStringAsync().ConfigureAwait(false); + var message = await m.Content.ReadAsStringAsync().ConfigureAwait(ConfigureAwaitOptions.None); return (false, message); }).ConfigureAwait(false); try { var response = await result.Content - .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); - if (response == null) + .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None); + if (response?.Data == null || response.Data.Count() != 1) { - throw new ServiceException("Response was empty", true); + throw new ConflictException("There is no matching company"); } - return response.Id; + return response.Data.Single(); } catch (JsonException je) { @@ -112,62 +61,22 @@ public async Task CreateApplication(BasicAuthSettings dimAuth, string di } } - public async Task GetApplication(BasicAuthSettings dimAuth, string dimBaseUrl, string applicationId, CancellationToken cancellationToken) - { - var client = await basicAuthTokenService.GetBasicAuthorizedClient(dimAuth, cancellationToken).ConfigureAwait(false); - var result = await client.GetAsync($"{dimBaseUrl}/api/v2.0.0/applications/{applicationId}", cancellationToken) - .CatchingIntoServiceExceptionFor("get-application", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, - async m => - { - var message = await m.Content.ReadAsStringAsync().ConfigureAwait(false); - return (false, message); - }).ConfigureAwait(false); - try - { - var response = await result.Content - .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); - if (response == null) - { - throw new ServiceException("Response must not be null"); - } - - return response.ApplicaitonKey; - } - catch (JsonException je) - { - throw new ServiceException(je.Message); - } - } - - public async Task AssignApplicationToCompany(BasicAuthSettings dimAuth, string dimBaseUrl, string applicationKey, Guid companyId, CancellationToken cancellationToken) + public async Task GetStatusList(BasicAuthSettings dimAuth, string dimBaseUrl, Guid companyId, CancellationToken cancellationToken) { - var client = await basicAuthTokenService.GetBasicAuthorizedClient(dimAuth, cancellationToken).ConfigureAwait(false); - var data = new CompanyIdentityPatch(new ApplicationUpdates(Enumerable.Repeat(applicationKey, 1))); - await client.PatchAsJsonAsync($"{dimBaseUrl}/api/v2.0.0/companyIdentities/{companyId}", data, JsonSerializerExtensions.Options, cancellationToken) - .CatchingIntoServiceExceptionFor("assign-application", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, + var client = await basicAuthTokenService.GetBasicAuthorizedClient(dimAuth, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + var result = await client.GetAsync($"{dimBaseUrl}/api/v2.0.0/companyIdentities/{companyId}/revocationLists", cancellationToken) + .CatchingIntoServiceExceptionFor("get-status-list", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, async m => { - var message = await m.Content.ReadAsStringAsync().ConfigureAwait(false); + var message = await m.Content.ReadAsStringAsync().ConfigureAwait(ConfigureAwaitOptions.None); return (false, message); }).ConfigureAwait(false); - } - - public async Task GetStatusList(BasicAuthSettings dimAuth, string dimBaseUrl, Guid companyId, CancellationToken cancellationToken) - { - var client = await basicAuthTokenService.GetBasicAuthorizedClient(dimAuth, cancellationToken).ConfigureAwait(false); - var result = await client.GetAsync($"{dimBaseUrl}/api/v2.0.0/companyIdentities/{companyId}/revocationLists", cancellationToken); try { var response = await result.Content .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); - if (response == null) - { - throw new ServiceException("Response must not be null"); - } - - if (!response.Data.Any(x => x.RemainingSpace > 0)) + .ConfigureAwait(ConfigureAwaitOptions.None); + if (response?.Data == null || !response.Data.Any(x => x.RemainingSpace > 0)) { throw new ConflictException("There is no status list with remaining space, please create a new one."); } @@ -182,20 +91,20 @@ public async Task GetStatusList(BasicAuthSettings dimAuth, string dimBas public async Task CreateStatusList(BasicAuthSettings dimAuth, string dimBaseUrl, Guid companyId, CancellationToken cancellationToken) { - var client = await basicAuthTokenService.GetBasicAuthorizedClient(dimAuth, cancellationToken).ConfigureAwait(false); + var client = await basicAuthTokenService.GetBasicAuthorizedClient(dimAuth, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); var data = new CreateStatusListRequest(new CreateStatusListPaypload(new CreateStatusList("StatusList2021", DateTimeOffset.UtcNow.ToString("yyyyMMdd"), "New revocation list", 2097152))); var result = await client.PostAsJsonAsync($"{dimBaseUrl}/api/v2.0.0/companyIdentities/{companyId}/revocationLists", data, JsonSerializerExtensions.Options, cancellationToken) .CatchingIntoServiceExceptionFor("assign-application", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, async m => { - var message = await m.Content.ReadAsStringAsync().ConfigureAwait(false); + var message = await m.Content.ReadAsStringAsync().ConfigureAwait(ConfigureAwaitOptions.None); return (false, message); }).ConfigureAwait(false); try { var response = await result.Content .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); + .ConfigureAwait(ConfigureAwaitOptions.None); if (response == null) { throw new ServiceException("Response must not be null"); diff --git a/src/clients/Dim.Clients/Api/Dim/IDimClient.cs b/src/clients/Dim.Clients/Api/Dim/IDimClient.cs index bff98c4..985a840 100644 --- a/src/clients/Dim.Clients/Api/Dim/IDimClient.cs +++ b/src/clients/Dim.Clients/Api/Dim/IDimClient.cs @@ -18,18 +18,14 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Dim.Clients.Api.Dim.Models; using Dim.Clients.Token; -using System.Text.Json; namespace Dim.Clients.Api.Dim; public interface IDimClient { - Task CreateCompanyIdentity(BasicAuthSettings dimBasicAuth, Guid tenantId, string hostingUrl, string baseUrl, bool isIssuer, CancellationToken cancellationToken); - Task GetDidDocument(string url, CancellationToken cancellationToken); - Task CreateApplication(BasicAuthSettings dimAuth, string dimBaseUrl, string tenantName, CancellationToken cancellationToken); - Task GetApplication(BasicAuthSettings dimAuth, string dimBaseUrl, string applicationId, CancellationToken cancellationToken); - Task AssignApplicationToCompany(BasicAuthSettings dimAuth, string dimBaseUrl, string applicationKey, Guid companyId, CancellationToken cancellationToken); + Task GetCompanyData(BasicAuthSettings dimAuth, string dimBaseUrl, string tenantName, string application, CancellationToken cancellationToken); Task GetStatusList(BasicAuthSettings dimAuth, string dimBaseUrl, Guid companyId, CancellationToken cancellationToken); Task CreateStatusList(BasicAuthSettings dimAuth, string dimBaseUrl, Guid companyId, CancellationToken cancellationToken); } diff --git a/src/clients/Dim.Clients/Api/Dim/CompanyIdentityPatch.cs b/src/clients/Dim.Clients/Api/Dim/Models/CompanyData.cs similarity index 77% rename from src/clients/Dim.Clients/Api/Dim/CompanyIdentityPatch.cs rename to src/clients/Dim.Clients/Api/Dim/Models/CompanyData.cs index f66264e..db8b66b 100644 --- a/src/clients/Dim.Clients/Api/Dim/CompanyIdentityPatch.cs +++ b/src/clients/Dim.Clients/Api/Dim/Models/CompanyData.cs @@ -20,12 +20,13 @@ using System.Text.Json.Serialization; -namespace Dim.Clients.Api.Dim; +namespace Dim.Clients.Api.Dim.Models; -public record CompanyIdentityPatch( - [property: JsonPropertyName("applicationUpdates")] ApplicationUpdates ApplicationUpdates +public record CompanyIdentitiesResponse( + [property: JsonPropertyName("data")] IEnumerable Data ); -public record ApplicationUpdates( - [property: JsonPropertyName("assignApplications")] IEnumerable AssignApplications +public record CompanyData( + [property: JsonPropertyName("id")] Guid CompanyId, + [property: JsonPropertyName("downloadURL")] string DownloadUrl ); diff --git a/src/clients/Dim.Clients/Api/Dim/StatusListResponse.cs b/src/clients/Dim.Clients/Api/Dim/Models/StatusListResponse.cs similarity index 98% rename from src/clients/Dim.Clients/Api/Dim/StatusListResponse.cs rename to src/clients/Dim.Clients/Api/Dim/Models/StatusListResponse.cs index fa3eb40..82ac365 100644 --- a/src/clients/Dim.Clients/Api/Dim/StatusListResponse.cs +++ b/src/clients/Dim.Clients/Api/Dim/Models/StatusListResponse.cs @@ -20,7 +20,7 @@ using System.Text.Json.Serialization; -namespace Dim.Clients.Api.Dim; +namespace Dim.Clients.Api.Dim.Models; public record CreateStatusListRequest( [property: JsonPropertyName("payload")] CreateStatusListPaypload Payload diff --git a/src/clients/Dim.Clients/Api/Directories/DependencyInjection/DirectoryClientServiceExtensions.cs b/src/clients/Dim.Clients/Api/Directories/DependencyInjection/DirectoryClientServiceExtensions.cs deleted file mode 100644 index 3d5a9e1..0000000 --- a/src/clients/Dim.Clients/Api/Directories/DependencyInjection/DirectoryClientServiceExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Extensions; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace Dim.Clients.Api.Directories.DependencyInjection; - -public static class DirectoryClientServiceExtensions -{ - public static IServiceCollection AddDirectoryClient(this IServiceCollection services, IConfigurationSection section) - { - services.AddOptions() - .Bind(section) - .ValidateOnStart(); - - var sp = services.BuildServiceProvider(); - var settings = sp.GetRequiredService>(); - services - .AddCustomHttpClientWithAuthentication(settings.Value.BaseUrl, settings.Value.TokenAddress) - .AddTransient(); - - return services; - } -} diff --git a/src/clients/Dim.Clients/Api/Directories/DependencyInjection/DirectorySettings.cs b/src/clients/Dim.Clients/Api/Directories/DependencyInjection/DirectorySettings.cs deleted file mode 100644 index f4ce443..0000000 --- a/src/clients/Dim.Clients/Api/Directories/DependencyInjection/DirectorySettings.cs +++ /dev/null @@ -1,30 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Token; -using System.ComponentModel.DataAnnotations; - -namespace Dim.Clients.Api.Directories.DependencyInjection; - -public class DirectorySettings : BasicAuthSettings -{ - [Required] - public string BaseUrl { get; set; } = null!; -} diff --git a/src/clients/Dim.Clients/Api/Directories/DirectoryClient.cs b/src/clients/Dim.Clients/Api/Directories/DirectoryClient.cs deleted file mode 100644 index fd50052..0000000 --- a/src/clients/Dim.Clients/Api/Directories/DirectoryClient.cs +++ /dev/null @@ -1,76 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Api.Directories.DependencyInjection; -using Dim.Clients.Extensions; -using Dim.Clients.Token; -using Microsoft.Extensions.Options; -using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; -using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; -using System.Net.Http.Json; -using System.Text.Json; - -namespace Dim.Clients.Api.Directories; - -public class DirectoryClient : IDirectoryClient -{ - private readonly DirectorySettings _settings; - private readonly IBasicAuthTokenService _basicAuthTokenService; - - public DirectoryClient(IBasicAuthTokenService basicAuthTokenService, IOptions settings) - { - _basicAuthTokenService = basicAuthTokenService; - _settings = settings.Value; - } - - public async Task CreateDirectory(string description, string bpnl, Guid parentId, CancellationToken cancellationToken) - { - var client = await _basicAuthTokenService.GetBasicAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); - var directory = new DirectoryRequest( - description, - Enumerable.Repeat("phil.schneider@digitalnativesolutions.de", 1), - bpnl, - new Dictionary>() - { - { "cloud_management_service", new[] { "Created by API - Don't change it" } } - } - ); - - var result = await client.PostAsJsonAsync($"/accounts/v1/directories?parentId={parentId}", directory, JsonSerializerExtensions.Options, cancellationToken) - .CatchingIntoServiceExceptionFor("create-directory", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE).ConfigureAwait(false); - try - { - var response = await result.Content - .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); - - if (response == null) - { - throw new ServiceException("Directory response must not be null"); - } - - return response.Id; - } - catch (JsonException je) - { - throw new ServiceException(je.Message); - } - } -} diff --git a/src/clients/Dim.Clients/Api/Directories/DirectoryRequest.cs b/src/clients/Dim.Clients/Api/Directories/DirectoryRequest.cs deleted file mode 100644 index 64cdd7a..0000000 --- a/src/clients/Dim.Clients/Api/Directories/DirectoryRequest.cs +++ /dev/null @@ -1,35 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -using System.Text.Json.Serialization; - -namespace Dim.Clients.Api.Directories; - -public record DirectoryRequest( - [property: JsonPropertyName("description")] string Description, - [property: JsonPropertyName("directoryAdmins")] IEnumerable DirectoryAdmins, - [property: JsonPropertyName("displayName")] string DisplayName, - [property: JsonPropertyName("labels")] Dictionary> Labels -); - -public record DirectoryResponse( - [property: JsonPropertyName("guid")] Guid Id, - [property: JsonPropertyName("subdomain")] string Subdomain -); diff --git a/src/clients/Dim.Clients/Api/Directories/IDirectoryClient.cs b/src/clients/Dim.Clients/Api/Directories/IDirectoryClient.cs deleted file mode 100644 index a145d93..0000000 --- a/src/clients/Dim.Clients/Api/Directories/IDirectoryClient.cs +++ /dev/null @@ -1,26 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Api.Directories; - -public interface IDirectoryClient -{ - Task CreateDirectory(string description, string bpnl, Guid parentId, CancellationToken cancellationToken); -} diff --git a/src/clients/Dim.Clients/Api/Provisioning/DependencyInjection/ProvisioningClientServiceExtensions.cs b/src/clients/Dim.Clients/Api/Div/DependencyInjection/ProvisioningClientServiceExtensions.cs similarity index 71% rename from src/clients/Dim.Clients/Api/Provisioning/DependencyInjection/ProvisioningClientServiceExtensions.cs rename to src/clients/Dim.Clients/Api/Div/DependencyInjection/ProvisioningClientServiceExtensions.cs index c46310d..1f6d825 100644 --- a/src/clients/Dim.Clients/Api/Provisioning/DependencyInjection/ProvisioningClientServiceExtensions.cs +++ b/src/clients/Dim.Clients/Api/Div/DependencyInjection/ProvisioningClientServiceExtensions.cs @@ -19,16 +19,25 @@ ********************************************************************************/ using Dim.Clients.Extensions; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; -namespace Dim.Clients.Api.Provisioning.DependencyInjection; +namespace Dim.Clients.Api.Div.DependencyInjection; public static class ProvisioningClientServiceExtensions { - public static IServiceCollection AddProvisioningClient(this IServiceCollection services) + public static IServiceCollection AddProvisioningClient(this IServiceCollection services, IConfigurationSection section) { + services.AddOptions() + .Bind(section) + .ValidateOnStart() + .ValidateDataAnnotations(); + + var sp = services.BuildServiceProvider(); + var settings = sp.GetRequiredService>(); services - .AddCustomHttpClientWithAuthentication(null, null) + .AddCustomHttpClientWithAuthentication(settings.Value.BaseUrl, settings.Value.TokenAddress) .AddTransient(); return services; diff --git a/src/clients/Dim.Clients/Api/Provisioning/DependencyInjection/ProvisioningSettings.cs b/src/clients/Dim.Clients/Api/Div/DependencyInjection/ProvisioningSettings.cs similarity index 95% rename from src/clients/Dim.Clients/Api/Provisioning/DependencyInjection/ProvisioningSettings.cs rename to src/clients/Dim.Clients/Api/Div/DependencyInjection/ProvisioningSettings.cs index 48fd5a3..034953d 100644 --- a/src/clients/Dim.Clients/Api/Provisioning/DependencyInjection/ProvisioningSettings.cs +++ b/src/clients/Dim.Clients/Api/Div/DependencyInjection/ProvisioningSettings.cs @@ -21,7 +21,7 @@ using Dim.Clients.Token; using System.ComponentModel.DataAnnotations; -namespace Dim.Clients.Api.Provisioning.DependencyInjection; +namespace Dim.Clients.Api.Div.DependencyInjection; public class ProvisioningSettings : BasicAuthSettings { diff --git a/src/clients/Dim.Clients/Api/Provisioning/IProvisioningClient.cs b/src/clients/Dim.Clients/Api/Div/IProvisioningClient.cs similarity index 58% rename from src/clients/Dim.Clients/Api/Provisioning/IProvisioningClient.cs rename to src/clients/Dim.Clients/Api/Div/IProvisioningClient.cs index 41e3825..ccf0233 100644 --- a/src/clients/Dim.Clients/Api/Provisioning/IProvisioningClient.cs +++ b/src/clients/Dim.Clients/Api/Div/IProvisioningClient.cs @@ -18,11 +18,16 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -using Dim.Clients.Api.Services; +using Dim.Clients.Api.Div.Models; +using Dim.Clients.Token; -namespace Dim.Clients.Api.Provisioning; +namespace Dim.Clients.Api.Div; public interface IProvisioningClient { - Task CreateCloudFoundryEnvironment(string authUrl, BindingItem bindingData, string tenantName, string user, CancellationToken cancellationToken); + public Task CreateOperation(Guid customerId, string customerName, string applicationName, string companyName, string didDocumentLocation, bool isIssuer, CancellationToken cancellationToken); + public Task GetOperation(Guid operationId, CancellationToken cancellationToken); + Task CreateServiceKey(string technicalUserName, Guid walletId, CancellationToken cancellationToken); + Task DeleteServiceKey(Guid walletId, Guid serviceKeyId, CancellationToken cancellationToken); + Task GetServiceKey(string technicalUserName, Guid walletId, CancellationToken cancellationToken); } diff --git a/src/clients/Dim.Clients/Api/Cf/CreateCfeRequest.cs b/src/clients/Dim.Clients/Api/Div/Models/CustomerWalletsResponse.cs similarity index 56% rename from src/clients/Dim.Clients/Api/Cf/CreateCfeRequest.cs rename to src/clients/Dim.Clients/Api/Div/Models/CustomerWalletsResponse.cs index d832e4e..95f1ca4 100644 --- a/src/clients/Dim.Clients/Api/Cf/CreateCfeRequest.cs +++ b/src/clients/Dim.Clients/Api/Div/Models/CustomerWalletsResponse.cs @@ -20,34 +20,23 @@ using System.Text.Json.Serialization; -namespace Dim.Clients.Api.Cf; +namespace Dim.Clients.Api.Div.Models; -public record CreateSpaceRequest( - [property: JsonPropertyName("name")] string Name, - [property: JsonPropertyName("relationships")] SpaceRelationship Relationship +public record CustomerWalletsResponse( + [property: JsonPropertyName("count")] int Count, + [property: JsonPropertyName("data")] IEnumerable Data ); -public record SpaceRelationship( - [property: JsonPropertyName("organization")] SpaceOrganization Organization +public record CustomerWallet( + [property: JsonPropertyName("id")] Guid Id, + [property: JsonPropertyName("meteringId")] Guid MeteringId, + [property: JsonPropertyName("customerName")] string CustomerName, + [property: JsonPropertyName("customerId")] string CustomerId, + [property: JsonPropertyName("subscriptionUrl")] string SubscriptionUrl, + [property: JsonPropertyName("serviceKeys")] IEnumerable ServiceKeys ); -public record SpaceOrganization( - [property: JsonPropertyName("data")] SpaceRelationshipData Data -); - -public record SpaceRelationshipData( - [property: JsonPropertyName("guid")] Guid Id -); - -public record CreateSpaceResponse( - [property: JsonPropertyName("guid")] Guid Id -); - -public record GetEnvironmentsResponse( - [property: JsonPropertyName("resources")] IEnumerable Resources -); - -public record EnvironmentResource( - [property: JsonPropertyName("guid")] Guid EnvironmentId, +public record CustomerServiceKey( + [property: JsonPropertyName("id")] Guid Id, [property: JsonPropertyName("name")] string Name ); diff --git a/src/clients/Dim.Clients/Api/Div/Models/OperationRequest.cs b/src/clients/Dim.Clients/Api/Div/Models/OperationRequest.cs new file mode 100644 index 0000000..ad14eef --- /dev/null +++ b/src/clients/Dim.Clients/Api/Div/Models/OperationRequest.cs @@ -0,0 +1,72 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using System.Text.Json.Serialization; + +namespace Dim.Clients.Api.Div.Models; + +public record OperationCreationRequest( + [property: JsonPropertyName("action")] string Action, + [property: JsonPropertyName("entity")] string Entity, + [property: JsonPropertyName("payload")] OperationPayloadData Payload +); + +public record OperationPayloadData( + [property: JsonPropertyName("customerId")] string CustomerId, + [property: JsonPropertyName("customerName")] string CustomerName, + [property: JsonPropertyName("divWalletServiceName")] string WalletServiceName, + [property: JsonPropertyName("divWalletServiceParameters")] WalletServiceParameter WalletServiceParameter +); + +public record WalletServiceParameter( + [property: JsonPropertyName("applications")] IEnumerable Applications +); + +public record WalletApplication( + [property: JsonPropertyName("application")] string Application, + [property: JsonPropertyName("companies")] IEnumerable Companies, + [property: JsonPropertyName("trustedIssuers")] IEnumerable TrustedIssuers +); + +public record ApplicationCompany( + [property: JsonPropertyName("name")] string Name, + [property: JsonPropertyName("hostingURL")] string HostingUrl, + [property: JsonPropertyName("services")] IEnumerable Services, + [property: JsonPropertyName("keys")] IEnumerable Keys +); + +public record ApplicationCompanyService( + [property: JsonPropertyName("type")] string Type, + [property: JsonPropertyName("serviceEndpoint")] string ServiceEndpoint +); + +public record ApplicationCompanyKey( + [property: JsonPropertyName("type")] string Type +); + +public record TrustedIssuer( + [property: JsonPropertyName("name")] string Name, + [property: JsonPropertyName("did")] string Did, + [property: JsonPropertyName("ignoreMissingHashlist")] bool IgnoreMissingHashlist +); + +public record OperationRequest( + [property: JsonPropertyName("operationId")] Guid OperationId +); diff --git a/src/clients/Dim.Clients/Api/Div/Models/OperationResponse.cs b/src/clients/Dim.Clients/Api/Div/Models/OperationResponse.cs new file mode 100644 index 0000000..61f77cf --- /dev/null +++ b/src/clients/Dim.Clients/Api/Div/Models/OperationResponse.cs @@ -0,0 +1,58 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using System.Text.Json.Serialization; + +namespace Dim.Clients.Api.Div.Models; + +public record OperationResponse( + [property: JsonPropertyName("operationId")] Guid OperationId, + [property: JsonPropertyName("status")] OperationResponseStatus Status, + [property: JsonPropertyName("createdAt")] DateTimeOffset? CreatedAt, + [property: JsonPropertyName("error")] string? Error, + [property: JsonPropertyName("data")] OperationResponseData? Data +); + +public record OperationResponseData( + [property: JsonPropertyName("customerWalletId")] Guid CustomerWalletId, + [property: JsonPropertyName("customerId")] string CustomerId, + [property: JsonPropertyName("customerName")] string CustomerName, + [property: JsonPropertyName("serviceKey")] ServiceKey ServiceKey +); + +public record ServiceKey( + [property: JsonPropertyName("uaa")] ServiceUaa Uaa, + [property: JsonPropertyName("url")] string Url, + [property: JsonPropertyName("vendor")] string Vendor +); + +public record ServiceUaa( + [property: JsonPropertyName("apiurl")] string ApiUrl, + [property: JsonPropertyName("url")] string Url, + [property: JsonPropertyName("clientid")] string ClientId, + [property: JsonPropertyName("clientsecret")] string ClientSecret +); + +public enum OperationResponseStatus +{ + pending = 1, + failed = 2, + completed = 3 +} diff --git a/src/clients/Dim.Clients/Api/Cf/AddSpaceRoleToUserRequest.cs b/src/clients/Dim.Clients/Api/Div/Models/ServiceKeyOperationCreationRequest.cs similarity index 52% rename from src/clients/Dim.Clients/Api/Cf/AddSpaceRoleToUserRequest.cs rename to src/clients/Dim.Clients/Api/Div/Models/ServiceKeyOperationCreationRequest.cs index b165b63..72c4122 100644 --- a/src/clients/Dim.Clients/Api/Cf/AddSpaceRoleToUserRequest.cs +++ b/src/clients/Dim.Clients/Api/Div/Models/ServiceKeyOperationCreationRequest.cs @@ -20,31 +20,26 @@ using System.Text.Json.Serialization; -namespace Dim.Clients.Api.Cf; +namespace Dim.Clients.Api.Div.Models; -public record AddSpaceRoleToUserRequest( - [property: JsonPropertyName("type")] string Type, - [property: JsonPropertyName("relationships")] SpaceRoleRelationship Relationship +public record ServiceKeyOperationCreationRequest( + [property: JsonPropertyName("entity")] string Entity, + [property: JsonPropertyName("action")] string Action, + [property: JsonPropertyName("payload")] ServiceKeyCreationPayloadData CreationPayload ); -public record SpaceRoleRelationship( - [property: JsonPropertyName("user")] RelationshipUser User, - [property: JsonPropertyName("space")] SpaceRoleSpace Space +public record ServiceKeyCreationPayloadData( + [property: JsonPropertyName("customerWalletId")] Guid CustomerWalletId, + [property: JsonPropertyName("divWalletServiceName")] string ServiceKeyName ); -public record RelationshipUser( - [property: JsonPropertyName("data")] UserData Data +public record ServiceKeyOperationDeletionRequest( + [property: JsonPropertyName("entity")] string Entity, + [property: JsonPropertyName("action")] string Action, + [property: JsonPropertyName("payload")] ServiceKeyDeletionPayloadData DeletionPayload ); -public record UserData( - [property: JsonPropertyName("username")] string Username, - [property: JsonPropertyName("origin")] string Origin -); - -public record SpaceRoleSpace( - [property: JsonPropertyName("data")] SpaceRoleData Data -); - -public record SpaceRoleData( - [property: JsonPropertyName("guid")] Guid Id +public record ServiceKeyDeletionPayloadData( + [property: JsonPropertyName("customerWalletKeyId")] Guid ServiceKeyId, + [property: JsonPropertyName("customerWalletId")] Guid CustomerWalletId ); diff --git a/src/clients/Dim.Clients/Api/Div/ProvisioningClient.cs b/src/clients/Dim.Clients/Api/Div/ProvisioningClient.cs new file mode 100644 index 0000000..5591e53 --- /dev/null +++ b/src/clients/Dim.Clients/Api/Div/ProvisioningClient.cs @@ -0,0 +1,255 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * 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 Dim.Clients.Api.Div.DependencyInjection; +using Dim.Clients.Api.Div.Models; +using Dim.Clients.Extensions; +using Dim.Clients.Token; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; +using System.Net.Http.Json; +using System.Text.Json; + +namespace Dim.Clients.Api.Div; + +public class ProvisioningClient(IBasicAuthTokenService basicAuthTokenService, IOptions options) + : IProvisioningClient +{ + private readonly ProvisioningSettings _settings = options.Value; + + public async Task CreateOperation(Guid customerId, string customerName, string applicationName, string companyName, string didDocumentLocation, bool isIssuer, CancellationToken cancellationToken) + { + var data = new OperationCreationRequest( + "provision", + "customer-wallet", + new OperationPayloadData( + customerId.ToString(), + customerName, + "main", + new WalletServiceParameter( + Enumerable.Repeat(new WalletApplication( + applicationName, + Enumerable.Repeat(new ApplicationCompany( + companyName, + didDocumentLocation, + [new ApplicationCompanyService("CredentialService", "https://dis-agent-prod.eu10.dim.cloud.sap/api/v1.0.0/iatp")], + isIssuer ? + [ + new("SIGNING"), + new("SIGNING_VC") + ] : + new ApplicationCompanyKey[] + { + new("SIGNING") + } + ), 1), + Enumerable.Empty() + ), 1) + ) + ) + ); + var client = await basicAuthTokenService + .GetBasicAuthorizedClient(_settings, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None); + var result = await client.PostAsJsonAsync("/api/v1.0.0/operations", data, JsonSerializerExtensions.Options, cancellationToken) + .CatchingIntoServiceExceptionFor("create-operation", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, + async response => + { + var content = await response.Content.ReadAsStringAsync().ConfigureAwait(ConfigureAwaitOptions.None); + return (false, content); + }) + .ConfigureAwait(false); + try + { + var response = await result.Content + .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None); + + if (response == null) + { + throw new ServiceException("response should never be null here"); + } + + return response.OperationId; + } + catch (JsonException je) + { + throw new ServiceException(je.Message); + } + } + + public async Task GetOperation(Guid operationId, CancellationToken cancellationToken) + { + var client = await basicAuthTokenService + .GetBasicAuthorizedClient(_settings, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None); + var result = await client.GetAsync($"/api/v1.0.0/operations/{operationId}", cancellationToken) + .CatchingIntoServiceExceptionFor("get-operation", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, + async response => + { + var content = await response.Content.ReadAsStringAsync().ConfigureAwait(ConfigureAwaitOptions.None); + return (false, content); + }).ConfigureAwait(false); + try + { + var response = await result.Content + .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None); + if (response == null) + { + throw new ServiceException("Response must not be null"); + } + + if (response.Status == OperationResponseStatus.failed) + { + throw new ServiceException($"Operation Creation failed with error: {response.Error}"); + } + + return response; + } + catch (JsonException je) + { + throw new ServiceException(je.Message); + } + } + + public async Task CreateServiceKey(string technicalUserName, Guid walletId, CancellationToken cancellationToken) + { + var data = new ServiceKeyOperationCreationRequest( + "customer-wallet-key", + "create", + new ServiceKeyCreationPayloadData( + walletId, + technicalUserName + ) + ); + var client = await basicAuthTokenService + .GetBasicAuthorizedClient(_settings, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None); + var result = await client.PostAsJsonAsync("/api/v1.0.0/operations", data, JsonSerializerExtensions.Options, cancellationToken) + .CatchingIntoServiceExceptionFor("create-service-key", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, + async response => + { + var content = await response.Content.ReadAsStringAsync().ConfigureAwait(ConfigureAwaitOptions.None); + return (false, content); + }) + .ConfigureAwait(false); + try + { + var response = await result.Content + .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None); + + if (response == null) + { + throw new ServiceException("response should never be null here"); + } + + return response.OperationId; + } + catch (JsonException je) + { + throw new ServiceException(je.Message); + } + } + + public async Task GetServiceKey(string technicalUserName, Guid walletId, CancellationToken cancellationToken) + { + var client = await basicAuthTokenService + .GetBasicAuthorizedClient(_settings, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None); + var result = await client.GetAsync($"/api/v1.0.0/customerWallets?CustomerWalletId={walletId}", cancellationToken) + .CatchingIntoServiceExceptionFor("get-service-key", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, + async response => + { + var content = await response.Content.ReadAsStringAsync().ConfigureAwait(ConfigureAwaitOptions.None); + return (false, content); + }).ConfigureAwait(false); + try + { + var response = await result.Content + .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None); + if (response == null) + { + throw new ServiceException("Response must not be null"); + } + + var customers = response.Data.Where(x => x.Id == walletId); + if (customers.Count() != 1) + { + throw new ServiceException($"Must have exactly one customer for wallet id {walletId}"); + } + + var serviceKey = customers.Single().ServiceKeys.Where(sk => sk.Name.Equals(technicalUserName, StringComparison.OrdinalIgnoreCase)); + if (serviceKey.Count() != 1) + { + throw new ServiceException($"Must have exactly one wallet and a service key with name {technicalUserName}"); + } + + return serviceKey.Single().Id; + } + catch (JsonException je) + { + throw new ServiceException(je.Message); + } + } + + public async Task DeleteServiceKey(Guid walletId, Guid serviceKeyId, CancellationToken cancellationToken) + { + var data = new ServiceKeyOperationDeletionRequest( + "customer-wallet-key", + "delete", + new ServiceKeyDeletionPayloadData( + serviceKeyId, + walletId + ) + ); + var client = await basicAuthTokenService + .GetBasicAuthorizedClient(_settings, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None); + var result = await client.PostAsJsonAsync("/api/v1.0.0/operations", data, JsonSerializerExtensions.Options, cancellationToken) + .CatchingIntoServiceExceptionFor("delete-service-key", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, + async response => + { + var content = await response.Content.ReadAsStringAsync().ConfigureAwait(ConfigureAwaitOptions.None); + return (false, content); + }) + .ConfigureAwait(false); + try + { + var response = await result.Content + .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None); + + if (response == null) + { + throw new ServiceException("response should never be null here"); + } + + return response.OperationId; + } + catch (JsonException je) + { + throw new ServiceException(je.Message); + } + } +} diff --git a/src/clients/Dim.Clients/Api/Entitlements/CreateSubAccountRequest.cs b/src/clients/Dim.Clients/Api/Entitlements/CreateSubAccountRequest.cs deleted file mode 100644 index ce4d5c2..0000000 --- a/src/clients/Dim.Clients/Api/Entitlements/CreateSubAccountRequest.cs +++ /dev/null @@ -1,39 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -using System.Text.Json.Serialization; - -namespace Dim.Clients.Api.Entitlements; - -public record CreateSubAccountRequest( - [property: JsonPropertyName("subaccountServicePlans")] IEnumerable SubaccountServicePlans -); - -public record SubaccountServicePlan( - [property: JsonPropertyName("assignmentInfo")] IEnumerable AssignmentInfo, - [property: JsonPropertyName("serviceName")] string ServiceName, - [property: JsonPropertyName("servicePlanName")] string ServicePlanName -); - -public record AssignmentInfo( - [property: JsonPropertyName("enable")] bool? Enabled, - [property: JsonPropertyName("amount")] int? Amount, - [property: JsonPropertyName("subaccountGUID")] Guid SubaccountId -); diff --git a/src/clients/Dim.Clients/Api/Entitlements/DependencyInjection/EntitlementClientServiceExtensions.cs b/src/clients/Dim.Clients/Api/Entitlements/DependencyInjection/EntitlementClientServiceExtensions.cs deleted file mode 100644 index 751b551..0000000 --- a/src/clients/Dim.Clients/Api/Entitlements/DependencyInjection/EntitlementClientServiceExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Extensions; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace Dim.Clients.Api.Entitlements.DependencyInjection; - -public static class EntitlementClientServiceExtensions -{ - public static IServiceCollection AddEntitlementClient(this IServiceCollection services, IConfigurationSection section) - { - services.AddOptions() - .Bind(section) - .ValidateOnStart(); - - var sp = services.BuildServiceProvider(); - var settings = sp.GetRequiredService>(); - services - .AddCustomHttpClientWithAuthentication(settings.Value.BaseUrl, null) - .AddTransient(); - - return services; - } -} diff --git a/src/clients/Dim.Clients/Api/Entitlements/DependencyInjection/EntitlementSettings.cs b/src/clients/Dim.Clients/Api/Entitlements/DependencyInjection/EntitlementSettings.cs deleted file mode 100644 index 6fdd346..0000000 --- a/src/clients/Dim.Clients/Api/Entitlements/DependencyInjection/EntitlementSettings.cs +++ /dev/null @@ -1,29 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Api.Entitlements.DependencyInjection; - -public class EntitlementSettings -{ - [Required] - public string BaseUrl { get; set; } = null!; -} diff --git a/src/clients/Dim.Clients/Api/Entitlements/EntitlementClient.cs b/src/clients/Dim.Clients/Api/Entitlements/EntitlementClient.cs deleted file mode 100644 index 5b79bdd..0000000 --- a/src/clients/Dim.Clients/Api/Entitlements/EntitlementClient.cs +++ /dev/null @@ -1,53 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Extensions; -using Dim.Clients.Token; -using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; -using System.Net.Http.Json; - -namespace Dim.Clients.Api.Entitlements; - -public class EntitlementClient : IEntitlementClient -{ - private readonly IBasicAuthTokenService _basicAuthTokenService; - - public EntitlementClient(IBasicAuthTokenService basicAuthTokenService) - { - _basicAuthTokenService = basicAuthTokenService; - } - - public async Task AssignEntitlements(BasicAuthSettings basicAuthSettings, Guid subAccountId, CancellationToken cancellationToken) - { - var client = await _basicAuthTokenService.GetBasicAuthorizedClient(basicAuthSettings, cancellationToken).ConfigureAwait(false); - var data = new CreateSubAccountRequest( - new List - { - new(Enumerable.Repeat(new AssignmentInfo(true, null, subAccountId), 1), "cis", "local"), - new(Enumerable.Repeat(new AssignmentInfo(true, null, subAccountId), 1), "decentralized-identity-management-app", "standard"), - new(Enumerable.Repeat(new AssignmentInfo(null, 1, subAccountId), 1), "decentralized-identity-management", "standard"), - new(Enumerable.Repeat(new AssignmentInfo(true, null, subAccountId), 1), "auditlog-viewer", "free") - } - ); - - await client.PutAsJsonAsync("/entitlements/v1/subaccountServicePlans", data, JsonSerializerExtensions.Options, cancellationToken) - .CatchingIntoServiceExceptionFor("assign-entitlements", HttpAsyncResponseMessageExtension.RecoverOptions.ALLWAYS).ConfigureAwait(false); - } -} diff --git a/src/clients/Dim.Clients/Api/Provisioning/CreateCfeRequest.cs b/src/clients/Dim.Clients/Api/Provisioning/CreateCfeRequest.cs deleted file mode 100644 index 692da27..0000000 --- a/src/clients/Dim.Clients/Api/Provisioning/CreateCfeRequest.cs +++ /dev/null @@ -1,36 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -using System.Text.Json.Serialization; - -namespace Dim.Clients.Api.Provisioning; - -public record CreateCfeRequest( - [property: JsonPropertyName("environmentType")] string EnvironmentType, - [property: JsonPropertyName("parameters")] Dictionary Parameters, - [property: JsonPropertyName("landscapeLabel")] string LandscapeLabel, - [property: JsonPropertyName("planName")] string PlanName, - [property: JsonPropertyName("serviceName")] string ServiceName, - [property: JsonPropertyName("user")] string User -); - -public record CreateCfeResponse( - [property: JsonPropertyName("id")] Guid Id -); diff --git a/src/clients/Dim.Clients/Api/Provisioning/ProvisioningClient.cs b/src/clients/Dim.Clients/Api/Provisioning/ProvisioningClient.cs deleted file mode 100644 index a986b87..0000000 --- a/src/clients/Dim.Clients/Api/Provisioning/ProvisioningClient.cs +++ /dev/null @@ -1,62 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Api.Services; -using Dim.Clients.Extensions; -using Dim.Clients.Token; -using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; -using System.Net.Http.Json; - -namespace Dim.Clients.Api.Provisioning; - -public class ProvisioningClient : IProvisioningClient -{ - private readonly IBasicAuthTokenService _basicAuthTokenService; - - public ProvisioningClient(IBasicAuthTokenService basicAuthTokenService) - { - _basicAuthTokenService = basicAuthTokenService; - } - - public async Task CreateCloudFoundryEnvironment(string authUrl, BindingItem bindingData, string tenantName, string user, CancellationToken cancellationToken) - { - var authSettings = new BasicAuthSettings - { - TokenAddress = $"{authUrl}/oauth/token", - ClientId = bindingData.Credentials.Uaa.ClientId, - ClientSecret = bindingData.Credentials.Uaa.ClientSecret - }; - var client = await _basicAuthTokenService.GetBasicAuthorizedClient(authSettings, cancellationToken).ConfigureAwait(false); - var data = new CreateCfeRequest( - "cloudfoundry", - new Dictionary - { - { "instance_name", tenantName } - }, - "cf-eu10", - "standard", - "cloudfoundry", - user - ); - - await client.PostAsJsonAsync($"{bindingData.Credentials.Endpoints.ProvisioningServiceUrl}/provisioning/v1/environments", data, JsonSerializerExtensions.Options, cancellationToken) - .CatchingIntoServiceExceptionFor("create-cf-env", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE).ConfigureAwait(false); - } -} diff --git a/src/clients/Dim.Clients/Api/Services/CreateServiceInstanceRequest.cs b/src/clients/Dim.Clients/Api/Services/CreateServiceInstanceRequest.cs deleted file mode 100644 index e311673..0000000 --- a/src/clients/Dim.Clients/Api/Services/CreateServiceInstanceRequest.cs +++ /dev/null @@ -1,71 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -using System.Text.Json.Serialization; - -namespace Dim.Clients.Api.Services; - -public record CreateServiceInstanceRequest( - [property: JsonPropertyName("name")] string Name, - [property: JsonPropertyName("service_offering_name")] string ServiceOfferingName, - [property: JsonPropertyName("service_plan_name")] string ServicePlanName, - [property: JsonPropertyName("parameters")] Dictionary Parameters -); - -public record CreateServiceInstanceResponse( - [property: JsonPropertyName("id")] string Id, - [property: JsonPropertyName("name")] string Name -); - -public record CreateServiceBindingRequest( - [property: JsonPropertyName("name")] string Name, - [property: JsonPropertyName("service_instance_id")] string ServiceInstanceId -); - -public record CreateServiceBindingResponse( - [property: JsonPropertyName("id")] string Id, - [property: JsonPropertyName("name")] string Name -); - -public record GetBindingResponse( - [property: JsonPropertyName("items")] IEnumerable Items -); - -public record BindingItem( - [property: JsonPropertyName("name")] string Name, - [property: JsonPropertyName("service_instance_id")] Guid ServiceInstanceId, - [property: JsonPropertyName("credentials")] BindingCredentials Credentials -); - -public record BindingCredentials( - [property: JsonPropertyName("endpoints")] GetBindingEndpoints Endpoints, - [property: JsonPropertyName("grant_type")] string GrantType, - [property: JsonPropertyName("uaa")] GetBindingUaa Uaa -); - -public record GetBindingUaa( - [property: JsonPropertyName("clientid")] string ClientId, - [property: JsonPropertyName("clientsecret")] string ClientSecret -); - -public record GetBindingEndpoints( - [property: JsonPropertyName("provisioning_service_url")] string ProvisioningServiceUrl, - [property: JsonPropertyName("saas_registry_service_url")] string SaasRegistryServiceUrl -); diff --git a/src/clients/Dim.Clients/Api/Services/IServiceClient.cs b/src/clients/Dim.Clients/Api/Services/IServiceClient.cs deleted file mode 100644 index 230c7ce..0000000 --- a/src/clients/Dim.Clients/Api/Services/IServiceClient.cs +++ /dev/null @@ -1,30 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Api.SubAccounts; - -namespace Dim.Clients.Api.Services; - -public interface IServiceClient -{ - Task CreateServiceInstance(ServiceManagementBindingItem saBinding, CancellationToken cancellationToken); - Task CreateServiceBinding(ServiceManagementBindingItem saBinding, string serviceInstanceId, CancellationToken cancellationToken); - Task GetServiceBinding(ServiceManagementBindingItem saBinding, string serviceBindingName, CancellationToken cancellationToken); -} diff --git a/src/clients/Dim.Clients/Api/Services/ServiceClient.cs b/src/clients/Dim.Clients/Api/Services/ServiceClient.cs deleted file mode 100644 index d6da273..0000000 --- a/src/clients/Dim.Clients/Api/Services/ServiceClient.cs +++ /dev/null @@ -1,146 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Api.SubAccounts; -using Dim.Clients.Extensions; -using Dim.Clients.Token; -using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; -using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; -using System.Net.Http.Json; -using System.Text.Json; - -namespace Dim.Clients.Api.Services; - -public class ServiceClient : IServiceClient -{ - private readonly IBasicAuthTokenService _basicAuthTokenService; - - public ServiceClient(IBasicAuthTokenService basicAuthTokenService) - { - _basicAuthTokenService = basicAuthTokenService; - } - - public async Task CreateServiceInstance(ServiceManagementBindingItem saBinding, CancellationToken cancellationToken) - { - var serviceAuth = new BasicAuthSettings - { - TokenAddress = $"{saBinding.Url}/oauth/token", - ClientId = saBinding.ClientId, - ClientSecret = saBinding.ClientSecret - }; - var client = await _basicAuthTokenService.GetBasicAuthorizedClient(serviceAuth, cancellationToken).ConfigureAwait(false); - var directory = new CreateServiceInstanceRequest( - "cis-local-instance", - "cis", - "local", - new Dictionary - { - { "grantType", "clientCredentials" } - } - ); - - var result = await client.PostAsJsonAsync($"{saBinding.SmUrl}/v1/service_instances?async=false", directory, JsonSerializerExtensions.Options, cancellationToken) - .CatchingIntoServiceExceptionFor("create-service-instance", HttpAsyncResponseMessageExtension.RecoverOptions.ALLWAYS).ConfigureAwait(false); - try - { - var response = await result.Content - .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); - if (response == null) - { - throw new ServiceException("Response was empty", true); - } - - return response; - } - catch (JsonException je) - { - throw new ServiceException(je.Message); - } - } - - public async Task CreateServiceBinding(ServiceManagementBindingItem saBinding, string serviceInstanceId, CancellationToken cancellationToken) - { - var serviceAuth = new BasicAuthSettings - { - TokenAddress = $"{saBinding.Url}/oauth/token", - ClientId = saBinding.ClientId, - ClientSecret = saBinding.ClientSecret - }; - var client = await _basicAuthTokenService.GetBasicAuthorizedClient(serviceAuth, cancellationToken).ConfigureAwait(false); - var data = new CreateServiceBindingRequest( - "cis-local-binding", - serviceInstanceId - ); - - var result = await client.PostAsJsonAsync($"{saBinding.SmUrl}/v1/service_bindings?async=false", data, JsonSerializerExtensions.Options, cancellationToken) - .CatchingIntoServiceExceptionFor("create-service-binding", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE).ConfigureAwait(false); - try - { - var response = await result.Content - .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); - if (response == null) - { - throw new ServiceException("Response was empty", true); - } - - return response; - } - catch (JsonException je) - { - throw new ServiceException(je.Message); - } - } - - public async Task GetServiceBinding(ServiceManagementBindingItem saBinding, string serviceBindingName, CancellationToken cancellationToken) - { - var serviceAuth = new BasicAuthSettings - { - TokenAddress = $"{saBinding.Url}/oauth/token", - ClientId = saBinding.ClientId, - ClientSecret = saBinding.ClientSecret - }; - var client = await _basicAuthTokenService.GetBasicAuthorizedClient(serviceAuth, cancellationToken).ConfigureAwait(false); - var result = await client.GetAsync($"{saBinding.SmUrl}/v1/service_bindings?fieldQuery=name eq '{serviceBindingName}'", cancellationToken) - .CatchingIntoServiceExceptionFor("get-service-binding", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE).ConfigureAwait(false); - try - { - var response = await result.Content - .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); - if (response == null) - { - throw new ServiceException("Response was empty", true); - } - - if (response.Items.Count() != 1) - { - throw new ServiceException($"There must be exactly one binding for {serviceBindingName}"); - } - - return response.Items.Single(); - } - catch (JsonException je) - { - throw new ServiceException(je.Message); - } - } -} diff --git a/src/clients/Dim.Clients/Api/SubAccounts/CreateSubAccountRequest.cs b/src/clients/Dim.Clients/Api/SubAccounts/CreateSubAccountRequest.cs deleted file mode 100644 index d7a7ccb..0000000 --- a/src/clients/Dim.Clients/Api/SubAccounts/CreateSubAccountRequest.cs +++ /dev/null @@ -1,51 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * See the NOTICE file(s) distributed with this work for additional - * information regarding copyright ownership. - * - * This program and the accompanying materials are made available under the - * terms of the Apache License, Version 2.0 which is available at - * https://www.apache.org/licenses/LICENSE-2.0. - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * SPDX-License-Identifier: Apache-2.0 - ********************************************************************************/ - -using System.Text.Json.Serialization; - -namespace Dim.Clients.Api.SubAccounts; - -public record CreateSubAccountRequest( - [property: JsonPropertyName("betaEnabled")] bool BetaEnabled, - [property: JsonPropertyName("description")] string Description, - [property: JsonPropertyName("displayName")] string DisplayName, - [property: JsonPropertyName("labels")] Dictionary> Labels, - [property: JsonPropertyName("origin")] string Origin, - [property: JsonPropertyName("parentGUID")] Guid ParentId, - [property: JsonPropertyName("region")] string Region, - [property: JsonPropertyName("subaccountAdmins")] IEnumerable SubaccountAdmins, - [property: JsonPropertyName("subdomain")] string Subdomain, - [property: JsonPropertyName("usedForProduction")] UsedForProduction UsedForProduction -); - -public record CreateSubaccountResponse( - [property: JsonPropertyName("guid")] Guid Id -); - -public record ServiceManagementBindingResponse( - [property: JsonPropertyName("items")] IEnumerable Items -); - -public record ServiceManagementBindingItem( - [property: JsonPropertyName("clientid")] string ClientId, - [property: JsonPropertyName("clientsecret")] string ClientSecret, - [property: JsonPropertyName("sm_url")] string SmUrl, - [property: JsonPropertyName("url")] string Url -); diff --git a/src/clients/Dim.Clients/Api/SubAccounts/DependencyInjection/SubAccountClientServiceExtensions.cs b/src/clients/Dim.Clients/Api/SubAccounts/DependencyInjection/SubAccountClientServiceExtensions.cs deleted file mode 100644 index 77be287..0000000 --- a/src/clients/Dim.Clients/Api/SubAccounts/DependencyInjection/SubAccountClientServiceExtensions.cs +++ /dev/null @@ -1,44 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Extensions; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace Dim.Clients.Api.SubAccounts.DependencyInjection; - -public static class SubAccountClientServiceExtensions -{ - public static IServiceCollection AddSubAccountClient(this IServiceCollection services, IConfigurationSection section) - { - services.AddOptions() - .Bind(section) - .ValidateOnStart(); - - var sp = services.BuildServiceProvider(); - var settings = sp.GetRequiredService>(); - services - .AddCustomHttpClientWithAuthentication(settings.Value.BaseUrl, null) - .AddTransient(); - - return services; - } -} diff --git a/src/clients/Dim.Clients/Api/SubAccounts/DependencyInjection/SubAccountSettings.cs b/src/clients/Dim.Clients/Api/SubAccounts/DependencyInjection/SubAccountSettings.cs deleted file mode 100644 index aec5c64..0000000 --- a/src/clients/Dim.Clients/Api/SubAccounts/DependencyInjection/SubAccountSettings.cs +++ /dev/null @@ -1,29 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Api.SubAccounts.DependencyInjection; - -public class SubAccountSettings -{ - [Required] - public string BaseUrl { get; set; } = null!; -} diff --git a/src/clients/Dim.Clients/Api/SubAccounts/ISubAccountClient.cs b/src/clients/Dim.Clients/Api/SubAccounts/ISubAccountClient.cs deleted file mode 100644 index 7c73a57..0000000 --- a/src/clients/Dim.Clients/Api/SubAccounts/ISubAccountClient.cs +++ /dev/null @@ -1,30 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Token; - -namespace Dim.Clients.Api.SubAccounts; - -public interface ISubAccountClient -{ - Task CreateSubaccount(BasicAuthSettings basicAuthSettings, string adminMail, string tenantName, Guid directoryId, string bpn, CancellationToken cancellationToken); - Task CreateServiceManagerBindings(BasicAuthSettings basicAuthSettings, Guid subAccountId, CancellationToken cancellationToken); - Task GetServiceManagerBindings(BasicAuthSettings basicAuthSettings, Guid subAccountId, CancellationToken cancellationToken); -} diff --git a/src/clients/Dim.Clients/Api/SubAccounts/SubAccountClient.cs b/src/clients/Dim.Clients/Api/SubAccounts/SubAccountClient.cs deleted file mode 100644 index f968336..0000000 --- a/src/clients/Dim.Clients/Api/SubAccounts/SubAccountClient.cs +++ /dev/null @@ -1,114 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Extensions; -using Dim.Clients.Token; -using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; -using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; -using System.Net.Http.Json; -using System.Text.Json; -using System.Text.RegularExpressions; - -namespace Dim.Clients.Api.SubAccounts; - -public class SubAccountClient(IBasicAuthTokenService basicAuthTokenService) : ISubAccountClient -{ - public async Task CreateSubaccount(BasicAuthSettings basicAuthSettings, string adminMail, string tenantName, Guid directoryId, string bpn, CancellationToken cancellationToken) - { - var client = await basicAuthTokenService.GetBasicAuthorizedClient(basicAuthSettings, cancellationToken).ConfigureAwait(false); - var directory = new CreateSubAccountRequest( - false, - $"CX customer sub-account {tenantName}", - tenantName, - new Dictionary> - { - { "cloud_management_service", new[] { "Created by API - Don't change it" } }, - { "tenantName", new[] { tenantName } } - }, - "API", - directoryId, - "eu10", - Enumerable.Repeat(adminMail, 1), - bpn, - UsedForProduction.USED_FOR_PRODUCTION - ); - - var result = await client.PostAsJsonAsync("/accounts/v1/subaccounts", directory, JsonSerializerExtensions.Options, cancellationToken) - .CatchingIntoServiceExceptionFor("create-subaccount", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE, async message => - { - var errorMessage = await message.Content.ReadAsStringAsync().ConfigureAwait(false); - return new(false, errorMessage); - }).ConfigureAwait(false); - try - { - var response = await result.Content - .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); - - if (response == null) - { - throw new ServiceException("Response must not be null"); - } - - return response.Id; - } - catch (JsonException je) - { - throw new ServiceException(je.Message); - } - } - - public async Task CreateServiceManagerBindings(BasicAuthSettings basicAuthSettings, Guid subAccountId, CancellationToken cancellationToken) - { - var client = await basicAuthTokenService.GetBasicAuthorizedClient(basicAuthSettings, cancellationToken).ConfigureAwait(false); - var data = new - { - name = "accessServiceManager" - }; - - await client.PostAsJsonAsync($"/accounts/v2/subaccounts/{subAccountId}/serviceManagerBindings", data, JsonSerializerExtensions.Options, cancellationToken) - .CatchingIntoServiceExceptionFor("create-subaccount", HttpAsyncResponseMessageExtension.RecoverOptions.ALLWAYS).ConfigureAwait(false); - } - - public async Task GetServiceManagerBindings(BasicAuthSettings basicAuthSettings, Guid subAccountId, CancellationToken cancellationToken) - { - var client = await basicAuthTokenService.GetBasicAuthorizedClient(basicAuthSettings, cancellationToken).ConfigureAwait(false); - - var result = await client.GetAsync($"/accounts/v2/subaccounts/{subAccountId}/serviceManagerBindings", cancellationToken) - .CatchingIntoServiceExceptionFor("create-subaccount", HttpAsyncResponseMessageExtension.RecoverOptions.ALLWAYS).ConfigureAwait(false); - try - { - var response = await result.Content - .ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken) - .ConfigureAwait(false); - - if (response == null || response.Items.Count() != 1) - { - throw new ServiceException("Response must not be null and contain exactly 1 item"); - } - - return response.Items.Single(); - } - catch (JsonException je) - { - throw new ServiceException(je.Message); - } - } -} diff --git a/src/clients/Dim.Clients/Api/SubAccounts/UsedForProduction.cs b/src/clients/Dim.Clients/Api/SubAccounts/UsedForProduction.cs deleted file mode 100644 index 619234a..0000000 --- a/src/clients/Dim.Clients/Api/SubAccounts/UsedForProduction.cs +++ /dev/null @@ -1,28 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Api.SubAccounts; - -public enum UsedForProduction -{ - USED_FOR_PRODUCTION = 1, - NOT_USED_FOR_PRODUCTION = 2, - UNSET = 3 -} diff --git a/src/clients/Dim.Clients/Api/Subscriptions/DependencyInjection/SubscriptionClientServiceExtensions.cs b/src/clients/Dim.Clients/Api/Subscriptions/DependencyInjection/SubscriptionClientServiceExtensions.cs deleted file mode 100644 index 6a6e846..0000000 --- a/src/clients/Dim.Clients/Api/Subscriptions/DependencyInjection/SubscriptionClientServiceExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Extensions; -using Microsoft.Extensions.DependencyInjection; - -namespace Dim.Clients.Api.Subscriptions.DependencyInjection; - -public static class SubscriptionClientServiceExtensions -{ - public static IServiceCollection AddSubscriptionClient(this IServiceCollection services) - { - services - .AddCustomHttpClientWithAuthentication(null, null) - .AddTransient(); - - return services; - } -} diff --git a/src/clients/Dim.Clients/Api/Subscriptions/ISubscriptionClient.cs b/src/clients/Dim.Clients/Api/Subscriptions/ISubscriptionClient.cs deleted file mode 100644 index 35fbd9a..0000000 --- a/src/clients/Dim.Clients/Api/Subscriptions/ISubscriptionClient.cs +++ /dev/null @@ -1,28 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Api.Services; - -namespace Dim.Clients.Api.Subscriptions; - -public interface ISubscriptionClient -{ - Task SubscribeApplication(string authUrl, BindingItem bindingData, string applicationName, string planName, CancellationToken cancellationToken); -} diff --git a/src/clients/Dim.Clients/Api/Subscriptions/SubscriptionClient.cs b/src/clients/Dim.Clients/Api/Subscriptions/SubscriptionClient.cs deleted file mode 100644 index 04bb6b8..0000000 --- a/src/clients/Dim.Clients/Api/Subscriptions/SubscriptionClient.cs +++ /dev/null @@ -1,55 +0,0 @@ -/******************************************************************************** - * Copyright (c) 2024 BMW Group AG - * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. - * - * 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 Dim.Clients.Api.Services; -using Dim.Clients.Extensions; -using Dim.Clients.Token; -using Org.Eclipse.TractusX.Portal.Backend.Framework.HttpClientExtensions; -using System.Net.Http.Json; - -namespace Dim.Clients.Api.Subscriptions; - -public class SubscriptionClient : ISubscriptionClient -{ - private readonly IBasicAuthTokenService _basicAuthTokenService; - - public SubscriptionClient(IBasicAuthTokenService basicAuthTokenService) - { - _basicAuthTokenService = basicAuthTokenService; - } - - public async Task SubscribeApplication(string authUrl, BindingItem bindingData, string applicationName, string planName, CancellationToken cancellationToken) - { - var authSettings = new BasicAuthSettings - { - TokenAddress = $"{authUrl}/oauth/token", - ClientId = bindingData.Credentials.Uaa.ClientId, - ClientSecret = bindingData.Credentials.Uaa.ClientSecret - }; - var client = await _basicAuthTokenService.GetBasicAuthorizedClient(authSettings, cancellationToken).ConfigureAwait(false); - var data = new - { - planName = planName - }; - - await client.PostAsJsonAsync($"{bindingData.Credentials.Endpoints.SaasRegistryServiceUrl}/saas-manager/v1/applications/{applicationName}/subscription", data, JsonSerializerExtensions.Options, cancellationToken) - .CatchingIntoServiceExceptionFor("subscribe-application", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE).ConfigureAwait(false); - } -} diff --git a/src/clients/Dim.Clients/Dim.Clients.csproj b/src/clients/Dim.Clients/Dim.Clients.csproj index 51fb073..25e2339 100644 --- a/src/clients/Dim.Clients/Dim.Clients.csproj +++ b/src/clients/Dim.Clients/Dim.Clients.csproj @@ -35,6 +35,7 @@ + diff --git a/src/clients/Dim.Clients/Token/AuthResponse.cs b/src/clients/Dim.Clients/Token/AuthResponse.cs index ac64494..c19a5dd 100644 --- a/src/clients/Dim.Clients/Token/AuthResponse.cs +++ b/src/clients/Dim.Clients/Token/AuthResponse.cs @@ -29,13 +29,3 @@ public record AuthResponse( [property: JsonPropertyName("jti")] string? Jti, [property: JsonPropertyName("scope")] string? Scope ); - -public record LegacyAuthResponse( - [property: JsonPropertyName("access_token")] string? AccessToken, - [property: JsonPropertyName("token_type")] string? TokenType, - [property: JsonPropertyName("id_token")] string? IdToken, - [property: JsonPropertyName("refresh_token")] string? RefreshToken, - [property: JsonPropertyName("expires_in")] int ExpiresIn, - [property: JsonPropertyName("scope")] string? Scope, - [property: JsonPropertyName("jti")] string? Jti -); diff --git a/src/clients/Dim.Clients/Token/BasicAuthTokenService.cs b/src/clients/Dim.Clients/Token/BasicAuthTokenService.cs index d817fbb..003ddc1 100644 --- a/src/clients/Dim.Clients/Token/BasicAuthTokenService.cs +++ b/src/clients/Dim.Clients/Token/BasicAuthTokenService.cs @@ -25,15 +25,9 @@ namespace Dim.Clients.Token; -public class BasicAuthTokenService : IBasicAuthTokenService +public class BasicAuthTokenService(IHttpClientFactory httpClientFactory) + : IBasicAuthTokenService { - private readonly IHttpClientFactory _httpClientFactory; - - public BasicAuthTokenService(IHttpClientFactory httpClientFactory) - { - _httpClientFactory = httpClientFactory; - } - public async Task GetBasicAuthorizedClient(BasicAuthSettings settings, CancellationToken cancellationToken) { var tokenParameters = new GetBasicTokenSettings( @@ -42,24 +36,9 @@ public async Task GetBasicAuthorizedClient(BasicAuthSettings sett settings.ClientSecret, settings.TokenAddress); - var token = await this.GetBasicTokenAsync(tokenParameters, cancellationToken).ConfigureAwait(false); + var token = await this.GetBasicTokenAsync(tokenParameters, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); - var httpClient = _httpClientFactory.CreateClient(typeof(T).Name); - httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); - return httpClient; - } - - public async Task GetBasicAuthorizedLegacyClient(BasicAuthSettings settings, CancellationToken cancellationToken) - { - var tokenParameters = new GetBasicTokenSettings( - $"{typeof(T).Name}Auth", - settings.ClientId, - settings.ClientSecret, - settings.TokenAddress); - - var token = await this.GetBasicLegacyToken(tokenParameters, cancellationToken).ConfigureAwait(false); - - var httpClient = _httpClientFactory.CreateClient(typeof(T).Name); + var httpClient = httpClientFactory.CreateClient(typeof(T).Name); httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); return httpClient; } @@ -71,7 +50,7 @@ public async Task GetBasicAuthorizedLegacyClient(BasicAuthSetting { "grant_type", "client_credentials" } }; var content = new FormUrlEncodedContent(formParameters); - var authClient = _httpClientFactory.CreateClient(settings.HttpClientName); + var authClient = httpClientFactory.CreateClient(settings.HttpClientName); var authenticationString = $"{settings.ClientId}:{settings.ClientSecret}"; var base64String = Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes(authenticationString)); @@ -80,29 +59,7 @@ public async Task GetBasicAuthorizedLegacyClient(BasicAuthSetting var response = await authClient.PostAsync(settings.TokenAddress, content, cancellationToken) .CatchingIntoServiceExceptionFor("token-post", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE).ConfigureAwait(false); - var responseObject = await response.Content.ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken).ConfigureAwait(false); - return responseObject?.AccessToken; - } - - private async Task GetBasicLegacyToken(GetBasicTokenSettings settings, CancellationToken cancellationToken) - { - var formParameters = new Dictionary - { - { "username", settings.ClientId }, - { "password", settings.ClientSecret }, - { "client_id", "cf" }, - { "grant_type", "password" }, - { "response_type", "token" } - }; - var content = new FormUrlEncodedContent(formParameters); - var authClient = _httpClientFactory.CreateClient(settings.HttpClientName); - var base64String = Convert.ToBase64String("cf:"u8.ToArray()); - authClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64String); - - var response = await authClient.PostAsync(settings.TokenAddress, content, cancellationToken) - .CatchingIntoServiceExceptionFor("token-post", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE).ConfigureAwait(false); - - var responseObject = await response.Content.ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken).ConfigureAwait(false); + var responseObject = await response.Content.ReadFromJsonAsync(JsonSerializerExtensions.Options, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); return responseObject?.AccessToken; } } diff --git a/src/clients/Dim.Clients/Token/GetBasicTokenSettings.cs b/src/clients/Dim.Clients/Token/GetBasicTokenSettings.cs index 54d8f03..dcd945d 100644 --- a/src/clients/Dim.Clients/Token/GetBasicTokenSettings.cs +++ b/src/clients/Dim.Clients/Token/GetBasicTokenSettings.cs @@ -21,13 +21,3 @@ namespace Dim.Clients.Token; public record GetBasicTokenSettings(string HttpClientName, string ClientId, string ClientSecret, string TokenAddress); - -public record GetTokenSettings( - string HttpClientName, - string Username, - string Password, - string ClientId, - string GrantType, - string ClientSecret, - string Scope, - string TokenUrl); diff --git a/src/clients/Dim.Clients/Token/IBasicAuthTokenService.cs b/src/clients/Dim.Clients/Token/IBasicAuthTokenService.cs index 9c37f2c..299030b 100644 --- a/src/clients/Dim.Clients/Token/IBasicAuthTokenService.cs +++ b/src/clients/Dim.Clients/Token/IBasicAuthTokenService.cs @@ -23,5 +23,4 @@ namespace Dim.Clients.Token; public interface IBasicAuthTokenService { Task GetBasicAuthorizedClient(BasicAuthSettings settings, CancellationToken cancellationToken); - Task GetBasicAuthorizedLegacyClient(BasicAuthSettings settings, CancellationToken cancellationToken); } diff --git a/src/database/Dim.DbAccess/DimRepositories.cs b/src/database/Dim.DbAccess/DimRepositories.cs index 542b173..370cbf9 100644 --- a/src/database/Dim.DbAccess/DimRepositories.cs +++ b/src/database/Dim.DbAccess/DimRepositories.cs @@ -26,27 +26,22 @@ namespace Dim.DbAccess; -public class DimRepositories : IDimRepositories +public class DimRepositories(DimDbContext dbContext) + : IDimRepositories { - private readonly DimDbContext _dbContext; - - private static readonly IReadOnlyDictionary> _types = new Dictionary> { + private static readonly IReadOnlyDictionary> Types = new Dictionary> { { typeof(IProcessStepRepository), context => new ProcessStepRepository(context) }, - { typeof(ITenantRepository), context => new TenantRepository(context) } + { typeof(ITenantRepository), context => new TenantRepository(context) }, + { typeof(ITechnicalUserRepository), context => new TechnicalUserRepository(context) } }.ToImmutableDictionary(); - public DimRepositories(DimDbContext dimDbContext) - { - _dbContext = dimDbContext; - } - public RepositoryType GetInstance() { - Object? repository = default; + object? repository = default; - if (_types.TryGetValue(typeof(RepositoryType), out var createFunc)) + if (Types.TryGetValue(typeof(RepositoryType), out var createFunc)) { - repository = createFunc(_dbContext); + repository = createFunc(dbContext); } return (RepositoryType)(repository ?? throw new ArgumentException($"unexpected type {typeof(RepositoryType).Name}", nameof(RepositoryType))); @@ -55,41 +50,17 @@ public RepositoryType GetInstance() /// public TEntity Attach(TEntity entity, Action? setOptionalParameters = null) where TEntity : class { - var attachedEntity = _dbContext.Attach(entity).Entity; + var attachedEntity = dbContext.Attach(entity).Entity; setOptionalParameters?.Invoke(attachedEntity); return attachedEntity; } - public void AttachRange(IEnumerable entities, Action setOptionalParameters) where TEntity : class - { - foreach (var entity in entities) - { - var attachedEntity = _dbContext.Attach(entity).Entity; - setOptionalParameters.Invoke(attachedEntity); - } - } - - public IEnumerable AttachRange(IEnumerable entities) where TEntity : class - { - foreach (var entity in entities) - { - yield return _dbContext.Attach(entity).Entity; - } - } - - /// - public TEntity Remove(TEntity entity) where TEntity : class - => _dbContext.Remove(entity).Entity; - - public void RemoveRange(IEnumerable entities) where TEntity : class - => _dbContext.RemoveRange(entities); - public Task SaveAsync() { try { - return _dbContext.SaveChangesAsync(); + return dbContext.SaveChangesAsync(); } catch (DbUpdateConcurrencyException e) { @@ -97,5 +68,5 @@ public Task SaveAsync() } } - public void Clear() => _dbContext.ChangeTracker.Clear(); + public void Clear() => dbContext.ChangeTracker.Clear(); } diff --git a/src/database/Dim.DbAccess/Extensions/WalletDataExtensions.cs b/src/database/Dim.DbAccess/Extensions/WalletDataExtensions.cs new file mode 100644 index 0000000..209379c --- /dev/null +++ b/src/database/Dim.DbAccess/Extensions/WalletDataExtensions.cs @@ -0,0 +1,38 @@ +using Dim.DbAccess.Models; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; + +namespace Dim.DbAccess.Extensions; + +public static class WalletDataExtensions +{ + public static (string TokenAddress, string ClientId, byte[] ClientSecret, byte[] InitializationVector, int EncryptionMode) ValidateData(this WalletData walletData) + { + var (tokenAddress, clientId, clientSecret, initializationVector, encryptionMode) = walletData; + if (string.IsNullOrWhiteSpace(tokenAddress)) + { + throw new ConflictException("TokenAddress must not be null"); + } + + if (string.IsNullOrWhiteSpace(clientId)) + { + throw new ConflictException("ClientId must not be null"); + } + + if (clientSecret == null) + { + throw new ConflictException("Secret must not be null"); + } + + if (initializationVector == null) + { + throw new ConflictException("Vector must not be null"); + } + + if (encryptionMode == null) + { + throw new ConflictException("EncryptionMode must not be null"); + } + + return (tokenAddress, clientId, clientSecret, initializationVector, encryptionMode.Value); + } +} diff --git a/src/database/Dim.DbAccess/IDimRepositories.cs b/src/database/Dim.DbAccess/IDimRepositories.cs index b054bf8..1670e59 100644 --- a/src/database/Dim.DbAccess/IDimRepositories.cs +++ b/src/database/Dim.DbAccess/IDimRepositories.cs @@ -23,17 +23,7 @@ namespace Dim.DbAccess; public interface IDimRepositories { RepositoryType GetInstance(); - - /// TEntity Attach(TEntity entity, Action? setOptionalParameters = null) where TEntity : class; - - void AttachRange(IEnumerable entities, Action setOptionalParameters) where TEntity : class; - IEnumerable AttachRange(IEnumerable entities) where TEntity : class; - - /// - TEntity Remove(TEntity entity) where TEntity : class; - - void RemoveRange(IEnumerable entities) where TEntity : class; Task SaveAsync(); void Clear(); } diff --git a/src/clients/Dim.Clients/Api/Entitlements/IEntitlementClient.cs b/src/database/Dim.DbAccess/Models/ProcessData.cs similarity index 77% rename from src/clients/Dim.Clients/Api/Entitlements/IEntitlementClient.cs rename to src/database/Dim.DbAccess/Models/ProcessData.cs index da46df6..2656d8e 100644 --- a/src/clients/Dim.Clients/Api/Entitlements/IEntitlementClient.cs +++ b/src/database/Dim.DbAccess/Models/ProcessData.cs @@ -18,11 +18,16 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -using Dim.Clients.Token; +using Dim.Entities.Enums; -namespace Dim.Clients.Api.Entitlements; +namespace Dim.DbAccess.Models; -public interface IEntitlementClient -{ - Task AssignEntitlements(BasicAuthSettings basicAuthSettings, Guid subAccountId, CancellationToken cancellationToken); -} +public record ProcessData( + Guid ProcessId, + IEnumerable ProcessStepData +); + +public record ProcessStepData( + ProcessStepTypeId ProcessStepTypeId, + ProcessStepStatusId ProcessStepStatusId +); diff --git a/src/database/Dim.Entities/Entities/VerifyProcessData.cs b/src/database/Dim.DbAccess/Models/VerifyProcessData.cs similarity index 94% rename from src/database/Dim.Entities/Entities/VerifyProcessData.cs rename to src/database/Dim.DbAccess/Models/VerifyProcessData.cs index 01805b3..e07b2af 100644 --- a/src/database/Dim.Entities/Entities/VerifyProcessData.cs +++ b/src/database/Dim.DbAccess/Models/VerifyProcessData.cs @@ -18,7 +18,9 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -namespace Dim.Entities.Entities; +using Dim.Entities.Entities; + +namespace Dim.DbAccess.Models; public record VerifyProcessData( Process? Process, diff --git a/src/database/Dim.DbAccess/Models/WalletData.cs b/src/database/Dim.DbAccess/Models/WalletData.cs new file mode 100644 index 0000000..0a9d9a5 --- /dev/null +++ b/src/database/Dim.DbAccess/Models/WalletData.cs @@ -0,0 +1,9 @@ +namespace Dim.DbAccess.Models; + +public record WalletData( + string? TokenAddress, + string? ClientId, + byte[]? ClientSecret, + byte[]? InitializationVector, + int? EncryptionMode +); diff --git a/src/database/Dim.DbAccess/Repositories/IProcessStepRepository.cs b/src/database/Dim.DbAccess/Repositories/IProcessStepRepository.cs index f9b26df..e225f61 100644 --- a/src/database/Dim.DbAccess/Repositories/IProcessStepRepository.cs +++ b/src/database/Dim.DbAccess/Repositories/IProcessStepRepository.cs @@ -18,6 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Dim.DbAccess.Models; using Dim.Entities.Entities; using Dim.Entities.Enums; @@ -35,4 +36,5 @@ public interface IProcessStepRepository void AttachAndModifyProcessSteps(IEnumerable<(Guid ProcessStepId, Action? Initialize, Action Modify)> processStepIdsInitializeModifyData); IAsyncEnumerable GetActiveProcesses(IEnumerable processTypeIds, IEnumerable processStepTypeIds, DateTimeOffset lockExpiryDate); IAsyncEnumerable<(Guid ProcessStepId, ProcessStepTypeId ProcessStepTypeId)> GetProcessStepData(Guid processId); + Task<(bool ProcessExists, VerifyProcessData ProcessData)> IsValidProcess(Guid processId, ProcessTypeId processTypeId, IEnumerable processSetpTypeIds); } diff --git a/src/database/Dim.DbAccess/Repositories/ITechnicalUserRepository.cs b/src/database/Dim.DbAccess/Repositories/ITechnicalUserRepository.cs new file mode 100644 index 0000000..533a2e8 --- /dev/null +++ b/src/database/Dim.DbAccess/Repositories/ITechnicalUserRepository.cs @@ -0,0 +1,40 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * 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 Dim.DbAccess.Models; +using Dim.Entities.Entities; + +namespace Dim.DbAccess.Repositories; + +public interface ITechnicalUserRepository +{ + void CreateTenantTechnicalUser(Guid tenantId, string technicalUserName, Guid externalId, Guid processId); + void AttachAndModifyTechnicalUser(Guid technicalUserId, Action? initialize, Action modify); + Task<(bool Exists, Guid TechnicalUserId)> GetTenantDataForTechnicalUserProcessId(Guid processId); + Task<(Guid ExternalId, WalletData WalletData)> GetTechnicalUserCallbackData(Guid technicalUserId); + Task<(bool Exists, Guid TechnicalUserId, Guid ProcessId)> GetTechnicalUserForBpn(string bpn, string technicalUserName); + void RemoveTechnicalUser(Guid technicalUserId); + Task<(Guid? WalletId, string TechnicalUserName)> GetTechnicalUserNameAndWalletId(Guid technicalUserId); + Task GetOperationIdForTechnicalUser(Guid technicalUserId); + Task<(Guid? OperationId, Guid ExternalId)> GetOperationAndExternalIdForTechnicalUser(Guid technicalUserId); + Task<(Guid? ServiceKeyId, Guid? WalletId)> GetServiceKeyAndWalletId(Guid technicalUserId); + Task GetTechnicalUserProcess(string bpn, string companyName, string technicalUserName); + Task<(Guid? WalletId, string TechnicalUserName)> GetWalletIdAndNameForTechnicalUser(Guid technicalUserId); +} diff --git a/src/database/Dim.DbAccess/Repositories/ITenantRepository.cs b/src/database/Dim.DbAccess/Repositories/ITenantRepository.cs index 1b9240e..c6e384f 100644 --- a/src/database/Dim.DbAccess/Repositories/ITenantRepository.cs +++ b/src/database/Dim.DbAccess/Repositories/ITenantRepository.cs @@ -18,9 +18,8 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Dim.DbAccess.Models; using Dim.Entities.Entities; -using System; -using System.Threading.Tasks; namespace Dim.DbAccess.Repositories; @@ -29,25 +28,14 @@ public interface ITenantRepository Tenant CreateTenant(string companyName, string bpn, string didDocumentLocation, bool isIssuer, Guid processId, Guid operatorId); Task<(bool Exists, Guid TenantId, string CompanyName, string Bpn)> GetTenantDataForProcessId(Guid processId); void AttachAndModifyTenant(Guid tenantId, Action? initialize, Action modify); - Task GetSubAccountIdByTenantId(Guid tenantId); - Task<(Guid? SubAccountId, string? ServiceInstanceId)> GetSubAccountAndServiceInstanceIdsByTenantId(Guid tenantId); - Task<(Guid? SubAccountId, string? ServiceBindingName)> GetSubAccountIdAndServiceBindingNameByTenantId(Guid tenantId); - Task GetSpaceId(Guid tenantId); - Task GetDimInstanceId(Guid tenantId); - Task<(string bpn, string? DownloadUrl, string? Did, Guid? DimInstanceId)> GetCallbackData(Guid tenantId); - Task<(Guid? DimInstanceId, string HostingUrl, bool IsIssuer)> GetDimInstanceIdAndHostingUrl(Guid tenantId); - Task<(string? ApplicationId, Guid? CompanyId, Guid? DimInstanceId, bool IsIssuer)> GetApplicationAndCompanyId(Guid tenantId); - Task<(bool Exists, Guid? CompanyId, Guid? InstanceId)> GetCompanyAndInstanceIdForBpn(string bpn); + Task<(bool IsIssuer, string? HostingUrl)> GetHostingUrlAndIsIssuer(Guid tenantId); Task<(bool Exists, Guid TenantId)> GetTenantForBpn(string bpn); - void CreateTenantTechnicalUser(Guid tenantId, string technicalUserName, Guid externalId, Guid processId); - void AttachAndModifyTechnicalUser(Guid technicalUserId, Action? initialize, Action modify); - Task<(bool Exists, Guid TechnicalUserId, string CompanyName, string Bpn)> GetTenantDataForTechnicalUserProcessId(Guid processId); - Task<(Guid? spaceId, string technicalUserName)> GetSpaceIdAndTechnicalUserName(Guid technicalUserId); - Task<(Guid ExternalId, string? TokenAddress, string? ClientId, byte[]? ClientSecret, byte[]? InitializationVector, int? EncryptionMode)> GetTechnicalUserCallbackData(Guid technicalUserId); - Task<(Guid? DimInstanceId, Guid? CompanyId)> GetDimInstanceIdAndDid(Guid tenantId); - Task<(bool Exists, Guid TechnicalUserId, Guid ProcessId)> GetTechnicalUserForBpn(string bpn, string technicalUserName); - Task GetExternalIdForTechnicalUser(Guid technicalUserId); - void RemoveTechnicalUser(Guid technicalUserId); Task IsTenantExisting(string companyName, string bpn); - Task GetTenantBpn(Guid tenantId); + Task GetOperationId(Guid tenantId); + Task<(string? BaseUrl, WalletData WalletData)> GetCompanyRequestData(Guid tenantId); + Task<(bool Exists, Guid? CompanyId, string? BaseUrl, WalletData WalletData)> GetCompanyAndWalletDataForBpn(string bpn); + Task<(Guid? CompanyId, string? BaseUrl, WalletData WalletData)> GetStatusListCreationData(Guid tenantId); + Task<(string Bpn, string? BaseUrl, WalletData WalletData, string? Did, string? DownloadUrl)> GetCallbackData(Guid tenantId); + Task<(string? DownloadUrl, bool IsIssuer)> GetDownloadUrlAndIsIssuer(Guid tenantId); + Task GetWalletProcessForTenant(string bpn, string companyName); } diff --git a/src/database/Dim.DbAccess/Repositories/ProcessStepRepository.cs b/src/database/Dim.DbAccess/Repositories/ProcessStepRepository.cs index cc66c45..3e7d703 100644 --- a/src/database/Dim.DbAccess/Repositories/ProcessStepRepository.cs +++ b/src/database/Dim.DbAccess/Repositories/ProcessStepRepository.cs @@ -18,6 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Dim.DbAccess.Models; using Dim.Entities; using Dim.Entities.Entities; using Dim.Entities.Enums; @@ -26,29 +27,19 @@ namespace Dim.DbAccess.Repositories; -public class ProcessStepRepository : IProcessStepRepository +public class ProcessStepRepository(DimDbContext dbContext) + : IProcessStepRepository { - private readonly DimDbContext _context; - - /// - /// Constructor - /// - /// DimDb context. - public ProcessStepRepository(DimDbContext dimDbContext) - { - _context = dimDbContext; - } - public Process CreateProcess(ProcessTypeId processTypeId) => - _context.Add(new Process(Guid.NewGuid(), processTypeId, Guid.NewGuid())).Entity; + dbContext.Add(new Process(Guid.NewGuid(), processTypeId, Guid.NewGuid())).Entity; - public ProcessStep CreateProcessStep(Dim.Entities.Enums.ProcessStepTypeId processStepTypeId, Dim.Entities.Enums.ProcessStepStatusId processStepStatusId, Guid processId) => - _context.Add(new ProcessStep(Guid.NewGuid(), processStepTypeId, processStepStatusId, processId, DateTimeOffset.UtcNow)).Entity; + public ProcessStep CreateProcessStep(ProcessStepTypeId processStepTypeId, ProcessStepStatusId processStepStatusId, Guid processId) => + dbContext.Add(new ProcessStep(Guid.NewGuid(), processStepTypeId, processStepStatusId, processId, DateTimeOffset.UtcNow)).Entity; - public IEnumerable CreateProcessStepRange(IEnumerable<(Dim.Entities.Enums.ProcessStepTypeId ProcessStepTypeId, Dim.Entities.Enums.ProcessStepStatusId ProcessStepStatusId, Guid ProcessId)> processStepTypeStatus) + public IEnumerable CreateProcessStepRange(IEnumerable<(ProcessStepTypeId ProcessStepTypeId, ProcessStepStatusId ProcessStepStatusId, Guid ProcessId)> processStepTypeStatus) { var processSteps = processStepTypeStatus.Select(x => new ProcessStep(Guid.NewGuid(), x.ProcessStepTypeId, x.ProcessStepStatusId, x.ProcessId, DateTimeOffset.UtcNow)).ToList(); - _context.AddRange(processSteps); + dbContext.AddRange(processSteps); return processSteps; } @@ -56,7 +47,7 @@ public void AttachAndModifyProcessStep(Guid processStepId, Action? { var step = new ProcessStep(processStepId, default, default, Guid.Empty, default); initialize?.Invoke(step); - _context.Attach(step); + dbContext.Attach(step); step.DateLastChanged = DateTimeOffset.UtcNow; modify(step); } @@ -69,7 +60,7 @@ public void AttachAndModifyProcessSteps(IEnumerable<(Guid ProcessStepId, Action< data.Initialize?.Invoke(step); return (Step: step, data.Modify); }).ToList(); - _context.AttachRange(stepModifyData.Select(data => data.Step)); + dbContext.AttachRange(stepModifyData.Select(data => data.Step)); stepModifyData.ForEach(data => { data.Step.DateLastChanged = DateTimeOffset.UtcNow; @@ -78,7 +69,7 @@ public void AttachAndModifyProcessSteps(IEnumerable<(Guid ProcessStepId, Action< } public IAsyncEnumerable GetActiveProcesses(IEnumerable processTypeIds, IEnumerable processStepTypeIds, DateTimeOffset lockExpiryDate) => - _context.Processes + dbContext.Processes .AsNoTracking() .Where(process => processTypeIds.Contains(process.ProcessTypeId) && @@ -87,7 +78,7 @@ public IAsyncEnumerable GetActiveProcesses(IEnumerable p .AsAsyncEnumerable(); public IAsyncEnumerable<(Guid ProcessStepId, ProcessStepTypeId ProcessStepTypeId)> GetProcessStepData(Guid processId) => - _context.ProcessSteps + dbContext.ProcessSteps .AsNoTracking() .Where(step => step.ProcessId == processId && @@ -98,4 +89,19 @@ public IAsyncEnumerable GetActiveProcesses(IEnumerable p step.Id, step.ProcessStepTypeId)) .AsAsyncEnumerable(); + + public Task<(bool ProcessExists, VerifyProcessData ProcessData)> IsValidProcess(Guid processId, ProcessTypeId processTypeId, IEnumerable processStepTypeIds) => + dbContext.Processes + .AsNoTracking() + .Where(x => x.Id == processId && x.ProcessTypeId == processTypeId) + .Select(x => new ValueTuple( + true, + new VerifyProcessData( + x, + x.ProcessSteps + .Where(step => + processStepTypeIds.Contains(step.ProcessStepTypeId) && + step.ProcessStepStatusId == ProcessStepStatusId.TODO)) + )) + .SingleOrDefaultAsync(); } diff --git a/src/database/Dim.DbAccess/Repositories/TechnicalUserRepository.cs b/src/database/Dim.DbAccess/Repositories/TechnicalUserRepository.cs new file mode 100644 index 0000000..6173e46 --- /dev/null +++ b/src/database/Dim.DbAccess/Repositories/TechnicalUserRepository.cs @@ -0,0 +1,117 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * 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 Dim.DbAccess.Models; +using Dim.Entities; +using Dim.Entities.Entities; +using Dim.Entities.Enums; +using Microsoft.EntityFrameworkCore; + +namespace Dim.DbAccess.Repositories; + +public class TechnicalUserRepository(DimDbContext dbContext) + : ITechnicalUserRepository +{ + public void CreateTenantTechnicalUser(Guid tenantId, string technicalUserName, Guid externalId, Guid processId) => + dbContext.TechnicalUsers.Add(new TechnicalUser(Guid.NewGuid(), tenantId, externalId, technicalUserName, processId)); + + public void AttachAndModifyTechnicalUser(Guid technicalUserId, Action? initialize, Action modify) + { + var technicalUser = new TechnicalUser(technicalUserId, Guid.Empty, Guid.Empty, null!, Guid.Empty); + initialize?.Invoke(technicalUser); + dbContext.TechnicalUsers.Attach(technicalUser); + modify(technicalUser); + } + + public Task<(bool Exists, Guid TechnicalUserId)> GetTenantDataForTechnicalUserProcessId(Guid processId) => + dbContext.TechnicalUsers + .Where(x => x.ProcessId == processId) + .Select(x => new ValueTuple(true, x.Id)) + .SingleOrDefaultAsync(); + + public Task<(Guid ExternalId, WalletData WalletData)> GetTechnicalUserCallbackData(Guid technicalUserId) => + dbContext.TechnicalUsers + .Where(x => x.Id == technicalUserId) + .Select(x => new ValueTuple( + x.ExternalId, + new WalletData( + x.TokenAddress, + x.ClientId, + x.ClientSecret, + x.InitializationVector, + x.EncryptionMode))) + .SingleOrDefaultAsync(); + + public Task<(bool Exists, Guid TechnicalUserId, Guid ProcessId)> GetTechnicalUserForBpn(string bpn, string technicalUserName) => + dbContext.TechnicalUsers + .Where(x => x.TechnicalUserName == technicalUserName && x.Tenant!.Bpn == bpn) + .Select(x => new ValueTuple(true, x.Id, x.ProcessId)) + .SingleOrDefaultAsync(); + + public void RemoveTechnicalUser(Guid technicalUserId) => + dbContext.TechnicalUsers + .Remove(new TechnicalUser(technicalUserId, Guid.Empty, Guid.Empty, null!, Guid.Empty)); + + public Task<(Guid? WalletId, string TechnicalUserName)> GetTechnicalUserNameAndWalletId(Guid technicalUserId) => + dbContext.TechnicalUsers + .Where(x => x.Id == technicalUserId) + .Select(x => new ValueTuple(x.Tenant!.WalletId, x.TechnicalUserName)) + .SingleOrDefaultAsync(); + + public Task GetOperationIdForTechnicalUser(Guid technicalUserId) => + dbContext.TechnicalUsers + .Where(x => x.Id == technicalUserId) + .Select(x => x.OperationId) + .SingleOrDefaultAsync(); + + public Task<(Guid? OperationId, Guid ExternalId)> GetOperationAndExternalIdForTechnicalUser(Guid technicalUserId) => + dbContext.TechnicalUsers + .Where(x => x.Id == technicalUserId) + .Select(x => new ValueTuple(x.OperationId, x.ExternalId)) + .SingleOrDefaultAsync(); + + public Task<(Guid? ServiceKeyId, Guid? WalletId)> GetServiceKeyAndWalletId(Guid technicalUserId) => + dbContext.TechnicalUsers + .Where(x => x.Id == technicalUserId) + .Select(x => new ValueTuple( + x.ServiceKeyId, + x.Tenant!.WalletId)) + .SingleOrDefaultAsync(); + + public Task GetTechnicalUserProcess(string bpn, string companyName, string technicalUserName) => + dbContext.TechnicalUsers + .Where(x => + x.TechnicalUserName == technicalUserName && + x.Tenant!.Bpn == bpn && + x.Tenant!.CompanyName == companyName && + x.Process!.ProcessTypeId == ProcessTypeId.TECHNICAL_USER) + .Select(x => new ProcessData( + x.ProcessId, + x.Process!.ProcessSteps.Select(ps => new ProcessStepData( + ps.ProcessStepTypeId, + ps.ProcessStepStatusId)))) + .SingleOrDefaultAsync(); + + public Task<(Guid? WalletId, string TechnicalUserName)> GetWalletIdAndNameForTechnicalUser(Guid technicalUserId) => + dbContext.TechnicalUsers + .Where(x => x.Id == technicalUserId) + .Select(x => new ValueTuple(x.Tenant!.WalletId, x.TechnicalUserName)) + .SingleOrDefaultAsync(); +} diff --git a/src/database/Dim.DbAccess/Repositories/TenantRepository.cs b/src/database/Dim.DbAccess/Repositories/TenantRepository.cs index 1ff7088..2a6d55a 100644 --- a/src/database/Dim.DbAccess/Repositories/TenantRepository.cs +++ b/src/database/Dim.DbAccess/Repositories/TenantRepository.cs @@ -18,21 +18,22 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Dim.DbAccess.Models; using Dim.Entities; using Dim.Entities.Entities; +using Dim.Entities.Enums; using Microsoft.EntityFrameworkCore; -using System; -using System.Threading.Tasks; namespace Dim.DbAccess.Repositories; -public class TenantRepository(DimDbContext context) : ITenantRepository +public class TenantRepository(DimDbContext dbContext) + : ITenantRepository { public Tenant CreateTenant(string companyName, string bpn, string didDocumentLocation, bool isIssuer, Guid processId, Guid operatorId) => - context.Tenants.Add(new Tenant(Guid.NewGuid(), companyName, bpn, didDocumentLocation, isIssuer, processId, operatorId)).Entity; + dbContext.Tenants.Add(new Tenant(Guid.NewGuid(), companyName, bpn, didDocumentLocation, isIssuer, processId, operatorId)).Entity; public Task<(bool Exists, Guid TenantId, string CompanyName, string Bpn)> GetTenantDataForProcessId(Guid processId) => - context.Tenants + dbContext.Tenants .Where(x => x.ProcessId == processId) .Select(x => new ValueTuple(true, x.Id, x.CompanyName, x.Bpn)) .SingleOrDefaultAsync(); @@ -41,136 +42,109 @@ public void AttachAndModifyTenant(Guid tenantId, Action? initialize, Act { var tenant = new Tenant(tenantId, null!, null!, null!, default, Guid.Empty, Guid.Empty); initialize?.Invoke(tenant); - context.Tenants.Attach(tenant); + dbContext.Tenants.Attach(tenant); modify(tenant); } - public Task GetSubAccountIdByTenantId(Guid tenantId) - => context.Tenants + public Task<(bool IsIssuer, string? HostingUrl)> GetHostingUrlAndIsIssuer(Guid tenantId) + => dbContext.Tenants .Where(x => x.Id == tenantId) - .Select(x => x.SubAccountId) + .Select(x => new ValueTuple(x.IsIssuer, x.DidDocumentLocation)) .SingleOrDefaultAsync(); - public Task<(Guid? SubAccountId, string? ServiceInstanceId)> GetSubAccountAndServiceInstanceIdsByTenantId(Guid tenantId) - => context.Tenants - .Where(x => x.Id == tenantId) - .Select(x => new ValueTuple(x.SubAccountId, x.ServiceInstanceId)) - .SingleOrDefaultAsync(); - - public Task<(Guid? SubAccountId, string? ServiceBindingName)> GetSubAccountIdAndServiceBindingNameByTenantId(Guid tenantId) - => context.Tenants - .Where(x => x.Id == tenantId) - .Select(x => new ValueTuple(x.SubAccountId, x.ServiceBindingName)) - .SingleOrDefaultAsync(); - - public Task GetSpaceId(Guid tenantId) - => context.Tenants - .Where(x => x.Id == tenantId) - .Select(x => x.SpaceId) + public Task<(bool Exists, Guid TenantId)> GetTenantForBpn(string bpn) => + dbContext.Tenants.Where(x => x.Bpn == bpn) + .Select(x => new ValueTuple(true, x.Id)) .SingleOrDefaultAsync(); - public Task GetDimInstanceId(Guid tenantId) - => context.Tenants - .Where(x => x.Id == tenantId) - .Select(x => x.DimInstanceId) - .SingleOrDefaultAsync(); + public Task IsTenantExisting(string companyName, string bpn) => + dbContext.Tenants + .AnyAsync(x => x.CompanyName == companyName && x.Bpn == bpn); - public Task<(string bpn, string? DownloadUrl, string? Did, Guid? DimInstanceId)> GetCallbackData(Guid tenantId) - => context.Tenants + public Task GetOperationId(Guid tenantId) => + dbContext.Tenants .Where(x => x.Id == tenantId) - .Select(x => new ValueTuple(x.Bpn, x.DidDownloadUrl, x.Did, x.DimInstanceId)) + .Select(x => x.OperationId) .SingleOrDefaultAsync(); - public Task<(Guid? DimInstanceId, string HostingUrl, bool IsIssuer)> GetDimInstanceIdAndHostingUrl(Guid tenantId) - => context.Tenants + public Task<(string? BaseUrl, WalletData WalletData)> GetCompanyRequestData(Guid tenantId) => + dbContext.Tenants .Where(x => x.Id == tenantId) - .Select(x => new ValueTuple(x.DimInstanceId, x.DidDocumentLocation, x.IsIssuer)) - .SingleOrDefaultAsync(); - - public Task<(string? ApplicationId, Guid? CompanyId, Guid? DimInstanceId, bool IsIssuer)> GetApplicationAndCompanyId(Guid tenantId) => - context.Tenants + .Select(x => new ValueTuple( + x.BaseUrl, + new WalletData( + x.TokenAddress, + x.ClientId, + x.ClientSecret, + x.InitializationVector, + x.EncryptionMode + ))) + .SingleOrDefaultAsync(); + + public Task<(bool Exists, Guid? CompanyId, string? BaseUrl, WalletData WalletData)> GetCompanyAndWalletDataForBpn(string bpn) => + dbContext.Tenants + .Where(x => x.Bpn == bpn) + .Select(x => new ValueTuple( + true, + x.CompanyId, + x.BaseUrl, + new WalletData( + x.TokenAddress, + x.ClientId, + x.ClientSecret, + x.InitializationVector, + x.EncryptionMode + ))) + .SingleOrDefaultAsync(); + + public Task<(Guid? CompanyId, string? BaseUrl, WalletData WalletData)> GetStatusListCreationData(Guid tenantId) => + dbContext.Tenants .Where(x => x.Id == tenantId) - .Select(x => new ValueTuple( - x.ApplicationId, + .Select(x => new ValueTuple( x.CompanyId, - x.DimInstanceId, - x.IsIssuer)) - .SingleOrDefaultAsync(); - - public Task<(bool Exists, Guid? CompanyId, Guid? InstanceId)> GetCompanyAndInstanceIdForBpn(string bpn) => - context.Tenants.Where(x => x.Bpn == bpn) - .Select(x => new ValueTuple(true, x.CompanyId, x.DimInstanceId)) - .SingleOrDefaultAsync(); - - public void CreateTenantTechnicalUser(Guid tenantId, string technicalUserName, Guid externalId, Guid processId) => - context.TechnicalUsers.Add(new TechnicalUser(Guid.NewGuid(), tenantId, externalId, technicalUserName, processId)); - - public void AttachAndModifyTechnicalUser(Guid technicalUserId, Action? initialize, Action modify) - { - var technicalUser = new TechnicalUser(technicalUserId, Guid.Empty, Guid.Empty, null!, Guid.Empty); - initialize?.Invoke(technicalUser); - context.TechnicalUsers.Attach(technicalUser); - modify(technicalUser); - } - - public Task<(bool Exists, Guid TenantId)> GetTenantForBpn(string bpn) => - context.Tenants.Where(x => x.Bpn == bpn) - .Select(x => new ValueTuple(true, x.Id)) - .SingleOrDefaultAsync(); - - public Task<(bool Exists, Guid TechnicalUserId, string CompanyName, string Bpn)> GetTenantDataForTechnicalUserProcessId(Guid processId) => - context.TechnicalUsers - .Where(x => x.ProcessId == processId) - .Select(x => new ValueTuple(true, x.Id, x.Tenant!.CompanyName, x.Tenant.Bpn)) - .SingleOrDefaultAsync(); - - public Task<(Guid? spaceId, string technicalUserName)> GetSpaceIdAndTechnicalUserName(Guid technicalUserId) => - context.TechnicalUsers - .Where(x => x.Id == technicalUserId) - .Select(x => new ValueTuple(x.Tenant!.SpaceId, x.TechnicalUserName)) - .SingleOrDefaultAsync(); - - public Task<(Guid ExternalId, string? TokenAddress, string? ClientId, byte[]? ClientSecret, byte[]? InitializationVector, int? EncryptionMode)> GetTechnicalUserCallbackData(Guid technicalUserId) => - context.TechnicalUsers - .Where(x => x.Id == technicalUserId) - .Select(x => new ValueTuple( - x.ExternalId, - x.TokenAddress, - x.ClientId, - x.ClientSecret, - x.InitializationVector, - x.EncryptionMode)) - .SingleOrDefaultAsync(); - - public Task<(Guid? DimInstanceId, Guid? CompanyId)> GetDimInstanceIdAndDid(Guid tenantId) => - context.Tenants + x.BaseUrl, + new WalletData( + x.TokenAddress, + x.ClientId, + x.ClientSecret, + x.InitializationVector, + x.EncryptionMode + ))) + .SingleOrDefaultAsync(); + + public Task<(string Bpn, string? BaseUrl, WalletData WalletData, string? Did, string? DownloadUrl)> GetCallbackData(Guid tenantId) => + dbContext.Tenants .Where(x => x.Id == tenantId) - .Select(x => new ValueTuple(x.DimInstanceId, x.CompanyId)) - .SingleOrDefaultAsync(); - - public Task<(bool Exists, Guid TechnicalUserId, Guid ProcessId)> GetTechnicalUserForBpn(string bpn, string technicalUserName) => - context.TechnicalUsers - .Where(x => x.TechnicalUserName == technicalUserName && x.Tenant!.Bpn == bpn) - .Select(x => new ValueTuple(true, x.Id, x.ProcessId)) + .Select(x => new ValueTuple( + x.Bpn, + x.BaseUrl, + new WalletData( + x.TokenAddress, + x.ClientId, + x.ClientSecret, + x.InitializationVector, + x.EncryptionMode + ), + x.Did, + x.DidDownloadUrl)) + .SingleOrDefaultAsync(); + + public Task<(string? DownloadUrl, bool IsIssuer)> GetDownloadUrlAndIsIssuer(Guid tenantId) => + dbContext.Tenants + .Where(x => x.Id == tenantId) + .Select(x => new ValueTuple(x.DidDownloadUrl, x.IsIssuer)) .SingleOrDefaultAsync(); - public Task GetExternalIdForTechnicalUser(Guid technicalUserId) => - context.TechnicalUsers - .Where(x => x.Id == technicalUserId) - .Select(x => x.ExternalId) + public Task GetWalletProcessForTenant(string bpn, string companyName) => + dbContext.Tenants + .Where(x => + x.Bpn == bpn && + x.CompanyName == companyName && + x.Process!.ProcessTypeId == ProcessTypeId.SETUP_DIM) + .Select(x => new ProcessData( + x.ProcessId, + x.Process!.ProcessSteps.Select(ps => new ProcessStepData( + ps.ProcessStepTypeId, + ps.ProcessStepStatusId)))) .SingleOrDefaultAsync(); - - public void RemoveTechnicalUser(Guid technicalUserId) => - context.TechnicalUsers - .Remove(new TechnicalUser(technicalUserId, Guid.Empty, Guid.Empty, null!, Guid.Empty)); - - public Task IsTenantExisting(string companyName, string bpn) => - context.Tenants - .AnyAsync(x => x.CompanyName == companyName && x.Bpn == bpn); - - public Task GetTenantBpn(Guid tenantId) => - context.Tenants - .Where(x => x.Id == tenantId) - .Select(x => x.Bpn) - .SingleAsync(); } diff --git a/src/database/Dim.Entities/Entities/ProcessStep.cs b/src/database/Dim.Entities/Entities/ProcessStep.cs index 09b1dd4..513d5ad 100644 --- a/src/database/Dim.Entities/Entities/ProcessStep.cs +++ b/src/database/Dim.Entities/Entities/ProcessStep.cs @@ -22,26 +22,22 @@ namespace Dim.Entities.Entities; -public class ProcessStep +public class ProcessStep( + Guid id, + ProcessStepTypeId processStepTypeId, + ProcessStepStatusId processStepStatusId, + Guid processId, + DateTimeOffset dateCreated) { - public ProcessStep(Guid id, ProcessStepTypeId processStepTypeId, ProcessStepStatusId processStepStatusId, Guid processId, DateTimeOffset dateCreated) - { - Id = id; - ProcessStepTypeId = processStepTypeId; - ProcessStepStatusId = processStepStatusId; - ProcessId = processId; - DateCreated = dateCreated; - } + public Guid Id { get; private set; } = id; - public Guid Id { get; private set; } + public ProcessStepTypeId ProcessStepTypeId { get; private set; } = processStepTypeId; - public ProcessStepTypeId ProcessStepTypeId { get; private set; } + public ProcessStepStatusId ProcessStepStatusId { get; set; } = processStepStatusId; - public ProcessStepStatusId ProcessStepStatusId { get; set; } + public Guid ProcessId { get; private set; } = processId; - public Guid ProcessId { get; private set; } - - public DateTimeOffset DateCreated { get; private set; } + public DateTimeOffset DateCreated { get; private set; } = dateCreated; public DateTimeOffset? DateLastChanged { get; set; } diff --git a/src/database/Dim.Entities/Entities/TechnicalUser.cs b/src/database/Dim.Entities/Entities/TechnicalUser.cs index 5b64a6e..9bd5df4 100644 --- a/src/database/Dim.Entities/Entities/TechnicalUser.cs +++ b/src/database/Dim.Entities/Entities/TechnicalUser.cs @@ -31,11 +31,13 @@ public class TechnicalUser( public Guid TenantId { get; set; } = tenantId; public Guid ExternalId { get; set; } = externalId; public string TechnicalUserName { get; set; } = technicalUserName; + public Guid? OperationId { get; set; } public string? TokenAddress { get; set; } public string? ClientId { get; set; } public byte[]? ClientSecret { get; set; } public byte[]? InitializationVector { get; set; } public int? EncryptionMode { get; set; } + public Guid? ServiceKeyId { get; set; } public Guid ProcessId { get; set; } = processId; public virtual Tenant? Tenant { get; set; } public virtual Process? Process { get; set; } diff --git a/src/database/Dim.Entities/Entities/Tenant.cs b/src/database/Dim.Entities/Entities/Tenant.cs index 03053ec..6536806 100644 --- a/src/database/Dim.Entities/Entities/Tenant.cs +++ b/src/database/Dim.Entities/Entities/Tenant.cs @@ -32,28 +32,21 @@ public class Tenant( public Guid Id { get; set; } = id; public string CompanyName { get; set; } = companyName; public string Bpn { get; set; } = bpn; - public string DidDocumentLocation { get; set; } = didDocumentLocation; - public bool IsIssuer { get; set; } = isIssuer; - + public Guid OperatorId { get; set; } = operatorId; public Guid ProcessId { get; set; } = processId; - - public Guid? SubAccountId { get; set; } - - public string? ServiceInstanceId { get; set; } - - public string? ServiceBindingName { get; set; } - - public Guid? SpaceId { get; set; } - - public Guid? DimInstanceId { get; set; } + public Guid? OperationId { get; set; } + public Guid? WalletId { get; set; } + public string? TokenAddress { get; set; } + public string? BaseUrl { get; set; } + public string? ClientId { get; set; } + public byte[]? ClientSecret { get; set; } + public byte[]? InitializationVector { get; set; } + public int? EncryptionMode { get; set; } + public Guid? CompanyId { get; set; } public string? DidDownloadUrl { get; set; } public string? Did { get; set; } - public string? ApplicationId { get; set; } - public Guid? CompanyId { get; set; } - public string? ApplicationKey { get; set; } - public Guid OperatorId { get; set; } = operatorId; public virtual Process? Process { get; set; } public virtual ICollection TechnicalUsers { get; private set; } = new HashSet(); } diff --git a/src/database/Dim.Entities/Enums/ProcessStepTypeId.cs b/src/database/Dim.Entities/Enums/ProcessStepTypeId.cs index c11b7de..6904e4f 100644 --- a/src/database/Dim.Entities/Enums/ProcessStepTypeId.cs +++ b/src/database/Dim.Entities/Enums/ProcessStepTypeId.cs @@ -23,31 +23,32 @@ namespace Dim.Entities.Enums; public enum ProcessStepTypeId { // Setup Dim Process - CREATE_SUBACCOUNT = 1, - CREATE_SERVICEMANAGER_BINDINGS = 2, - ASSIGN_ENTITLEMENTS = 3, - CREATE_SERVICE_INSTANCE = 4, - CREATE_SERVICE_BINDING = 5, - SUBSCRIBE_APPLICATION = 6, - CREATE_CLOUD_FOUNDRY_ENVIRONMENT = 7, - CREATE_CLOUD_FOUNDRY_SPACE = 8, - ADD_SPACE_MANAGER_ROLE = 9, - ADD_SPACE_DEVELOPER_ROLE = 10, - CREATE_DIM_SERVICE_INSTANCE = 11, - CREATE_SERVICE_INSTANCE_BINDING = 12, - GET_DIM_DETAILS = 13, - CREATE_APPLICATION = 14, - CREATE_COMPANY_IDENTITY = 15, - ASSIGN_COMPANY_APPLICATION = 16, - CREATE_STATUS_LIST = 17, - SEND_CALLBACK = 18, + CREATE_WALLET = 1, + CHECK_OPERATION = 2, + GET_COMPANY = 3, + GET_DID_DOCUMENT = 4, + CREATE_STATUS_LIST = 5, + SEND_CALLBACK = 6, + RETRIGGER_CREATE_WALLET = 7, + RETRIGGER_CHECK_OPERATION = 8, + RETRIGGER_GET_COMPANY = 9, + RETRIGGER_GET_DID_DOCUMENT = 10, + RETRIGGER_CREATE_STATUS_LIST = 11, + RETRIGGER_SEND_CALLBACK = 12, // Create Technical User CREATE_TECHNICAL_USER = 100, GET_TECHNICAL_USER_DATA = 101, - SEND_TECHNICAL_USER_CREATION_CALLBACK = 102, + GET_TECHNICAL_USER_SERVICE_KEY = 102, + SEND_TECHNICAL_USER_CREATION_CALLBACK = 103, + RETRIGGER_CREATE_TECHNICAL_USER = 104, + RETRIGGER_GET_TECHNICAL_USER_DATA = 105, + RETRIGGER_GET_TECHNICAL_USER_SERVICE_KEY = 106, + RETRIGGER_SEND_TECHNICAL_USER_CREATION_CALLBACK = 107, // Delete Technical User DELETE_TECHNICAL_USER = 200, - SEND_TECHNICAL_USER_DELETION_CALLBACK = 201 + SEND_TECHNICAL_USER_DELETION_CALLBACK = 201, + RETRIGGER_DELETE_TECHNICAL_USER = 202, + RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK = 203 } diff --git a/src/database/Dim.Entities/Extensions/ProcessStepTypeIdExtensions.cs b/src/database/Dim.Entities/Extensions/ProcessStepTypeIdExtensions.cs new file mode 100644 index 0000000..000b77c --- /dev/null +++ b/src/database/Dim.Entities/Extensions/ProcessStepTypeIdExtensions.cs @@ -0,0 +1,42 @@ +using Dim.Entities.Enums; + +namespace Dim.Entities.Extensions; + +public static class ProcessStepTypeIdExtensions +{ + public static ProcessStepTypeId GetRetriggerStep(this ProcessStepTypeId processStepTypeId, ProcessTypeId processTypeId) => + processStepTypeId switch + { + ProcessStepTypeId.CREATE_WALLET when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.RETRIGGER_CREATE_WALLET, + ProcessStepTypeId.CHECK_OPERATION when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.RETRIGGER_CHECK_OPERATION, + ProcessStepTypeId.GET_COMPANY when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.RETRIGGER_GET_COMPANY, + ProcessStepTypeId.GET_DID_DOCUMENT when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.RETRIGGER_GET_DID_DOCUMENT, + ProcessStepTypeId.CREATE_STATUS_LIST when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.RETRIGGER_CREATE_STATUS_LIST, + ProcessStepTypeId.SEND_CALLBACK when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.RETRIGGER_SEND_CALLBACK, + ProcessStepTypeId.CREATE_TECHNICAL_USER when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.RETRIGGER_CREATE_TECHNICAL_USER, + ProcessStepTypeId.GET_TECHNICAL_USER_DATA when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.RETRIGGER_GET_TECHNICAL_USER_DATA, + ProcessStepTypeId.GET_TECHNICAL_USER_SERVICE_KEY when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.RETRIGGER_GET_TECHNICAL_USER_SERVICE_KEY, + ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.RETRIGGER_SEND_TECHNICAL_USER_CREATION_CALLBACK, + ProcessStepTypeId.DELETE_TECHNICAL_USER when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.RETRIGGER_DELETE_TECHNICAL_USER, + ProcessStepTypeId.SEND_TECHNICAL_USER_DELETION_CALLBACK when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK, + _ => throw new ArgumentOutOfRangeException(nameof(processStepTypeId), processStepTypeId, null) + }; + + public static ProcessStepTypeId GetStepForRetrigger(this ProcessStepTypeId processStepTypeId, ProcessTypeId processTypeId) => + processStepTypeId switch + { + ProcessStepTypeId.RETRIGGER_CREATE_WALLET when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.CREATE_WALLET, + ProcessStepTypeId.RETRIGGER_CHECK_OPERATION when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.CHECK_OPERATION, + ProcessStepTypeId.RETRIGGER_GET_COMPANY when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.GET_COMPANY, + ProcessStepTypeId.RETRIGGER_GET_DID_DOCUMENT when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.GET_DID_DOCUMENT, + ProcessStepTypeId.RETRIGGER_CREATE_STATUS_LIST when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.CREATE_STATUS_LIST, + ProcessStepTypeId.RETRIGGER_SEND_CALLBACK when processTypeId is ProcessTypeId.SETUP_DIM => ProcessStepTypeId.SEND_CALLBACK, + ProcessStepTypeId.RETRIGGER_CREATE_TECHNICAL_USER when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.CREATE_TECHNICAL_USER, + ProcessStepTypeId.RETRIGGER_GET_TECHNICAL_USER_DATA when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.GET_TECHNICAL_USER_DATA, + ProcessStepTypeId.RETRIGGER_GET_TECHNICAL_USER_SERVICE_KEY when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.GET_TECHNICAL_USER_SERVICE_KEY, + ProcessStepTypeId.RETRIGGER_SEND_TECHNICAL_USER_CREATION_CALLBACK when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK, + ProcessStepTypeId.RETRIGGER_DELETE_TECHNICAL_USER when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.DELETE_TECHNICAL_USER, + ProcessStepTypeId.RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK when processTypeId is ProcessTypeId.TECHNICAL_USER => ProcessStepTypeId.SEND_TECHNICAL_USER_DELETION_CALLBACK, + _ => throw new ArgumentOutOfRangeException(nameof(processStepTypeId), processStepTypeId, null) + }; +} diff --git a/src/database/Dim.Migrations/Migrations/20241001062337_2.0.0.Designer.cs b/src/database/Dim.Migrations/Migrations/20241001062337_2.0.0.Designer.cs new file mode 100644 index 0000000..a1ed4a5 --- /dev/null +++ b/src/database/Dim.Migrations/Migrations/20241001062337_2.0.0.Designer.cs @@ -0,0 +1,592 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * 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; +using Dim.Entities; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +#nullable disable + +namespace Dim.Migrations.Migrations +{ + [DbContext(typeof(DimDbContext))] + [Migration("20241001062337_2.0.0")] + partial class _200 + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("dim") + .UseCollation("en_US.utf8") + .HasAnnotation("ProductVersion", "8.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); + + modelBuilder.Entity("Dim.Entities.Entities.Process", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("LockExpiryDate") + .HasColumnType("timestamp with time zone") + .HasColumnName("lock_expiry_date"); + + b.Property("ProcessTypeId") + .HasColumnType("integer") + .HasColumnName("process_type_id"); + + b.Property("Version") + .IsConcurrencyToken() + .HasColumnType("uuid") + .HasColumnName("version"); + + b.HasKey("Id") + .HasName("pk_processes"); + + b.HasIndex("ProcessTypeId") + .HasDatabaseName("ix_processes_process_type_id"); + + b.ToTable("processes", "dim"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.ProcessStep", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("DateCreated") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_created"); + + b.Property("DateLastChanged") + .HasColumnType("timestamp with time zone") + .HasColumnName("date_last_changed"); + + b.Property("Message") + .HasColumnType("text") + .HasColumnName("message"); + + b.Property("ProcessId") + .HasColumnType("uuid") + .HasColumnName("process_id"); + + b.Property("ProcessStepStatusId") + .HasColumnType("integer") + .HasColumnName("process_step_status_id"); + + b.Property("ProcessStepTypeId") + .HasColumnType("integer") + .HasColumnName("process_step_type_id"); + + b.HasKey("Id") + .HasName("pk_process_steps"); + + b.HasIndex("ProcessId") + .HasDatabaseName("ix_process_steps_process_id"); + + b.HasIndex("ProcessStepStatusId") + .HasDatabaseName("ix_process_steps_process_step_status_id"); + + b.HasIndex("ProcessStepTypeId") + .HasDatabaseName("ix_process_steps_process_step_type_id"); + + b.ToTable("process_steps", "dim"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.ProcessStepStatus", b => + { + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("id"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("label"); + + b.HasKey("Id") + .HasName("pk_process_step_statuses"); + + b.ToTable("process_step_statuses", "dim"); + + b.HasData( + new + { + Id = 1, + Label = "TODO" + }, + new + { + Id = 2, + Label = "DONE" + }, + new + { + Id = 3, + Label = "SKIPPED" + }, + new + { + Id = 4, + Label = "FAILED" + }, + new + { + Id = 5, + Label = "DUPLICATE" + }); + }); + + modelBuilder.Entity("Dim.Entities.Entities.ProcessStepType", b => + { + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("id"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("label"); + + b.HasKey("Id") + .HasName("pk_process_step_types"); + + b.ToTable("process_step_types", "dim"); + + b.HasData( + new + { + Id = 1, + Label = "CREATE_WALLET" + }, + new + { + Id = 2, + Label = "CHECK_OPERATION" + }, + new + { + Id = 3, + Label = "GET_COMPANY" + }, + new + { + Id = 4, + Label = "GET_DID_DOCUMENT" + }, + new + { + Id = 5, + Label = "CREATE_STATUS_LIST" + }, + new + { + Id = 6, + Label = "SEND_CALLBACK" + }, + new + { + Id = 7, + Label = "RETRIGGER_CREATE_WALLET" + }, + new + { + Id = 8, + Label = "RETRIGGER_CHECK_OPERATION" + }, + new + { + Id = 9, + Label = "RETRIGGER_GET_COMPANY" + }, + new + { + Id = 10, + Label = "RETRIGGER_GET_DID_DOCUMENT" + }, + new + { + Id = 11, + Label = "RETRIGGER_CREATE_STATUS_LIST" + }, + new + { + Id = 12, + Label = "RETRIGGER_SEND_CALLBACK" + }, + new + { + Id = 100, + Label = "CREATE_TECHNICAL_USER" + }, + new + { + Id = 101, + Label = "GET_TECHNICAL_USER_DATA" + }, + new + { + Id = 102, + Label = "GET_TECHNICAL_USER_SERVICE_KEY" + }, + new + { + Id = 103, + Label = "SEND_TECHNICAL_USER_CREATION_CALLBACK" + }, + new + { + Id = 104, + Label = "RETRIGGER_CREATE_TECHNICAL_USER" + }, + new + { + Id = 105, + Label = "RETRIGGER_GET_TECHNICAL_USER_DATA" + }, + new + { + Id = 106, + Label = "RETRIGGER_GET_TECHNICAL_USER_SERVICE_KEY" + }, + new + { + Id = 107, + Label = "RETRIGGER_SEND_TECHNICAL_USER_CREATION_CALLBACK" + }, + new + { + Id = 200, + Label = "DELETE_TECHNICAL_USER" + }, + new + { + Id = 201, + Label = "SEND_TECHNICAL_USER_DELETION_CALLBACK" + }, + new + { + Id = 202, + Label = "RETRIGGER_DELETE_TECHNICAL_USER" + }, + new + { + Id = 203, + Label = "RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK" + }); + }); + + modelBuilder.Entity("Dim.Entities.Entities.ProcessType", b => + { + b.Property("Id") + .HasColumnType("integer") + .HasColumnName("id"); + + b.Property("Label") + .IsRequired() + .HasMaxLength(255) + .HasColumnType("character varying(255)") + .HasColumnName("label"); + + b.HasKey("Id") + .HasName("pk_process_types"); + + b.ToTable("process_types", "dim"); + + b.HasData( + new + { + Id = 1, + Label = "SETUP_DIM" + }, + new + { + Id = 2, + Label = "TECHNICAL_USER" + }); + }); + + modelBuilder.Entity("Dim.Entities.Entities.TechnicalUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("ClientId") + .HasColumnType("text") + .HasColumnName("client_id"); + + b.Property("ClientSecret") + .HasColumnType("bytea") + .HasColumnName("client_secret"); + + b.Property("EncryptionMode") + .HasColumnType("integer") + .HasColumnName("encryption_mode"); + + b.Property("ExternalId") + .HasColumnType("uuid") + .HasColumnName("external_id"); + + b.Property("InitializationVector") + .HasColumnType("bytea") + .HasColumnName("initialization_vector"); + + b.Property("OperationId") + .HasColumnType("uuid") + .HasColumnName("operation_id"); + + b.Property("ProcessId") + .HasColumnType("uuid") + .HasColumnName("process_id"); + + b.Property("ServiceKeyId") + .HasColumnType("uuid") + .HasColumnName("service_key_id"); + + b.Property("TechnicalUserName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("technical_user_name"); + + b.Property("TenantId") + .HasColumnType("uuid") + .HasColumnName("tenant_id"); + + b.Property("TokenAddress") + .HasColumnType("text") + .HasColumnName("token_address"); + + b.HasKey("Id") + .HasName("pk_technical_users"); + + b.HasIndex("ProcessId") + .HasDatabaseName("ix_technical_users_process_id"); + + b.HasIndex("TenantId") + .HasDatabaseName("ix_technical_users_tenant_id"); + + b.ToTable("technical_users", "dim"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.Tenant", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uuid") + .HasColumnName("id"); + + b.Property("BaseUrl") + .HasColumnType("text") + .HasColumnName("base_url"); + + b.Property("Bpn") + .IsRequired() + .HasColumnType("text") + .HasColumnName("bpn"); + + b.Property("ClientId") + .HasColumnType("text") + .HasColumnName("client_id"); + + b.Property("ClientSecret") + .HasColumnType("bytea") + .HasColumnName("client_secret"); + + b.Property("CompanyId") + .HasColumnType("uuid") + .HasColumnName("company_id"); + + b.Property("CompanyName") + .IsRequired() + .HasColumnType("text") + .HasColumnName("company_name"); + + b.Property("Did") + .HasColumnType("text") + .HasColumnName("did"); + + b.Property("DidDocumentLocation") + .IsRequired() + .HasColumnType("text") + .HasColumnName("did_document_location"); + + b.Property("DidDownloadUrl") + .HasColumnType("text") + .HasColumnName("did_download_url"); + + b.Property("EncryptionMode") + .HasColumnType("integer") + .HasColumnName("encryption_mode"); + + b.Property("InitializationVector") + .HasColumnType("bytea") + .HasColumnName("initialization_vector"); + + b.Property("IsIssuer") + .HasColumnType("boolean") + .HasColumnName("is_issuer"); + + b.Property("OperationId") + .HasColumnType("uuid") + .HasColumnName("operation_id"); + + b.Property("OperatorId") + .HasColumnType("uuid") + .HasColumnName("operator_id"); + + b.Property("ProcessId") + .HasColumnType("uuid") + .HasColumnName("process_id"); + + b.Property("TokenAddress") + .HasColumnType("text") + .HasColumnName("token_address"); + + b.Property("WalletId") + .HasColumnType("uuid") + .HasColumnName("wallet_id"); + + b.HasKey("Id") + .HasName("pk_tenants"); + + b.HasIndex("ProcessId") + .HasDatabaseName("ix_tenants_process_id"); + + b.ToTable("tenants", "dim"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.Process", b => + { + b.HasOne("Dim.Entities.Entities.ProcessType", "ProcessType") + .WithMany("Processes") + .HasForeignKey("ProcessTypeId") + .IsRequired() + .HasConstraintName("fk_processes_process_types_process_type_id"); + + b.Navigation("ProcessType"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.ProcessStep", b => + { + b.HasOne("Dim.Entities.Entities.Process", "Process") + .WithMany("ProcessSteps") + .HasForeignKey("ProcessId") + .IsRequired() + .HasConstraintName("fk_process_steps_processes_process_id"); + + b.HasOne("Dim.Entities.Entities.ProcessStepStatus", "ProcessStepStatus") + .WithMany("ProcessSteps") + .HasForeignKey("ProcessStepStatusId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_process_steps_process_step_statuses_process_step_status_id"); + + b.HasOne("Dim.Entities.Entities.ProcessStepType", "ProcessStepType") + .WithMany("ProcessSteps") + .HasForeignKey("ProcessStepTypeId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired() + .HasConstraintName("fk_process_steps_process_step_types_process_step_type_id"); + + b.Navigation("Process"); + + b.Navigation("ProcessStepStatus"); + + b.Navigation("ProcessStepType"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.TechnicalUser", b => + { + b.HasOne("Dim.Entities.Entities.Process", "Process") + .WithMany("TechnicalUsers") + .HasForeignKey("ProcessId") + .IsRequired() + .HasConstraintName("fk_technical_users_processes_process_id"); + + b.HasOne("Dim.Entities.Entities.Tenant", "Tenant") + .WithMany("TechnicalUsers") + .HasForeignKey("TenantId") + .IsRequired() + .HasConstraintName("fk_technical_users_tenants_tenant_id"); + + b.Navigation("Process"); + + b.Navigation("Tenant"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.Tenant", b => + { + b.HasOne("Dim.Entities.Entities.Process", "Process") + .WithMany("Tenants") + .HasForeignKey("ProcessId") + .IsRequired() + .HasConstraintName("fk_tenants_processes_process_id"); + + b.Navigation("Process"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.Process", b => + { + b.Navigation("ProcessSteps"); + + b.Navigation("TechnicalUsers"); + + b.Navigation("Tenants"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.ProcessStepStatus", b => + { + b.Navigation("ProcessSteps"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.ProcessStepType", b => + { + b.Navigation("ProcessSteps"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.ProcessType", b => + { + b.Navigation("Processes"); + }); + + modelBuilder.Entity("Dim.Entities.Entities.Tenant", b => + { + b.Navigation("TechnicalUsers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/database/Dim.Migrations/Migrations/20241001062337_2.0.0.cs b/src/database/Dim.Migrations/Migrations/20241001062337_2.0.0.cs new file mode 100644 index 0000000..b471c16 --- /dev/null +++ b/src/database/Dim.Migrations/Migrations/20241001062337_2.0.0.cs @@ -0,0 +1,530 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * 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.EntityFrameworkCore.Migrations; +using System; + +#nullable disable + +#pragma warning disable CA1814 // Prefer jagged arrays over multidimensional + +namespace Dim.Migrations.Migrations +{ + /// + public partial class _200 : Migration + { + private const string technicalUsers = "technical_users"; + private const string processStepTypes = "process_step_types"; + private const string tenants = "tenants"; + private const string operationId = "operation_id"; + private const string label = "label"; + + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 13); + + migrationBuilder.DeleteData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 14); + + migrationBuilder.DeleteData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 15); + + migrationBuilder.DeleteData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 16); + + migrationBuilder.DeleteData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 17); + + migrationBuilder.DeleteData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 18); + + migrationBuilder.DropColumn( + name: "application_id", + schema: "dim", + table: tenants); + + migrationBuilder.DropColumn( + name: "sub_account_id", + schema: "dim", + table: tenants); + + migrationBuilder.RenameColumn( + name: "dim_instance_id", + schema: "dim", + table: tenants, + newName: "wallet_id"); + + migrationBuilder.DropColumn( + name: "space_id", + schema: "dim", + table: tenants); + + migrationBuilder.AddColumn( + name: operationId, + schema: "dim", + table: tenants, + type: "uuid", + nullable: true); + + migrationBuilder.DropColumn( + name: "service_instance_id", + schema: "dim", + table: tenants); + + migrationBuilder.AddColumn( + name: "token_address", + schema: "dim", + table: tenants, + type: "text", + nullable: true); + + migrationBuilder.DropColumn( + name: "service_binding_name", + schema: "dim", + table: tenants); + + migrationBuilder.AddColumn( + name: "client_id", + schema: "dim", + table: tenants, + type: "text", + nullable: true); + + migrationBuilder.DropColumn( + name: "application_key", + schema: "dim", + table: tenants); + + migrationBuilder.AddColumn( + name: "base_url", + schema: "dim", + table: tenants, + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "client_secret", + schema: "dim", + table: tenants, + type: "bytea", + nullable: true); + + migrationBuilder.AddColumn( + name: "encryption_mode", + schema: "dim", + table: tenants, + type: "integer", + nullable: true); + + migrationBuilder.AddColumn( + name: "initialization_vector", + schema: "dim", + table: tenants, + type: "bytea", + nullable: true); + + migrationBuilder.AddColumn( + name: operationId, + schema: "dim", + table: technicalUsers, + type: "uuid", + nullable: true); + + migrationBuilder.AddColumn( + name: "service_key_id", + schema: "dim", + table: technicalUsers, + type: "uuid", + nullable: true); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 1, + column: label, + value: "CREATE_WALLET"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 2, + column: label, + value: "CHECK_OPERATION"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 3, + column: label, + value: "GET_COMPANY"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 4, + column: label, + value: "GET_DID_DOCUMENT"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 5, + column: label, + value: "CREATE_STATUS_LIST"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 6, + column: label, + value: "SEND_CALLBACK"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 7, + column: label, + value: "RETRIGGER_CREATE_WALLET"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 8, + column: label, + value: "RETRIGGER_CHECK_OPERATION"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 9, + column: label, + value: "RETRIGGER_GET_COMPANY"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 10, + column: label, + value: "RETRIGGER_GET_DID_DOCUMENT"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 11, + column: label, + value: "RETRIGGER_CREATE_STATUS_LIST"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 12, + column: label, + value: "RETRIGGER_SEND_CALLBACK"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 102, + column: label, + value: "GET_TECHNICAL_USER_SERVICE_KEY"); + + migrationBuilder.InsertData( + schema: "dim", + table: processStepTypes, + columns: new[] { "id", label }, + values: new object[,] + { + { 103, "SEND_TECHNICAL_USER_CREATION_CALLBACK" }, + { 104, "RETRIGGER_CREATE_TECHNICAL_USER" }, + { 105, "RETRIGGER_GET_TECHNICAL_USER_DATA" }, + { 106, "RETRIGGER_GET_TECHNICAL_USER_SERVICE_KEY" }, + { 107, "RETRIGGER_SEND_TECHNICAL_USER_CREATION_CALLBACK" }, + { 202, "RETRIGGER_DELETE_TECHNICAL_USER" }, + { 203, "RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK" } + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DeleteData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 103); + + migrationBuilder.DeleteData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 104); + + migrationBuilder.DeleteData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 105); + + migrationBuilder.DeleteData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 106); + + migrationBuilder.DeleteData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 107); + + migrationBuilder.DeleteData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 202); + + migrationBuilder.DeleteData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 203); + + migrationBuilder.DropColumn( + name: "client_secret", + schema: "dim", + table: tenants); + + migrationBuilder.DropColumn( + name: "encryption_mode", + schema: "dim", + table: tenants); + + migrationBuilder.DropColumn( + name: "initialization_vector", + schema: "dim", + table: tenants); + + migrationBuilder.DropColumn( + name: operationId, + schema: "dim", + table: technicalUsers); + + migrationBuilder.DropColumn( + name: "service_key_id", + schema: "dim", + table: technicalUsers); + + migrationBuilder.RenameColumn( + name: "wallet_id", + schema: "dim", + table: tenants, + newName: "sub_account_id"); + + migrationBuilder.RenameColumn( + name: "token_address", + schema: "dim", + table: tenants, + newName: "service_instance_id"); + + migrationBuilder.RenameColumn( + name: operationId, + schema: "dim", + table: tenants, + newName: "space_id"); + + migrationBuilder.RenameColumn( + name: "client_id", + schema: "dim", + table: tenants, + newName: "service_binding_name"); + + migrationBuilder.RenameColumn( + name: "base_url", + schema: "dim", + table: tenants, + newName: "application_key"); + + migrationBuilder.AddColumn( + name: "application_id", + schema: "dim", + table: tenants, + type: "text", + nullable: true); + + migrationBuilder.AddColumn( + name: "dim_instance_id", + schema: "dim", + table: tenants, + type: "uuid", + nullable: true); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 1, + column: label, + value: "CREATE_SUBACCOUNT"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 2, + column: label, + value: "CREATE_SERVICEMANAGER_BINDINGS"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 3, + column: label, + value: "ASSIGN_ENTITLEMENTS"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 4, + column: label, + value: "CREATE_SERVICE_INSTANCE"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 5, + column: label, + value: "CREATE_SERVICE_BINDING"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 6, + column: label, + value: "SUBSCRIBE_APPLICATION"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 7, + column: label, + value: "CREATE_CLOUD_FOUNDRY_ENVIRONMENT"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 8, + column: label, + value: "CREATE_CLOUD_FOUNDRY_SPACE"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 9, + column: label, + value: "ADD_SPACE_MANAGER_ROLE"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 10, + column: label, + value: "ADD_SPACE_DEVELOPER_ROLE"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 11, + column: label, + value: "CREATE_DIM_SERVICE_INSTANCE"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 12, + column: label, + value: "CREATE_SERVICE_INSTANCE_BINDING"); + + migrationBuilder.UpdateData( + schema: "dim", + table: processStepTypes, + keyColumn: "id", + keyValue: 102, + column: label, + value: "SEND_TECHNICAL_USER_CREATION_CALLBACK"); + + migrationBuilder.InsertData( + schema: "dim", + table: processStepTypes, + columns: new[] { "id", label }, + values: new object[,] + { + { 13, "GET_DIM_DETAILS" }, + { 14, "CREATE_APPLICATION" }, + { 15, "CREATE_COMPANY_IDENTITY" }, + { 16, "ASSIGN_COMPANY_APPLICATION" }, + { 17, "CREATE_STATUS_LIST" }, + { 18, "SEND_CALLBACK" } + }); + } + } +} diff --git a/src/database/Dim.Migrations/Migrations/DimDbContextModelSnapshot.cs b/src/database/Dim.Migrations/Migrations/DimDbContextModelSnapshot.cs index e3b42c2..74825a6 100644 --- a/src/database/Dim.Migrations/Migrations/DimDbContextModelSnapshot.cs +++ b/src/database/Dim.Migrations/Migrations/DimDbContextModelSnapshot.cs @@ -35,7 +35,7 @@ protected override void BuildModel(ModelBuilder modelBuilder) modelBuilder .HasDefaultSchema("dim") .UseCollation("en_US.utf8") - .HasAnnotation("ProductVersion", "8.0.5") + .HasAnnotation("ProductVersion", "8.0.7") .HasAnnotation("Relational:MaxIdentifierLength", 63); NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder); @@ -181,107 +181,102 @@ protected override void BuildModel(ModelBuilder modelBuilder) new { Id = 1, - Label = "CREATE_SUBACCOUNT" + Label = "CREATE_WALLET" }, new { Id = 2, - Label = "CREATE_SERVICEMANAGER_BINDINGS" + Label = "CHECK_OPERATION" }, new { Id = 3, - Label = "ASSIGN_ENTITLEMENTS" + Label = "GET_COMPANY" }, new { Id = 4, - Label = "CREATE_SERVICE_INSTANCE" + Label = "GET_DID_DOCUMENT" }, new { Id = 5, - Label = "CREATE_SERVICE_BINDING" + Label = "CREATE_STATUS_LIST" }, new { Id = 6, - Label = "SUBSCRIBE_APPLICATION" + Label = "SEND_CALLBACK" }, new { Id = 7, - Label = "CREATE_CLOUD_FOUNDRY_ENVIRONMENT" + Label = "RETRIGGER_CREATE_WALLET" }, new { Id = 8, - Label = "CREATE_CLOUD_FOUNDRY_SPACE" + Label = "RETRIGGER_CHECK_OPERATION" }, new { Id = 9, - Label = "ADD_SPACE_MANAGER_ROLE" + Label = "RETRIGGER_GET_COMPANY" }, new { Id = 10, - Label = "ADD_SPACE_DEVELOPER_ROLE" + Label = "RETRIGGER_GET_DID_DOCUMENT" }, new { Id = 11, - Label = "CREATE_DIM_SERVICE_INSTANCE" + Label = "RETRIGGER_CREATE_STATUS_LIST" }, new { Id = 12, - Label = "CREATE_SERVICE_INSTANCE_BINDING" + Label = "RETRIGGER_SEND_CALLBACK" }, new { - Id = 13, - Label = "GET_DIM_DETAILS" - }, - new - { - Id = 14, - Label = "CREATE_APPLICATION" + Id = 100, + Label = "CREATE_TECHNICAL_USER" }, new { - Id = 15, - Label = "CREATE_COMPANY_IDENTITY" + Id = 101, + Label = "GET_TECHNICAL_USER_DATA" }, new { - Id = 16, - Label = "ASSIGN_COMPANY_APPLICATION" + Id = 102, + Label = "GET_TECHNICAL_USER_SERVICE_KEY" }, new { - Id = 17, - Label = "CREATE_STATUS_LIST" + Id = 103, + Label = "SEND_TECHNICAL_USER_CREATION_CALLBACK" }, new { - Id = 18, - Label = "SEND_CALLBACK" + Id = 104, + Label = "RETRIGGER_CREATE_TECHNICAL_USER" }, new { - Id = 100, - Label = "CREATE_TECHNICAL_USER" + Id = 105, + Label = "RETRIGGER_GET_TECHNICAL_USER_DATA" }, new { - Id = 101, - Label = "GET_TECHNICAL_USER_DATA" + Id = 106, + Label = "RETRIGGER_GET_TECHNICAL_USER_SERVICE_KEY" }, new { - Id = 102, - Label = "SEND_TECHNICAL_USER_CREATION_CALLBACK" + Id = 107, + Label = "RETRIGGER_SEND_TECHNICAL_USER_CREATION_CALLBACK" }, new { @@ -292,6 +287,16 @@ protected override void BuildModel(ModelBuilder modelBuilder) { Id = 201, Label = "SEND_TECHNICAL_USER_DELETION_CALLBACK" + }, + new + { + Id = 202, + Label = "RETRIGGER_DELETE_TECHNICAL_USER" + }, + new + { + Id = 203, + Label = "RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK" }); }); @@ -352,10 +357,18 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("bytea") .HasColumnName("initialization_vector"); + b.Property("OperationId") + .HasColumnType("uuid") + .HasColumnName("operation_id"); + b.Property("ProcessId") .HasColumnType("uuid") .HasColumnName("process_id"); + b.Property("ServiceKeyId") + .HasColumnType("uuid") + .HasColumnName("service_key_id"); + b.Property("TechnicalUserName") .IsRequired() .HasColumnType("text") @@ -388,19 +401,23 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("uuid") .HasColumnName("id"); - b.Property("ApplicationId") - .HasColumnType("text") - .HasColumnName("application_id"); - - b.Property("ApplicationKey") + b.Property("BaseUrl") .HasColumnType("text") - .HasColumnName("application_key"); + .HasColumnName("base_url"); b.Property("Bpn") .IsRequired() .HasColumnType("text") .HasColumnName("bpn"); + b.Property("ClientId") + .HasColumnType("text") + .HasColumnName("client_id"); + + b.Property("ClientSecret") + .HasColumnType("bytea") + .HasColumnName("client_secret"); + b.Property("CompanyId") .HasColumnType("uuid") .HasColumnName("company_id"); @@ -423,14 +440,22 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("text") .HasColumnName("did_download_url"); - b.Property("DimInstanceId") - .HasColumnType("uuid") - .HasColumnName("dim_instance_id"); + b.Property("EncryptionMode") + .HasColumnType("integer") + .HasColumnName("encryption_mode"); + + b.Property("InitializationVector") + .HasColumnType("bytea") + .HasColumnName("initialization_vector"); b.Property("IsIssuer") .HasColumnType("boolean") .HasColumnName("is_issuer"); + b.Property("OperationId") + .HasColumnType("uuid") + .HasColumnName("operation_id"); + b.Property("OperatorId") .HasColumnType("uuid") .HasColumnName("operator_id"); @@ -439,21 +464,13 @@ protected override void BuildModel(ModelBuilder modelBuilder) .HasColumnType("uuid") .HasColumnName("process_id"); - b.Property("ServiceBindingName") - .HasColumnType("text") - .HasColumnName("service_binding_name"); - - b.Property("ServiceInstanceId") + b.Property("TokenAddress") .HasColumnType("text") - .HasColumnName("service_instance_id"); - - b.Property("SpaceId") - .HasColumnType("uuid") - .HasColumnName("space_id"); + .HasColumnName("token_address"); - b.Property("SubAccountId") + b.Property("WalletId") .HasColumnType("uuid") - .HasColumnName("sub_account_id"); + .HasColumnName("wallet_id"); b.HasKey("Id") .HasName("pk_tenants"); diff --git a/src/database/Dim.Migrations/Program.cs b/src/database/Dim.Migrations/Program.cs index 8b257e6..5a661f9 100644 --- a/src/database/Dim.Migrations/Program.cs +++ b/src/database/Dim.Migrations/Program.cs @@ -49,7 +49,7 @@ } catch (Exception ex) when (!ex.GetType().Name.Equals("StopTheHostException", StringComparison.Ordinal)) { - Log.Fatal("Unhandled exception {Exception}", ex); + Log.Fatal(ex, "Unhandled exception {Exception}", ex); throw; } finally diff --git a/src/processes/DimProcess.Executor/DependencyInjection/DimProcessCollectionExtensions.cs b/src/processes/DimProcess.Executor/DependencyInjection/DimProcessCollectionExtensions.cs index 78be5c0..741d88c 100644 --- a/src/processes/DimProcess.Executor/DependencyInjection/DimProcessCollectionExtensions.cs +++ b/src/processes/DimProcess.Executor/DependencyInjection/DimProcessCollectionExtensions.cs @@ -21,7 +21,7 @@ using DimProcess.Library.DependencyInjection; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; -using Org.Eclipse.TractusX.Portal.Backend.Processes.Worker.Library; +using Processes.Worker.Library; namespace DimProcess.Executor.DependencyInjection; diff --git a/src/processes/DimProcess.Executor/DimProcessTypeExecutor.cs b/src/processes/DimProcess.Executor/DimProcessTypeExecutor.cs index db9c5fd..e890d10 100644 --- a/src/processes/DimProcess.Executor/DimProcessTypeExecutor.cs +++ b/src/processes/DimProcess.Executor/DimProcessTypeExecutor.cs @@ -21,9 +21,10 @@ using Dim.DbAccess; using Dim.DbAccess.Repositories; using Dim.Entities.Enums; +using Dim.Entities.Extensions; using DimProcess.Library; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; -using Org.Eclipse.TractusX.Portal.Backend.Processes.Worker.Library; +using Processes.Worker.Library; using System.Collections.Immutable; namespace DimProcess.Executor; @@ -34,23 +35,11 @@ public class DimProcessTypeExecutor( : IProcessTypeExecutor { private readonly IEnumerable _executableProcessSteps = ImmutableArray.Create( - ProcessStepTypeId.CREATE_SUBACCOUNT, - ProcessStepTypeId.CREATE_SERVICEMANAGER_BINDINGS, - ProcessStepTypeId.ASSIGN_ENTITLEMENTS, - ProcessStepTypeId.CREATE_SERVICE_INSTANCE, - ProcessStepTypeId.CREATE_SERVICE_BINDING, - ProcessStepTypeId.SUBSCRIBE_APPLICATION, - ProcessStepTypeId.CREATE_CLOUD_FOUNDRY_ENVIRONMENT, - ProcessStepTypeId.CREATE_CLOUD_FOUNDRY_SPACE, - ProcessStepTypeId.ADD_SPACE_MANAGER_ROLE, - ProcessStepTypeId.ADD_SPACE_DEVELOPER_ROLE, - ProcessStepTypeId.CREATE_DIM_SERVICE_INSTANCE, - ProcessStepTypeId.CREATE_SERVICE_INSTANCE_BINDING, - ProcessStepTypeId.GET_DIM_DETAILS, - ProcessStepTypeId.CREATE_APPLICATION, - ProcessStepTypeId.CREATE_COMPANY_IDENTITY, + ProcessStepTypeId.CREATE_WALLET, + ProcessStepTypeId.CHECK_OPERATION, + ProcessStepTypeId.GET_COMPANY, + ProcessStepTypeId.GET_DID_DOCUMENT, ProcessStepTypeId.CREATE_STATUS_LIST, - ProcessStepTypeId.ASSIGN_COMPANY_APPLICATION, ProcessStepTypeId.SEND_CALLBACK); private Guid _tenantId; @@ -63,14 +52,14 @@ public class DimProcessTypeExecutor( public async ValueTask InitializeProcess(Guid processId, IEnumerable processStepTypeIds) { - var (exists, tenantId, companyName, bpn) = await dimRepositories.GetInstance().GetTenantDataForProcessId(processId).ConfigureAwait(false); + var (exists, tenantId, companyName, bpn) = await dimRepositories.GetInstance().GetTenantDataForProcessId(processId).ConfigureAwait(ConfigureAwaitOptions.None); if (!exists) { throw new NotFoundException($"process {processId} does not exist or is not associated with an tenant"); } _tenantId = tenantId; - _tenantName = $"{bpn}_{companyName}"; + _tenantName = $"{bpn}{companyName}"; return new IProcessTypeExecutor.InitializationResult(false, null); } @@ -90,60 +79,36 @@ public class DimProcessTypeExecutor( { (nextStepTypeIds, stepStatusId, modified, processMessage) = processStepTypeId switch { - ProcessStepTypeId.CREATE_SUBACCOUNT => await dimProcessHandler.CreateSubaccount(_tenantId, _tenantName, cancellationToken) - .ConfigureAwait(false), - ProcessStepTypeId.CREATE_SERVICEMANAGER_BINDINGS => await dimProcessHandler.CreateServiceManagerBindings(_tenantId, cancellationToken) - .ConfigureAwait(false), - ProcessStepTypeId.ASSIGN_ENTITLEMENTS => await dimProcessHandler.AssignEntitlements(_tenantId, cancellationToken) - .ConfigureAwait(false), - ProcessStepTypeId.CREATE_SERVICE_INSTANCE => await dimProcessHandler.CreateServiceInstance(_tenantId, cancellationToken) - .ConfigureAwait(false), - ProcessStepTypeId.CREATE_SERVICE_BINDING => await dimProcessHandler.CreateServiceBindings(_tenantId, cancellationToken) - .ConfigureAwait(false), - ProcessStepTypeId.SUBSCRIBE_APPLICATION => await dimProcessHandler.SubscribeApplication(_tenantId, cancellationToken) - .ConfigureAwait(false), - ProcessStepTypeId.CREATE_CLOUD_FOUNDRY_ENVIRONMENT => await dimProcessHandler.CreateCloudFoundryEnvironment(_tenantId, _tenantName, cancellationToken) - .ConfigureAwait(false), - ProcessStepTypeId.CREATE_CLOUD_FOUNDRY_SPACE => await dimProcessHandler.CreateCloudFoundrySpace(_tenantId, _tenantName, cancellationToken) - .ConfigureAwait(false), - ProcessStepTypeId.ADD_SPACE_MANAGER_ROLE => await dimProcessHandler.AddSpaceManagerRole(_tenantId, cancellationToken) - .ConfigureAwait(false), - ProcessStepTypeId.ADD_SPACE_DEVELOPER_ROLE => await dimProcessHandler.AddSpaceDeveloperRole(_tenantId, cancellationToken) - .ConfigureAwait(false), - ProcessStepTypeId.CREATE_DIM_SERVICE_INSTANCE => await dimProcessHandler.CreateDimServiceInstance(_tenantName, _tenantId, cancellationToken) - .ConfigureAwait(false), - ProcessStepTypeId.CREATE_SERVICE_INSTANCE_BINDING => await dimProcessHandler.CreateServiceInstanceBindings(_tenantName, _tenantId, cancellationToken) - .ConfigureAwait(false), - ProcessStepTypeId.GET_DIM_DETAILS => await dimProcessHandler.GetDimDetails(_tenantName, _tenantId, cancellationToken) - .ConfigureAwait(false), - ProcessStepTypeId.CREATE_APPLICATION => await dimProcessHandler.CreateApplication(_tenantName, _tenantId, cancellationToken) - .ConfigureAwait(false), - ProcessStepTypeId.CREATE_COMPANY_IDENTITY => await dimProcessHandler.CreateCompanyIdentity(_tenantId, cancellationToken) - .ConfigureAwait(false), - ProcessStepTypeId.ASSIGN_COMPANY_APPLICATION => await dimProcessHandler.AssignCompanyApplication(_tenantId, cancellationToken) - .ConfigureAwait(false), + ProcessStepTypeId.CREATE_WALLET => await dimProcessHandler.CreateWallet(_tenantId, _tenantName, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None), + ProcessStepTypeId.CHECK_OPERATION => await dimProcessHandler.CheckOperation(_tenantId, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None), + ProcessStepTypeId.GET_COMPANY => await dimProcessHandler.GetCompany(_tenantId, _tenantName, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None), + ProcessStepTypeId.GET_DID_DOCUMENT => await dimProcessHandler.GetDidDocument(_tenantId, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None), ProcessStepTypeId.CREATE_STATUS_LIST => await dimProcessHandler.CreateStatusList(_tenantId, cancellationToken) - .ConfigureAwait(false), + .ConfigureAwait(ConfigureAwaitOptions.None), ProcessStepTypeId.SEND_CALLBACK => await dimProcessHandler.SendCallback(_tenantId, cancellationToken) - .ConfigureAwait(false), + .ConfigureAwait(ConfigureAwaitOptions.None), _ => (null, ProcessStepStatusId.TODO, false, null) }; } catch (Exception ex) when (ex is not SystemException) { - (stepStatusId, processMessage, nextStepTypeIds) = ProcessError(ex); + (stepStatusId, processMessage, nextStepTypeIds) = ProcessError(ex, processStepTypeId); modified = true; } return new IProcessTypeExecutor.StepExecutionResult(modified, stepStatusId, nextStepTypeIds, null, processMessage); } - private static (ProcessStepStatusId StatusId, string? ProcessMessage, IEnumerable? nextSteps) ProcessError(Exception ex) + private static (ProcessStepStatusId StatusId, string? ProcessMessage, IEnumerable? nextSteps) ProcessError(Exception ex, ProcessStepTypeId processStepTypeId) { return ex switch { ServiceException { IsRecoverable: true } => (ProcessStepStatusId.TODO, ex.Message, null), - _ => (ProcessStepStatusId.FAILED, ex.Message, null) + _ => (ProcessStepStatusId.FAILED, ex.Message, Enumerable.Repeat(processStepTypeId.GetRetriggerStep(ProcessTypeId.SETUP_DIM), 1)) }; } } diff --git a/src/processes/DimProcess.Executor/TechnicalUserProcessTypeExecutor.cs b/src/processes/DimProcess.Executor/TechnicalUserProcessTypeExecutor.cs index 0919575..70ca15e 100644 --- a/src/processes/DimProcess.Executor/TechnicalUserProcessTypeExecutor.cs +++ b/src/processes/DimProcess.Executor/TechnicalUserProcessTypeExecutor.cs @@ -21,9 +21,10 @@ using Dim.DbAccess; using Dim.DbAccess.Repositories; using Dim.Entities.Enums; +using Dim.Entities.Extensions; using DimProcess.Library; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; -using Org.Eclipse.TractusX.Portal.Backend.Processes.Worker.Library; +using Processes.Worker.Library; using System.Collections.Immutable; namespace DimProcess.Executor; @@ -36,12 +37,12 @@ public class TechnicalUserProcessTypeExecutor( private readonly IEnumerable _executableProcessSteps = ImmutableArray.Create( ProcessStepTypeId.CREATE_TECHNICAL_USER, ProcessStepTypeId.GET_TECHNICAL_USER_DATA, + ProcessStepTypeId.GET_TECHNICAL_USER_SERVICE_KEY, ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK, ProcessStepTypeId.DELETE_TECHNICAL_USER, ProcessStepTypeId.SEND_TECHNICAL_USER_DELETION_CALLBACK); private Guid _technicalUserId; - private string? _tenantName; public ProcessTypeId GetProcessTypeId() => ProcessTypeId.TECHNICAL_USER; public bool IsExecutableStepTypeId(ProcessStepTypeId processStepTypeId) => _executableProcessSteps.Contains(processStepTypeId); @@ -50,22 +51,21 @@ public class TechnicalUserProcessTypeExecutor( public async ValueTask InitializeProcess(Guid processId, IEnumerable processStepTypeIds) { - var (exists, technicalUserId, companyName, bpn) = await dimRepositories.GetInstance().GetTenantDataForTechnicalUserProcessId(processId).ConfigureAwait(false); + var (exists, technicalUserId) = await dimRepositories.GetInstance().GetTenantDataForTechnicalUserProcessId(processId).ConfigureAwait(ConfigureAwaitOptions.None); if (!exists) { throw new NotFoundException($"process {processId} does not exist or is not associated with an technical user"); } _technicalUserId = technicalUserId; - _tenantName = $"{bpn}_{companyName}"; return new IProcessTypeExecutor.InitializationResult(false, null); } public async ValueTask ExecuteProcessStep(ProcessStepTypeId processStepTypeId, IEnumerable processStepTypeIds, CancellationToken cancellationToken) { - if (_technicalUserId == Guid.Empty || _tenantName is null) + if (_technicalUserId == Guid.Empty) { - throw new UnexpectedConditionException("technicalUserId and tenantName should never be empty here"); + throw new UnexpectedConditionException("technicalUserId should never be empty here"); } IEnumerable? nextStepTypeIds; @@ -77,34 +77,36 @@ public class TechnicalUserProcessTypeExecutor( { (nextStepTypeIds, stepStatusId, modified, processMessage) = processStepTypeId switch { - ProcessStepTypeId.CREATE_TECHNICAL_USER => await technicalUserProcessHandler.CreateServiceInstanceBindings(_tenantName, _technicalUserId, cancellationToken) - .ConfigureAwait(false), - ProcessStepTypeId.GET_TECHNICAL_USER_DATA => await technicalUserProcessHandler.GetTechnicalUserData(_tenantName, _technicalUserId, cancellationToken) - .ConfigureAwait(false), + ProcessStepTypeId.CREATE_TECHNICAL_USER => await technicalUserProcessHandler.CreateServiceInstanceBindings(_technicalUserId, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None), + ProcessStepTypeId.GET_TECHNICAL_USER_DATA => await technicalUserProcessHandler.GetTechnicalUserData(_technicalUserId, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None), + ProcessStepTypeId.GET_TECHNICAL_USER_SERVICE_KEY => await technicalUserProcessHandler.GetTechnicalUserServiceKey(_technicalUserId, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None), ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK => await technicalUserProcessHandler.SendCreateCallback(_technicalUserId, cancellationToken) - .ConfigureAwait(false), - ProcessStepTypeId.DELETE_TECHNICAL_USER => await technicalUserProcessHandler.DeleteServiceInstanceBindings(_tenantName, _technicalUserId, cancellationToken) - .ConfigureAwait(false), + .ConfigureAwait(ConfigureAwaitOptions.None), + ProcessStepTypeId.DELETE_TECHNICAL_USER => await technicalUserProcessHandler.DeleteServiceInstanceBindings(_technicalUserId, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None), ProcessStepTypeId.SEND_TECHNICAL_USER_DELETION_CALLBACK => await technicalUserProcessHandler.SendDeleteCallback(_technicalUserId, cancellationToken) - .ConfigureAwait(false), + .ConfigureAwait(ConfigureAwaitOptions.None), _ => (null, ProcessStepStatusId.TODO, false, null) }; } catch (Exception ex) when (ex is not SystemException) { - (stepStatusId, processMessage, nextStepTypeIds) = ProcessError(ex); + (stepStatusId, processMessage, nextStepTypeIds) = ProcessError(ex, processStepTypeId); modified = true; } return new IProcessTypeExecutor.StepExecutionResult(modified, stepStatusId, nextStepTypeIds, null, processMessage); } - private static (ProcessStepStatusId StatusId, string? ProcessMessage, IEnumerable? nextSteps) ProcessError(Exception ex) + private static (ProcessStepStatusId StatusId, string? ProcessMessage, IEnumerable? nextSteps) ProcessError(Exception ex, ProcessStepTypeId processStepTypeId) { return ex switch { ServiceException { IsRecoverable: true } => (ProcessStepStatusId.TODO, ex.Message, null), - _ => (ProcessStepStatusId.FAILED, ex.Message, null) + _ => (ProcessStepStatusId.FAILED, ex.Message, Enumerable.Repeat(processStepTypeId.GetRetriggerStep(ProcessTypeId.TECHNICAL_USER), 1)) }; } } diff --git a/src/processes/DimProcess.Library/Callback/CallbackService.cs b/src/processes/DimProcess.Library/Callback/CallbackService.cs index cd0fa66..8c46b81 100644 --- a/src/processes/DimProcess.Library/Callback/CallbackService.cs +++ b/src/processes/DimProcess.Library/Callback/CallbackService.cs @@ -18,7 +18,6 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -using Dim.Clients.Api.Cf; using Dim.Clients.Extensions; using DimProcess.Library.Callback.DependencyInjection; using Microsoft.Extensions.Options; @@ -34,17 +33,14 @@ public class CallbackService(ITokenService tokenService, IOptions(_settings, cancellationToken) - .ConfigureAwait(false); + .ConfigureAwait(ConfigureAwaitOptions.None); var data = new CallbackDataModel( did, didDocument, - new AuthenticationDetail( - dimDetails.Credentials.Uaa.Url, - dimDetails.Credentials.Uaa.ClientId, - dimDetails.Credentials.Uaa.ClientSecret) + authenticationDetail ); await httpClient.PostAsJsonAsync($"/api/administration/registration/dim/{bpn}", data, JsonSerializerExtensions.Options, cancellationToken) .CatchingIntoServiceExceptionFor("send-callback", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE) @@ -54,7 +50,7 @@ await httpClient.PostAsJsonAsync($"/api/administration/registration/dim/{bpn}", public async Task SendTechnicalUserCallback(Guid externalId, string tokenAddress, string clientId, string clientSecret, CancellationToken cancellationToken) { var httpClient = await tokenService.GetAuthorizedClient(_settings, cancellationToken) - .ConfigureAwait(false); + .ConfigureAwait(ConfigureAwaitOptions.None); var data = new AuthenticationDetail( tokenAddress, clientId, @@ -67,7 +63,7 @@ await httpClient.PostAsJsonAsync($"/api/administration/serviceAccount/callback/{ public async Task SendTechnicalUserDeletionCallback(Guid externalId, CancellationToken cancellationToken) { var httpClient = await tokenService.GetAuthorizedClient(_settings, cancellationToken) - .ConfigureAwait(false); + .ConfigureAwait(ConfigureAwaitOptions.None); await httpClient.PostAsync($"/api/administration/serviceAccount/callback/{externalId}/delete", null, cancellationToken) .CatchingIntoServiceExceptionFor("send-technical-user-deletion-callback", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE) .ConfigureAwait(false); diff --git a/src/processes/DimProcess.Library/Callback/ICallbackService.cs b/src/processes/DimProcess.Library/Callback/ICallbackService.cs index 5f8806d..daa18ce 100644 --- a/src/processes/DimProcess.Library/Callback/ICallbackService.cs +++ b/src/processes/DimProcess.Library/Callback/ICallbackService.cs @@ -18,14 +18,13 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -using Dim.Clients.Api.Cf; using System.Text.Json; namespace DimProcess.Library.Callback; public interface ICallbackService { - Task SendCallback(string bpn, ServiceCredentialBindingDetailResponse dimDetails, JsonDocument didDocument, string did, CancellationToken cancellationToken); + Task SendCallback(string bpn, AuthenticationDetail authenticationDetail, JsonDocument didDocument, string did, CancellationToken cancellationToken); Task SendTechnicalUserCallback(Guid externalId, string tokenAddress, string clientId, string clientSecret, CancellationToken cancellationToken); diff --git a/src/processes/DimProcess.Library/DependencyInjection/DimHandlerExtensions.cs b/src/processes/DimProcess.Library/DependencyInjection/DimHandlerExtensions.cs index 09ca0de..590206b 100644 --- a/src/processes/DimProcess.Library/DependencyInjection/DimHandlerExtensions.cs +++ b/src/processes/DimProcess.Library/DependencyInjection/DimHandlerExtensions.cs @@ -18,13 +18,8 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -using Dim.Clients.Api.Cf.DependencyInjection; using Dim.Clients.Api.Dim.DependencyInjection; -using Dim.Clients.Api.Entitlements.DependencyInjection; -using Dim.Clients.Api.Provisioning.DependencyInjection; -using Dim.Clients.Api.Services.DependencyInjection; -using Dim.Clients.Api.SubAccounts.DependencyInjection; -using Dim.Clients.Api.Subscriptions.DependencyInjection; +using Dim.Clients.Api.Div.DependencyInjection; using Dim.Clients.Token; using DimProcess.Library.Callback.DependencyInjection; using Microsoft.Extensions.Configuration; @@ -43,12 +38,7 @@ public static IServiceCollection AddDimProcessHandler(this IServiceCollection se services .AddTransient() .AddTransient() - .AddSubAccountClient(config.GetSection("SubAccount")) - .AddEntitlementClient(config.GetSection("Entitlement")) - .AddServiceClient() - .AddSubscriptionClient() - .AddProvisioningClient() - .AddCfClient(config.GetSection("Cf")) + .AddProvisioningClient(config.GetSection("Provisioning")) .AddDimClient() .AddCallbackClient(config.GetSection("Callback")); @@ -64,7 +54,7 @@ public static IServiceCollection AddTechnicalUserProcessHandler(this IServiceCol services .AddTransient() .AddTransient() - .AddCfClient(config.GetSection("Cf")) + .AddProvisioningClient(config.GetSection("Provisioning")) .AddCallbackClient(config.GetSection("Callback")); return services; diff --git a/src/processes/DimProcess.Library/DependencyInjection/DimHandlerSettings.cs b/src/processes/DimProcess.Library/DependencyInjection/DimHandlerSettings.cs index 6b09d1d..bae117c 100644 --- a/src/processes/DimProcess.Library/DependencyInjection/DimHandlerSettings.cs +++ b/src/processes/DimProcess.Library/DependencyInjection/DimHandlerSettings.cs @@ -18,6 +18,8 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Validation; using System.ComponentModel.DataAnnotations; namespace DimProcess.Library.DependencyInjection; @@ -25,20 +27,12 @@ namespace DimProcess.Library.DependencyInjection; public class DimHandlerSettings { [Required(AllowEmptyStrings = false)] - public string AdminMail { get; set; } = null!; + public string ApplicationName { get; set; } = null!; [Required] - public Guid RootDirectoryId { get; set; } + public int EncryptionConfigIndex { get; set; } - [Required(AllowEmptyStrings = false)] - public string AuthUrl { get; set; } = null!; - - [Required(AllowEmptyStrings = false)] - public string ClientidCisCentral { get; set; } = null!; - - [Required(AllowEmptyStrings = false)] - public string ClientsecretCisCentral { get; set; } = null!; - - [Required(AllowEmptyStrings = false)] - public string EncryptionKey { get; set; } = null!; + [Required] + [DistinctValues("x => x.Index")] + public IEnumerable EncryptionConfigs { get; set; } = null!; } diff --git a/src/processes/DimProcess.Library/DimProcessHandler.cs b/src/processes/DimProcess.Library/DimProcessHandler.cs index d7c8946..d484c58 100644 --- a/src/processes/DimProcess.Library/DimProcessHandler.cs +++ b/src/processes/DimProcess.Library/DimProcessHandler.cs @@ -18,464 +18,171 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -using Dim.Clients.Api.Cf; using Dim.Clients.Api.Dim; -using Dim.Clients.Api.Entitlements; -using Dim.Clients.Api.Provisioning; -using Dim.Clients.Api.Services; -using Dim.Clients.Api.SubAccounts; -using Dim.Clients.Api.Subscriptions; +using Dim.Clients.Api.Div; +using Dim.Clients.Api.Div.Models; using Dim.Clients.Token; using Dim.DbAccess; +using Dim.DbAccess.Extensions; using Dim.DbAccess.Repositories; using Dim.Entities.Enums; using DimProcess.Library.Callback; using DimProcess.Library.DependencyInjection; using Microsoft.Extensions.Options; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration; +using System.Text.Json; namespace DimProcess.Library; public class DimProcessHandler( IDimRepositories dimRepositories, - ISubAccountClient subAccountClient, - IServiceClient serviceClient, - ISubscriptionClient subscriptionClient, - IEntitlementClient entitlementClient, IProvisioningClient provisioningClient, - ICfClient cfClient, IDimClient dimClient, ICallbackService callbackService, + IHttpClientFactory httpClientFactory, IOptions options) : IDimProcessHandler { private readonly DimHandlerSettings _settings = options.Value; - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateSubaccount(Guid tenantId, string tenantName, CancellationToken cancellationToken) + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateWallet(Guid tenantId, string tenantName, CancellationToken cancellationToken) { - var parentDirectoryId = _settings.RootDirectoryId; - var adminMail = _settings.AdminMail; - var subAccountAuth = new BasicAuthSettings - { - TokenAddress = $"{_settings.AuthUrl}/oauth/token", - ClientId = _settings.ClientidCisCentral, - ClientSecret = _settings.ClientsecretCisCentral - }; - - var bpn = await dimRepositories.GetInstance().GetTenantBpn(tenantId); - var subAccountId = await subAccountClient.CreateSubaccount(subAccountAuth, adminMail, tenantName, parentDirectoryId, bpn, cancellationToken).ConfigureAwait(false); - dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => - { - tenant.SubAccountId = null; - }, - tenant => - { - tenant.SubAccountId = subAccountId; - }); - return new ValueTuple?, ProcessStepStatusId, bool, string?>( - Enumerable.Repeat(ProcessStepTypeId.CREATE_SERVICEMANAGER_BINDINGS, 1), - ProcessStepStatusId.DONE, - false, - null); - } - - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateServiceManagerBindings(Guid tenantId, CancellationToken cancellationToken) - { - var subAccountAuth = new BasicAuthSettings - { - TokenAddress = $"{_settings.AuthUrl}/oauth/token", - ClientId = _settings.ClientidCisCentral, - ClientSecret = _settings.ClientsecretCisCentral - }; - var tenantRepository = dimRepositories.GetInstance(); - var subAccountId = await tenantRepository.GetSubAccountIdByTenantId(tenantId).ConfigureAwait(false); - if (subAccountId == null) - { - throw new ConflictException("SubAccountId must not be null."); - } - - await subAccountClient.CreateServiceManagerBindings(subAccountAuth, subAccountId.Value, cancellationToken).ConfigureAwait(false); - - return new ValueTuple?, ProcessStepStatusId, bool, string?>( - Enumerable.Repeat(ProcessStepTypeId.ASSIGN_ENTITLEMENTS, 1), - ProcessStepStatusId.DONE, - false, - null); - } - - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> AssignEntitlements(Guid tenantId, CancellationToken cancellationToken) - { - var subAccountAuth = new BasicAuthSettings - { - TokenAddress = $"{_settings.AuthUrl}/oauth/token", - ClientId = _settings.ClientidCisCentral, - ClientSecret = _settings.ClientsecretCisCentral - }; - var subAccountId = await dimRepositories.GetInstance().GetSubAccountIdByTenantId(tenantId).ConfigureAwait(false); - if (subAccountId == null) - { - throw new ConflictException("SubAccountId must not be null."); - } - - await entitlementClient.AssignEntitlements(subAccountAuth, subAccountId.Value, cancellationToken).ConfigureAwait(false); - - return new ValueTuple?, ProcessStepStatusId, bool, string?>( - Enumerable.Repeat(ProcessStepTypeId.CREATE_SERVICE_INSTANCE, 1), - ProcessStepStatusId.DONE, - false, - null); - } - - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateServiceInstance(Guid tenantId, CancellationToken cancellationToken) - { - var subAccountAuth = new BasicAuthSettings - { - TokenAddress = $"{_settings.AuthUrl}/oauth/token", - ClientId = _settings.ClientidCisCentral, - ClientSecret = _settings.ClientsecretCisCentral - }; - var subAccountId = await dimRepositories.GetInstance().GetSubAccountIdByTenantId(tenantId).ConfigureAwait(false); - if (subAccountId == null) - { - throw new ConflictException("SubAccountId must not be null."); - } - - var saBinding = await subAccountClient.GetServiceManagerBindings(subAccountAuth, subAccountId.Value, cancellationToken).ConfigureAwait(false); - var serviceInstance = await serviceClient.CreateServiceInstance(saBinding, cancellationToken).ConfigureAwait(false); - - dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => - { - tenant.ServiceInstanceId = null; - }, - tenant => - { - tenant.ServiceInstanceId = serviceInstance.Id; - }); - return new ValueTuple?, ProcessStepStatusId, bool, string?>( - Enumerable.Repeat(ProcessStepTypeId.CREATE_SERVICE_BINDING, 1), - ProcessStepStatusId.DONE, - false, - null); - } - - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateServiceBindings(Guid tenantId, CancellationToken cancellationToken) - { - var subAccountAuth = new BasicAuthSettings + var (isIssuer, didDocumentLocation) = await tenantRepository.GetHostingUrlAndIsIssuer(tenantId).ConfigureAwait(ConfigureAwaitOptions.None); + if (didDocumentLocation == null) { - TokenAddress = $"{_settings.AuthUrl}/oauth/token", - ClientId = _settings.ClientidCisCentral, - ClientSecret = _settings.ClientsecretCisCentral - }; - var (subAccountId, serviceInstanceId) = await dimRepositories.GetInstance().GetSubAccountAndServiceInstanceIdsByTenantId(tenantId).ConfigureAwait(false); - if (subAccountId == null) - { - throw new ConflictException("SubAccountId must not be null."); - } - - if (string.IsNullOrEmpty(serviceInstanceId)) - { - throw new ConflictException("ServiceInstanceId must not be null."); + throw new UnexpectedConditionException("DidDocumentLocation must always be set"); } - var saBinding = await subAccountClient.GetServiceManagerBindings(subAccountAuth, subAccountId.Value, cancellationToken).ConfigureAwait(false); - var serviceBinding = await serviceClient.CreateServiceBinding(saBinding, serviceInstanceId, cancellationToken).ConfigureAwait(false); + var operationId = await provisioningClient.CreateOperation(tenantId, tenantName, _settings.ApplicationName, tenantName, didDocumentLocation, isIssuer, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + tenantRepository.AttachAndModifyTenant(tenantId, t => t.OperationId = null, t => t.OperationId = operationId); - dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => - { - tenant.ServiceBindingName = null; - }, - tenant => - { - tenant.ServiceBindingName = serviceBinding.Name; - }); return new ValueTuple?, ProcessStepStatusId, bool, string?>( - Enumerable.Repeat(ProcessStepTypeId.SUBSCRIBE_APPLICATION, 1), + Enumerable.Repeat(ProcessStepTypeId.CHECK_OPERATION, 1), ProcessStepStatusId.DONE, false, null); } - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SubscribeApplication(Guid tenantId, CancellationToken cancellationToken) + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CheckOperation(Guid tenantId, CancellationToken cancellationToken) { - var subAccountAuth = new BasicAuthSettings - { - TokenAddress = $"{_settings.AuthUrl}/oauth/token", - ClientId = _settings.ClientidCisCentral, - ClientSecret = _settings.ClientsecretCisCentral - }; - var (subAccountId, serviceBindingName) = await dimRepositories.GetInstance().GetSubAccountIdAndServiceBindingNameByTenantId(tenantId).ConfigureAwait(false); - if (subAccountId == null) + var tenantRepository = dimRepositories.GetInstance(); + var operationId = await tenantRepository.GetOperationId(tenantId).ConfigureAwait(ConfigureAwaitOptions.None); + if (operationId == null) { - throw new ConflictException("SubAccountId must not be null."); + throw new UnexpectedConditionException("OperationId must always be set"); } - if (string.IsNullOrEmpty(serviceBindingName)) + var response = await provisioningClient.GetOperation(operationId.Value, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + if (response.Status == OperationResponseStatus.pending) { - throw new ConflictException("ServiceBindingName must not be null."); + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + null, + ProcessStepStatusId.TODO, + false, + null); } - var saBinding = await subAccountClient.GetServiceManagerBindings(subAccountAuth, subAccountId.Value, cancellationToken).ConfigureAwait(false); - var bindingResponse = await serviceClient.GetServiceBinding(saBinding, serviceBindingName, cancellationToken).ConfigureAwait(false); - await subscriptionClient.SubscribeApplication(saBinding.Url, bindingResponse, "decentralized-identity-management-app", "standard", cancellationToken).ConfigureAwait(false); - - return new ValueTuple?, ProcessStepStatusId, bool, string?>( - Enumerable.Repeat(ProcessStepTypeId.CREATE_CLOUD_FOUNDRY_ENVIRONMENT, 1), - ProcessStepStatusId.DONE, - false, - null); - } - - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateCloudFoundryEnvironment(Guid tenantId, string tenantName, CancellationToken cancellationToken) - { - var adminMail = _settings.AdminMail; - var subAccountAuth = new BasicAuthSettings + if (response is { Status: OperationResponseStatus.completed, Data: null }) { - TokenAddress = $"{_settings.AuthUrl}/oauth/token", - ClientId = _settings.ClientidCisCentral, - ClientSecret = _settings.ClientsecretCisCentral - }; - var (subAccountId, serviceBindingName) = await dimRepositories.GetInstance().GetSubAccountIdAndServiceBindingNameByTenantId(tenantId).ConfigureAwait(false); - if (subAccountId == null) - { - throw new ConflictException("SubAccountId must not be null."); + throw new UnexpectedConditionException($"Data should never be null when in status {OperationResponseStatus.completed}"); } - if (string.IsNullOrEmpty(serviceBindingName)) - { - throw new ConflictException("ServiceBindingName must not be null."); - } + var serviceKey = response.Data!.ServiceKey; - var saBinding = await subAccountClient.GetServiceManagerBindings(subAccountAuth, subAccountId.Value, cancellationToken).ConfigureAwait(false); - var bindingResponse = await serviceClient.GetServiceBinding(saBinding, serviceBindingName, cancellationToken).ConfigureAwait(false); - await provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingResponse, tenantName, adminMail, cancellationToken) - .ConfigureAwait(false); + var cryptoHelper = _settings.EncryptionConfigs.GetCryptoHelper(_settings.EncryptionConfigIndex); + var (secret, initializationVector) = cryptoHelper.Encrypt(serviceKey.Uaa.ClientSecret); - return new ValueTuple?, ProcessStepStatusId, bool, string?>( - Enumerable.Repeat(ProcessStepTypeId.CREATE_CLOUD_FOUNDRY_SPACE, 1), - ProcessStepStatusId.DONE, - false, - null); - } - - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateCloudFoundrySpace(Guid tenantId, string tenantName, CancellationToken cancellationToken) - { - var spaceId = await cfClient.CreateCloudFoundrySpace(tenantName, cancellationToken).ConfigureAwait(false); - - dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => + tenantRepository.AttachAndModifyTenant(tenantId, + t => { - tenant.SpaceId = null; + t.TokenAddress = null; + t.BaseUrl = null; + t.ClientId = null; + t.ClientSecret = null; + t.WalletId = null; }, - tenant => + t => { - tenant.SpaceId = spaceId; + t.WalletId = response.Data.CustomerWalletId; + t.TokenAddress = serviceKey.Uaa.Url; + t.BaseUrl = serviceKey.Url; + t.ClientId = serviceKey.Uaa.ClientId; + t.ClientSecret = secret; + t.InitializationVector = initializationVector; + t.EncryptionMode = _settings.EncryptionConfigIndex; }); - return new ValueTuple?, ProcessStepStatusId, bool, string?>( - Enumerable.Repeat(ProcessStepTypeId.ADD_SPACE_MANAGER_ROLE, 1), - ProcessStepStatusId.DONE, - false, - null); - } - - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> AddSpaceManagerRole(Guid tenantId, CancellationToken cancellationToken) - { - var adminMail = _settings.AdminMail; - var spaceId = await dimRepositories.GetInstance().GetSpaceId(tenantId).ConfigureAwait(false); - if (spaceId == null) - { - throw new ConflictException("SpaceId must not be null."); - } - - await cfClient.AddSpaceRoleToUser("space_manager", adminMail, spaceId.Value, cancellationToken).ConfigureAwait(false); - - return new ValueTuple?, ProcessStepStatusId, bool, string?>( - Enumerable.Repeat(ProcessStepTypeId.ADD_SPACE_DEVELOPER_ROLE, 1), - ProcessStepStatusId.DONE, - false, - null); - } - - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> AddSpaceDeveloperRole(Guid tenantId, CancellationToken cancellationToken) - { - var adminMail = _settings.AdminMail; - var spaceId = await dimRepositories.GetInstance().GetSpaceId(tenantId).ConfigureAwait(false); - if (spaceId == null) - { - throw new ConflictException("SpaceId must not be null."); - } - - await cfClient.AddSpaceRoleToUser("space_developer", adminMail, spaceId.Value, cancellationToken).ConfigureAwait(false); - - return new ValueTuple?, ProcessStepStatusId, bool, string?>( - Enumerable.Repeat(ProcessStepTypeId.CREATE_DIM_SERVICE_INSTANCE, 1), - ProcessStepStatusId.DONE, - false, - null); - } - - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateDimServiceInstance(string tenantName, Guid tenantId, CancellationToken cancellationToken) - { - var servicePlanId = await cfClient.GetServicePlan("decentralized-identity-management", "standard", cancellationToken).ConfigureAwait(false); - var spaceId = await cfClient.GetSpace(tenantName, cancellationToken).ConfigureAwait(false); - await cfClient.CreateDimServiceInstance(tenantName, spaceId, servicePlanId, cancellationToken).ConfigureAwait(false); return new ValueTuple?, ProcessStepStatusId, bool, string?>( - Enumerable.Repeat(ProcessStepTypeId.CREATE_SERVICE_INSTANCE_BINDING, 1), + Enumerable.Repeat(ProcessStepTypeId.GET_COMPANY, 1), ProcessStepStatusId.DONE, false, null); } - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateServiceInstanceBindings(string tenantName, Guid tenantId, CancellationToken cancellationToken) + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> GetCompany(Guid tenantId, string tenantName, CancellationToken cancellationToken) { - var spaceId = await dimRepositories.GetInstance().GetSpaceId(tenantId).ConfigureAwait(false); - if (spaceId == null) - { - throw new ConflictException("SpaceId must not be null."); - } - - await cfClient.CreateServiceInstanceBindings(tenantName, null, spaceId.Value, cancellationToken).ConfigureAwait(false); - - return new ValueTuple?, ProcessStepStatusId, bool, string?>( - Enumerable.Repeat(ProcessStepTypeId.GET_DIM_DETAILS, 1), - ProcessStepStatusId.DONE, - false, - null); - } - - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> GetDimDetails(string tenantName, Guid tenantId, CancellationToken cancellationToken) - { - var spaceId = await dimRepositories.GetInstance().GetSpaceId(tenantId).ConfigureAwait(false); - if (spaceId == null) - { - throw new ConflictException("SpaceId must not be null."); - } - - var dimInstanceId = await cfClient.GetServiceBinding(tenantName, spaceId.Value, $"{tenantName}-dim-key01", cancellationToken).ConfigureAwait(false); - - dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => - { - tenant.DimInstanceId = null; - }, - tenant => - { - tenant.DimInstanceId = dimInstanceId; - }); - return new ValueTuple?, ProcessStepStatusId, bool, string?>( - Enumerable.Repeat(ProcessStepTypeId.CREATE_APPLICATION, 1), - ProcessStepStatusId.DONE, - false, - null); - } + var tenantRepository = dimRepositories.GetInstance(); + var (baseUrl, walletData) = await tenantRepository.GetCompanyRequestData(tenantId).ConfigureAwait(ConfigureAwaitOptions.None); + var (tokenAddress, clientId, clientSecret, initializationVector, encryptionMode) = walletData.ValidateData(); - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateApplication(string tenantName, Guid tenantId, CancellationToken cancellationToken) - { - var (dimInstanceId, _, _) = await dimRepositories.GetInstance().GetDimInstanceIdAndHostingUrl(tenantId).ConfigureAwait(false); - if (dimInstanceId == null) + if (string.IsNullOrWhiteSpace(baseUrl)) { - throw new ConflictException("DimInstanceId must not be null."); + throw new UnexpectedConditionException("BaseAddress must not be null"); } - var dimDetails = await cfClient.GetServiceBindingDetails(dimInstanceId.Value, cancellationToken).ConfigureAwait(false); + var cryptoHelper = _settings.EncryptionConfigs.GetCryptoHelper(encryptionMode); + var secret = cryptoHelper.Decrypt(clientSecret, initializationVector); var dimAuth = new BasicAuthSettings { - TokenAddress = $"{dimDetails.Credentials.Uaa.Url}/oauth/token", - ClientId = dimDetails.Credentials.Uaa.ClientId, - ClientSecret = dimDetails.Credentials.Uaa.ClientSecret + TokenAddress = $"{tokenAddress}/oauth/token", + ClientId = clientId, + ClientSecret = secret }; - var dimBaseUrl = dimDetails.Credentials.Url; - var applicationId = await dimClient.CreateApplication(dimAuth, dimBaseUrl, tenantName, cancellationToken).ConfigureAwait(false); - dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => - { - tenant.ApplicationId = null; - }, - tenant => - { - tenant.ApplicationId = applicationId; - }); - return new ValueTuple?, ProcessStepStatusId, bool, string?>( - Enumerable.Repeat(ProcessStepTypeId.CREATE_COMPANY_IDENTITY, 1), - ProcessStepStatusId.DONE, - false, - null); - } - - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateCompanyIdentity(Guid tenantId, CancellationToken cancellationToken) - { - var (dimInstanceId, hostingUrl, isIssuer) = await dimRepositories.GetInstance().GetDimInstanceIdAndHostingUrl(tenantId).ConfigureAwait(false); - if (dimInstanceId == null) + var companyData = await dimClient + .GetCompanyData(dimAuth, baseUrl, tenantName, _settings.ApplicationName, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None); + tenantRepository.AttachAndModifyTenant(tenantId, t => { - throw new ConflictException("DimInstanceId must not be null."); - } - - var dimDetails = await cfClient.GetServiceBindingDetails(dimInstanceId.Value, cancellationToken).ConfigureAwait(false); - - var dimAuth = new BasicAuthSettings + t.DidDownloadUrl = null; + t.CompanyId = null; + }, + t => { - TokenAddress = $"{dimDetails.Credentials.Uaa.Url}/oauth/token", - ClientId = dimDetails.Credentials.Uaa.ClientId, - ClientSecret = dimDetails.Credentials.Uaa.ClientSecret - }; - var dimBaseUrl = dimDetails.Credentials.Url; - var result = await dimClient.CreateCompanyIdentity(dimAuth, tenantId, hostingUrl, dimBaseUrl, isIssuer, cancellationToken).ConfigureAwait(false); + t.DidDownloadUrl = companyData.DownloadUrl; + t.CompanyId = companyData.CompanyId; + }); - dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => - { - tenant.DidDownloadUrl = null; - tenant.Did = null; - tenant.CompanyId = null; - }, - tenant => - { - tenant.DidDownloadUrl = result.DownloadUrl; - tenant.Did = result.Did; - tenant.CompanyId = result.CompanyId; - }); return new ValueTuple?, ProcessStepStatusId, bool, string?>( - Enumerable.Repeat(ProcessStepTypeId.ASSIGN_COMPANY_APPLICATION, 1), + Enumerable.Repeat(ProcessStepTypeId.GET_DID_DOCUMENT, 1), ProcessStepStatusId.DONE, false, null); } - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> AssignCompanyApplication(Guid tenantId, CancellationToken cancellationToken) + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> GetDidDocument(Guid tenantId, CancellationToken cancellationToken) { - var (applicationId, companyId, dimInstanceId, isIssuer) = await dimRepositories.GetInstance().GetApplicationAndCompanyId(tenantId).ConfigureAwait(false); - if (applicationId == null) - { - throw new ConflictException("ApplicationId must always be set here"); - } - - if (companyId == null) + var tenantRepository = dimRepositories.GetInstance(); + var (downloadUrl, isIssuer) = await tenantRepository.GetDownloadUrlAndIsIssuer(tenantId).ConfigureAwait(ConfigureAwaitOptions.None); + if (string.IsNullOrWhiteSpace(downloadUrl)) { - throw new ConflictException("CompanyId must always be set here"); + throw new UnexpectedConditionException("DownloadUrl must not be null"); } - if (dimInstanceId == null) - { - throw new ConflictException("DimInstanceId must not be null."); - } + var didDocument = await GetDidDocument(downloadUrl, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + var did = didDocument.RootElement.GetProperty("id").GetString(); - var dimDetails = await cfClient.GetServiceBindingDetails(dimInstanceId.Value, cancellationToken).ConfigureAwait(false); - var dimAuth = new BasicAuthSettings - { - TokenAddress = $"{dimDetails.Credentials.Uaa.Url}/oauth/token", - ClientId = dimDetails.Credentials.Uaa.ClientId, - ClientSecret = dimDetails.Credentials.Uaa.ClientSecret - }; - var dimBaseUrl = dimDetails.Credentials.Url; - var applicationKey = await dimClient.GetApplication(dimAuth, dimBaseUrl, applicationId, cancellationToken); - await dimClient.AssignApplicationToCompany(dimAuth, dimBaseUrl, applicationKey, companyId.Value, cancellationToken).ConfigureAwait(false); - - dimRepositories.GetInstance().AttachAndModifyTenant(tenantId, tenant => + tenantRepository.AttachAndModifyTenant(tenantId, t => { - tenant.ApplicationKey = null; + t.Did = null; }, - tenant => + t => { - tenant.ApplicationKey = applicationKey; + t.Did = did; }); + return new ValueTuple?, ProcessStepStatusId, bool, string?>( Enumerable.Repeat(isIssuer ? ProcessStepTypeId.CREATE_STATUS_LIST : ProcessStepTypeId.SEND_CALLBACK, 1), ProcessStepStatusId.DONE, @@ -485,26 +192,29 @@ await provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRes public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateStatusList(Guid tenantId, CancellationToken cancellationToken) { - var (_, companyId, dimInstanceId, _) = await dimRepositories.GetInstance().GetApplicationAndCompanyId(tenantId).ConfigureAwait(false); + var (companyId, baseUrl, walletData) = await dimRepositories.GetInstance().GetStatusListCreationData(tenantId).ConfigureAwait(ConfigureAwaitOptions.None); + var (tokenAddress, clientId, clientSecret, initializationVector, encryptionMode) = walletData.ValidateData(); + if (companyId == null) { - throw new ConflictException("CompanyId must always be set here"); + throw new UnexpectedConditionException("CompanyId must not be null"); } - if (dimInstanceId == null) + if (baseUrl == null) { - throw new ConflictException("DimInstanceId must not be null."); + throw new UnexpectedConditionException("BaseUrl must not be null"); } - var dimDetails = await cfClient.GetServiceBindingDetails(dimInstanceId.Value, cancellationToken).ConfigureAwait(false); + var cryptoHelper = _settings.EncryptionConfigs.GetCryptoHelper(encryptionMode); + var secret = cryptoHelper.Decrypt(clientSecret, initializationVector); + var dimAuth = new BasicAuthSettings { - TokenAddress = $"{dimDetails.Credentials.Uaa.Url}/oauth/token", - ClientId = dimDetails.Credentials.Uaa.ClientId, - ClientSecret = dimDetails.Credentials.Uaa.ClientSecret + TokenAddress = $"{tokenAddress}/oauth/token", + ClientId = clientId, + ClientSecret = secret }; - var dimBaseUrl = dimDetails.Credentials.Url; - await dimClient.CreateStatusList(dimAuth, dimBaseUrl, companyId.Value, cancellationToken).ConfigureAwait(false); + await dimClient.CreateStatusList(dimAuth, baseUrl, companyId.Value, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); return new ValueTuple?, ProcessStepStatusId, bool, string?>( Enumerable.Repeat(ProcessStepTypeId.SEND_CALLBACK, 1), @@ -515,26 +225,29 @@ await provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRes public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SendCallback(Guid tenantId, CancellationToken cancellationToken) { - var (bpn, downloadUrl, did, dimInstanceId) = await dimRepositories.GetInstance().GetCallbackData(tenantId).ConfigureAwait(false); - if (downloadUrl == null) + var (bpn, baseUrl, walletData, did, downloadUrl) = await dimRepositories.GetInstance().GetCallbackData(tenantId).ConfigureAwait(ConfigureAwaitOptions.None); + var (tokenAddress, clientId, clientSecret, initializationVector, encryptionMode) = walletData.ValidateData(); + if (baseUrl == null) { - throw new ConflictException("DownloadUrl must not be null."); + throw new UnexpectedConditionException("BaseUrl must always be set"); } if (did == null) { - throw new ConflictException("Did must not be null."); + throw new UnexpectedConditionException("Did must always be set"); } - if (dimInstanceId == null) + if (downloadUrl == null) { - throw new ConflictException("DimInstanceId must not be null."); + throw new UnexpectedConditionException("DownloadUrl must always be set"); } - var dimDetails = await cfClient.GetServiceBindingDetails(dimInstanceId.Value, cancellationToken).ConfigureAwait(false); - var didDocument = await dimClient.GetDidDocument(downloadUrl, cancellationToken).ConfigureAwait(false); + var cryptoHelper = _settings.EncryptionConfigs.GetCryptoHelper(encryptionMode); + var secret = cryptoHelper.Decrypt(clientSecret, initializationVector); - await callbackService.SendCallback(bpn, dimDetails, didDocument, did, cancellationToken).ConfigureAwait(false); + var didDocument = await GetDidDocument(downloadUrl, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + var uaa = new AuthenticationDetail(tokenAddress, clientId, secret); + await callbackService.SendCallback(bpn, uaa, didDocument, did, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); return new ValueTuple?, ProcessStepStatusId, bool, string?>( null, @@ -542,4 +255,11 @@ await provisioningClient.CreateCloudFoundryEnvironment(saBinding.Url, bindingRes false, null); } + + private async Task GetDidDocument(string downloadUrl, CancellationToken cancellationToken) + { + var client = httpClientFactory.CreateClient("didDocumentDownload"); + using var result = await client.GetStreamAsync(downloadUrl, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + return await JsonDocument.ParseAsync(result, cancellationToken: cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + } } diff --git a/src/processes/DimProcess.Library/IDimProcessHandler.cs b/src/processes/DimProcess.Library/IDimProcessHandler.cs index 0fd68ad..964da62 100644 --- a/src/processes/DimProcess.Library/IDimProcessHandler.cs +++ b/src/processes/DimProcess.Library/IDimProcessHandler.cs @@ -24,22 +24,10 @@ namespace DimProcess.Library; public interface IDimProcessHandler { - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateSubaccount(Guid tenantId, string tenantName, CancellationToken cancellationToken); - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateServiceManagerBindings(Guid tenantId, CancellationToken cancellationToken); - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> AssignEntitlements(Guid tenantId, CancellationToken cancellationToken); - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateServiceInstance(Guid tenantId, CancellationToken cancellationToken); - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateServiceBindings(Guid tenantId, CancellationToken cancellationToken); - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SubscribeApplication(Guid tenantId, CancellationToken cancellationToken); - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateCloudFoundryEnvironment(Guid tenantId, string tenantName, CancellationToken cancellationToken); - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateCloudFoundrySpace(Guid tenantId, string tenantName, CancellationToken cancellationToken); - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> AddSpaceManagerRole(Guid tenantId, CancellationToken cancellationToken); - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> AddSpaceDeveloperRole(Guid tenantId, CancellationToken cancellationToken); - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateDimServiceInstance(string tenantName, Guid tenantId, CancellationToken cancellationToken); - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateServiceInstanceBindings(string tenantName, Guid tenantId, CancellationToken cancellationToken); - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateApplication(string tenantName, Guid tenantId, CancellationToken cancellationToken); - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> GetDimDetails(string tenantName, Guid tenantId, CancellationToken cancellationToken); - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateCompanyIdentity(Guid tenantId, CancellationToken cancellationToken); + Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateWallet(Guid tenantId, string tenantName, CancellationToken cancellationToken); + Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CheckOperation(Guid tenantId, CancellationToken cancellationToken); + Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> GetCompany(Guid tenantId, string tenantName, CancellationToken cancellationToken); + Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> GetDidDocument(Guid tenantId, CancellationToken cancellationToken); Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateStatusList(Guid tenantId, CancellationToken cancellationToken); - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> AssignCompanyApplication(Guid tenantId, CancellationToken cancellationToken); Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SendCallback(Guid tenantId, CancellationToken cancellationToken); } diff --git a/src/processes/DimProcess.Library/ITechnicalUserProcessHandler.cs b/src/processes/DimProcess.Library/ITechnicalUserProcessHandler.cs index 800c055..a0e58ff 100644 --- a/src/processes/DimProcess.Library/ITechnicalUserProcessHandler.cs +++ b/src/processes/DimProcess.Library/ITechnicalUserProcessHandler.cs @@ -24,9 +24,10 @@ namespace DimProcess.Library; public interface ITechnicalUserProcessHandler { - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateServiceInstanceBindings(string tenantName, Guid technicalUserId, CancellationToken cancellationToken); - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> GetTechnicalUserData(string tenantName, Guid technicalUserId, CancellationToken cancellationToken); + Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateServiceInstanceBindings(Guid technicalUserId, CancellationToken cancellationToken); + Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> GetTechnicalUserData(Guid technicalUserId, CancellationToken cancellationToken); + Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> GetTechnicalUserServiceKey(Guid technicalUserId, CancellationToken cancellationToken); Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SendCreateCallback(Guid technicalUserId, CancellationToken cancellationToken); - Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> DeleteServiceInstanceBindings(string tenantName, Guid technicalUserId, CancellationToken cancellationToken); + Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> DeleteServiceInstanceBindings(Guid technicalUserId, CancellationToken cancellationToken); Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SendDeleteCallback(Guid technicalUserId, CancellationToken cancellationToken); } diff --git a/src/processes/DimProcess.Library/TechnicalUserProcessHandler.cs b/src/processes/DimProcess.Library/TechnicalUserProcessHandler.cs index 18f414e..0f5ec0d 100644 --- a/src/processes/DimProcess.Library/TechnicalUserProcessHandler.cs +++ b/src/processes/DimProcess.Library/TechnicalUserProcessHandler.cs @@ -18,8 +18,11 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -using Dim.Clients.Api.Cf; +using Dim.Clients.Api.Div; +using Dim.Clients.Api.Div.Models; +using Dim.Clients.Token; using Dim.DbAccess; +using Dim.DbAccess.Extensions; using Dim.DbAccess.Repositories; using Dim.Entities.Enums; using DimProcess.Library.Callback; @@ -27,27 +30,29 @@ using Microsoft.Extensions.Options; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration; -using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Encryption; namespace DimProcess.Library; public class TechnicalUserProcessHandler( IDimRepositories dimRepositories, - ICfClient cfClient, + IProvisioningClient provisioningClient, ICallbackService callbackService, IOptions options) : ITechnicalUserProcessHandler { private readonly TechnicalUserSettings _settings = options.Value; - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateServiceInstanceBindings(string tenantName, Guid technicalUserId, CancellationToken cancellationToken) + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> CreateServiceInstanceBindings(Guid technicalUserId, CancellationToken cancellationToken) { - var (spaceId, technicalUserName) = await dimRepositories.GetInstance().GetSpaceIdAndTechnicalUserName(technicalUserId).ConfigureAwait(false); - if (spaceId == null) + var tenantRepository = dimRepositories.GetInstance(); + var (walletId, technicalUserName) = await tenantRepository.GetTechnicalUserNameAndWalletId(technicalUserId).ConfigureAwait(ConfigureAwaitOptions.None); + if (walletId == null) { - throw new ConflictException("SpaceId must not be null."); + throw new ConflictException("WalletId must not be null"); } - await cfClient.CreateServiceInstanceBindings(tenantName, technicalUserName, spaceId.Value, cancellationToken).ConfigureAwait(false); + var operationId = await provisioningClient.CreateServiceKey(technicalUserName, walletId.Value, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + + tenantRepository.AttachAndModifyTechnicalUser(technicalUserId, t => t.OperationId = null, t => t.OperationId = operationId); return new ValueTuple?, ProcessStepStatusId, bool, string?>( Enumerable.Repeat(ProcessStepTypeId.GET_TECHNICAL_USER_DATA, 1), @@ -56,66 +61,94 @@ public class TechnicalUserProcessHandler( null); } - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> GetTechnicalUserData(string tenantName, Guid technicalUserId, CancellationToken cancellationToken) + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> GetTechnicalUserData(Guid technicalUserId, CancellationToken cancellationToken) { - var (spaceId, technicalUserName) = await dimRepositories.GetInstance().GetSpaceIdAndTechnicalUserName(technicalUserId).ConfigureAwait(false); - if (spaceId == null) + var tenantRepository = dimRepositories.GetInstance(); + var operationId = await tenantRepository.GetOperationIdForTechnicalUser(technicalUserId) + .ConfigureAwait(ConfigureAwaitOptions.None); + if (operationId is null) { - throw new ConflictException("SpaceId must not be null."); + throw new ConflictException("OperationId must not be null"); } - var dimInstanceId = await cfClient.GetServiceBinding(tenantName, spaceId.Value, $"{technicalUserName}-dim-key01", cancellationToken).ConfigureAwait(false); - var dimDetails = await cfClient.GetServiceBindingDetails(dimInstanceId, cancellationToken).ConfigureAwait(false); + var response = await provisioningClient.GetOperation(operationId.Value, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None); + if (response.Status == OperationResponseStatus.pending) + { + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + null, + ProcessStepStatusId.TODO, + true, + null); + } + + if (response is { Status: OperationResponseStatus.completed, Data: null }) + { + throw new UnexpectedConditionException($"Data should never be null when in status {OperationResponseStatus.completed}"); + } + var serviceKey = response.Data!.ServiceKey; var cryptoHelper = _settings.EncryptionConfigs.GetCryptoHelper(_settings.EncryptionConfigIndex); - var (secret, initializationVector) = cryptoHelper.Encrypt(dimDetails.Credentials.Uaa.ClientSecret); + var (secret, initializationVector) = cryptoHelper.Encrypt(serviceKey.Uaa.ClientSecret); - dimRepositories.GetInstance().AttachAndModifyTechnicalUser(technicalUserId, technicalUser => + tenantRepository.AttachAndModifyTechnicalUser(technicalUserId, + t => { - technicalUser.TokenAddress = null; - technicalUser.ClientId = null; - technicalUser.ClientSecret = null; - technicalUser.InitializationVector = null; - technicalUser.EncryptionMode = null; + t.TokenAddress = null; + t.ClientId = null; + t.ClientSecret = null; }, - technicalUser => + t => { - technicalUser.TokenAddress = $"{dimDetails.Credentials.Uaa.Url}/oauth/token"; - technicalUser.ClientId = dimDetails.Credentials.Uaa.ClientId; - technicalUser.ClientSecret = secret; - technicalUser.InitializationVector = initializationVector; - technicalUser.EncryptionMode = _settings.EncryptionConfigIndex; + t.TokenAddress = serviceKey.Uaa.Url; + t.ClientId = serviceKey.Uaa.ClientId; + t.ClientSecret = secret; + t.InitializationVector = initializationVector; + t.EncryptionMode = _settings.EncryptionConfigIndex; }); + return new ValueTuple?, ProcessStepStatusId, bool, string?>( - Enumerable.Repeat(ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK, 1), + Enumerable.Repeat(ProcessStepTypeId.GET_TECHNICAL_USER_SERVICE_KEY, 1), ProcessStepStatusId.DONE, false, null); } - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SendCreateCallback(Guid technicalUserId, CancellationToken cancellationToken) + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> GetTechnicalUserServiceKey(Guid technicalUserId, CancellationToken cancellationToken) { - var (externalId, tokenAddress, clientId, clientSecret, initializationVector, _) = await dimRepositories.GetInstance().GetTechnicalUserCallbackData(technicalUserId).ConfigureAwait(false); - - if (string.IsNullOrWhiteSpace(clientId)) + var (walletId, technicalUserName) = await dimRepositories.GetInstance().GetWalletIdAndNameForTechnicalUser(technicalUserId).ConfigureAwait(ConfigureAwaitOptions.None); + if (walletId == null) { - throw new ConflictException("ClientId must not be null"); + throw new ConflictException("WalletId must be set"); } - if (string.IsNullOrWhiteSpace(tokenAddress)) - { - throw new ConflictException("TokenAddress must not be null"); - } + var serviceKeyId = await provisioningClient.GetServiceKey(technicalUserName, walletId.Value, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + dimRepositories.GetInstance().AttachAndModifyTechnicalUser(technicalUserId, + t => + { + t.ServiceKeyId = null; + }, + t => + { + t.ServiceKeyId = serviceKeyId; + }); - if (clientSecret == null) - { - throw new ConflictException("Secret must not be null"); - } + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + Enumerable.Repeat(ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK, 1), + ProcessStepStatusId.DONE, + false, + null); + } - var cryptoHelper = _settings.EncryptionConfigs.GetCryptoHelper(_settings.EncryptionConfigIndex); + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SendCreateCallback(Guid technicalUserId, CancellationToken cancellationToken) + { + var (externalId, walletData) = await dimRepositories.GetInstance().GetTechnicalUserCallbackData(technicalUserId).ConfigureAwait(ConfigureAwaitOptions.None); + var (tokenAddress, clientId, clientSecret, initializationVector, encryptionMode) = walletData.ValidateData(); + + var cryptoHelper = _settings.EncryptionConfigs.GetCryptoHelper(encryptionMode); var secret = cryptoHelper.Decrypt(clientSecret, initializationVector); - await callbackService.SendTechnicalUserCallback(externalId, tokenAddress, clientId, secret, cancellationToken).ConfigureAwait(false); + await callbackService.SendTechnicalUserCallback(externalId, tokenAddress, clientId, secret, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); return new ValueTuple?, ProcessStepStatusId, bool, string?>( null, @@ -124,16 +157,24 @@ public class TechnicalUserProcessHandler( null); } - public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> DeleteServiceInstanceBindings(string tenantName, Guid technicalUserId, CancellationToken cancellationToken) + public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> DeleteServiceInstanceBindings(Guid technicalUserId, CancellationToken cancellationToken) { - var (spaceId, technicalUserName) = await dimRepositories.GetInstance().GetSpaceIdAndTechnicalUserName(technicalUserId).ConfigureAwait(false); - if (spaceId == null) + var technicalUserRepository = dimRepositories.GetInstance(); + var (serviceKeyId, walletId) = await technicalUserRepository.GetServiceKeyAndWalletId(technicalUserId).ConfigureAwait(ConfigureAwaitOptions.None); + if (walletId == null) { - throw new ConflictException("SpaceId must not be null."); + throw new ConflictException("WalletId must not be null"); } - var serviceBindingId = await cfClient.GetServiceBinding(tenantName, spaceId.Value, $"{technicalUserName}-dim-key01", cancellationToken).ConfigureAwait(false); - await cfClient.DeleteServiceInstanceBindings(serviceBindingId, cancellationToken).ConfigureAwait(false); + if (serviceKeyId == null) + { + throw new ConflictException("ServiceKeyId must not be null"); + } + + var operationId = await provisioningClient.DeleteServiceKey(walletId.Value, serviceKeyId.Value, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None); + + technicalUserRepository.AttachAndModifyTechnicalUser(technicalUserId, t => t.OperationId = null, t => t.OperationId = operationId); return new ValueTuple?, ProcessStepStatusId, bool, string?>( Enumerable.Repeat(ProcessStepTypeId.SEND_TECHNICAL_USER_DELETION_CALLBACK, 1), @@ -144,11 +185,28 @@ public class TechnicalUserProcessHandler( public async Task<(IEnumerable? nextStepTypeIds, ProcessStepStatusId stepStatusId, bool modified, string? processMessage)> SendDeleteCallback(Guid technicalUserId, CancellationToken cancellationToken) { - var tenantRepository = dimRepositories.GetInstance(); + var tenantRepository = dimRepositories.GetInstance(); + + var (operationId, externalId) = await tenantRepository.GetOperationAndExternalIdForTechnicalUser(technicalUserId) + .ConfigureAwait(ConfigureAwaitOptions.None); + if (operationId is null) + { + throw new ConflictException("OperationId must not be null"); + } + + var response = await provisioningClient.GetOperation(operationId.Value, cancellationToken) + .ConfigureAwait(ConfigureAwaitOptions.None); + if (response.Status == OperationResponseStatus.pending) + { + return new ValueTuple?, ProcessStepStatusId, bool, string?>( + null, + ProcessStepStatusId.TODO, + true, + null); + } - var externalId = await tenantRepository.GetExternalIdForTechnicalUser(technicalUserId).ConfigureAwait(false); tenantRepository.RemoveTechnicalUser(technicalUserId); - await callbackService.SendTechnicalUserDeletionCallback(externalId, cancellationToken).ConfigureAwait(false); + await callbackService.SendTechnicalUserDeletionCallback(externalId, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); return new ValueTuple?, ProcessStepStatusId, bool, string?>( null, diff --git a/src/processes/Processes.Library/ManualProcessStepDataExtensions.cs b/src/processes/Processes.Library/ManualProcessStepDataExtensions.cs index 9820578..f6f8522 100644 --- a/src/processes/Processes.Library/ManualProcessStepDataExtensions.cs +++ b/src/processes/Processes.Library/ManualProcessStepDataExtensions.cs @@ -19,6 +19,7 @@ ********************************************************************************/ using Dim.DbAccess; +using Dim.DbAccess.Models; using Dim.DbAccess.Repositories; using Dim.Entities.Entities; using Dim.Entities.Enums; diff --git a/src/processes/Processes.Worker.Library/IProcessExecutor.cs b/src/processes/Processes.Worker.Library/IProcessExecutor.cs index 88a0b87..22bb752 100644 --- a/src/processes/Processes.Worker.Library/IProcessExecutor.cs +++ b/src/processes/Processes.Worker.Library/IProcessExecutor.cs @@ -20,7 +20,7 @@ using Dim.Entities.Enums; -namespace Org.Eclipse.TractusX.Portal.Backend.Processes.Worker.Library; +namespace Processes.Worker.Library; public interface IProcessExecutor { diff --git a/src/processes/Processes.Worker.Library/IProcessTypeExecutor.cs b/src/processes/Processes.Worker.Library/IProcessTypeExecutor.cs index 051803a..213de9f 100644 --- a/src/processes/Processes.Worker.Library/IProcessTypeExecutor.cs +++ b/src/processes/Processes.Worker.Library/IProcessTypeExecutor.cs @@ -21,7 +21,7 @@ using Dim.Entities.Enums; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; -namespace Org.Eclipse.TractusX.Portal.Backend.Processes.Worker.Library; +namespace Processes.Worker.Library; public interface IProcessTypeExecutor { diff --git a/src/processes/Processes.Worker.Library/ProcessExecutionService.cs b/src/processes/Processes.Worker.Library/ProcessExecutionService.cs index d8644c2..6f0e449 100644 --- a/src/processes/Processes.Worker.Library/ProcessExecutionService.cs +++ b/src/processes/Processes.Worker.Library/ProcessExecutionService.cs @@ -27,7 +27,7 @@ using Org.Eclipse.TractusX.Portal.Backend.Framework.DBAccess; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; -namespace Org.Eclipse.TractusX.Portal.Backend.Processes.Worker.Library; +namespace Processes.Worker.Library; /// /// Service that reads all open/pending processSteps of a checklist and triggers their execution. @@ -109,7 +109,7 @@ bool UpdateVersion() return true; } - await foreach (var executionResult in processExecutor.ExecuteProcess(process.Id, process.ProcessTypeId, stoppingToken).WithCancellation(stoppingToken).ConfigureAwait(false)) + await foreach (var executionResult in processExecutor.ExecuteProcess(process.Id, process.ProcessTypeId, stoppingToken).ConfigureAwait(false)) { if (executionResult switch { @@ -118,14 +118,14 @@ bool UpdateVersion() _ => false }) { - await executorRepositories.SaveAsync().ConfigureAwait(false); + await executorRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); } executorRepositories.Clear(); } if (process.ReleaseLock()) { - await executorRepositories.SaveAsync().ConfigureAwait(false); + await executorRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); executorRepositories.Clear(); } _logger.LogInformation("finished processing process {processId}", process.Id); diff --git a/src/processes/Processes.Worker.Library/ProcessExecutionServiceExtensions.cs b/src/processes/Processes.Worker.Library/ProcessExecutionServiceExtensions.cs index 4642c0c..8fd0c74 100644 --- a/src/processes/Processes.Worker.Library/ProcessExecutionServiceExtensions.cs +++ b/src/processes/Processes.Worker.Library/ProcessExecutionServiceExtensions.cs @@ -22,7 +22,7 @@ using Microsoft.Extensions.DependencyInjection; using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider; -namespace Org.Eclipse.TractusX.Portal.Backend.Processes.Worker.Library; +namespace Processes.Worker.Library; public static class ProcessExecutionServiceExtensions { diff --git a/src/processes/Processes.Worker.Library/ProcessExecutionServiceSettings.cs b/src/processes/Processes.Worker.Library/ProcessExecutionServiceSettings.cs index 1508a17..3f31291 100644 --- a/src/processes/Processes.Worker.Library/ProcessExecutionServiceSettings.cs +++ b/src/processes/Processes.Worker.Library/ProcessExecutionServiceSettings.cs @@ -20,7 +20,7 @@ using System.ComponentModel.DataAnnotations; -namespace Org.Eclipse.TractusX.Portal.Backend.Processes.Worker.Library; +namespace Processes.Worker.Library; public class ProcessExecutionServiceSettings { diff --git a/src/processes/Processes.Worker.Library/ProcessExecutor.cs b/src/processes/Processes.Worker.Library/ProcessExecutor.cs index 96905e0..2c31944 100644 --- a/src/processes/Processes.Worker.Library/ProcessExecutor.cs +++ b/src/processes/Processes.Worker.Library/ProcessExecutor.cs @@ -27,7 +27,7 @@ using System.Collections.Immutable; using System.Runtime.CompilerServices; -namespace Org.Eclipse.TractusX.Portal.Backend.Processes.Worker.Library; +namespace Processes.Worker.Library; public class ProcessExecutor : IProcessExecutor { @@ -78,6 +78,7 @@ public ProcessExecutor(IEnumerable executors, IDimReposito { yield return IProcessExecutor.ProcessExecutionResult.LockRequested; } + ProcessStepStatusId resultStepStatusId; IEnumerable? scheduleStepTypeIds; IEnumerable? skipStepTypeIds; diff --git a/src/processes/Processes.Worker/Program.cs b/src/processes/Processes.Worker/Program.cs index 1d26e66..8420912 100644 --- a/src/processes/Processes.Worker/Program.cs +++ b/src/processes/Processes.Worker/Program.cs @@ -24,7 +24,7 @@ using Microsoft.Extensions.Hosting; using Org.Eclipse.TractusX.Portal.Backend.Framework.Logging; using Org.Eclipse.TractusX.Portal.Backend.Framework.Token; -using Org.Eclipse.TractusX.Portal.Backend.Processes.Worker.Library; +using Processes.Worker.Library; using Serilog; LoggingExtensions.EnsureInitialized(); @@ -56,7 +56,7 @@ Log.Information("Start processing"); var workerInstance = host.Services.GetRequiredService(); - await workerInstance.ExecuteAsync(tokenSource.Token).ConfigureAwait(false); + await workerInstance.ExecuteAsync(tokenSource.Token).ConfigureAwait(ConfigureAwaitOptions.None); Log.Information("Execution finished shutting down"); } catch (Exception ex) when (!ex.GetType().Name.Equals("StopTheHostException", StringComparison.Ordinal)) diff --git a/src/processes/Processes.Worker/appsettings.json b/src/processes/Processes.Worker/appsettings.json index 1e79cbc..1bf1544 100644 --- a/src/processes/Processes.Worker/appsettings.json +++ b/src/processes/Processes.Worker/appsettings.json @@ -24,24 +24,26 @@ "DimDb": "Server=placeholder;Database=placeholder;Port=5432;User Id=placeholder;Password=placeholder;Ssl Mode=Disable;" }, "Dim": { - "AdminMail": "", - "RootDirectoryId": "", - "ClientidCisCentral": "", - "ClientsecretCisCentral": "", - "AuthUrl": "" + "ApplicationName": "" }, - "SubAccount": { - "BaseUrl": "" + "Processes": { + "LockExpirySeconds": 300 }, - "Entitlement": { - "BaseUrl": "" - }, - "Cf": { + "Provisioning": { "ClientId": "", "ClientSecret": "", "TokenAddress": "", "BaseUrl": "", - "GrantType": "" + "GrantType": "", + "EncryptionConfigIndex": 0, + "EncryptionConfigs": [ + { + "Index": 0, + "EncryptionKey": "", + "CipherMode": "", + "PaddingMode": "" + } + ] }, "Callback": { "Username": "", diff --git a/src/web/Dim.Web/BusinessLogic/DimBusinessLogic.cs b/src/web/Dim.Web/BusinessLogic/DimBusinessLogic.cs index a57dd66..2ba198c 100644 --- a/src/web/Dim.Web/BusinessLogic/DimBusinessLogic.cs +++ b/src/web/Dim.Web/BusinessLogic/DimBusinessLogic.cs @@ -18,28 +18,32 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -using Dim.Clients.Api.Cf; using Dim.Clients.Api.Dim; using Dim.Clients.Token; using Dim.DbAccess; +using Dim.DbAccess.Extensions; +using Dim.DbAccess.Models; using Dim.DbAccess.Repositories; using Dim.Entities.Enums; +using Dim.Entities.Extensions; +using Dim.Processes.Library; using Dim.Web.ErrorHandling; using Dim.Web.Models; using Microsoft.Extensions.Options; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration; using System.Text.RegularExpressions; namespace Dim.Web.BusinessLogic; public class DimBusinessLogic( IDimRepositories dimRepositories, - ICfClient cfClient, IDimClient dimClient, IOptions options) : IDimBusinessLogic { private static readonly Regex TenantName = new(@"(?<=[^\w-])|(?<=[^-])[\W_]+|(?<=[^-])$", RegexOptions.Compiled, new TimeSpan(0, 0, 0, 1)); + private static readonly Regex TechnicalUserName = new("[^a-zA-Z0-9]+", RegexOptions.Compiled, new TimeSpan(0, 0, 0, 1)); private readonly DimSettings _settings = options.Value; public async Task StartSetupDim(string companyName, string bpn, string didDocumentLocation, bool isIssuer) @@ -47,21 +51,22 @@ public async Task StartSetupDim(string companyName, string bpn, string didDocume var tenant = TenantName.Replace(companyName, string.Empty).TrimStart('-').TrimEnd('-').ToLower(); if (await dimRepositories.GetInstance().IsTenantExisting(companyName, bpn).ConfigureAwait(ConfigureAwaitOptions.None)) { - throw new ConflictException($"Tenant {companyName} with Bpn {bpn} already exists"); + throw ConflictException.Create(DimErrors.TENANT_ALREADY_EXISTS, new ErrorParameter[] { new("companyName", companyName), new("bpn", bpn) }); } var processStepRepository = dimRepositories.GetInstance(); var processId = processStepRepository.CreateProcess(ProcessTypeId.SETUP_DIM).Id; - processStepRepository.CreateProcessStep(ProcessStepTypeId.CREATE_SUBACCOUNT, ProcessStepStatusId.TODO, processId); + processStepRepository.CreateProcessStep(ProcessStepTypeId.CREATE_WALLET, ProcessStepStatusId.TODO, processId); dimRepositories.GetInstance().CreateTenant(tenant, bpn, didDocumentLocation, isIssuer, processId, _settings.OperatorId); - await dimRepositories.SaveAsync().ConfigureAwait(false); + await dimRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); } public async Task GetStatusList(string bpn, CancellationToken cancellationToken) { - var (exists, companyId, instanceId) = await dimRepositories.GetInstance().GetCompanyAndInstanceIdForBpn(bpn).ConfigureAwait(false); + var (exists, companyId, baseUrl, walletData) = await dimRepositories.GetInstance().GetCompanyAndWalletDataForBpn(bpn).ConfigureAwait(ConfigureAwaitOptions.None); + var (tokenAddress, clientId, clientSecret, initializationVector, encryptionMode) = walletData.ValidateData(); if (!exists) { throw NotFoundException.Create(DimErrors.NO_COMPANY_FOR_BPN, new ErrorParameter[] { new("bpn", bpn) }); @@ -72,25 +77,27 @@ public async Task GetStatusList(string bpn, CancellationToken cancellati throw ConflictException.Create(DimErrors.NO_COMPANY_ID_SET); } - if (instanceId is null) + if (baseUrl is null) { - throw ConflictException.Create(DimErrors.NO_INSTANCE_ID_SET); + throw ConflictException.Create(DimErrors.NO_BASE_URL_SET); } - var dimDetails = await cfClient.GetServiceBindingDetails(instanceId.Value, cancellationToken).ConfigureAwait(false); + var cryptoHelper = _settings.EncryptionConfigs.GetCryptoHelper(encryptionMode); + var secret = cryptoHelper.Decrypt(clientSecret, initializationVector); + var dimAuth = new BasicAuthSettings { - TokenAddress = $"{dimDetails.Credentials.Uaa.Url}/oauth/token", - ClientId = dimDetails.Credentials.Uaa.ClientId, - ClientSecret = dimDetails.Credentials.Uaa.ClientSecret + TokenAddress = $"{tokenAddress}/oauth/token", + ClientId = clientId, + ClientSecret = secret }; - var dimBaseUrl = dimDetails.Credentials.Url; - return await dimClient.GetStatusList(dimAuth, dimBaseUrl, companyId.Value, cancellationToken).ConfigureAwait(false); + return await dimClient.GetStatusList(dimAuth, baseUrl, companyId.Value, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } public async Task CreateStatusList(string bpn, CancellationToken cancellationToken) { - var (exists, companyId, instanceId) = await dimRepositories.GetInstance().GetCompanyAndInstanceIdForBpn(bpn).ConfigureAwait(false); + var (exists, companyId, baseUrl, walletData) = await dimRepositories.GetInstance().GetCompanyAndWalletDataForBpn(bpn).ConfigureAwait(ConfigureAwaitOptions.None); + var (tokenAddress, clientId, clientSecret, initializationVector, encryptionMode) = walletData.ValidateData(); if (!exists) { throw NotFoundException.Create(DimErrors.NO_COMPANY_FOR_BPN, new ErrorParameter[] { new("bpn", bpn) }); @@ -101,25 +108,26 @@ public async Task CreateStatusList(string bpn, CancellationToken cancell throw ConflictException.Create(DimErrors.NO_COMPANY_ID_SET); } - if (instanceId is null) + if (baseUrl is null) { - throw ConflictException.Create(DimErrors.NO_INSTANCE_ID_SET); + throw ConflictException.Create(DimErrors.NO_BASE_URL_SET); } - var dimDetails = await cfClient.GetServiceBindingDetails(instanceId.Value, cancellationToken).ConfigureAwait(false); + var cryptoHelper = _settings.EncryptionConfigs.GetCryptoHelper(encryptionMode); + var secret = cryptoHelper.Decrypt(clientSecret, initializationVector); + var dimAuth = new BasicAuthSettings { - TokenAddress = $"{dimDetails.Credentials.Uaa.Url}/oauth/token", - ClientId = dimDetails.Credentials.Uaa.ClientId, - ClientSecret = dimDetails.Credentials.Uaa.ClientSecret + TokenAddress = $"{tokenAddress}/oauth/token", + ClientId = clientId, + ClientSecret = secret }; - var dimBaseUrl = dimDetails.Credentials.Url; - return await dimClient.CreateStatusList(dimAuth, dimBaseUrl, companyId.Value, cancellationToken).ConfigureAwait(false); + return await dimClient.CreateStatusList(dimAuth, baseUrl, companyId.Value, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); } public async Task CreateTechnicalUser(string bpn, TechnicalUserData technicalUserData) { - var (exists, tenantId) = await dimRepositories.GetInstance().GetTenantForBpn(bpn).ConfigureAwait(false); + var (exists, tenantId) = await dimRepositories.GetInstance().GetTenantForBpn(bpn).ConfigureAwait(ConfigureAwaitOptions.None); if (!exists) { @@ -130,14 +138,16 @@ public async Task CreateTechnicalUser(string bpn, TechnicalUserData technicalUse var processId = processStepRepository.CreateProcess(ProcessTypeId.TECHNICAL_USER).Id; processStepRepository.CreateProcessStep(ProcessStepTypeId.CREATE_TECHNICAL_USER, ProcessStepStatusId.TODO, processId); - dimRepositories.GetInstance().CreateTenantTechnicalUser(tenantId, technicalUserData.Name, technicalUserData.ExternalId, processId); + var technicalUserName = TechnicalUserName.Replace(technicalUserData.Name, string.Empty).ToLower(); + dimRepositories.GetInstance().CreateTenantTechnicalUser(tenantId, technicalUserName, technicalUserData.ExternalId, processId); - await dimRepositories.SaveAsync().ConfigureAwait(false); + await dimRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); } public async Task DeleteTechnicalUser(string bpn, TechnicalUserData technicalUserData) { - var (exists, technicalUserId, processId) = await dimRepositories.GetInstance().GetTechnicalUserForBpn(bpn, technicalUserData.Name).ConfigureAwait(false); + var technicalUserName = TechnicalUserName.Replace(technicalUserData.Name, string.Empty).ToLower(); + var (exists, technicalUserId, processId) = await dimRepositories.GetInstance().GetTechnicalUserForBpn(bpn, technicalUserName).ConfigureAwait(ConfigureAwaitOptions.None); if (!exists) { throw NotFoundException.Create(DimErrors.NO_TECHNICAL_USER_FOUND, new ErrorParameter[] { new("bpn", bpn) }); @@ -146,12 +156,59 @@ public async Task DeleteTechnicalUser(string bpn, TechnicalUserData technicalUse var processStepRepository = dimRepositories.GetInstance(); processStepRepository.CreateProcessStep(ProcessStepTypeId.DELETE_TECHNICAL_USER, ProcessStepStatusId.TODO, processId); - dimRepositories.GetInstance().AttachAndModifyTechnicalUser(technicalUserId, null, t => + dimRepositories.GetInstance().AttachAndModifyTechnicalUser(technicalUserId, + t => + { + t.ExternalId = Guid.Empty; + t.ProcessId = processId; + }, + t => + { + t.ExternalId = technicalUserData.ExternalId; + t.ProcessId = processId; + }); + + await dimRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); + } + + public async Task GetSetupProcess(string bpn, string companyName) + { + var processData = await dimRepositories.GetInstance().GetWalletProcessForTenant(bpn, companyName) + .ConfigureAwait(ConfigureAwaitOptions.None); + if (processData == null) + { + throw new NotFoundException($"No process data found for BPN {bpn} and company name {companyName}"); + } + + return processData; + } + + public async Task GetTechnicalUserProcess(string bpn, string companyName, string technicalUserName) + { + var processData = await dimRepositories.GetInstance().GetTechnicalUserProcess(bpn, companyName, technicalUserName) + .ConfigureAwait(ConfigureAwaitOptions.None); + if (processData == null) + { + throw new NotFoundException($"No process data found for technical user {technicalUserName}"); + } + + return processData; + } + + public async Task RetriggerProcess(ProcessTypeId processTypeId, Guid processId, ProcessStepTypeId processStepTypeId) + { + var stepToTrigger = processStepTypeId.GetStepForRetrigger(processTypeId); + + var (validProcessId, processData) = await dimRepositories.GetInstance().IsValidProcess(processId, processTypeId, Enumerable.Repeat(processStepTypeId, 1)).ConfigureAwait(ConfigureAwaitOptions.None); + if (!validProcessId) { - t.ExternalId = technicalUserData.ExternalId; - t.ProcessId = processId; - }); + throw new NotFoundException($"process {processId} does not exist"); + } + + var context = processData.CreateManualProcessData(stepToTrigger, dimRepositories, () => $"processId {processId}"); - await dimRepositories.SaveAsync().ConfigureAwait(false); + context.ScheduleProcessSteps(Enumerable.Repeat(stepToTrigger, 1)); + context.FinalizeProcessStep(); + await dimRepositories.SaveAsync().ConfigureAwait(ConfigureAwaitOptions.None); } } diff --git a/src/web/Dim.Web/BusinessLogic/DimSettings.cs b/src/web/Dim.Web/BusinessLogic/DimSettings.cs index 2800a08..da6d6bb 100644 --- a/src/web/Dim.Web/BusinessLogic/DimSettings.cs +++ b/src/web/Dim.Web/BusinessLogic/DimSettings.cs @@ -18,9 +18,17 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Validation; +using System.ComponentModel.DataAnnotations; + namespace Dim.Web.BusinessLogic; public class DimSettings { public Guid OperatorId { get; set; } + + [Required] + [DistinctValues("x => x.Index")] + public IEnumerable EncryptionConfigs { get; set; } = null!; } diff --git a/src/web/Dim.Web/BusinessLogic/IDimBusinessLogic.cs b/src/web/Dim.Web/BusinessLogic/IDimBusinessLogic.cs index 603bb0e..ee75f04 100644 --- a/src/web/Dim.Web/BusinessLogic/IDimBusinessLogic.cs +++ b/src/web/Dim.Web/BusinessLogic/IDimBusinessLogic.cs @@ -18,6 +18,8 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Dim.DbAccess.Models; +using Dim.Entities.Enums; using Dim.Web.Models; using Org.Eclipse.TractusX.Portal.Backend.Framework.DependencyInjection; @@ -30,4 +32,7 @@ public interface IDimBusinessLogic : ITransient Task CreateStatusList(string bpn, CancellationToken cancellationToken); Task CreateTechnicalUser(string bpn, TechnicalUserData technicalUserData); Task DeleteTechnicalUser(string bpn, TechnicalUserData technicalUserData); + Task GetSetupProcess(string bpn, string companyName); + Task GetTechnicalUserProcess(string bpn, string companyName, string technicalUserName); + Task RetriggerProcess(ProcessTypeId processTypeId, Guid processId, ProcessStepTypeId processStepTypeId); } diff --git a/src/web/Dim.Web/Controllers/DimController.cs b/src/web/Dim.Web/Controllers/DimController.cs index 486ecdb..642a32a 100644 --- a/src/web/Dim.Web/Controllers/DimController.cs +++ b/src/web/Dim.Web/Controllers/DimController.cs @@ -18,6 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Dim.Entities.Enums; using Dim.Web.BusinessLogic; using Dim.Web.Extensions; using Dim.Web.Models; @@ -80,6 +81,59 @@ public static RouteGroupBuilder MapDimApi(this RouteGroupBuilder group) .RequireAuthorization(r => r.RequireRole("delete_technical_user")) .Produces(StatusCodes.Status200OK, contentType: Constants.JsonContentType); + dim.MapGet("process/setup", ( + [FromQuery] string bpn, + [FromQuery] string companyName, + [FromServices] IDimBusinessLogic dimBusinessLogic) + => dimBusinessLogic.GetSetupProcess(bpn, companyName) + ) + .WithSwaggerDescription("Gets the wallet creation process id for the given bpn and companyName", + "Example: Post: api/dim/process/setup?bpn={bpn}&companyName={companyName}", + "bpn of the company", + "name of the company") + .RequireAuthorization(r => r.RequireRole("get_process")) + .Produces(StatusCodes.Status200OK, contentType: Constants.JsonContentType); + + dim.MapGet("process/technical-user", ( + [FromQuery] string bpn, + [FromQuery] string companyName, + [FromQuery] string technicalUserName, + [FromServices] IDimBusinessLogic dimBusinessLogic) + => dimBusinessLogic.GetTechnicalUserProcess(bpn, companyName, technicalUserName) + ) + .WithSwaggerDescription("Gets the technical user creation process id for the given technicalUserName", + "Example: Post: api/dim/process/technical-user?bpn={bpn}&companyName={companyName}&technicalUserName={technicalUserName}", + "bpn of the company", + "name of the company", + "name of the techincal user to get the process for") + .RequireAuthorization(r => r.RequireRole("get_process")) + .Produces(StatusCodes.Status200OK, contentType: Constants.JsonContentType); + + dim.MapGet("process/wallet/{processId}/retrigger", ( + [FromRoute] Guid processId, + [FromQuery] ProcessStepTypeId processStepTypeId, + [FromServices] IDimBusinessLogic dimBusinessLogic) + => dimBusinessLogic.RetriggerProcess(ProcessTypeId.SETUP_DIM, processId, processStepTypeId) + ) + .WithSwaggerDescription("Retriggers the given process step of the wallet creation process", + "Example: Post: api/dim/process/wallet/{processId}/retrigger?processStepTypeId={processStepTypeId}", + "Id of the process", + "The process step that should be retriggered") + .RequireAuthorization(r => r.RequireRole("retrigger_process")) + .Produces(StatusCodes.Status200OK, contentType: Constants.JsonContentType); + + dim.MapGet("process/technicalUser/{processId}/retrigger", ( + [FromRoute] Guid processId, + [FromQuery] ProcessStepTypeId processStepTypeId, + [FromServices] IDimBusinessLogic dimBusinessLogic) + => dimBusinessLogic.RetriggerProcess(ProcessTypeId.TECHNICAL_USER, processId, processStepTypeId) + ) + .WithSwaggerDescription("Retriggers the given process step of a technical user process", + "Example: Post: api/dim/process/technicalUser/{processId}/retrigger?processStepTypeId={processStepTypeId}", + "Id of the process", + "The process step that should be retriggered") + .RequireAuthorization(r => r.RequireRole("retrigger_process")) + .Produces(StatusCodes.Status200OK, contentType: Constants.JsonContentType); return group; } } diff --git a/src/web/Dim.Web/Dim.Web.csproj b/src/web/Dim.Web/Dim.Web.csproj index 886a45f..4a29c3d 100644 --- a/src/web/Dim.Web/Dim.Web.csproj +++ b/src/web/Dim.Web/Dim.Web.csproj @@ -21,6 +21,7 @@ + diff --git a/src/web/Dim.Web/ErrorHandling/DimErrorMessageContainer.cs b/src/web/Dim.Web/ErrorHandling/DimErrorMessageContainer.cs index 3843828..b8ced57 100644 --- a/src/web/Dim.Web/ErrorHandling/DimErrorMessageContainer.cs +++ b/src/web/Dim.Web/ErrorHandling/DimErrorMessageContainer.cs @@ -28,10 +28,12 @@ namespace Dim.Web.ErrorHandling; public class DimErrorMessageContainer : IErrorMessageContainer { private static readonly IReadOnlyDictionary _messageContainer = new Dictionary { + { DimErrors.TENANT_ALREADY_EXISTS, "Tenant {companyName} with Bpn {bpn} already exists" }, { DimErrors.NO_COMPANY_FOR_BPN, "No Tenant found for Bpn {bpn}" }, { DimErrors.NO_COMPANY_ID_SET, "No Company Id set" }, { DimErrors.NO_INSTANCE_ID_SET, "No Instnace Id set" }, { DimErrors.NO_TECHNICAL_USER_FOUND, "No Technical User found" }, + { DimErrors.NO_BASE_URL_SET, "No BaseUrl for the wallet set" }, }.ToImmutableDictionary(x => (int)x.Key, x => x.Value); public Type Type { get => typeof(DimErrors); } @@ -40,8 +42,10 @@ public class DimErrorMessageContainer : IErrorMessageContainer public enum DimErrors { + TENANT_ALREADY_EXISTS, NO_COMPANY_FOR_BPN, NO_COMPANY_ID_SET, NO_INSTANCE_ID_SET, - NO_TECHNICAL_USER_FOUND + NO_TECHNICAL_USER_FOUND, + NO_BASE_URL_SET, } diff --git a/src/web/Dim.Web/Extensions/ServiceCollectionExtensions.cs b/src/web/Dim.Web/Extensions/ServiceCollectionExtensions.cs index 9eca2df..122edae 100644 --- a/src/web/Dim.Web/Extensions/ServiceCollectionExtensions.cs +++ b/src/web/Dim.Web/Extensions/ServiceCollectionExtensions.cs @@ -28,7 +28,8 @@ public static IServiceCollection AddDim(this IServiceCollection services, IConfi { services.AddOptions() .Bind(section) - .ValidateOnStart(); + .ValidateOnStart() + .ValidateDataAnnotations(); return services; } } diff --git a/src/web/Dim.Web/Program.cs b/src/web/Dim.Web/Program.cs index 6b5e0be..f6e02fc 100644 --- a/src/web/Dim.Web/Program.cs +++ b/src/web/Dim.Web/Program.cs @@ -18,7 +18,6 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -using Dim.Clients.Api.Cf.DependencyInjection; using Dim.Clients.Api.Dim.DependencyInjection; using Dim.Clients.Token; using Dim.DbAccess.DependencyInjection; @@ -39,7 +38,6 @@ await WebApplicationBuildRunner .AddTransient() .AddTransient() .AddDimClient() - .AddCfClient(builder.Configuration.GetSection("Cf")) .AddDim(builder.Configuration.GetSection("Dim")) .AddEndpointsApiExplorer() .AddDatabase(builder.Configuration) diff --git a/src/web/Dim.Web/appsettings.json b/src/web/Dim.Web/appsettings.json index ec589e9..0c5c620 100644 --- a/src/web/Dim.Web/appsettings.json +++ b/src/web/Dim.Web/appsettings.json @@ -10,15 +10,15 @@ "DimDb": "Server=placeholder;Database=placeholder;Port=5432;User Id=placeholder;Password=placeholder;Ssl Mode=Disable;" }, "Dim": { - "RootDirectoryId": "", - "OperatorId": "" - }, - "Cf": { - "ClientId": "", - "ClientSecret": "", - "TokenAddress": "", - "BaseUrl": "", - "GrantType": "" + "OperatorId": "", + "EncryptionConfigs": [ + { + "Index": 0, + "EncryptionKey": "", + "CipherMode": "", + "PaddingMode": "" + } + ] }, "SwaggerEnabled": false, "JwtBearerOptions": { diff --git a/tests/clients/Dim.Clients.Tests/Dim.Clients.Tests.csproj b/tests/clients/Dim.Clients.Tests/Dim.Clients.Tests.csproj new file mode 100644 index 0000000..af31a14 --- /dev/null +++ b/tests/clients/Dim.Clients.Tests/Dim.Clients.Tests.csproj @@ -0,0 +1,48 @@ + + + + + net8.0 + enable + enable + false + Dim.Clients.Tests + Dim.Clients.Tests + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + diff --git a/tests/clients/Dim.Clients.Tests/DimClientTests.cs b/tests/clients/Dim.Clients.Tests/DimClientTests.cs new file mode 100644 index 0000000..3b8be3f --- /dev/null +++ b/tests/clients/Dim.Clients.Tests/DimClientTests.cs @@ -0,0 +1,191 @@ +using AutoFixture; +using AutoFixture.AutoFakeItEasy; +using Dim.Clients.Api.Dim; +using Dim.Clients.Api.Dim.Models; +using Dim.Clients.Extensions; +using Dim.Clients.Tests.Extensions; +using Dim.Clients.Token; +using FluentAssertions; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using System.Net; +using System.Text.Json; +using Xunit; + +namespace Dim.Clients.Tests; + +public class DimClientTests +{ + #region Initialization + + private readonly IFixture _fixture; + + public DimClientTests() + { + _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + _fixture.Behaviors.OfType().ToList() + .ForEach(b => _fixture.Behaviors.Remove(b)); + _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + } + + #endregion + + #region GetCompanyData + + [Fact] + public async Task GetCompanyData_WithNoContent_ThrowsServiceException() + { + // Arrange + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.OK)); + var sut = _fixture.Create(); + Task Act() => sut.GetCompanyData(_fixture.Create(), "https://example.org", "tenant", "app", CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Contain("The input does not contain any JSON tokens"); + } + + [Fact] + public async Task GetCompanyData_WithNoCompany_ThrowsConflictException() + { + // Arrange + var data = new CompanyIdentitiesResponse(Enumerable.Empty()); + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(data, JsonSerializerExtensions.Options)) }); + var sut = _fixture.Create(); + Task Act() => sut.GetCompanyData(_fixture.Create(), "https://example.org", "tenant", "app", CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("There is no matching company"); + } + + [Fact] + public async Task GetCompanyData_WithValidData_ReturnsCompanyData() + { + // Arrange + var companyData = new CompanyData(Guid.NewGuid(), "https://example.org/download"); + var data = new CompanyIdentitiesResponse(Enumerable.Repeat(companyData, 1)); + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(data, JsonSerializerExtensions.Options)) }); + var sut = _fixture.Create(); + + // Act + var result = await sut.GetCompanyData(_fixture.Create(), "https://example.org", "tenant", "app", CancellationToken.None); + + // Assert + result.CompanyId.Should().Be(companyData.CompanyId); + result.DownloadUrl.Should().Be(companyData.DownloadUrl); + } + + #endregion + + #region GetStatusList + + [Fact] + public async Task GetStatusList_WithNoContent_ThrowsServiceException() + { + // Arrange + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.OK)); + var sut = _fixture.Create(); + Task Act() => sut.GetStatusList(_fixture.Create(), "https://example.org", Guid.NewGuid(), CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Contain("The input does not contain any JSON tokens"); + } + + [Fact] + public async Task GetStatusList_WithNoSpaceLeft_ThrowsConflictException() + { + // Arrange + var data = new StatusListListResponse(1, new[] { new StatusListResponse(Guid.NewGuid().ToString(), "test", "test", "test", 1024, 0) }); + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(data, JsonSerializerExtensions.Options)) }); + var sut = _fixture.Create(); + Task Act() => sut.GetStatusList(_fixture.Create(), "https://example.org", Guid.NewGuid(), CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("There is no status list with remaining space, please create a new one."); + } + + [Fact] + public async Task GetStatusList_WithValidData_ReturnsCompanyData() + { + // Arrange + var data = new StatusListListResponse(1, new[] { new StatusListResponse(Guid.NewGuid().ToString(), "test", "testCred", "test", 1024, 100) }); + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(data, JsonSerializerExtensions.Options)) }); + var sut = _fixture.Create(); + + // Act + var result = await sut.GetStatusList(_fixture.Create(), "https://example.org", Guid.NewGuid(), CancellationToken.None); + + // Assert + result.Should().Be("testCred"); + } + + #endregion + + #region CreateStatusList + + [Fact] + public async Task CreateStatusList_WithNoContent_ThrowsServiceException() + { + // Arrange + HttpRequestMessage? request = null; + _fixture.ConfigureTokenServiceFixture( + new HttpResponseMessage(HttpStatusCode.OK), + requestMessage => request = requestMessage); + var sut = _fixture.Create(); + Task Act() => sut.CreateStatusList(_fixture.Create(), "https://example.org", Guid.NewGuid(), CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Contain("The input does not contain any JSON tokens"); + } + + [Fact] + public async Task CreateStatusList_WithNoSpaceLeft_ThrowsConflictException() + { + // Arrange + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.BadRequest)); + var sut = _fixture.Create(); + Task Act() => sut.CreateStatusList(_fixture.Create(), "https://example.org", Guid.NewGuid(), CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("call to external system assign-application failed with statuscode 400"); + } + + [Fact] + public async Task CreateStatusList_WithValidData_ReturnsCompanyData() + { + // Arrange + var recovationVc = Guid.NewGuid().ToString(); + var data = new CreateStatusListResponse(Guid.NewGuid(), new RevocationVc(recovationVc)); + + _fixture.ConfigureTokenServiceFixture( + new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(JsonSerializer.Serialize(data, JsonSerializerExtensions.Options)) + }); + var sut = _fixture.Create(); + + // Act + var result = await sut.CreateStatusList(_fixture.Create(), "https://example.org", Guid.NewGuid(), CancellationToken.None); + + // Assert + result.Should().Be(recovationVc); + } + + #endregion +} diff --git a/tests/clients/Dim.Clients.Tests/Extensions/AutoFixtureExtensions.cs b/tests/clients/Dim.Clients.Tests/Extensions/AutoFixtureExtensions.cs new file mode 100644 index 0000000..44d787c --- /dev/null +++ b/tests/clients/Dim.Clients.Tests/Extensions/AutoFixtureExtensions.cs @@ -0,0 +1,27 @@ +using AutoFixture; +using Dim.Clients.Token; +using FakeItEasy; + +namespace Dim.Clients.Tests.Extensions; + +public static class AutoFixtureExtensions +{ + public static void ConfigureTokenServiceFixture(this IFixture fixture, HttpResponseMessage httpResponseMessage, Action? setMessage = null) + { + var messageHandler = A.Fake(); + A.CallTo(messageHandler) // mock protected method + .Where(x => x.Method.Name == "SendAsync") + .WithReturnType>() + .ReturnsLazily(call => + { + var message = call.Arguments.Get(0); + setMessage?.Invoke(message); + return Task.FromResult(httpResponseMessage); + }); + var httpClient = new HttpClient(messageHandler) { BaseAddress = new Uri("https://example.com/path/test/") }; + fixture.Inject(httpClient); + + var tokenService = fixture.Freeze>(); + A.CallTo(() => tokenService.FakedObject.GetBasicAuthorizedClient(A._, A._)).Returns(httpClient); + } +} diff --git a/tests/clients/Dim.Clients.Tests/ProvisioningClientTests.cs b/tests/clients/Dim.Clients.Tests/ProvisioningClientTests.cs new file mode 100644 index 0000000..bade2dc --- /dev/null +++ b/tests/clients/Dim.Clients.Tests/ProvisioningClientTests.cs @@ -0,0 +1,267 @@ +using AutoFixture; +using AutoFixture.AutoFakeItEasy; +using Dim.Clients.Api.Div; +using Dim.Clients.Api.Div.Models; +using Dim.Clients.Extensions; +using Dim.Clients.Tests.Extensions; +using Dim.Clients.Token; +using FluentAssertions; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using System.Net; +using System.Text.Json; +using Xunit; + +namespace Dim.Clients.Tests; + +public class ProvisioningClientTests +{ + #region Initialization + + private readonly IFixture _fixture; + + public ProvisioningClientTests() + { + _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + _fixture.Behaviors.OfType().ToList() + .ForEach(b => _fixture.Behaviors.Remove(b)); + _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + } + + #endregion + + #region GetOperation + + [Fact] + public async Task GetOperation_WithStatePending_Returns() + { + // Arrange + var data = new OperationResponse(Guid.NewGuid(), OperationResponseStatus.pending, null, null, null); + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(data, JsonSerializerExtensions.Options)) }); + var sut = _fixture.Create(); + + // Act + var response = await sut.GetOperation(Guid.NewGuid(), CancellationToken.None); + + // Assert + response.Should().Be(data); + } + + [Fact] + public async Task GetOperation_WithStatusFailed_ThrowsServiceException() + { + // Arrange + var data = new OperationResponse(Guid.NewGuid(), OperationResponseStatus.failed, null, "test error", null); + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(data, JsonSerializerExtensions.Options)) }); + var sut = _fixture.Create(); + Task Act() => sut.GetOperation(Guid.NewGuid(), CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("Operation Creation failed with error: test error"); + } + + [Fact] + public async Task GetOperation_WithValidData_ReturnsCompanyData() + { + // Arrange + var data = new OperationResponse(Guid.NewGuid(), OperationResponseStatus.completed, null, null, _fixture.Create()); + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(data, JsonSerializerExtensions.Options)) }); + var sut = _fixture.Create(); + + // Act + var result = await sut.GetOperation(Guid.NewGuid(), CancellationToken.None); + + // Assert + result.Should().Be(data); + } + + #endregion + + #region CreateOperation + + [Fact] + public async Task CreateOperation_WithNoContent_ThrowsServiceException() + { + // Arrange + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.OK)); + var sut = _fixture.Create(); + Task Act() => sut.CreateOperation(Guid.NewGuid(), "corp", "application1", "test corp", "https://example.org/did", false, CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Contain("The input does not contain any JSON tokens"); + } + + [Fact] + public async Task CreateOperation_WithNoSpaceLeft_ThrowsConflictException() + { + // Arrange + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.BadRequest)); + var sut = _fixture.Create(); + Task Act() => sut.CreateOperation(Guid.NewGuid(), "corp", "application1", "test corp", "https://example.org/did", false, CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("call to external system create-operation failed with statuscode 400"); + } + + [Fact] + public async Task CreateOperation_WithValidData_ReturnsCompanyData() + { + // Arrange + var operationId = Guid.NewGuid(); + var data = new OperationRequest(operationId); + + _fixture.ConfigureTokenServiceFixture( + new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(JsonSerializer.Serialize(data, JsonSerializerExtensions.Options)) + }); + var sut = _fixture.Create(); + + // Act + var result = await sut.CreateOperation(Guid.NewGuid(), "corp", "application1", "test corp", "https://example.org/did", false, CancellationToken.None); + + // Assert + result.Should().Be(operationId); + } + + #endregion + + #region CreateServiceKey + + [Fact] + public async Task CreateServiceKey_WithValid_Returns() + { + // Arrange + var walletId = Guid.NewGuid(); + var data = new OperationRequest(Guid.NewGuid()); + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(data, JsonSerializerExtensions.Options)) }); + var sut = _fixture.Create(); + + // Act + var response = await sut.CreateServiceKey("test", walletId, CancellationToken.None); + + // Assert + response.Should().Be(data.OperationId); + } + + [Fact] + public async Task CreateServiceKey_WithBadRequest_ThrowsServiceException() + { + // Arrange + var walletId = Guid.NewGuid(); + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.BadRequest)); + var sut = _fixture.Create(); + Task Act() => sut.CreateServiceKey("test", walletId, CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("call to external system create-service-key failed with statuscode 400"); + } + + #endregion + + #region DeleteServiceKey + + [Fact] + public async Task DeleteServiceKey_WithValid_Returns() + { + // Arrange + var walletId = Guid.NewGuid(); + var serviceKeyId = Guid.NewGuid(); + var data = new OperationRequest(Guid.NewGuid()); + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(data, JsonSerializerExtensions.Options)) }); + var sut = _fixture.Create(); + + // Act + var response = await sut.DeleteServiceKey(walletId, serviceKeyId, CancellationToken.None); + + // Assert + response.Should().Be(data.OperationId); + } + + [Fact] + public async Task DeleteServiceKey_WithBadRequest_ThrowsServiceException() + { + // Arrange + var walletId = Guid.NewGuid(); + var serviceKeyId = Guid.NewGuid(); + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.BadRequest)); + var sut = _fixture.Create(); + Task Act() => sut.DeleteServiceKey(walletId, serviceKeyId, CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("call to external system delete-service-key failed with statuscode 400"); + } + + #endregion + + #region GetOperation + + [Fact] + public async Task GetServiceKey_WithNoServiceKey_ThrowsServiceException() + { + // Arrange + var walletId = Guid.NewGuid(); + var customerWallet = new CustomerWallet(walletId, Guid.NewGuid(), "test", "123", "https://example.org", Enumerable.Repeat(new CustomerServiceKey(Guid.NewGuid(), "non-existing"), 1)); + var data = new CustomerWalletsResponse(3, Enumerable.Repeat(customerWallet, 1)); + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(data, JsonSerializerExtensions.Options)) }); + var sut = _fixture.Create(); + Task Act() => sut.GetServiceKey("sa-test", walletId, CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("Must have exactly one wallet and a service key with name sa-test"); + } + + [Fact] + public async Task GetServiceKey_WithMultipleEntries_ThrowsServiceException() + { + // Arrange + var walletId = Guid.NewGuid(); + var data = new CustomerWalletsResponse(3, _fixture.CreateMany(3)); + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(data, JsonSerializerExtensions.Options)) }); + var sut = _fixture.Create(); + Task Act() => sut.GetServiceKey("sa-test", walletId, CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be($"Must have exactly one customer for wallet id {walletId}"); + } + + [Fact] + public async Task GetServiceKey_WithValidData_ReturnsCompanyData() + { + // Arrange + var walletId = Guid.NewGuid(); + var serviceKeyId = Guid.NewGuid(); + var customerWallet = new CustomerWallet(walletId, Guid.NewGuid(), "test", "123", "https://example.org", Enumerable.Repeat(new CustomerServiceKey(serviceKeyId, "sa-test"), 1)); + var data = new CustomerWalletsResponse(1, Enumerable.Repeat(customerWallet, 1)); + _fixture.ConfigureTokenServiceFixture(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonSerializer.Serialize(data, JsonSerializerExtensions.Options)) }); + var sut = _fixture.Create(); + + // Act + var result = await sut.GetServiceKey("sa-test", walletId, CancellationToken.None); + + // Assert + result.Should().Be(serviceKeyId); + } + + #endregion +} diff --git a/tests/database/Dim.DbAccess.Tests/Dim.DbAccess.Tests.csproj b/tests/database/Dim.DbAccess.Tests/Dim.DbAccess.Tests.csproj new file mode 100644 index 0000000..d3cd159 --- /dev/null +++ b/tests/database/Dim.DbAccess.Tests/Dim.DbAccess.Tests.csproj @@ -0,0 +1,63 @@ + + + + + Dim.DbAccess.Tests + Dim.DbAccess.Tests + net8.0 + enable + enable + false + + + + + + + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + + + + + + + + + + diff --git a/tests/database/Dim.DbAccess.Tests/DimRepositoriesTests.cs b/tests/database/Dim.DbAccess.Tests/DimRepositoriesTests.cs new file mode 100644 index 0000000..a5e3b64 --- /dev/null +++ b/tests/database/Dim.DbAccess.Tests/DimRepositoriesTests.cs @@ -0,0 +1,152 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * 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 Dim.DbAccess.Repositories; +using Dim.DbAccess.Tests.Setup; +using Dim.Entities; +using Dim.Entities.Entities; +using Dim.Entities.Enums; +using FluentAssertions; +using Microsoft.EntityFrameworkCore; +using Xunit; +using Xunit.Extensions.AssemblyFixture; + +namespace Dim.DbAccess.Tests; + +public class DimRepositoriesTests : IAssemblyFixture +{ + private readonly TestDbFixture _dbTestDbFixture; + + public DimRepositoriesTests(TestDbFixture testDbFixture) + { + var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + fixture.Behaviors.OfType().ToList() + .ForEach(b => fixture.Behaviors.Remove(b)); + + fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + _dbTestDbFixture = testDbFixture; + } + + #region GetInstance + + [Fact] + public async Task GetInstance_TenantRepository_CreatesSuccessfully() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = sut.GetInstance(); + + // Assert + result.Should().BeOfType(); + } + + [Fact] + public async Task GetInstance_TechnicalUserRepository_CreatesSuccessfully() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = sut.GetInstance(); + + // Assert + result.Should().BeOfType(); + } + + [Fact] + public async Task GetInstance_ProcessStepRepository_CreatesSuccessfully() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = sut.GetInstance(); + + // Assert + result.Should().BeOfType(); + } + + #endregion + + #region Clear + + [Fact] + public async Task Clear_CreateSuccessfully() + { + // Arrange + var (sut, dbContext) = await CreateSutWithContext(); + var changeTracker = dbContext.ChangeTracker; + dbContext.Processes.Add(new Process(Guid.NewGuid(), ProcessTypeId.SETUP_DIM, Guid.NewGuid())); + + // Act + sut.Clear(); + + // Assert + changeTracker.HasChanges().Should().BeFalse(); + changeTracker.Entries().Should().BeEmpty(); + } + + #endregion + + #region Attach + + [Fact] + public async Task Attach_CreateSuccessfully() + { + // Arrange + var (sut, dbContext) = await CreateSutWithContext(); + var changeTracker = dbContext.ChangeTracker; + var now = DateTimeOffset.Now; + + // Act + sut.Attach(new Process(new Guid("dd371565-9489-4907-a2e4-b8cbfe7a8cd2"), default, Guid.Empty), p => + { + p.LockExpiryDate = now; + p.ProcessTypeId = ProcessTypeId.SETUP_DIM; + }); + + // Assert + changeTracker.HasChanges().Should().BeTrue(); + changeTracker.Entries().Should() + .ContainSingle() + .Which.State.Should().Be(EntityState.Modified); + changeTracker.Entries().Select(x => x.Entity).Cast() + .Should().Satisfy(x => x.ProcessTypeId == ProcessTypeId.SETUP_DIM); + } + + #endregion + + private async Task<(DimRepositories sut, DimDbContext dbContext)> CreateSutWithContext() + { + var context = await _dbTestDbFixture.GetDbContext(); + var sut = new DimRepositories(context); + return (sut, context); + } + + private async Task CreateSut() + { + var context = await _dbTestDbFixture.GetDbContext(); + return new DimRepositories(context); + } +} diff --git a/tests/database/Dim.DbAccess.Tests/Extensions/ProcessStepTypeIdExtensionsTests.cs b/tests/database/Dim.DbAccess.Tests/Extensions/ProcessStepTypeIdExtensionsTests.cs new file mode 100644 index 0000000..d28e153 --- /dev/null +++ b/tests/database/Dim.DbAccess.Tests/Extensions/ProcessStepTypeIdExtensionsTests.cs @@ -0,0 +1,51 @@ +using Dim.Entities.Enums; +using Dim.Entities.Extensions; +using FluentAssertions; +using Xunit; + +namespace Dim.DbAccess.Tests.Extensions; + +public class ProcessStepTypeIdExtensionsTests +{ + [Theory] + [InlineData(ProcessStepTypeId.CREATE_WALLET, ProcessTypeId.SETUP_DIM, ProcessStepTypeId.RETRIGGER_CREATE_WALLET)] + [InlineData(ProcessStepTypeId.CHECK_OPERATION, ProcessTypeId.SETUP_DIM, ProcessStepTypeId.RETRIGGER_CHECK_OPERATION)] + [InlineData(ProcessStepTypeId.GET_COMPANY, ProcessTypeId.SETUP_DIM, ProcessStepTypeId.RETRIGGER_GET_COMPANY)] + [InlineData(ProcessStepTypeId.GET_DID_DOCUMENT, ProcessTypeId.SETUP_DIM, ProcessStepTypeId.RETRIGGER_GET_DID_DOCUMENT)] + [InlineData(ProcessStepTypeId.CREATE_STATUS_LIST, ProcessTypeId.SETUP_DIM, ProcessStepTypeId.RETRIGGER_CREATE_STATUS_LIST)] + [InlineData(ProcessStepTypeId.SEND_CALLBACK, ProcessTypeId.SETUP_DIM, ProcessStepTypeId.RETRIGGER_SEND_CALLBACK)] + [InlineData(ProcessStepTypeId.CREATE_TECHNICAL_USER, ProcessTypeId.TECHNICAL_USER, ProcessStepTypeId.RETRIGGER_CREATE_TECHNICAL_USER)] + [InlineData(ProcessStepTypeId.GET_TECHNICAL_USER_DATA, ProcessTypeId.TECHNICAL_USER, ProcessStepTypeId.RETRIGGER_GET_TECHNICAL_USER_DATA)] + [InlineData(ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK, ProcessTypeId.TECHNICAL_USER, ProcessStepTypeId.RETRIGGER_SEND_TECHNICAL_USER_CREATION_CALLBACK)] + [InlineData(ProcessStepTypeId.DELETE_TECHNICAL_USER, ProcessTypeId.TECHNICAL_USER, ProcessStepTypeId.RETRIGGER_DELETE_TECHNICAL_USER)] + [InlineData(ProcessStepTypeId.SEND_TECHNICAL_USER_DELETION_CALLBACK, ProcessTypeId.TECHNICAL_USER, ProcessStepTypeId.RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK)] + public void GetRetriggerStep(ProcessStepTypeId processStepTypeId, ProcessTypeId processTypeId, ProcessStepTypeId expected) + { + // Act + var typeId = processStepTypeId.GetRetriggerStep(processTypeId); + + // Assert + typeId.Should().Be(expected); + } + + [Theory] + [InlineData(ProcessStepTypeId.CREATE_WALLET, ProcessTypeId.SETUP_DIM, ProcessStepTypeId.RETRIGGER_CREATE_WALLET)] + [InlineData(ProcessStepTypeId.CHECK_OPERATION, ProcessTypeId.SETUP_DIM, ProcessStepTypeId.RETRIGGER_CHECK_OPERATION)] + [InlineData(ProcessStepTypeId.GET_COMPANY, ProcessTypeId.SETUP_DIM, ProcessStepTypeId.RETRIGGER_GET_COMPANY)] + [InlineData(ProcessStepTypeId.GET_DID_DOCUMENT, ProcessTypeId.SETUP_DIM, ProcessStepTypeId.RETRIGGER_GET_DID_DOCUMENT)] + [InlineData(ProcessStepTypeId.CREATE_STATUS_LIST, ProcessTypeId.SETUP_DIM, ProcessStepTypeId.RETRIGGER_CREATE_STATUS_LIST)] + [InlineData(ProcessStepTypeId.SEND_CALLBACK, ProcessTypeId.SETUP_DIM, ProcessStepTypeId.RETRIGGER_SEND_CALLBACK)] + [InlineData(ProcessStepTypeId.CREATE_TECHNICAL_USER, ProcessTypeId.TECHNICAL_USER, ProcessStepTypeId.RETRIGGER_CREATE_TECHNICAL_USER)] + [InlineData(ProcessStepTypeId.GET_TECHNICAL_USER_DATA, ProcessTypeId.TECHNICAL_USER, ProcessStepTypeId.RETRIGGER_GET_TECHNICAL_USER_DATA)] + [InlineData(ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK, ProcessTypeId.TECHNICAL_USER, ProcessStepTypeId.RETRIGGER_SEND_TECHNICAL_USER_CREATION_CALLBACK)] + [InlineData(ProcessStepTypeId.DELETE_TECHNICAL_USER, ProcessTypeId.TECHNICAL_USER, ProcessStepTypeId.RETRIGGER_DELETE_TECHNICAL_USER)] + [InlineData(ProcessStepTypeId.SEND_TECHNICAL_USER_DELETION_CALLBACK, ProcessTypeId.TECHNICAL_USER, ProcessStepTypeId.RETRIGGER_SEND_TECHNICAL_USER_DELETION_CALLBACK)] + public void GetStepForRetrigger(ProcessStepTypeId expected, ProcessTypeId processTypeId, ProcessStepTypeId processStepTypeId) + { + // Act + var typeId = processStepTypeId.GetStepForRetrigger(processTypeId); + + // Assert + typeId.Should().Be(expected); + } +} diff --git a/tests/database/Dim.DbAccess.Tests/Extensions/WalletDataExtensionsTests.cs b/tests/database/Dim.DbAccess.Tests/Extensions/WalletDataExtensionsTests.cs new file mode 100644 index 0000000..46fa493 --- /dev/null +++ b/tests/database/Dim.DbAccess.Tests/Extensions/WalletDataExtensionsTests.cs @@ -0,0 +1,80 @@ +using Dim.DbAccess.Extensions; +using Dim.DbAccess.Models; +using FluentAssertions; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Xunit; + +namespace Dim.DbAccess.Tests.Extensions; + +public class WalletDataExtensionsTests +{ + [Fact] + public void ValidateData_WithMissingTokenAddress_ThrowsConflictException() + { + // Arrange + var walletData = new WalletData(null, null, null, null, null); + void Act() => walletData.ValidateData(); + + // Act + var ex = Assert.Throws(Act); + + // Assert + ex.Message.Should().Be("TokenAddress must not be null"); + } + + [Fact] + public void ValidateData_WithMissingClientId_ThrowsConflictException() + { + // Arrange + var walletData = new WalletData("https://example.org/token", null, null, null, null); + void Act() => walletData.ValidateData(); + + // Act + var ex = Assert.Throws(Act); + + // Assert + ex.Message.Should().Be("ClientId must not be null"); + } + + [Fact] + public void ValidateData_WithMissingSecret_ThrowsConflictException() + { + // Arrange + var walletData = new WalletData("https://example.org/token", "cl1", null, null, null); + void Act() => walletData.ValidateData(); + + // Act + var ex = Assert.Throws(Act); + + // Assert + ex.Message.Should().Be("Secret must not be null"); + } + + [Fact] + public void ValidateData_WithMissingVector_ThrowsConflictException() + { + // Arrange + var walletData = new WalletData("https://example.org/token", "cl1", "test"u8.ToArray(), null, null); + void Act() => walletData.ValidateData(); + + // Act + var ex = Assert.Throws(Act); + + // Assert + ex.Message.Should().Be("Vector must not be null"); + } + + [Fact] + public void ValidateData_WithMissingMode_ThrowsConflictException() + { + // Arrange + var walletData = new WalletData("https://example.org/token", "cl1", "test"u8.ToArray(), "test"u8.ToArray(), null); + void Act() => walletData.ValidateData(); + + // Act + var ex = Assert.Throws(Act); + + // Assert + ex.Message.Should().Be("EncryptionMode must not be null"); + } +} diff --git a/tests/database/Dim.DbAccess.Tests/ProcessStepRepositoryTests.cs b/tests/database/Dim.DbAccess.Tests/ProcessStepRepositoryTests.cs new file mode 100644 index 0000000..5b5c6ec --- /dev/null +++ b/tests/database/Dim.DbAccess.Tests/ProcessStepRepositoryTests.cs @@ -0,0 +1,375 @@ +/******************************************************************************** + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using AutoFixture; +using AutoFixture.AutoFakeItEasy; +using Dim.DbAccess.Repositories; +using Dim.DbAccess.Tests.Setup; +using Dim.Entities; +using Dim.Entities.Entities; +using Dim.Entities.Enums; +using FluentAssertions; +using Microsoft.EntityFrameworkCore; +using System.Collections.Immutable; +using Xunit; +using Xunit.Extensions.AssemblyFixture; + +namespace Dim.DbAccess.Tests; + +public class ProcessStepRepositoryTests : IAssemblyFixture +{ + private readonly IFixture _fixture; + private readonly TestDbFixture _dbTestDbFixture; + + public ProcessStepRepositoryTests(TestDbFixture testDbFixture) + { + _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + _fixture.Behaviors.OfType().ToList() + .ForEach(b => _fixture.Behaviors.Remove(b)); + + _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + _dbTestDbFixture = testDbFixture; + } + + #region CreateProcess + + [Fact] + public async Task CreateProcess_CreatesSuccessfully() + { + // Arrange + var (sut, dbContext) = await CreateSutWithContext(); + var changeTracker = dbContext.ChangeTracker; + + // Act + var result = sut.CreateProcess(ProcessTypeId.SETUP_DIM); + + // Assert + changeTracker.HasChanges().Should().BeTrue(); + changeTracker.Entries().Should().HaveCount(1) + .And.AllSatisfy(x => + { + x.State.Should().Be(EntityState.Added); + x.Entity.Should().BeOfType(); + }); + changeTracker.Entries().Select(x => x.Entity).Cast() + .Should().Satisfy( + x => x.Id == result.Id && x.ProcessTypeId == ProcessTypeId.SETUP_DIM + ); + } + + #endregion + + #region CreateProcessStepRange + + [Fact] + public async Task CreateProcessStepRange_CreateSuccessfully() + { + // Arrange + var processId = Guid.NewGuid(); + var processStepTypeIds = _fixture.CreateMany(3).ToImmutableArray(); + var (sut, dbContext) = await CreateSutWithContext(); + var changeTracker = dbContext.ChangeTracker; + + // Act + var result = sut.CreateProcessStepRange(processStepTypeIds.Select(processStepTypeId => (processStepTypeId, ProcessStepStatusId.TODO, processId))); + + // Assert + changeTracker.HasChanges().Should().BeTrue(); + changeTracker.Entries().Should() + .HaveSameCount(processStepTypeIds) + .And.AllSatisfy(x => + { + x.State.Should().Be(EntityState.Added); + x.Entity.Should().BeOfType(); + }); + changeTracker.Entries().Select(x => x.Entity).Cast() + .Should().Satisfy( + x => x.Id == result.ElementAt(0).Id && x.ProcessId == processId && x.ProcessStepTypeId == processStepTypeIds[0] && x.ProcessStepStatusId == ProcessStepStatusId.TODO, + x => x.Id == result.ElementAt(1).Id && x.ProcessId == processId && x.ProcessStepTypeId == processStepTypeIds[1] && x.ProcessStepStatusId == ProcessStepStatusId.TODO, + x => x.Id == result.ElementAt(2).Id && x.ProcessId == processId && x.ProcessStepTypeId == processStepTypeIds[2] && x.ProcessStepStatusId == ProcessStepStatusId.TODO + ); + } + + #endregion + + #region CreateProcessStep + + [Fact] + public async Task CreateProcessStep_CreateSuccessfully() + { + // Arrange + var processId = Guid.NewGuid(); + var (sut, dbContext) = await CreateSutWithContext(); + var changeTracker = dbContext.ChangeTracker; + + // Act + sut.CreateProcessStep(ProcessStepTypeId.CREATE_WALLET, ProcessStepStatusId.TODO, processId); + + // Assert + changeTracker.HasChanges().Should().BeTrue(); + changeTracker.Entries().Should() + .ContainSingle() + .Which.State.Should().Be(EntityState.Added); + changeTracker.Entries().Select(x => x.Entity).Cast() + .Should().Satisfy( + x => x.ProcessId == processId && x.ProcessStepTypeId == ProcessStepTypeId.CREATE_WALLET && x.ProcessStepStatusId == ProcessStepStatusId.TODO + ); + } + + #endregion + + #region AttachAndModifyProcessStep + + [Fact] + public async Task AttachAndModifyProcessStep_WithExistingProcessStep_UpdatesStatus() + { + // Arrange + var (sut, dbContext) = await CreateSutWithContext(); + + // Act + sut.AttachAndModifyProcessStep(new Guid("48f35f84-8d98-4fbd-ba80-8cbce5eeadb5"), + existing => + { + existing.ProcessStepStatusId = ProcessStepStatusId.TODO; + }, + modify => + { + modify.ProcessStepStatusId = ProcessStepStatusId.DONE; + } + ); + + // Assert + var changeTracker = dbContext.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeTrue(); + changedEntries.Should().NotBeEmpty(); + changedEntries.Should().HaveCount(1); + var changedEntity = changedEntries.Single(); + changedEntity.State.Should().Be(EntityState.Modified); + changedEntity.Entity.Should().BeOfType().Which.ProcessStepStatusId.Should().Be(ProcessStepStatusId.DONE); + } + + #endregion + + #region AttachAndModifyProcessSteps + + [Fact] + public async Task AttachAndModifyProcessSteps_UpdatesStatus() + { + // Arrange + var stepData = _fixture.CreateMany<(Guid ProcessStepId, ProcessStep InitialStep, ProcessStep ModifiedStep)>(5).ToImmutableArray(); + + var (sut, dbContext) = await CreateSutWithContext(); + + // Act + sut.AttachAndModifyProcessSteps(stepData.Select(data => new ValueTuple?, Action>( + data.ProcessStepId, + step => + { + step.ProcessStepStatusId = data.InitialStep.ProcessStepStatusId; + step.DateLastChanged = data.InitialStep.DateLastChanged; + step.Message = data.InitialStep.Message; + }, + step => + { + step.ProcessStepStatusId = data.ModifiedStep.ProcessStepStatusId; + step.DateLastChanged = data.ModifiedStep.DateLastChanged; + step.Message = data.ModifiedStep.Message; + }))); + + // Assert + var changeTracker = dbContext.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeTrue(); + changedEntries.Should().HaveCount(5).And.AllSatisfy(entry => entry.State.Should().Be(EntityState.Modified)); + changedEntries.Select(entry => entry.Entity).Should().AllBeOfType().Which.Should().Satisfy( + step => step.Id == stepData[0].ProcessStepId && step.ProcessStepStatusId == stepData[0].ModifiedStep.ProcessStepStatusId && step.DateLastChanged == stepData[0].ModifiedStep.DateLastChanged && step.Message == stepData[0].ModifiedStep.Message, + step => step.Id == stepData[1].ProcessStepId && step.ProcessStepStatusId == stepData[1].ModifiedStep.ProcessStepStatusId && step.DateLastChanged == stepData[1].ModifiedStep.DateLastChanged && step.Message == stepData[1].ModifiedStep.Message, + step => step.Id == stepData[2].ProcessStepId && step.ProcessStepStatusId == stepData[2].ModifiedStep.ProcessStepStatusId && step.DateLastChanged == stepData[2].ModifiedStep.DateLastChanged && step.Message == stepData[2].ModifiedStep.Message, + step => step.Id == stepData[3].ProcessStepId && step.ProcessStepStatusId == stepData[3].ModifiedStep.ProcessStepStatusId && step.DateLastChanged == stepData[3].ModifiedStep.DateLastChanged && step.Message == stepData[3].ModifiedStep.Message, + step => step.Id == stepData[4].ProcessStepId && step.ProcessStepStatusId == stepData[4].ModifiedStep.ProcessStepStatusId && step.DateLastChanged == stepData[4].ModifiedStep.DateLastChanged && step.Message == stepData[4].ModifiedStep.Message + ); + } + + [Fact] + public async Task AttachAndModifyProcessSteps_WithUnmodifiedData_SkipsUpdateStatus() + { + // Arrange + var stepData = _fixture.CreateMany<(Guid ProcessStepId, ProcessStep InitialStep)>(5).ToImmutableArray(); + + var (sut, dbContext) = await CreateSutWithContext(); + + // Act + sut.AttachAndModifyProcessSteps(stepData.Select(data => new ValueTuple?, Action>( + data.ProcessStepId, + step => + { + step.ProcessStepStatusId = data.InitialStep.ProcessStepStatusId; + step.DateLastChanged = data.InitialStep.DateLastChanged; + step.Message = data.InitialStep.Message; + }, + step => + { + step.DateLastChanged = data.InitialStep.DateLastChanged; + }))); + + // Assert + var changeTracker = dbContext.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeFalse(); + changedEntries.Should().HaveCount(5).And.AllSatisfy(entry => entry.State.Should().Be(EntityState.Unchanged)); + changedEntries.Select(entry => entry.Entity).Should().AllBeOfType().Which.Should().Satisfy( + step => step.Id == stepData[0].ProcessStepId && step.ProcessStepStatusId == stepData[0].InitialStep.ProcessStepStatusId && step.DateLastChanged == stepData[0].InitialStep.DateLastChanged && step.Message == stepData[0].InitialStep.Message, + step => step.Id == stepData[1].ProcessStepId && step.ProcessStepStatusId == stepData[1].InitialStep.ProcessStepStatusId && step.DateLastChanged == stepData[1].InitialStep.DateLastChanged && step.Message == stepData[1].InitialStep.Message, + step => step.Id == stepData[2].ProcessStepId && step.ProcessStepStatusId == stepData[2].InitialStep.ProcessStepStatusId && step.DateLastChanged == stepData[2].InitialStep.DateLastChanged && step.Message == stepData[2].InitialStep.Message, + step => step.Id == stepData[3].ProcessStepId && step.ProcessStepStatusId == stepData[3].InitialStep.ProcessStepStatusId && step.DateLastChanged == stepData[3].InitialStep.DateLastChanged && step.Message == stepData[3].InitialStep.Message, + step => step.Id == stepData[4].ProcessStepId && step.ProcessStepStatusId == stepData[4].InitialStep.ProcessStepStatusId && step.DateLastChanged == stepData[4].InitialStep.DateLastChanged && step.Message == stepData[4].InitialStep.Message + ); + } + + [Fact] + public async Task AttachAndModifyProcessSteps_WithUnmodifiedData_UpdatesLastChanged() + { + // Arrange + var stepData = _fixture.CreateMany<(Guid ProcessStepId, ProcessStep InitialStep)>(5).ToImmutableArray(); + + var (sut, dbContext) = await CreateSutWithContext(); + + // Act + sut.AttachAndModifyProcessSteps(stepData.Select(data => new ValueTuple?, Action>( + data.ProcessStepId, + step => + { + step.ProcessStepStatusId = data.InitialStep.ProcessStepStatusId; + step.DateLastChanged = data.InitialStep.DateLastChanged; + step.Message = data.InitialStep.Message; + }, + _ => { }))); + + // Assert + var changeTracker = dbContext.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeTrue(); + changedEntries.Should().HaveCount(5).And.AllSatisfy(entry => entry.State.Should().Be(EntityState.Modified)); + changedEntries.Select(entry => entry.Entity).Should().AllBeOfType().Which.Should().Satisfy( + step => step.Id == stepData[0].ProcessStepId && step.ProcessStepStatusId == stepData[0].InitialStep.ProcessStepStatusId && step.DateLastChanged != stepData[0].InitialStep.DateLastChanged && step.Message == stepData[0].InitialStep.Message, + step => step.Id == stepData[1].ProcessStepId && step.ProcessStepStatusId == stepData[1].InitialStep.ProcessStepStatusId && step.DateLastChanged != stepData[1].InitialStep.DateLastChanged && step.Message == stepData[1].InitialStep.Message, + step => step.Id == stepData[2].ProcessStepId && step.ProcessStepStatusId == stepData[2].InitialStep.ProcessStepStatusId && step.DateLastChanged != stepData[2].InitialStep.DateLastChanged && step.Message == stepData[2].InitialStep.Message, + step => step.Id == stepData[3].ProcessStepId && step.ProcessStepStatusId == stepData[3].InitialStep.ProcessStepStatusId && step.DateLastChanged != stepData[3].InitialStep.DateLastChanged && step.Message == stepData[3].InitialStep.Message, + step => step.Id == stepData[4].ProcessStepId && step.ProcessStepStatusId == stepData[4].InitialStep.ProcessStepStatusId && step.DateLastChanged != stepData[4].InitialStep.DateLastChanged && step.Message == stepData[4].InitialStep.Message + ); + } + + #endregion + + #region GetActiveProcesses + + [Fact] + public async Task GetActiveProcess_LockExpired_ReturnsExpected() + { + // Arrange + var processTypeIds = new[] { ProcessTypeId.SETUP_DIM }; + var processStepTypeIds = new[] { + ProcessStepTypeId.CREATE_WALLET, + ProcessStepTypeId.CHECK_OPERATION, + ProcessStepTypeId.GET_COMPANY, + ProcessStepTypeId.GET_DID_DOCUMENT + }; + + var sut = await CreateSut(); + + // Act + var result = await sut.GetActiveProcesses(processTypeIds, processStepTypeIds, DateTimeOffset.UtcNow).ToListAsync(); + result.Should().HaveCount(1) + .And.Satisfy( + x => x.Id == new Guid("dd371565-9489-4907-a2e4-b8cbfe7a8cd2") && x.ProcessTypeId == ProcessTypeId.SETUP_DIM && x.LockExpiryDate == null + ); + } + + [Fact] + public async Task GetActiveProcess_Locked_ReturnsExpected() + { + // Arrange + var processTypeIds = new[] { ProcessTypeId.SETUP_DIM }; + var processStepTypeIds = new[] { + ProcessStepTypeId.CREATE_WALLET, + ProcessStepTypeId.CHECK_OPERATION, + ProcessStepTypeId.GET_COMPANY, + ProcessStepTypeId.GET_DID_DOCUMENT + }; + + var sut = await CreateSut(); + + // Act + var result = await sut.GetActiveProcesses(processTypeIds, processStepTypeIds, DateTimeOffset.UtcNow).ToListAsync(); + result.Should().HaveCount(1); + } + + #endregion + + #region GetProcessStepData + + [Fact] + public async Task GetProcessStepData_ReturnsExpected() + { + // Arrange + var processId = new Guid("dd371565-9489-4907-a2e4-b8cbfe7a8cd2"); + var sut = await CreateSut(); + + // Act + var result = await sut.GetProcessStepData(processId).ToListAsync(); + result.Should().HaveCount(2) + .And.Satisfy( + x => x.ProcessStepId == new Guid("80771e4a-0d69-43b8-b278-25884da7f97d") && x.ProcessStepTypeId == ProcessStepTypeId.CREATE_WALLET, + x => x.ProcessStepId == new Guid("cd231cb8-55de-4ae4-b93f-d440512341fb") && x.ProcessStepTypeId == ProcessStepTypeId.GET_COMPANY + ); + } + + #endregion + + #region GetActiveProcesses + + [Fact] + public async Task IsValidProcess_WithValid_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.IsValidProcess(new Guid("dd371565-9489-4907-a2e4-b8cbfe7a8cd1"), ProcessTypeId.SETUP_DIM, Enumerable.Repeat(ProcessStepTypeId.CREATE_WALLET, 1)); + + // Assert + result.ProcessExists.Should().BeTrue(); + } + + #endregion + + private async Task<(ProcessStepRepository sut, DimDbContext dbContext)> CreateSutWithContext() + { + var context = await _dbTestDbFixture.GetDbContext(); + var sut = new ProcessStepRepository(context); + return (sut, context); + } + + private async Task CreateSut() + { + var context = await _dbTestDbFixture.GetDbContext(); + return new ProcessStepRepository(context); + } +} diff --git a/tests/database/Dim.DbAccess.Tests/Seeder/BatchInsertSeeder.cs b/tests/database/Dim.DbAccess.Tests/Seeder/BatchInsertSeeder.cs new file mode 100644 index 0000000..81c4af1 --- /dev/null +++ b/tests/database/Dim.DbAccess.Tests/Seeder/BatchInsertSeeder.cs @@ -0,0 +1,92 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * 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 Dim.Entities; +using Dim.Entities.Entities; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Seeding; + +namespace Dim.DbAccess.Tests.Seeder; + +/// +/// Seeder to seed the base entities (those with an id as primary key) +/// +public class BatchInsertSeeder : ICustomSeeder +{ + private readonly DimDbContext _context; + private readonly ILogger _logger; + private readonly SeederSettings _settings; + + /// + /// Constructor + /// + /// The database context + /// The logger + /// The options + public BatchInsertSeeder(DimDbContext context, ILogger logger, IOptions options) + { + _context = context; + _logger = logger; + _settings = options.Value; + } + + /// + public int Order => 1; + + /// + public async Task ExecuteAsync(CancellationToken cancellationToken) + { + if (!_settings.DataPaths.Any()) + { + _logger.LogInformation("There a no data paths configured, therefore the {SeederName} will be skipped", nameof(BatchInsertSeeder)); + return; + } + + _logger.LogInformation("Start BaseEntityBatch Seeder"); + await SeedTable("tenants", x => x.Id, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await SeedTable("process_steps", x => x.Id, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await SeedTable("processes", x => x.Id, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + await SeedTable("technical_users", x => x.Id, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + + await _context.SaveChangesAsync(cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + _logger.LogInformation("Finished BaseEntityBatch Seeder"); + } + + private async Task SeedTable(string fileName, Func keySelector, CancellationToken cancellationToken) where T : class + { + _logger.LogInformation("Start seeding {Filename}", fileName); + var additionalEnvironments = _settings.TestDataEnvironments ?? Enumerable.Empty(); + var data = await SeederHelper.GetSeedData(_logger, fileName, _settings.DataPaths, cancellationToken, additionalEnvironments.ToArray()).ConfigureAwait(ConfigureAwaitOptions.None); + _logger.LogInformation("Found {ElementCount} data", data.Count); + if (data.Any()) + { + var typeName = typeof(T).Name; + _logger.LogInformation("Started to Seed {TableName}", typeName); + data = data.GroupJoin(_context.Set(), keySelector, keySelector, (d, dbEntry) => new { d, dbEntry }) + .SelectMany(t => t.dbEntry.DefaultIfEmpty(), (t, x) => new { t, x }) + .Where(t => t.x == null) + .Select(t => t.t.d).ToList(); + _logger.LogInformation("Seeding {DataCount} {TableName}", data.Count, typeName); + await _context.Set().AddRangeAsync(data, cancellationToken).ConfigureAwait(ConfigureAwaitOptions.None); + _logger.LogInformation("Seeded {TableName}", typeName); + } + } +} diff --git a/tests/database/Dim.DbAccess.Tests/Seeder/Data/process_steps.json b/tests/database/Dim.DbAccess.Tests/Seeder/Data/process_steps.json new file mode 100644 index 0000000..e8c04f4 --- /dev/null +++ b/tests/database/Dim.DbAccess.Tests/Seeder/Data/process_steps.json @@ -0,0 +1,50 @@ +[ + { + "id": "5c9a4f56-0609-49a5-ab86-dd8f93dfd3fa", + "process_step_type_id": 1, + "process_step_status_id": 2, + "process_id": "dd371565-9489-4907-a2e4-b8cbfe7a8cd1", + "date_created": "2023-02-21 08:15:20.479000 +00:00", + "date_last_changed": null + }, + { + "id": "80771e4a-0d69-43b8-b278-25884da7f97d", + "process_step_type_id": 1, + "process_step_status_id": 1, + "process_id": "dd371565-9489-4907-a2e4-b8cbfe7a8cd2", + "date_created": "2023-02-21 08:15:20.479000 +00:00", + "date_last_changed": null + }, + { + "id": "629cc08f-bb0b-43ba-b20a-45c3538789b5", + "process_step_type_id": 2, + "process_step_status_id": 2, + "process_id": "dd371565-9489-4907-a2e4-b8cbfe7a8cd2", + "date_created": "2023-02-21 08:15:20.479000 +00:00", + "date_last_changed": null + }, + { + "id": "cd231cb8-55de-4ae4-b93f-d440512341fb", + "process_step_type_id": 3, + "process_step_status_id": 1, + "process_id": "dd371565-9489-4907-a2e4-b8cbfe7a8cd2", + "date_created": "2023-02-21 08:15:20.479000 +00:00", + "date_last_changed": null + }, + { + "id": "e64393ad-a885-45ad-8e7b-265ef1b4c691", + "process_step_type_id": 100, + "process_step_status_id": 1, + "process_id": "e64393ad-a885-45ad-8e7b-265ef1b4c691", + "date_created": "2023-02-21 08:15:20.479000 +00:00", + "date_last_changed": null + }, + { + "id": "e64393ad-a885-45ad-8e7b-265ef1b4c692", + "process_step_type_id": 200, + "process_step_status_id": 1, + "process_id": "e64393ad-a885-45ad-8e7b-265ef1b4c692", + "date_created": "2023-02-21 08:15:20.479000 +00:00", + "date_last_changed": null + } +] diff --git a/tests/database/Dim.DbAccess.Tests/Seeder/Data/processes.json b/tests/database/Dim.DbAccess.Tests/Seeder/Data/processes.json new file mode 100644 index 0000000..4f5f412 --- /dev/null +++ b/tests/database/Dim.DbAccess.Tests/Seeder/Data/processes.json @@ -0,0 +1,26 @@ +[ + { + "id": "dd371565-9489-4907-a2e4-b8cbfe7a8cd1", + "process_type_id" : 1, + "lock_expiry_date" : "2023-03-01 00:00:00.000000 +00:00", + "version" : "deadbeef-dead-beef-dead-beefdeadbeef" + }, + { + "id": "dd371565-9489-4907-a2e4-b8cbfe7a8cd2", + "process_type_id" : 1, + "lock_expiry_date" : null, + "version" : "deadbeef-dead-beef-dead-beefdeadbeef" + }, + { + "id": "e64393ad-a885-45ad-8e7b-265ef1b4c691", + "process_type_id" : 2, + "lock_expiry_date" : null, + "version" : "deadbeef-dead-beef-dead-beefdeadbeef" + }, + { + "id": "e64393ad-a885-45ad-8e7b-265ef1b4c692", + "process_type_id" : 2, + "lock_expiry_date" : null, + "version" : "deadbeef-dead-beef-dead-beefdeadbeef" + } +] diff --git a/tests/database/Dim.DbAccess.Tests/Seeder/Data/technical_users.json b/tests/database/Dim.DbAccess.Tests/Seeder/Data/technical_users.json new file mode 100644 index 0000000..148e379 --- /dev/null +++ b/tests/database/Dim.DbAccess.Tests/Seeder/Data/technical_users.json @@ -0,0 +1,16 @@ +[ + { + "id": "abb769d6-337f-4d1f-9f42-5230541a2d51", + "tenant_id": "5c9a4f56-0609-49a5-ab86-dd8f93dfd3fa", + "external_id": "a140e80f-f9fb-4e68-bd34-52943622c63d", + "technical_user_name": "dim-sa-1", + "process_id": "e64393ad-a885-45ad-8e7b-265ef1b4c691" + }, + { + "id": "abb769d6-337f-4d1f-9f42-5230541a2d52", + "tenant_id": "5ced499f-7e7a-4e3c-aee6-41ae96df6d40", + "external_id": "a140e80f-f9fb-4e68-bd34-52943622c63b", + "technical_user_name": "dim-sa-1", + "process_id": "e64393ad-a885-45ad-8e7b-265ef1b4c692" + } +] diff --git a/tests/database/Dim.DbAccess.Tests/Seeder/Data/tenants.json b/tests/database/Dim.DbAccess.Tests/Seeder/Data/tenants.json new file mode 100644 index 0000000..866bdd4 --- /dev/null +++ b/tests/database/Dim.DbAccess.Tests/Seeder/Data/tenants.json @@ -0,0 +1,23 @@ +[ + { + "id": "5c9a4f56-0609-49a5-ab86-dd8f93dfd3fa", + "company_name": "issuer company", + "bpn": "BPNL000001ISSUER", + "did_document_location": "https://example.org/BPNL000001ISSUER", + "process_id": "dd371565-9489-4907-a2e4-b8cbfe7a8cd1", + "is_issuer": true, + "operator_id": "a7444c10-5b07-430d-a3c1-c52675c4fa5a", + "operation_id": "6dcac248-57ab-4309-9477-ee21586b3738", + "company_id": "6dcac248-57ab-4309-9477-ee21586b3666", + "base_url": "https://example.org/base" + }, + { + "id": "5ced499f-7e7a-4e3c-aee6-41ae96df6d40", + "company_name": "test corp", + "bpn": "BPNL0000001CORP", + "did_document_location": "https://example.org/BPNL0000001CORP", + "process_id": "dd371565-9489-4907-a2e4-b8cbfe7a8cd2", + "is_issuer": false, + "operator_id": "a7444c10-5b07-430d-a3c1-c52675c4fa5a" + } +] diff --git a/tests/database/Dim.DbAccess.Tests/Setup/TestDbFixture.cs b/tests/database/Dim.DbAccess.Tests/Setup/TestDbFixture.cs new file mode 100644 index 0000000..be648d6 --- /dev/null +++ b/tests/database/Dim.DbAccess.Tests/Setup/TestDbFixture.cs @@ -0,0 +1,104 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * 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 Dim.DbAccess.Tests.Seeder; +using Dim.Entities; +using Dim.Migrations.Migrations; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Seeding; +using Testcontainers.PostgreSql; +using Xunit; +using Xunit.Extensions.AssemblyFixture; + +[assembly: TestFramework(AssemblyFixtureFramework.TypeName, AssemblyFixtureFramework.AssemblyName)] +namespace Dim.DbAccess.Tests.Setup; + +public class TestDbFixture : IAsyncLifetime +{ + private readonly PostgreSqlContainer _container = new PostgreSqlBuilder() + .WithDatabase("test_db") + .WithImage("postgres") + .WithCleanUp(true) + .WithName(Guid.NewGuid().ToString()) + .Build(); + + /// + /// Foreach test a new DimDbContext will be created and filled with the custom seeding data. + /// + /// + /// In this method the migrations don't need to get executed since they are already on the testcontainer. + /// Because of that the EnsureCreatedAsync is enough. + /// + /// Additional data for the database + /// Returns the created DimDbContext + public async Task GetDbContext(params Action[] seedActions) + { + var optionsBuilder = new DbContextOptionsBuilder(); + + optionsBuilder.UseNpgsql( + _container.GetConnectionString(), + x => x.MigrationsAssembly(typeof(_120).Assembly.GetName().Name) + .MigrationsHistoryTable("__efmigrations_history_dim") + ); + var context = new DimDbContext(optionsBuilder.Options); + await context.Database.EnsureCreatedAsync(); + foreach (var seedAction in seedActions) + { + seedAction.Invoke(context); + } + + await context.SaveChangesAsync(); + return context; + } + + /// + /// This method is used to initially setup the database and run all migrations + /// + public async Task InitializeAsync() + { + await _container.StartAsync() + ; + + var optionsBuilder = new DbContextOptionsBuilder(); + + optionsBuilder.UseNpgsql( + _container.GetConnectionString(), + x => x.MigrationsAssembly(typeof(_120).Assembly.GetName().Name) + .MigrationsHistoryTable("__efmigrations_history_dim") + ); + var context = new DimDbContext(optionsBuilder.Options); + await context.Database.MigrateAsync(); + + var seederOptions = Options.Create(new SeederSettings + { + TestDataEnvironments = Enumerable.Empty(), + DataPaths = new[] { "Seeder/Data" } + }); + var insertSeeder = new BatchInsertSeeder(context, + LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger(), + seederOptions); + await insertSeeder.ExecuteAsync(CancellationToken.None); + } + + /// + public async Task DisposeAsync() => await _container.DisposeAsync(); +} diff --git a/tests/database/Dim.DbAccess.Tests/TechnicalUserRepositoryTests.cs b/tests/database/Dim.DbAccess.Tests/TechnicalUserRepositoryTests.cs new file mode 100644 index 0000000..b97d2ee --- /dev/null +++ b/tests/database/Dim.DbAccess.Tests/TechnicalUserRepositoryTests.cs @@ -0,0 +1,291 @@ +using AutoFixture; +using AutoFixture.AutoFakeItEasy; +using Dim.DbAccess.Repositories; +using Dim.DbAccess.Tests.Setup; +using Dim.Entities; +using Dim.Entities.Entities; +using FluentAssertions; +using Microsoft.EntityFrameworkCore; +using Xunit; +using Xunit.Extensions.AssemblyFixture; + +namespace Dim.DbAccess.Tests; + +public class TechnicalUserRepositoryTests : IAssemblyFixture +{ + private readonly TestDbFixture _dbTestDbFixture; + + public TechnicalUserRepositoryTests(TestDbFixture testDbFixture) + { + var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + fixture.Behaviors.OfType().ToList() + .ForEach(b => fixture.Behaviors.Remove(b)); + + fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + _dbTestDbFixture = testDbFixture; + } + + #region CreateTechnicalUser + + [Fact] + public async Task CreateTechnicalUser_CreatesSuccessfully() + { + // Arrange + var (sut, dbContext) = await CreateSutWithContext(); + var changeTracker = dbContext.ChangeTracker; + + // Act + sut.CreateTenantTechnicalUser(Guid.NewGuid(), "testUser", Guid.NewGuid(), Guid.NewGuid()); + + // Assert + changeTracker.HasChanges().Should().BeTrue(); + changeTracker.Entries().Should().HaveCount(1) + .And.AllSatisfy(x => + { + x.State.Should().Be(EntityState.Added); + x.Entity.Should().BeOfType(); + }); + changeTracker.Entries().Select(x => x.Entity).Cast() + .Should().Satisfy( + x => x.TechnicalUserName == "testUser" + ); + } + + #endregion + + #region AttachAndModifyTechnicalUser + + [Fact] + public async Task AttachAndModifyTechnicalUser_WithExistingTechnicalUser_UpdatesStatus() + { + // Arrange + var (sut, dbContext) = await CreateSutWithContext(); + + // Act + sut.AttachAndModifyTechnicalUser(new Guid("48f35f84-8d98-4fbd-ba80-8cbce5eeadb5"), + existing => + { + existing.ClientId = "cl1"; + }, + modify => + { + modify.ClientId = "clNew"; + } + ); + + // Assert + var changeTracker = dbContext.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeTrue(); + changedEntries.Should().NotBeEmpty(); + changedEntries.Should().HaveCount(1); + var changedEntity = changedEntries.Single(); + changedEntity.State.Should().Be(EntityState.Modified); + changedEntity.Entity.Should().BeOfType().Which.ClientId.Should().Be("clNew"); + } + + #endregion + + #region GetTenantDataForTechnicalUserProcessId + + [Fact] + public async Task GetTenantDataForTechnicalUserProcessId_WithExistingTechnicalUser_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetTenantDataForTechnicalUserProcessId(new Guid("e64393ad-a885-45ad-8e7b-265ef1b4c691")); + + // Assert + result.Exists.Should().BeTrue(); + result.TechnicalUserId.Should().Be(new Guid("abb769d6-337f-4d1f-9f42-5230541a2d51")); + } + + #endregion + + #region GetTechnicalUserCallbackData + + [Fact] + public async Task GetTechnicalUserCallbackData_WithExistingTechnicalUser_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetTechnicalUserCallbackData(new Guid("abb769d6-337f-4d1f-9f42-5230541a2d51")); + + // Assert + result.ExternalId.Should().Be(new Guid("a140e80f-f9fb-4e68-bd34-52943622c63d")); + } + + #endregion + + #region GetTechnicalUserForBpn + + [Fact] + public async Task GetTechnicalUserForBpn_WithExistingTechnicalUser_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetTechnicalUserForBpn("BPNL000001ISSUER", "dim-sa-1"); + + // Assert + result.Exists.Should().BeTrue(); + result.TechnicalUserId.Should().Be(new Guid("abb769d6-337f-4d1f-9f42-5230541a2d51")); + result.ProcessId.Should().Be(new Guid("e64393ad-a885-45ad-8e7b-265ef1b4c691")); + } + + #endregion + + #region CreateTechnicalUser + + [Fact] + public async Task RemoveTechnicalUser_CreatesSuccessfully() + { + // Arrange + var technicalUserId = Guid.NewGuid(); + var (sut, dbContext) = await CreateSutWithContext(); + var changeTracker = dbContext.ChangeTracker; + + // Act + sut.RemoveTechnicalUser(technicalUserId); + + // Assert + changeTracker.HasChanges().Should().BeTrue(); + changeTracker.Entries().Should().HaveCount(1) + .And.AllSatisfy(x => + { + x.State.Should().Be(EntityState.Deleted); + x.Entity.Should().BeOfType(); + }); + changeTracker.Entries().Select(x => x.Entity).Cast() + .Should().Satisfy( + x => x.Id == technicalUserId + ); + } + + #endregion + + #region GetTechnicalUserNameAndWalletId + + [Fact] + public async Task GetTechnicalUserNameAndWalletId_WithExistingTechnicalUser_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetTechnicalUserNameAndWalletId(new Guid("abb769d6-337f-4d1f-9f42-5230541a2d51")); + + // Assert + result.WalletId.Should().BeNull(); + result.TechnicalUserName.Should().Be("dim-sa-1"); + } + + #endregion + + #region GetOperationIdForTechnicalUser + + [Fact] + public async Task GetOperationIdForTechnicalUser_WithExistingTechnicalUser_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetOperationIdForTechnicalUser(new Guid("abb769d6-337f-4d1f-9f42-5230541a2d51")); + + // Assert + result.Should().BeNull(); + } + + #endregion + + #region GetOperationAndExternalIdForTechnicalUser + + [Fact] + public async Task GetOperationAndExternalIdForTechnicalUser_WithExistingTechnicalUser_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetOperationAndExternalIdForTechnicalUser(new Guid("abb769d6-337f-4d1f-9f42-5230541a2d51")); + + // Assert + result.OperationId.Should().BeNull(); + result.ExternalId.Should().Be(new Guid("a140e80f-f9fb-4e68-bd34-52943622c63d")); + } + + #endregion + + #region GetTechnicalUserDataAndWalletId + + [Fact] + public async Task GetTechnicalUserDataAndWalletId_WithExistingTechnicalUser_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetServiceKeyAndWalletId(new Guid("abb769d6-337f-4d1f-9f42-5230541a2d51")); + + // Assert + result.WalletId.Should().BeNull(); + } + + #endregion + + #region GetTechnicalUserDataAndWalletId + + [Fact] + public async Task GetTechnicalUserProcess_WithExistingTechnicalUser_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetTechnicalUserProcess("BPNL000001ISSUER", "issuer company", "dim-sa-1"); + + // Assert + result.Should().NotBeNull(); + result!.ProcessId.Should().Be(new Guid("e64393ad-a885-45ad-8e7b-265ef1b4c691")); + } + + #endregion + + #region GetWalletIdAndNameForTechnicalUser + + [Fact] + public async Task GetWalletIdAndNameForTechnicalUser_WithExistingTechnicalUser_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetWalletIdAndNameForTechnicalUser(new Guid("abb769d6-337f-4d1f-9f42-5230541a2d51")); + + // Assert + result.Should().NotBeNull(); + result!.TechnicalUserName.Should().Be("dim-sa-1"); + result!.WalletId.Should().BeNull(); + } + + #endregion + + private async Task<(TechnicalUserRepository sut, DimDbContext dbContext)> CreateSutWithContext() + { + var context = await _dbTestDbFixture.GetDbContext(); + var sut = new TechnicalUserRepository(context); + return (sut, context); + } + + private async Task CreateSut() + { + var context = await _dbTestDbFixture.GetDbContext(); + return new TechnicalUserRepository(context); + } +} diff --git a/tests/database/Dim.DbAccess.Tests/TenantRepositoryTests.cs b/tests/database/Dim.DbAccess.Tests/TenantRepositoryTests.cs new file mode 100644 index 0000000..9eeeac0 --- /dev/null +++ b/tests/database/Dim.DbAccess.Tests/TenantRepositoryTests.cs @@ -0,0 +1,326 @@ +using AutoFixture; +using AutoFixture.AutoFakeItEasy; +using Dim.DbAccess.Repositories; +using Dim.DbAccess.Tests.Setup; +using Dim.Entities; +using Dim.Entities.Entities; +using FluentAssertions; +using Microsoft.EntityFrameworkCore; +using Xunit; +using Xunit.Extensions.AssemblyFixture; + +namespace Dim.DbAccess.Tests; + +public class TenantRepositoryTests : IAssemblyFixture +{ + private readonly TestDbFixture _dbTestDbFixture; + + public TenantRepositoryTests(TestDbFixture testDbFixture) + { + var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + fixture.Behaviors.OfType().ToList() + .ForEach(b => fixture.Behaviors.Remove(b)); + + fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + _dbTestDbFixture = testDbFixture; + } + + #region CreateTenant + + [Fact] + public async Task CreateTenant_CreatesSuccessfully() + { + // Arrange + var (sut, dbContext) = await CreateSutWithContext(); + var changeTracker = dbContext.ChangeTracker; + + // Act + var result = sut.CreateTenant("test corp", "BPNL00001TEST", "https://example.org/test", false, Guid.NewGuid(), Guid.NewGuid()); + + // Assert + changeTracker.HasChanges().Should().BeTrue(); + changeTracker.Entries().Should().HaveCount(1) + .And.AllSatisfy(x => + { + x.State.Should().Be(EntityState.Added); + x.Entity.Should().BeOfType(); + }); + changeTracker.Entries().Select(x => x.Entity).Cast() + .Should().Satisfy( + x => x.Id == result.Id && x.CompanyName == "test corp" + ); + } + + #endregion + + #region AttachAndModifyTenant + + [Fact] + public async Task AttachAndModifyTenant_WithExistingTenant_UpdatesStatus() + { + // Arrange + var (sut, dbContext) = await CreateSutWithContext(); + + // Act + sut.AttachAndModifyTenant(new Guid("48f35f84-8d98-4fbd-ba80-8cbce5eeadb5"), + existing => + { + existing.Bpn = "BPNL000001TEST"; + }, + modify => + { + modify.Bpn = "BPNL000001NEW"; + } + ); + + // Assert + var changeTracker = dbContext.ChangeTracker; + var changedEntries = changeTracker.Entries().ToList(); + changeTracker.HasChanges().Should().BeTrue(); + changedEntries.Should().NotBeEmpty(); + changedEntries.Should().HaveCount(1); + var changedEntity = changedEntries.Single(); + changedEntity.State.Should().Be(EntityState.Modified); + changedEntity.Entity.Should().BeOfType().Which.Bpn.Should().Be("BPNL000001NEW"); + } + + #endregion + + #region GetTenantDataForProcessId + + [Fact] + public async Task GetTenantDataForProcessId_WithExistingTenant_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetTenantDataForProcessId(new Guid("dd371565-9489-4907-a2e4-b8cbfe7a8cd1")); + + // Assert + result.Exists.Should().BeTrue(); + result.Bpn.Should().Be("BPNL000001ISSUER"); + result.CompanyName.Should().Be("issuer company"); + result.TenantId.Should().Be(new Guid("5c9a4f56-0609-49a5-ab86-dd8f93dfd3fa")); + } + + [Fact] + public async Task GetTenantDataForProcessId_WithoutExistingTenant_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetTenantDataForProcessId(Guid.NewGuid()); + + // Assert + result.Exists.Should().BeFalse(); + result.Bpn.Should().BeNull(); + result.CompanyName.Should().BeNull(); + result.TenantId.Should().BeEmpty(); + } + + #endregion + + #region GetHostingUrlAndIsIssuer + + [Fact] + public async Task GetHostingUrlAndIsIssuer_WithExistingTenant_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetHostingUrlAndIsIssuer(new Guid("5c9a4f56-0609-49a5-ab86-dd8f93dfd3fa")); + + // Assert + result.IsIssuer.Should().BeTrue(); + result.HostingUrl.Should().Be("https://example.org/BPNL000001ISSUER"); + } + + #endregion + + #region GetTenantForBpn + + [Fact] + public async Task GetTenantForBpn_WithExistingTenant_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetTenantForBpn("BPNL000001ISSUER"); + + // Assert + result.Exists.Should().BeTrue(); + result.TenantId.Should().Be(new Guid("5c9a4f56-0609-49a5-ab86-dd8f93dfd3fa")); + } + + #endregion + + #region IsTenantExisting + + [Fact] + public async Task IsTenantExisting_WithExisting_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.IsTenantExisting("issuer company", "BPNL000001ISSUER"); + + // Assert + result.Should().BeTrue(); + } + + [Fact] + public async Task IsTenantExisting_WithoutExisting_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.IsTenantExisting("issuer company", "BPNL000NOTEXISTING"); + + // Assert + result.Should().BeFalse(); + } + + #endregion + + #region GetOperationId + + [Fact] + public async Task GetOperationId_WithExisting_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetOperationId(new Guid("5c9a4f56-0609-49a5-ab86-dd8f93dfd3fa")); + + // Assert + result.Should().Be(new Guid("6dcac248-57ab-4309-9477-ee21586b3738")); + } + + #endregion + + #region GetCompanyRequestData + + [Fact] + public async Task GetCompanyRequestData_WithExisting_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetCompanyRequestData(new Guid("5c9a4f56-0609-49a5-ab86-dd8f93dfd3fa")); + + // Assert + result.BaseUrl.Should().Be("https://example.org/base"); + } + + #endregion + + #region GetCompanyAndWalletDataForBpn + + [Fact] + public async Task GetCompanyAndWalletDataForBpn_WithExistingTechnicalUser_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetCompanyAndWalletDataForBpn("BPNL000001ISSUER"); + + // Assert + result.Exists.Should().BeTrue(); + result.CompanyId.Should().Be(new Guid("6dcac248-57ab-4309-9477-ee21586b3666")); + } + + #endregion + + #region GetStatusListCreationData + + [Fact] + public async Task GetStatusListCreationData_WithExisting_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetStatusListCreationData(new Guid("5c9a4f56-0609-49a5-ab86-dd8f93dfd3fa")); + + // Assert + result.CompanyId.Should().Be(new Guid("6dcac248-57ab-4309-9477-ee21586b3666")); + result.BaseUrl.Should().Be("https://example.org/base"); + } + + #endregion + + #region GetCallbackData + + [Fact] + public async Task GetCallbackData_WithExisting_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetCallbackData(new Guid("5c9a4f56-0609-49a5-ab86-dd8f93dfd3fa")); + + // Assert + result.Bpn.Should().Be("BPNL000001ISSUER"); + result.BaseUrl.Should().Be("https://example.org/base"); + } + + #endregion + + #region GetDownloadUrlAndIsIssuer + + [Fact] + public async Task GetDownloadUrlAndIsIssuer_WithExisting_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetDownloadUrlAndIsIssuer(new Guid("5c9a4f56-0609-49a5-ab86-dd8f93dfd3fa")); + + // Assert + result.IsIssuer.Should().BeTrue(); + } + + #endregion + + #region GetWalletProcessForTenant + + [Fact] + public async Task GetWalletProcessForTenant_WithExisting_ReturnsExpected() + { + // Arrange + var sut = await CreateSut(); + + // Act + var result = await sut.GetWalletProcessForTenant("BPNL0000001CORP", "test corp"); + + // Assert + result.Should().NotBeNull(); + result!.ProcessId.Should().Be(new Guid("dd371565-9489-4907-a2e4-b8cbfe7a8cd2")); + } + + #endregion + + private async Task<(TenantRepository sut, DimDbContext dbContext)> CreateSutWithContext() + { + var context = await _dbTestDbFixture.GetDbContext(); + var sut = new TenantRepository(context); + return (sut, context); + } + + private async Task CreateSut() + { + var context = await _dbTestDbFixture.GetDbContext(); + return new TenantRepository(context); + } +} diff --git a/tests/processes/DimProcess.Executor.Tests/DimProcessTypeExecutorTests.cs b/tests/processes/DimProcess.Executor.Tests/DimProcessTypeExecutorTests.cs index 56ae8b3..4e59f7a 100644 --- a/tests/processes/DimProcess.Executor.Tests/DimProcessTypeExecutorTests.cs +++ b/tests/processes/DimProcess.Executor.Tests/DimProcessTypeExecutorTests.cs @@ -26,13 +26,13 @@ namespace DimProcess.Executor.Tests; -public class CredentialProcessTypeExecutorTests +public class DimProcessTypeExecutorTests { private readonly DimProcessTypeExecutor _sut; private readonly IDimProcessHandler _dimProcessHandler; private readonly ITenantRepository _tenantRepository; - public CredentialProcessTypeExecutorTests() + public DimProcessTypeExecutorTests() { var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); fixture.Behaviors.OfType().ToList() @@ -67,24 +67,12 @@ public void IsExecutableStepTypeId_WithValid_ReturnsExpected() public void GetExecutableStepTypeIds_ReturnsExpected() { // Assert - _sut.GetExecutableStepTypeIds().Should().HaveCount(18).And.Satisfy( - x => x == ProcessStepTypeId.CREATE_SUBACCOUNT, - x => x == ProcessStepTypeId.CREATE_SERVICEMANAGER_BINDINGS, - x => x == ProcessStepTypeId.ASSIGN_ENTITLEMENTS, - x => x == ProcessStepTypeId.CREATE_SERVICE_INSTANCE, - x => x == ProcessStepTypeId.CREATE_SERVICE_BINDING, - x => x == ProcessStepTypeId.SUBSCRIBE_APPLICATION, - x => x == ProcessStepTypeId.CREATE_CLOUD_FOUNDRY_ENVIRONMENT, - x => x == ProcessStepTypeId.CREATE_CLOUD_FOUNDRY_SPACE, - x => x == ProcessStepTypeId.ADD_SPACE_MANAGER_ROLE, - x => x == ProcessStepTypeId.ADD_SPACE_DEVELOPER_ROLE, - x => x == ProcessStepTypeId.CREATE_DIM_SERVICE_INSTANCE, - x => x == ProcessStepTypeId.CREATE_SERVICE_INSTANCE_BINDING, - x => x == ProcessStepTypeId.GET_DIM_DETAILS, - x => x == ProcessStepTypeId.CREATE_APPLICATION, - x => x == ProcessStepTypeId.CREATE_COMPANY_IDENTITY, + _sut.GetExecutableStepTypeIds().Should().HaveCount(6).And.Satisfy( + x => x == ProcessStepTypeId.CHECK_OPERATION, + x => x == ProcessStepTypeId.GET_COMPANY, + x => x == ProcessStepTypeId.GET_DID_DOCUMENT, + x => x == ProcessStepTypeId.CREATE_WALLET, x => x == ProcessStepTypeId.CREATE_STATUS_LIST, - x => x == ProcessStepTypeId.ASSIGN_COMPANY_APPLICATION, x => x == ProcessStepTypeId.SEND_CALLBACK); } @@ -125,7 +113,7 @@ public async Task InitializeProcess_WithNotExistingProcess_ThrowsNotFoundExcepti .Returns(new ValueTuple(false, Guid.Empty, string.Empty, string.Empty)); // Act - async Task Act() => await _sut.InitializeProcess(validProcessId, Enumerable.Empty()).ConfigureAwait(false); + async Task Act() => await _sut.InitializeProcess(validProcessId, Enumerable.Empty()); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -140,7 +128,7 @@ public async Task InitializeProcess_WithNotExistingProcess_ThrowsNotFoundExcepti public async Task ExecuteProcessStep_WithoutRegistrationId_ThrowsUnexpectedConditionException() { // Act - async Task Act() => await _sut.ExecuteProcessStep(ProcessStepTypeId.SEND_CALLBACK, Enumerable.Empty(), CancellationToken.None).ConfigureAwait(false); + async Task Act() => await _sut.ExecuteProcessStep(ProcessStepTypeId.SEND_CALLBACK, Enumerable.Empty(), CancellationToken.None); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -148,23 +136,10 @@ public async Task ExecuteProcessStep_WithoutRegistrationId_ThrowsUnexpectedCondi } [Theory] - [InlineData(ProcessStepTypeId.CREATE_SUBACCOUNT)] - [InlineData(ProcessStepTypeId.CREATE_SERVICEMANAGER_BINDINGS)] - [InlineData(ProcessStepTypeId.ASSIGN_ENTITLEMENTS)] - [InlineData(ProcessStepTypeId.CREATE_SERVICE_INSTANCE)] - [InlineData(ProcessStepTypeId.CREATE_SERVICE_BINDING)] - [InlineData(ProcessStepTypeId.SUBSCRIBE_APPLICATION)] - [InlineData(ProcessStepTypeId.CREATE_CLOUD_FOUNDRY_ENVIRONMENT)] - [InlineData(ProcessStepTypeId.CREATE_CLOUD_FOUNDRY_SPACE)] - [InlineData(ProcessStepTypeId.ADD_SPACE_MANAGER_ROLE)] - [InlineData(ProcessStepTypeId.ADD_SPACE_DEVELOPER_ROLE)] - [InlineData(ProcessStepTypeId.CREATE_DIM_SERVICE_INSTANCE)] - [InlineData(ProcessStepTypeId.CREATE_SERVICE_INSTANCE_BINDING)] - [InlineData(ProcessStepTypeId.GET_DIM_DETAILS)] - [InlineData(ProcessStepTypeId.CREATE_APPLICATION)] - [InlineData(ProcessStepTypeId.CREATE_COMPANY_IDENTITY)] - [InlineData(ProcessStepTypeId.CREATE_STATUS_LIST)] - [InlineData(ProcessStepTypeId.ASSIGN_COMPANY_APPLICATION)] + [InlineData(ProcessStepTypeId.CREATE_WALLET)] + [InlineData(ProcessStepTypeId.CHECK_OPERATION)] + [InlineData(ProcessStepTypeId.GET_COMPANY)] + [InlineData(ProcessStepTypeId.GET_DID_DOCUMENT)] [InlineData(ProcessStepTypeId.SEND_CALLBACK)] public async Task ExecuteProcessStep_WithValidData_CallsExpected(ProcessStepTypeId processStepTypeId) { @@ -182,7 +157,7 @@ public async Task ExecuteProcessStep_WithValidData_CallsExpected(ProcessStepType initializeResult.ScheduleStepTypeIds.Should().BeNull(); // Arrange - SetupMock(tenantId, "test1_test"); + SetupMock(tenantId, "test1test"); // Act var result = await _sut.ExecuteProcessStep(processStepTypeId, Enumerable.Empty(), CancellationToken.None); @@ -212,11 +187,11 @@ public async Task ExecuteProcessStep_WithRecoverableServiceException_ReturnsToDo initializeResult.ScheduleStepTypeIds.Should().BeNull(); // Arrange - A.CallTo(() => _dimProcessHandler.CreateSubaccount(tenantId, "test1_test", A._)) + A.CallTo(() => _dimProcessHandler.CreateWallet(tenantId, "test1test", A._)) .Throws(new ServiceException("this is a test", true)); // Act - var result = await _sut.ExecuteProcessStep(ProcessStepTypeId.CREATE_SUBACCOUNT, Enumerable.Empty(), CancellationToken.None); + var result = await _sut.ExecuteProcessStep(ProcessStepTypeId.CREATE_WALLET, Enumerable.Empty(), CancellationToken.None); // Assert result.Modified.Should().BeTrue(); @@ -243,15 +218,16 @@ public async Task ExecuteProcessStep_WithServiceException_ReturnsFailedAndRetrig initializeResult.ScheduleStepTypeIds.Should().BeNull(); // Arrange - A.CallTo(() => _dimProcessHandler.CreateSubaccount(tenantId, "test1_test", A._)) + A.CallTo(() => _dimProcessHandler.CreateWallet(tenantId, "test1test", A._)) .Throws(new ServiceException("this is a test")); // Act - var result = await _sut.ExecuteProcessStep(ProcessStepTypeId.CREATE_SUBACCOUNT, Enumerable.Empty(), CancellationToken.None); + var result = await _sut.ExecuteProcessStep(ProcessStepTypeId.CREATE_WALLET, Enumerable.Empty(), CancellationToken.None); // Assert result.Modified.Should().BeTrue(); - result.ScheduleStepTypeIds.Should().BeNull(); + result.ScheduleStepTypeIds.Should().ContainSingle() + .And.Satisfy(x => x == ProcessStepTypeId.RETRIGGER_CREATE_WALLET); result.ProcessStepStatusId.Should().Be(ProcessStepStatusId.FAILED); result.ProcessMessage.Should().Be("this is a test"); result.SkipStepTypeIds.Should().BeNull(); @@ -263,63 +239,21 @@ public async Task ExecuteProcessStep_WithServiceException_ReturnsFailedAndRetrig private void SetupMock(Guid tenantId, string tenantName) { - A.CallTo(() => _dimProcessHandler.CreateSubaccount(tenantId, tenantName, A._)) - .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); - - A.CallTo(() => _dimProcessHandler.CreateServiceManagerBindings(tenantId, A._)) - .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); - - A.CallTo(() => _dimProcessHandler.AssignEntitlements(tenantId, A._)) - .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); - - A.CallTo(() => _dimProcessHandler.CreateServiceInstance(tenantId, A._)) - .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); - - A.CallTo(() => _dimProcessHandler.CreateServiceBindings(tenantId, A._)) - .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); - - A.CallTo(() => _dimProcessHandler.SubscribeApplication(tenantId, A._)) - .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); - - A.CallTo(() => _dimProcessHandler.CreateCloudFoundryEnvironment(tenantId, tenantName, A._)) - .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); - - A.CallTo(() => _dimProcessHandler.CreateCloudFoundrySpace(tenantId, tenantName, A._)) + A.CallTo(() => _dimProcessHandler.CreateWallet(tenantId, tenantName, A._)) .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); - A.CallTo(() => _dimProcessHandler.AddSpaceManagerRole(tenantId, A._)) + A.CallTo(() => _dimProcessHandler.CheckOperation(tenantId, A._)) .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); - A.CallTo(() => _dimProcessHandler.AddSpaceDeveloperRole(tenantId, A._)) + A.CallTo(() => _dimProcessHandler.GetCompany(tenantId, tenantName, A._)) .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); - A.CallTo(() => _dimProcessHandler.CreateSubaccount(tenantId, tenantName, A._)) - .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); - - A.CallTo(() => _dimProcessHandler.CreateDimServiceInstance(tenantName, tenantId, A._)) - .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); - - A.CallTo(() => _dimProcessHandler.CreateServiceInstanceBindings(tenantName, tenantId, A._)) - .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); - - A.CallTo(() => _dimProcessHandler.GetDimDetails(tenantName, tenantId, A._)) - .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); - - A.CallTo(() => _dimProcessHandler.CreateApplication(tenantName, tenantId, A._)) - .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); - - A.CallTo(() => _dimProcessHandler.GetDimDetails(tenantName, tenantId, A._)) - .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); - - A.CallTo(() => _dimProcessHandler.CreateCompanyIdentity(tenantId, A._)) + A.CallTo(() => _dimProcessHandler.GetDidDocument(tenantId, A._)) .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); A.CallTo(() => _dimProcessHandler.CreateStatusList(tenantId, A._)) .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); - A.CallTo(() => _dimProcessHandler.AssignCompanyApplication(tenantId, A._)) - .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); - A.CallTo(() => _dimProcessHandler.SendCallback(tenantId, A._)) .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); } diff --git a/tests/processes/DimProcess.Executor.Tests/TechnicalUserProcessTypeExecutorTests.cs b/tests/processes/DimProcess.Executor.Tests/TechnicalUserProcessTypeExecutorTests.cs new file mode 100644 index 0000000..c57bfc6 --- /dev/null +++ b/tests/processes/DimProcess.Executor.Tests/TechnicalUserProcessTypeExecutorTests.cs @@ -0,0 +1,259 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * 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 Dim.DbAccess; +using Dim.DbAccess.Repositories; +using Dim.Entities.Enums; +using DimProcess.Library; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; + +namespace DimProcess.Executor.Tests; + +public class TechnicalUserProcessTypeExecutorTests +{ + private readonly TechnicalUserProcessTypeExecutor _sut; + private readonly ITechnicalUserProcessHandler _technicalUserProcessHandler; + private readonly ITechnicalUserRepository _technicalUserRepository; + + public TechnicalUserProcessTypeExecutorTests() + { + var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + fixture.Behaviors.OfType().ToList() + .ForEach(b => fixture.Behaviors.Remove(b)); + fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + + var repositories = A.Fake(); + _technicalUserProcessHandler = A.Fake(); + + _technicalUserRepository = A.Fake(); + + A.CallTo(() => repositories.GetInstance()).Returns(_technicalUserRepository); + + _sut = new TechnicalUserProcessTypeExecutor(repositories, _technicalUserProcessHandler); + } + + [Fact] + public void GetProcessTypeId_ReturnsExpected() + { + // Assert + _sut.GetProcessTypeId().Should().Be(ProcessTypeId.TECHNICAL_USER); + } + + [Fact] + public void IsExecutableStepTypeId_WithValid_ReturnsExpected() + { + // Assert + _sut.IsExecutableStepTypeId(ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK).Should().BeTrue(); + } + + [Fact] + public void GetExecutableStepTypeIds_ReturnsExpected() + { + // Assert + _sut.GetExecutableStepTypeIds().Should().HaveCount(6).And.Satisfy( + x => x == ProcessStepTypeId.CREATE_TECHNICAL_USER, + x => x == ProcessStepTypeId.GET_TECHNICAL_USER_DATA, + x => x == ProcessStepTypeId.GET_TECHNICAL_USER_SERVICE_KEY, + x => x == ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK, + x => x == ProcessStepTypeId.DELETE_TECHNICAL_USER, + x => x == ProcessStepTypeId.SEND_TECHNICAL_USER_DELETION_CALLBACK); + } + + [Fact] + public async Task IsLockRequested_ReturnsExpected() + { + // Act + var result = await _sut.IsLockRequested(ProcessStepTypeId.CREATE_TECHNICAL_USER); + + // Assert + result.Should().BeFalse(); + } + + #region InitializeProcess + + [Fact] + public async Task InitializeProcess_WithExistingProcess_ReturnsExpected() + { + // Arrange + var validProcessId = Guid.NewGuid(); + A.CallTo(() => _technicalUserRepository.GetTenantDataForTechnicalUserProcessId(validProcessId)) + .Returns(new ValueTuple(true, Guid.NewGuid())); + + // Act + var result = await _sut.InitializeProcess(validProcessId, Enumerable.Empty()); + + // Assert + result.Modified.Should().BeFalse(); + result.ScheduleStepTypeIds.Should().BeNull(); + } + + [Fact] + public async Task InitializeProcess_WithNotExistingProcess_ThrowsNotFoundException() + { + // Arrange + var validProcessId = Guid.NewGuid(); + A.CallTo(() => _technicalUserRepository.GetTenantDataForTechnicalUserProcessId(validProcessId)) + .Returns(new ValueTuple(false, Guid.Empty)); + + // Act + async Task Act() => await _sut.InitializeProcess(validProcessId, Enumerable.Empty()); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Be($"process {validProcessId} does not exist or is not associated with an technical user"); + } + + #endregion + + #region ExecuteProcessStep + + [Fact] + public async Task ExecuteProcessStep_WithoutInitialization_ThrowsUnexpectedConditionException() + { + // Act + async Task Act() => await _sut.ExecuteProcessStep(ProcessStepTypeId.CREATE_TECHNICAL_USER, Enumerable.Empty(), CancellationToken.None); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Be("technicalUserId should never be empty here"); + } + + [Theory] + [InlineData(ProcessStepTypeId.CREATE_TECHNICAL_USER)] + [InlineData(ProcessStepTypeId.GET_TECHNICAL_USER_DATA)] + [InlineData(ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK)] + [InlineData(ProcessStepTypeId.DELETE_TECHNICAL_USER)] + [InlineData(ProcessStepTypeId.SEND_TECHNICAL_USER_DELETION_CALLBACK)] + public async Task ExecuteProcessStep_WithValidData_CallsExpected(ProcessStepTypeId processStepTypeId) + { + // Arrange InitializeProcess + var validProcessId = Guid.NewGuid(); + var tenantId = Guid.NewGuid(); + A.CallTo(() => _technicalUserRepository.GetTenantDataForTechnicalUserProcessId(validProcessId)) + .Returns(new ValueTuple(true, tenantId)); + + // Act InitializeProcess + var initializeResult = await _sut.InitializeProcess(validProcessId, Enumerable.Empty()); + + // Assert InitializeProcess + initializeResult.Modified.Should().BeFalse(); + initializeResult.ScheduleStepTypeIds.Should().BeNull(); + + // Arrange + SetupMock(tenantId); + + // Act + var result = await _sut.ExecuteProcessStep(processStepTypeId, Enumerable.Empty(), CancellationToken.None); + + // Assert + result.Modified.Should().BeFalse(); + result.ScheduleStepTypeIds.Should().BeNull(); + result.ProcessStepStatusId.Should().Be(ProcessStepStatusId.DONE); + result.ProcessMessage.Should().BeNull(); + result.SkipStepTypeIds.Should().BeNull(); + } + + [Fact] + public async Task ExecuteProcessStep_WithRecoverableServiceException_ReturnsToDo() + { + // Arrange InitializeProcess + var validProcessId = Guid.NewGuid(); + var tenantId = Guid.NewGuid(); + A.CallTo(() => _technicalUserRepository.GetTenantDataForTechnicalUserProcessId(validProcessId)) + .Returns(new ValueTuple(true, tenantId)); + + // Act InitializeProcess + var initializeResult = await _sut.InitializeProcess(validProcessId, Enumerable.Empty()); + + // Assert InitializeProcess + initializeResult.Modified.Should().BeFalse(); + initializeResult.ScheduleStepTypeIds.Should().BeNull(); + + // Arrange + A.CallTo(() => _technicalUserProcessHandler.CreateServiceInstanceBindings(tenantId, A._)) + .Throws(new ServiceException("this is a test", true)); + + // Act + var result = await _sut.ExecuteProcessStep(ProcessStepTypeId.CREATE_TECHNICAL_USER, Enumerable.Empty(), CancellationToken.None); + + // Assert + result.Modified.Should().BeTrue(); + result.ScheduleStepTypeIds.Should().BeNull(); + result.ProcessStepStatusId.Should().Be(ProcessStepStatusId.TODO); + result.ProcessMessage.Should().Be("this is a test"); + result.SkipStepTypeIds.Should().BeNull(); + } + + [Fact] + public async Task ExecuteProcessStep_WithServiceException_ReturnsFailedAndRetriggerStep() + { + // Arrange InitializeProcess + var validProcessId = Guid.NewGuid(); + var tenantId = Guid.NewGuid(); + A.CallTo(() => _technicalUserRepository.GetTenantDataForTechnicalUserProcessId(validProcessId)) + .Returns(new ValueTuple(true, tenantId)); + + // Act InitializeProcess + var initializeResult = await _sut.InitializeProcess(validProcessId, Enumerable.Empty()); + + // Assert InitializeProcess + initializeResult.Modified.Should().BeFalse(); + initializeResult.ScheduleStepTypeIds.Should().BeNull(); + + // Arrange + A.CallTo(() => _technicalUserProcessHandler.CreateServiceInstanceBindings(tenantId, A._)) + .Throws(new ServiceException("this is a test")); + + // Act + var result = await _sut.ExecuteProcessStep(ProcessStepTypeId.CREATE_TECHNICAL_USER, Enumerable.Empty(), CancellationToken.None); + + // Assert + result.Modified.Should().BeTrue(); + result.ScheduleStepTypeIds.Should().ContainSingle() + .And.Satisfy(x => x == ProcessStepTypeId.RETRIGGER_CREATE_TECHNICAL_USER); + result.ProcessStepStatusId.Should().Be(ProcessStepStatusId.FAILED); + result.ProcessMessage.Should().Be("this is a test"); + result.SkipStepTypeIds.Should().BeNull(); + } + + #endregion + + #region Setup + + private void SetupMock(Guid tenantId) + { + A.CallTo(() => _technicalUserProcessHandler.CreateServiceInstanceBindings(tenantId, A._)) + .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); + + A.CallTo(() => _technicalUserProcessHandler.GetTechnicalUserData(tenantId, A._)) + .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); + + A.CallTo(() => _technicalUserProcessHandler.SendCreateCallback(tenantId, A._)) + .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); + + A.CallTo(() => _technicalUserProcessHandler.DeleteServiceInstanceBindings(tenantId, A._)) + .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); + + A.CallTo(() => _technicalUserProcessHandler.SendDeleteCallback(tenantId, A._)) + .Returns(new ValueTuple?, ProcessStepStatusId, bool, string?>(null, ProcessStepStatusId.DONE, false, null)); + } + + #endregion +} diff --git a/tests/processes/DimProcess.Library.Tests/CallbackServiceTests.cs b/tests/processes/DimProcess.Library.Tests/CallbackServiceTests.cs index 0ec0098..2bc5c76 100644 --- a/tests/processes/DimProcess.Library.Tests/CallbackServiceTests.cs +++ b/tests/processes/DimProcess.Library.Tests/CallbackServiceTests.cs @@ -18,7 +18,6 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -using Dim.Clients.Api.Cf; using Dim.Tests.Shared; using DimProcess.Library.Callback; using DimProcess.Library.Callback.DependencyInjection; @@ -76,7 +75,7 @@ public async Task SendCallback_WithValidData_DoesNotThrowException() var sut = new CallbackService(_tokenService, _options); // Act - await sut.SendCallback("BPNL00001TEST", _fixture.Create(), _fixture.Create(), "did:web:test123", CancellationToken.None); + await sut.SendCallback("BPNL00001TEST", _fixture.Create(), _fixture.Create(), "did:web:test123", CancellationToken.None); // Assert httpMessageHandlerMock.RequestMessage.Should().Match(x => @@ -97,7 +96,7 @@ public async Task SendCallback_WithInvalidData_ThrowsServiceException() var sut = new CallbackService(_tokenService, _options); // Act - async Task Act() => await sut.SendCallback("BPNL00001TEST", _fixture.Create(), _fixture.Create(), "did:web:test123", CancellationToken.None); + async Task Act() => await sut.SendCallback("BPNL00001TEST", _fixture.Create(), _fixture.Create(), "did:web:test123", CancellationToken.None); // Assert var ex = await Assert.ThrowsAsync(Act); diff --git a/tests/processes/DimProcess.Library.Tests/DimProcessHandlerTests.cs b/tests/processes/DimProcess.Library.Tests/DimProcessHandlerTests.cs index e3df309..7f8e240 100644 --- a/tests/processes/DimProcess.Library.Tests/DimProcessHandlerTests.cs +++ b/tests/processes/DimProcess.Library.Tests/DimProcessHandlerTests.cs @@ -18,15 +18,13 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -using Dim.Clients.Api.Cf; using Dim.Clients.Api.Dim; -using Dim.Clients.Api.Entitlements; -using Dim.Clients.Api.Provisioning; -using Dim.Clients.Api.Services; -using Dim.Clients.Api.SubAccounts; -using Dim.Clients.Api.Subscriptions; +using Dim.Clients.Api.Dim.Models; +using Dim.Clients.Api.Div; +using Dim.Clients.Api.Div.Models; using Dim.Clients.Token; using Dim.DbAccess; +using Dim.DbAccess.Models; using Dim.DbAccess.Repositories; using Dim.Entities.Entities; using Dim.Entities.Enums; @@ -34,6 +32,9 @@ using DimProcess.Library.DependencyInjection; using Microsoft.Extensions.Options; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration; +using System.Net; +using System.Security.Cryptography; using System.Text.Json; namespace DimProcess.Library.Tests; @@ -43,23 +44,16 @@ public class DimProcessHandlerTests private readonly Guid _tenantId = Guid.NewGuid(); private readonly Guid _processId = Guid.NewGuid(); private readonly Guid _operatorId = Guid.NewGuid(); - private readonly string _tenantName = "testCorp"; - private readonly Guid _rootDirectoryId = Guid.NewGuid(); + private const string TenantName = "testCorp"; - private readonly IDimRepositories _repositories; private readonly ITenantRepository _tenantRepositories; - private readonly ISubAccountClient _subAccountClient; - private readonly IServiceClient _serviceClient; - private readonly ISubscriptionClient _subscriptionClient; - private readonly IEntitlementClient _entitlementClient; private readonly IProvisioningClient _provisioningClient; - private readonly ICfClient _cfClient; private readonly IDimClient _dimClient; private readonly ICallbackService _callbackService; - private readonly IOptions _options; private readonly DimProcessHandler _sut; private readonly IFixture _fixture; + private readonly DimHandlerSettings _settings; public DimProcessHandlerTests() { @@ -68,405 +62,64 @@ public DimProcessHandlerTests() .ForEach(b => _fixture.Behaviors.Remove(b)); _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); - _repositories = A.Fake(); + var repositories = A.Fake(); _tenantRepositories = A.Fake(); - A.CallTo(() => _repositories.GetInstance()).Returns(_tenantRepositories); + A.CallTo(() => repositories.GetInstance()).Returns(_tenantRepositories); - _subAccountClient = A.Fake(); - _serviceClient = A.Fake(); - _subscriptionClient = A.Fake(); - _entitlementClient = A.Fake(); _provisioningClient = A.Fake(); - _cfClient = A.Fake(); _dimClient = A.Fake(); _callbackService = A.Fake(); - _options = Options.Create(new DimHandlerSettings + _settings = new DimHandlerSettings { - AdminMail = "test@example.org", - AuthUrl = "https://example.org/auth", - ClientidCisCentral = "test123", - ClientsecretCisCentral = "test654", - EncryptionKey = "test123", - RootDirectoryId = _rootDirectoryId - }); - - _sut = new DimProcessHandler(_repositories, _subAccountClient, _serviceClient, _subscriptionClient, - _entitlementClient, _provisioningClient, _cfClient, _dimClient, _callbackService, _options); - } - - #region CreateSubaccount - - [Fact] - public async Task CreateSubaccount_WithValidData_ReturnsExpected() - { - // Arrange - var subAccountId = Guid.NewGuid(); - var tenant = new Tenant(_tenantId, "test", "Corp", "https://example.org/did", false, _processId, _operatorId); - A.CallTo(() => _tenantRepositories.AttachAndModifyTenant(_tenantId, A>._, A>._)) - .Invokes((Guid _, Action? initialize, Action modify) => - { - initialize?.Invoke(tenant); - modify(tenant); - }); - A.CallTo(() => _subAccountClient.CreateSubaccount(A._, A._, _tenantName, A._, A._, A._)) - .Returns(subAccountId); - - // Act - var result = await _sut.CreateSubaccount(_tenantId, _tenantName, CancellationToken.None); - - // Assert - result.modified.Should().BeFalse(); - result.processMessage.Should().BeNull(); - result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); - result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.CREATE_SERVICEMANAGER_BINDINGS); - tenant.SubAccountId.Should().Be(subAccountId); - } - - #endregion - - #region CreateServiceManagerBindings - - [Fact] - public async Task CreateServiceManagerBindings_WithNotExisting_ReturnsExpected() - { - // Arrange - A.CallTo(() => _tenantRepositories.GetSubAccountIdByTenantId(_tenantId)) - .Returns((Guid?)null); - async Task Act() => await _sut.CreateServiceManagerBindings(_tenantId, CancellationToken.None).ConfigureAwait(false); - - // Act - var ex = await Assert.ThrowsAsync(Act); - - // Assert - ex.Message.Should().Be("SubAccountId must not be null."); - } - - [Fact] - public async Task CreateServiceManagerBindings_WithValidData_ReturnsExpected() - { - // Arrange - var subAccountId = Guid.NewGuid(); - A.CallTo(() => _tenantRepositories.GetSubAccountIdByTenantId(_tenantId)) - .Returns(subAccountId); - - // Act - var result = await _sut.CreateServiceManagerBindings(_tenantId, CancellationToken.None); - - // Assert - A.CallTo(() => _subAccountClient.CreateServiceManagerBindings(A._, subAccountId, A._)) - .MustHaveHappenedOnceExactly(); - - result.modified.Should().BeFalse(); - result.processMessage.Should().BeNull(); - result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); - result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.ASSIGN_ENTITLEMENTS); - } - - #endregion - - #region AssignEntitlements - - [Fact] - public async Task AssignEntitlements_WithNotExisting_ReturnsExpected() - { - // Arrange - A.CallTo(() => _tenantRepositories.GetSubAccountIdByTenantId(_tenantId)) - .Returns((Guid?)null); - async Task Act() => await _sut.AssignEntitlements(_tenantId, CancellationToken.None).ConfigureAwait(false); - - // Act - var ex = await Assert.ThrowsAsync(Act); - - // Assert - ex.Message.Should().Be("SubAccountId must not be null."); - } - - [Fact] - public async Task AssignEntitlements_WithValidData_ReturnsExpected() - { - // Arrange - var subAccountId = Guid.NewGuid(); - A.CallTo(() => _tenantRepositories.GetSubAccountIdByTenantId(_tenantId)) - .Returns(subAccountId); - - // Act - var result = await _sut.AssignEntitlements(_tenantId, CancellationToken.None); - - // Assert - A.CallTo(() => _entitlementClient.AssignEntitlements(A._, subAccountId, A._)) - .MustHaveHappenedOnceExactly(); - - result.modified.Should().BeFalse(); - result.processMessage.Should().BeNull(); - result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); - result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.CREATE_SERVICE_INSTANCE); - } - - #endregion - - #region CreateServiceInstance - - [Fact] - public async Task CreateServiceInstance_WithNotExisting_ReturnsExpected() - { - // Arrange - A.CallTo(() => _tenantRepositories.GetSubAccountIdByTenantId(_tenantId)) - .Returns((Guid?)null); - async Task Act() => await _sut.CreateServiceInstance(_tenantId, CancellationToken.None).ConfigureAwait(false); - - // Act - var ex = await Assert.ThrowsAsync(Act); - - // Assert - ex.Message.Should().Be("SubAccountId must not be null."); - } - - [Fact] - public async Task CreateServiceInstance_WithValidData_ReturnsExpected() - { - // Arrange - var subAccountId = Guid.NewGuid(); - var serviceInstance = new CreateServiceInstanceResponse(Guid.NewGuid().ToString(), "test"); - var tenant = new Tenant(_tenantId, "test", "Corp", "https://example.org/did", false, _processId, _operatorId); - A.CallTo(() => _tenantRepositories.GetSubAccountIdByTenantId(_tenantId)) - .Returns(subAccountId); - A.CallTo(() => _subAccountClient.GetServiceManagerBindings(A._, subAccountId, A._)) - .Returns(new ServiceManagementBindingItem("test", "test123", "https://example.org/sm", "https://example.org")); - A.CallTo(() => _tenantRepositories.AttachAndModifyTenant(_tenantId, A>._, A>._)) - .Invokes((Guid _, Action? initialize, Action modify) => - { - initialize?.Invoke(tenant); - modify(tenant); - }); - A.CallTo(() => _serviceClient.CreateServiceInstance(A._, A._)) - .Returns(serviceInstance); - - // Act - var result = await _sut.CreateServiceInstance(_tenantId, CancellationToken.None); - - // Assert - A.CallTo(() => _serviceClient.CreateServiceInstance(A._, A._)) - .MustHaveHappenedOnceExactly(); - - result.modified.Should().BeFalse(); - result.processMessage.Should().BeNull(); - result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); - result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.CREATE_SERVICE_BINDING); - tenant.ServiceInstanceId.Should().Be(serviceInstance.Id); - } - - #endregion - - #region CreateServiceBindings - - [Fact] - public async Task CreateServiceBindings_WithNotExistingSubAccount_ReturnsExpected() - { - // Arrange - A.CallTo(() => _tenantRepositories.GetSubAccountAndServiceInstanceIdsByTenantId(_tenantId)) - .Returns((null, null)); - async Task Act() => await _sut.CreateServiceBindings(_tenantId, CancellationToken.None).ConfigureAwait(false); - - // Act - var ex = await Assert.ThrowsAsync(Act); - - // Assert - ex.Message.Should().Be("SubAccountId must not be null."); - } - - [Fact] - public async Task CreateServiceBindings_WithNotExistingServiceInstance_ReturnsExpected() - { - // Arrange - A.CallTo(() => _tenantRepositories.GetSubAccountAndServiceInstanceIdsByTenantId(_tenantId)) - .Returns((Guid.NewGuid(), null)); - async Task Act() => await _sut.CreateServiceBindings(_tenantId, CancellationToken.None).ConfigureAwait(false); - - // Act - var ex = await Assert.ThrowsAsync(Act); - - // Assert - ex.Message.Should().Be("ServiceInstanceId must not be null."); - } - - [Fact] - public async Task CreateServiceBindings_WithValidData_ReturnsExpected() - { - // Arrange - var subAccountId = Guid.NewGuid(); - var serviceInstanceId = Guid.NewGuid().ToString(); - var binding = new ServiceManagementBindingItem("cl1", "s1", "https://example.org/sm", "https://example.org"); - var tenant = new Tenant(_tenantId, "test", "Corp", "https://example.org/did", false, _processId, _operatorId); - A.CallTo(() => _tenantRepositories.GetSubAccountAndServiceInstanceIdsByTenantId(_tenantId)) - .Returns((subAccountId, serviceInstanceId)); - A.CallTo(() => _subAccountClient.GetServiceManagerBindings(A._, subAccountId, A._)) - .Returns(new ServiceManagementBindingItem("test", "test123", "https://example.org/sm", "https://example.org")); - A.CallTo(() => _tenantRepositories.AttachAndModifyTenant(_tenantId, A>._, A>._)) - .Invokes((Guid _, Action? initialize, Action modify) => + ApplicationName = "catena-x-portal", + EncryptionConfigIndex = 0, + EncryptionConfigs = new[] { - initialize?.Invoke(tenant); - modify(tenant); - }); - A.CallTo(() => _subAccountClient.GetServiceManagerBindings(A._, subAccountId, A._)) - .Returns(binding); - A.CallTo(() => _serviceClient.CreateServiceBinding(binding, serviceInstanceId, A._)) - .Returns(new CreateServiceBindingResponse(Guid.NewGuid().ToString(), "expectedName")); - - // Act - var result = await _sut.CreateServiceBindings(_tenantId, CancellationToken.None); - - // Assert - A.CallTo(() => _serviceClient.CreateServiceBinding(binding, serviceInstanceId, A._)) - .MustHaveHappenedOnceExactly(); - - result.modified.Should().BeFalse(); - result.processMessage.Should().BeNull(); - result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); - result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.SUBSCRIBE_APPLICATION); - tenant.ServiceBindingName.Should().Be("expectedName"); - } - - #endregion - - #region SubscribeApplication - - [Fact] - public async Task SubscribeApplication_WithNotExistingSubAccount_ReturnsExpected() - { - // Arrange - A.CallTo(() => _tenantRepositories.GetSubAccountAndServiceInstanceIdsByTenantId(_tenantId)) - .Returns((null, null)); - async Task Act() => await _sut.SubscribeApplication(_tenantId, CancellationToken.None).ConfigureAwait(false); - - // Act - var ex = await Assert.ThrowsAsync(Act); - - // Assert - ex.Message.Should().Be("SubAccountId must not be null."); - } - - [Fact] - public async Task SubscribeApplication_WithNotExistingServiceBindingName_ReturnsExpected() - { - // Arrange - A.CallTo(() => _tenantRepositories.GetSubAccountAndServiceInstanceIdsByTenantId(_tenantId)) - .Returns((Guid.NewGuid(), null)); - async Task Act() => await _sut.SubscribeApplication(_tenantId, CancellationToken.None).ConfigureAwait(false); - - // Act - var ex = await Assert.ThrowsAsync(Act); - - // Assert - ex.Message.Should().Be("SubAccountId must not be null."); - } - - [Fact] - public async Task SubscribeApplication_WithValidData_ReturnsExpected() - { - // Arrange - var subAccountId = Guid.NewGuid(); - var serviceInstanceId = Guid.NewGuid(); - var serviceBindingName = Guid.NewGuid().ToString(); - var serviceManagementBinding = new ServiceManagementBindingItem("c1", "cs1", "https://example.org/sm", "https://example.org/"); - var binding = new BindingItem("binding1", serviceInstanceId, _fixture.Create()); - A.CallTo(() => _tenantRepositories.GetSubAccountIdAndServiceBindingNameByTenantId(_tenantId)) - .Returns((subAccountId, serviceBindingName)); - A.CallTo(() => _subAccountClient.GetServiceManagerBindings(A._, subAccountId, A._)) - .Returns(new ServiceManagementBindingItem("test", "test123", "https://example.org/sm", "https://example.org")); - A.CallTo(() => _subAccountClient.GetServiceManagerBindings(A._, subAccountId, A._)) - .Returns(serviceManagementBinding); - A.CallTo(() => _serviceClient.GetServiceBinding(A._, serviceBindingName, A._)) - .Returns(binding); - A.CallTo(() => _serviceClient.CreateServiceBinding(serviceManagementBinding, serviceBindingName, A._)) - .Returns(new CreateServiceBindingResponse(Guid.NewGuid().ToString(), "expectedName")); - - // Act - var result = await _sut.SubscribeApplication(_tenantId, CancellationToken.None); - - // Assert - A.CallTo(() => _subscriptionClient.SubscribeApplication(A._, binding, "decentralized-identity-management-app", "standard", A._)) - .MustHaveHappenedOnceExactly(); - - result.modified.Should().BeFalse(); - result.processMessage.Should().BeNull(); - result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); - result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.CREATE_CLOUD_FOUNDRY_ENVIRONMENT); - } - - #endregion + new EncryptionModeConfig + { + Index = 0, + CipherMode = CipherMode.CBC, + PaddingMode = PaddingMode.PKCS7, + EncryptionKey = "2c68516f23467028602524534824437e417e253c29546c563c2f5e3d485e7667" + } + } + }; - #region CreateCloudFoundryEnvironment - - [Fact] - public async Task CreateCloudFoundryEnvironment_WithNotExistingSubAccount_ReturnsExpected() - { - // Arrange - A.CallTo(() => _tenantRepositories.GetSubAccountIdAndServiceBindingNameByTenantId(_tenantId)) - .Returns((null, null)); - async Task Act() => await _sut.CreateCloudFoundryEnvironment(_tenantId, _tenantName, CancellationToken.None).ConfigureAwait(false); - - // Act - var ex = await Assert.ThrowsAsync(Act); + var messageHandler = A.Fake(); + A.CallTo(messageHandler) + .Where(x => x.Method.Name == "SendAsync") + .WithReturnType>() + .Returns(new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent("{\"id\": \"did:web:example:org:123TEST\"}") }); + var httpClient = new HttpClient(messageHandler) { BaseAddress = new Uri("http://localhost") }; - // Assert - ex.Message.Should().Be("SubAccountId must not be null."); + var httpClientFactory = _fixture.Freeze>(); + A.CallTo(() => httpClientFactory.FakedObject.CreateClient("didDocumentDownload")).Returns(httpClient); + _sut = new DimProcessHandler(repositories, _provisioningClient, _dimClient, _callbackService, httpClientFactory.FakedObject, Options.Create(_settings)); } - [Fact] - public async Task CreateCloudFoundryEnvironment_WithNotExistingServiceInstance_ReturnsExpected() - { - // Arrange - A.CallTo(() => _tenantRepositories.GetSubAccountIdAndServiceBindingNameByTenantId(_tenantId)) - .Returns((Guid.NewGuid(), null)); - async Task Act() => await _sut.CreateCloudFoundryEnvironment(_tenantId, _tenantName, CancellationToken.None).ConfigureAwait(false); - - // Act - var ex = await Assert.ThrowsAsync(Act); - - // Assert - ex.Message.Should().Be("ServiceBindingName must not be null."); - } + #region CreateOperation [Fact] - public async Task CreateCloudFoundryEnvironment_WithValidData_ReturnsExpected() + public async Task CreateOperation_WithDidLocationNull_ThrowsUnexpectedConditionException() { // Arrange - var subAccountId = Guid.NewGuid(); - var serviceInstanceId = Guid.NewGuid(); - var serviceManagementBinding = new ServiceManagementBindingItem("c1", "cs1", "https://example.org/sm", "https://example.org/"); - var binding = new BindingItem("binding1", serviceInstanceId, _fixture.Create()); - A.CallTo(() => _tenantRepositories.GetSubAccountIdAndServiceBindingNameByTenantId(_tenantId)) - .Returns((subAccountId, binding.Name)); - A.CallTo(() => _subAccountClient.GetServiceManagerBindings(A._, subAccountId, A._)) - .Returns(new ServiceManagementBindingItem("test", "test123", "https://example.org/sm", "https://example.org")); - A.CallTo(() => _subAccountClient.GetServiceManagerBindings(A._, subAccountId, A._)) - .Returns(serviceManagementBinding); - A.CallTo(() => _serviceClient.GetServiceBinding(serviceManagementBinding, binding.Name, A._)) - .Returns(binding); + A.CallTo(() => _tenantRepositories.GetHostingUrlAndIsIssuer(_tenantId)) + .Returns((true, null)); + Task Act() => _sut.CreateWallet(_tenantId, TenantName, CancellationToken.None); // Act - var result = await _sut.CreateCloudFoundryEnvironment(_tenantId, _tenantName, CancellationToken.None); + var ex = await Assert.ThrowsAsync(Act); // Assert - A.CallTo(() => _provisioningClient.CreateCloudFoundryEnvironment(A._, binding, _tenantName, A._, A._)) - .MustHaveHappenedOnceExactly(); - - result.modified.Should().BeFalse(); - result.processMessage.Should().BeNull(); - result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); - result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.CREATE_CLOUD_FOUNDRY_SPACE); + ex.Message.Should().Be("DidDocumentLocation must always be set"); } - #endregion - - #region CreateCloudFoundrySpace - [Fact] - public async Task CreateCloudFoundrySpace_WithValidData_ReturnsExpected() + public async Task CreateOperation_WithValidData_ReturnsExpected() { // Arrange - var spaceId = Guid.NewGuid(); + var operationId = Guid.NewGuid(); var tenant = new Tenant(_tenantId, "test", "Corp", "https://example.org/did", false, _processId, _operatorId); A.CallTo(() => _tenantRepositories.AttachAndModifyTenant(_tenantId, A>._, A>._)) .Invokes((Guid _, Action? initialize, Action modify) => @@ -474,420 +127,212 @@ public async Task CreateCloudFoundrySpace_WithValidData_ReturnsExpected() initialize?.Invoke(tenant); modify(tenant); }); - A.CallTo(() => _cfClient.CreateCloudFoundrySpace(_tenantName, A._)) - .Returns(spaceId); + A.CallTo(() => _provisioningClient.CreateOperation(A._, TenantName, A._, A._, A._, A._, A._)) + .Returns(operationId); // Act - var result = await _sut.CreateCloudFoundrySpace(_tenantId, _tenantName, CancellationToken.None); + var result = await _sut.CreateWallet(_tenantId, TenantName, CancellationToken.None); // Assert result.modified.Should().BeFalse(); result.processMessage.Should().BeNull(); result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); - result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.ADD_SPACE_MANAGER_ROLE); - tenant.SpaceId.Should().Be(spaceId); + result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.CHECK_OPERATION); + tenant.OperationId.Should().Be(operationId); } #endregion - #region AddSpaceManagerRole - - [Fact] - public async Task AddSpaceManagerRole_WithNotExisting_ReturnsExpected() - { - // Arrange - A.CallTo(() => _tenantRepositories.GetSpaceId(_tenantId)) - .Returns((Guid?)null); - async Task Act() => await _sut.AddSpaceManagerRole(_tenantId, CancellationToken.None).ConfigureAwait(false); - - // Act - var ex = await Assert.ThrowsAsync(Act); - - // Assert - ex.Message.Should().Be("SpaceId must not be null."); - } + #region CheckOperation [Fact] - public async Task AddSpaceManagerRole_WithValidData_ReturnsExpected() + public async Task CheckOperation_WithoutOperationId_ThrowsUnexpectedException() { // Arrange - var spaceId = Guid.NewGuid(); - A.CallTo(() => _tenantRepositories.GetSpaceId(_tenantId)) - .Returns(spaceId); + A.CallTo(() => _tenantRepositories.GetOperationId(_tenantId)) + .Returns(null); + Task Act() => _sut.CheckOperation(_tenantId, CancellationToken.None); // Act - var result = await _sut.AddSpaceManagerRole(_tenantId, CancellationToken.None); + var ex = await Assert.ThrowsAsync(Act); // Assert - A.CallTo(() => _cfClient.AddSpaceRoleToUser("space_manager", A._, spaceId, A._)) - .MustHaveHappenedOnceExactly(); - - result.modified.Should().BeFalse(); - result.processMessage.Should().BeNull(); - result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); - result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.ADD_SPACE_DEVELOPER_ROLE); + ex.Message.Should().Be("OperationId must always be set"); } - #endregion - - #region AddSpaceManagerRole - - [Fact] - public async Task AddSpaceDeveloperRole_WithNotExisting_ReturnsExpected() - { - // Arrange - A.CallTo(() => _tenantRepositories.GetSpaceId(_tenantId)) - .Returns((Guid?)null); - async Task Act() => await _sut.AddSpaceDeveloperRole(_tenantId, CancellationToken.None).ConfigureAwait(false); - - // Act - var ex = await Assert.ThrowsAsync(Act); - - // Assert - ex.Message.Should().Be("SpaceId must not be null."); - } - - [Fact] - public async Task AddSpaceDeveloperRole_WithValidData_ReturnsExpected() - { - // Arrange - var spaceId = Guid.NewGuid(); - A.CallTo(() => _tenantRepositories.GetSpaceId(_tenantId)) - .Returns(spaceId); - - // Act - var result = await _sut.AddSpaceDeveloperRole(_tenantId, CancellationToken.None); - - // Assert - A.CallTo(() => _cfClient.AddSpaceRoleToUser("space_developer", A._, spaceId, A._)) - .MustHaveHappenedOnceExactly(); - - result.modified.Should().BeFalse(); - result.processMessage.Should().BeNull(); - result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); - result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.CREATE_DIM_SERVICE_INSTANCE); - } - - #endregion - - #region CreateDimServiceInstance - - [Fact] - public async Task CreateDimServiceInstance_WithValidData_ReturnsExpected() - { - // Arrange - var spaceId = Guid.NewGuid(); - var servicePlanId = Guid.NewGuid(); - A.CallTo(() => _cfClient.GetSpace(_tenantName, A._)) - .Returns(spaceId); - A.CallTo(() => _cfClient.GetServicePlan("decentralized-identity-management", "standard", A._)) - .Returns(servicePlanId); - - // Act - var result = await _sut.CreateDimServiceInstance(_tenantName, _tenantId, CancellationToken.None); - - // Assert - A.CallTo(() => _cfClient.CreateDimServiceInstance(_tenantName, spaceId, servicePlanId, A._)) - .MustHaveHappenedOnceExactly(); - - result.modified.Should().BeFalse(); - result.processMessage.Should().BeNull(); - result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); - result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.CREATE_SERVICE_INSTANCE_BINDING); - } - - #endregion - - #region CreateServiceInstanceBindings - [Fact] - public async Task CreateServiceInstanceBindings_WithNotExisting_ReturnsExpected() + public async Task CheckOperation_WithCompletedAndDataNull_ThrowsUnexpectedException() { // Arrange - A.CallTo(() => _tenantRepositories.GetSpaceId(_tenantId)) - .Returns((Guid?)null); - async Task Act() => await _sut.CreateServiceInstanceBindings(_tenantName, _tenantId, CancellationToken.None).ConfigureAwait(false); + var operationId = Guid.NewGuid(); + A.CallTo(() => _tenantRepositories.GetOperationId(_tenantId)) + .Returns(operationId); + A.CallTo(() => _provisioningClient.GetOperation(A._, A._)) + .Returns(new OperationResponse(operationId, OperationResponseStatus.completed, null, null, null)); + Task Act() => _sut.CheckOperation(_tenantId, CancellationToken.None); // Act - var ex = await Assert.ThrowsAsync(Act); + var ex = await Assert.ThrowsAsync(Act); // Assert - ex.Message.Should().Be("SpaceId must not be null."); + ex.Message.Should().Be($"Data should never be null when in status {OperationResponseStatus.completed}"); } [Fact] - public async Task CreateServiceInstanceBindings_WithValidData_ReturnsExpected() + public async Task CheckOperation_WithPending_StaysInTodo() { // Arrange - var spaceId = Guid.NewGuid(); - var servicePlanId = Guid.NewGuid(); - A.CallTo(() => _tenantRepositories.GetSpaceId(_tenantId)) - .Returns(spaceId); - A.CallTo(() => _cfClient.GetServicePlan("decentralized-identity-management", "standard", A._)) - .Returns(servicePlanId); + var operationId = Guid.NewGuid(); + A.CallTo(() => _tenantRepositories.GetOperationId(_tenantId)) + .Returns(operationId); + A.CallTo(() => _provisioningClient.GetOperation(A._, A._)) + .Returns(new OperationResponse(operationId, OperationResponseStatus.pending, null, null, null)); // Act - var result = await _sut.CreateServiceInstanceBindings(_tenantName, _tenantId, CancellationToken.None); + var result = await _sut.CheckOperation(_tenantId, CancellationToken.None); // Assert - A.CallTo(() => _cfClient.CreateServiceInstanceBindings(_tenantName, null, spaceId, A._)) - .MustHaveHappenedOnceExactly(); - result.modified.Should().BeFalse(); result.processMessage.Should().BeNull(); - result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); - result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.GET_DIM_DETAILS); - } - - #endregion - - #region GetDimDetails - - [Fact] - public async Task GetDimDetails_WithNotExisting_ReturnsExpected() - { - // Arrange - A.CallTo(() => _tenantRepositories.GetSpaceId(_tenantId)) - .Returns((Guid?)null); - async Task Act() => await _sut.GetDimDetails(_tenantName, _tenantId, CancellationToken.None).ConfigureAwait(false); - - // Act - var ex = await Assert.ThrowsAsync(Act); - - // Assert - ex.Message.Should().Be("SpaceId must not be null."); + result.stepStatusId.Should().Be(ProcessStepStatusId.TODO); + result.nextStepTypeIds.Should().BeNull(); } [Fact] - public async Task GetDimDetails_WithValidData_ReturnsExpected() + public async Task CheckOperation_WithValid_UpdatesTenant() { // Arrange - var spaceId = Guid.NewGuid(); - var dimInstanceId = Guid.NewGuid(); + var operationId = Guid.NewGuid(); + var customerWalletId = Guid.NewGuid(); + var responseData = new OperationResponseData( + customerWalletId, + Guid.NewGuid().ToString(), + "test name", + new ServiceKey( + new ServiceUaa("https://example.org/api", "https://example.org", "cl1", "test123"), + "https://example.org/test", + "test")); var tenant = new Tenant(_tenantId, "test", "Corp", "https://example.org/did", false, _processId, _operatorId); - A.CallTo(() => _tenantRepositories.GetSpaceId(_tenantId)) - .Returns(spaceId); - A.CallTo(() => _cfClient.GetServiceBinding(_tenantName, spaceId, $"{_tenantName}-dim-key01", A._)) - .Returns(dimInstanceId); A.CallTo(() => _tenantRepositories.AttachAndModifyTenant(_tenantId, A>._, A>._)) .Invokes((Guid _, Action? initialize, Action modify) => { initialize?.Invoke(tenant); modify(tenant); }); + A.CallTo(() => _tenantRepositories.GetOperationId(_tenantId)) + .Returns(operationId); + A.CallTo(() => _provisioningClient.GetOperation(A._, A._)) + .Returns(new OperationResponse(operationId, OperationResponseStatus.completed, null, null, responseData)); // Act - var result = await _sut.GetDimDetails(_tenantName, _tenantId, CancellationToken.None); + var result = await _sut.CheckOperation(_tenantId, CancellationToken.None); // Assert result.modified.Should().BeFalse(); result.processMessage.Should().BeNull(); result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); - result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.CREATE_APPLICATION); - tenant.DimInstanceId.Should().Be(dimInstanceId); + result.nextStepTypeIds.Should().ContainSingle().And.Satisfy(x => x == ProcessStepTypeId.GET_COMPANY); + tenant.WalletId.Should().Be(customerWalletId); + tenant.BaseUrl.Should().Be(responseData.ServiceKey.Url); + tenant.TokenAddress.Should().Be(responseData.ServiceKey.Uaa.Url); + tenant.ClientId.Should().Be(responseData.ServiceKey.Uaa.ClientId); } #endregion - #region CreateApplication + #region GetCompany [Fact] - public async Task CreateApplication_WithNotExisting_ReturnsExpected() + public async Task GetCompany_WithDidLocationNull_ThrowsUnexpectedConditionException() { // Arrange - A.CallTo(() => _tenantRepositories.GetDimInstanceIdAndHostingUrl(_tenantId)) - .Returns(((Guid?)null, string.Empty, false)); - async Task Act() => await _sut.CreateApplication(_tenantName, _tenantId, CancellationToken.None).ConfigureAwait(false); + A.CallTo(() => _tenantRepositories.GetCompanyRequestData(_tenantId)) + .Returns((null, GetWalletData())); + Task Act() => _sut.GetCompany(_tenantId, TenantName, CancellationToken.None); // Act - var ex = await Assert.ThrowsAsync(Act); + var ex = await Assert.ThrowsAsync(Act); // Assert - ex.Message.Should().Be("DimInstanceId must not be null."); + ex.Message.Should().Be("BaseAddress must not be null"); } [Fact] - public async Task CreateApplication_WithValidData_ReturnsExpected() + public async Task GetCompany_WithValidData_ReturnsExpected() { // Arrange - var serviceCrenentialBinding = _fixture.Create(); - var dimInstanceId = Guid.NewGuid(); - var applicationId = Guid.NewGuid().ToString(); + var companyId = Guid.NewGuid(); var tenant = new Tenant(_tenantId, "test", "Corp", "https://example.org/did", false, _processId, _operatorId); - A.CallTo(() => _tenantRepositories.GetDimInstanceIdAndHostingUrl(_tenantId)) - .Returns((dimInstanceId, string.Empty, false)); - A.CallTo(() => _cfClient.GetServiceBindingDetails(dimInstanceId, A._)) - .Returns(serviceCrenentialBinding); + var baseUrl = "https://example.org/base"; + var downloadUrl = "https://example.org/download"; + A.CallTo(() => _tenantRepositories.GetCompanyRequestData(_tenantId)) + .Returns((baseUrl, GetWalletData())); A.CallTo(() => _tenantRepositories.AttachAndModifyTenant(_tenantId, A>._, A>._)) .Invokes((Guid _, Action? initialize, Action modify) => { initialize?.Invoke(tenant); modify(tenant); }); - A.CallTo(() => _dimClient.CreateApplication(A._, A._, _tenantName, A._)) - .Returns(applicationId); + A.CallTo(() => _dimClient.GetCompanyData(A._, baseUrl, TenantName, _settings.ApplicationName, A._)) + .Returns(new CompanyData(companyId, downloadUrl)); // Act - var result = await _sut.CreateApplication(_tenantName, _tenantId, CancellationToken.None); + var result = await _sut.GetCompany(_tenantId, TenantName, CancellationToken.None); // Assert - A.CallTo(() => _dimClient.CreateApplication(A._, A._, _tenantName, A._)) - .MustHaveHappenedOnceExactly(); - result.modified.Should().BeFalse(); result.processMessage.Should().BeNull(); result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); - result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.CREATE_COMPANY_IDENTITY); - tenant.ApplicationId.Should().Be(applicationId); + result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.GET_DID_DOCUMENT); + tenant.CompanyId.Should().Be(companyId); + tenant.DidDownloadUrl.Should().Be(downloadUrl); } #endregion - #region CreateCompanyIdentity + #region GetDidDocument [Fact] - public async Task CreateCompanyIdentity_WithNotExisting_ReturnsExpected() + public async Task GetDidDocument_WithDownloadUrlNull_ThrowsUnexpectedConditionException() { // Arrange - A.CallTo(() => _tenantRepositories.GetDimInstanceIdAndHostingUrl(_tenantId)) - .Returns(((Guid?)null, string.Empty, false)); - async Task Act() => await _sut.CreateCompanyIdentity(_tenantId, CancellationToken.None).ConfigureAwait(false); + A.CallTo(() => _tenantRepositories.GetDownloadUrlAndIsIssuer(_tenantId)) + .Returns((null, false)); + Task Act() => _sut.GetDidDocument(_tenantId, CancellationToken.None); // Act - var ex = await Assert.ThrowsAsync(Act); + var ex = await Assert.ThrowsAsync(Act); // Assert - ex.Message.Should().Be("DimInstanceId must not be null."); + ex.Message.Should().Be("DownloadUrl must not be null"); } [Theory] [InlineData(true)] [InlineData(false)] - public async Task CreateCompanyIdentity_WithValidData_ReturnsExpected(bool isIssuer) + public async Task GetDidDocument_WithValidData_ReturnsExpected(bool isIssuer) { // Arrange - var serviceCrenentialBinding = _fixture.Create(); - var identityResponse = _fixture.Create(); - var dimInstanceId = Guid.NewGuid(); - var tenant = new Tenant(_tenantId, "test", "Corp", "https://example.org/did", isIssuer, _processId, _operatorId); - A.CallTo(() => _tenantRepositories.GetDimInstanceIdAndHostingUrl(_tenantId)) - .Returns((dimInstanceId, "https://example.org/hosting", tenant.IsIssuer)); - A.CallTo(() => _cfClient.GetServiceBindingDetails(dimInstanceId, A._)) - .Returns(serviceCrenentialBinding); - A.CallTo(() => _tenantRepositories.AttachAndModifyTenant(_tenantId, A>._, A>._)) - .Invokes((Guid _, Action? initialize, Action modify) => - { - initialize?.Invoke(tenant); - modify(tenant); - }); - A.CallTo(() => _dimClient.CreateCompanyIdentity(A._, _tenantId, "https://example.org/hosting", A._, tenant.IsIssuer, A._)) - .Returns(identityResponse); - - // Act - var result = await _sut.CreateCompanyIdentity(_tenantId, CancellationToken.None); - - // Assert - A.CallTo(() => _dimClient.CreateCompanyIdentity(A._, A._, A._, A._, tenant.IsIssuer, A._)) - .MustHaveHappenedOnceExactly(); - - result.modified.Should().BeFalse(); - result.processMessage.Should().BeNull(); - result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); - result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.ASSIGN_COMPANY_APPLICATION); - tenant.Did.Should().Be(identityResponse.Did); - tenant.DidDownloadUrl.Should().Be(identityResponse.DownloadUrl); - tenant.CompanyId.Should().Be(identityResponse.CompanyId); - } - - #endregion - - #region AssignCompanyApplication - - [Fact] - public async Task AssignCompanyApplication_WithNotExisting_ReturnsExpected() - { - // Arrange - A.CallTo(() => _tenantRepositories.GetApplicationAndCompanyId(_tenantId)) - .Returns(((string?)null, (Guid?)null, (Guid?)null, false)); - async Task Act() => await _sut.AssignCompanyApplication(_tenantId, CancellationToken.None).ConfigureAwait(false); - - // Act - var ex = await Assert.ThrowsAsync(Act); - - // Assert - ex.Message.Should().Be("ApplicationId must always be set here"); - } - - [Fact] - public async Task AssignCompanyApplication_WithNoCompanyId_ReturnsExpected() - { - // Arrange - A.CallTo(() => _tenantRepositories.GetApplicationAndCompanyId(_tenantId)) - .Returns((Guid.NewGuid().ToString(), (Guid?)null, (Guid?)null, false)); - async Task Act() => await _sut.AssignCompanyApplication(_tenantId, CancellationToken.None).ConfigureAwait(false); - - // Act - var ex = await Assert.ThrowsAsync(Act); - - // Assert - ex.Message.Should().Be("CompanyId must always be set here"); - } - - [Fact] - public async Task AssignCompanyApplication_WithNoDimInstanceId_ReturnsExpected() - { - // Arrange - A.CallTo(() => _tenantRepositories.GetApplicationAndCompanyId(_tenantId)) - .Returns((Guid.NewGuid().ToString(), Guid.NewGuid(), null, false)); - async Task Act() => await _sut.AssignCompanyApplication(_tenantId, CancellationToken.None).ConfigureAwait(false); - - // Act - var ex = await Assert.ThrowsAsync(Act); - - // Assert - ex.Message.Should().Be("DimInstanceId must not be null."); - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public async Task AssignCompanyApplication_WithValidData_ReturnsExpected(bool isIssuer) - { - // Arrange - var serviceCrenentialBinding = _fixture.Create(); - var identityResponse = _fixture.Create(); - var applicationId = Guid.NewGuid().ToString(); - var applicationKey = Guid.NewGuid().ToString(); - var companyId = Guid.NewGuid(); - var dimInstanceId = Guid.NewGuid(); var tenant = new Tenant(_tenantId, "test", "Corp", "https://example.org/did", false, _processId, _operatorId); - A.CallTo(() => _tenantRepositories.GetApplicationAndCompanyId(_tenantId)) - .Returns((applicationId, companyId, dimInstanceId, isIssuer)); - A.CallTo(() => _cfClient.GetServiceBindingDetails(dimInstanceId, A._)) - .Returns(serviceCrenentialBinding); - A.CallTo(() => _dimClient.GetApplication(A._, A._, applicationId, A._)) - .Returns(applicationKey); + const string DownloadUrl = "https://example.org/download"; + A.CallTo(() => _tenantRepositories.GetDownloadUrlAndIsIssuer(_tenantId)) + .Returns((DownloadUrl, isIssuer)); A.CallTo(() => _tenantRepositories.AttachAndModifyTenant(_tenantId, A>._, A>._)) .Invokes((Guid _, Action? initialize, Action modify) => { initialize?.Invoke(tenant); modify(tenant); }); - A.CallTo(() => _dimClient.CreateCompanyIdentity(A._, _tenantId, "https://example.org/hosting", A._, false, A._)) - .Returns(identityResponse); // Act - var result = await _sut.AssignCompanyApplication(_tenantId, CancellationToken.None); + var result = await _sut.GetDidDocument(_tenantId, CancellationToken.None); // Assert - A.CallTo(() => _dimClient.AssignApplicationToCompany(A._, A._, applicationKey, companyId, A._)) - .MustHaveHappenedOnceExactly(); - result.modified.Should().BeFalse(); result.processMessage.Should().BeNull(); result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(isIssuer ? ProcessStepTypeId.CREATE_STATUS_LIST : ProcessStepTypeId.SEND_CALLBACK); - tenant.ApplicationKey.Should().Be(applicationKey); + tenant.Did.Should().Be("did:web:example:org:123TEST"); } #endregion @@ -895,53 +340,50 @@ public async Task AssignCompanyApplication_WithValidData_ReturnsExpected(bool is #region CreateStatusList [Fact] - public async Task CreateStatusList_WithNoCompanyId_ReturnsExpected() + public async Task CreateStatusList_WithCompanyIdNull_ThrowsUnexpectedConditionException() { // Arrange - A.CallTo(() => _tenantRepositories.GetApplicationAndCompanyId(_tenantId)) - .Returns((Guid.NewGuid().ToString(), (Guid?)null, (Guid?)null, false)); - async Task Act() => await _sut.CreateStatusList(_tenantId, CancellationToken.None).ConfigureAwait(false); + A.CallTo(() => _tenantRepositories.GetStatusListCreationData(_tenantId)) + .Returns((null, null, GetWalletData())); + Task Act() => _sut.CreateStatusList(_tenantId, CancellationToken.None); // Act - var ex = await Assert.ThrowsAsync(Act); + var ex = await Assert.ThrowsAsync(Act); // Assert - ex.Message.Should().Be("CompanyId must always be set here"); + ex.Message.Should().Be("CompanyId must not be null"); } [Fact] - public async Task CreateStatusList_WithNoDimInstanceId_ReturnsExpected() + public async Task CreateStatusList_WithBaseUrlNull_ThrowsUnexpectedConditionException() { // Arrange - A.CallTo(() => _tenantRepositories.GetApplicationAndCompanyId(_tenantId)) - .Returns((Guid.NewGuid().ToString(), Guid.NewGuid(), null, false)); - async Task Act() => await _sut.CreateStatusList(_tenantId, CancellationToken.None).ConfigureAwait(false); + var companyId = Guid.NewGuid(); + A.CallTo(() => _tenantRepositories.GetStatusListCreationData(_tenantId)) + .Returns((companyId, null, GetWalletData())); + Task Act() => _sut.CreateStatusList(_tenantId, CancellationToken.None); // Act - var ex = await Assert.ThrowsAsync(Act); + var ex = await Assert.ThrowsAsync(Act); // Assert - ex.Message.Should().Be("DimInstanceId must not be null."); + ex.Message.Should().Be("BaseUrl must not be null"); } [Fact] public async Task CreateStatusList_WithValidData_ReturnsExpected() { // Arrange - var serviceCrenentialBinding = _fixture.Create(); - var applicationId = Guid.NewGuid().ToString(); var companyId = Guid.NewGuid(); - var dimInstanceId = Guid.NewGuid(); - A.CallTo(() => _tenantRepositories.GetApplicationAndCompanyId(_tenantId)) - .Returns((applicationId, companyId, dimInstanceId, false)); - A.CallTo(() => _cfClient.GetServiceBindingDetails(dimInstanceId, A._)) - .Returns(serviceCrenentialBinding); + const string BaseUrl = "https://example.org"; + A.CallTo(() => _tenantRepositories.GetStatusListCreationData(_tenantId)) + .Returns((companyId, BaseUrl, GetWalletData())); // Act var result = await _sut.CreateStatusList(_tenantId, CancellationToken.None); // Assert - A.CallTo(() => _dimClient.CreateStatusList(A._, A._, companyId, A._)) + A.CallTo(() => _dimClient.CreateStatusList(A._, BaseUrl, companyId, A._)) .MustHaveHappenedOnceExactly(); result.modified.Should().BeFalse(); @@ -955,79 +397,73 @@ public async Task CreateStatusList_WithValidData_ReturnsExpected() #region SendCallback [Fact] - public async Task SendCallback_WithNotExisting_ReturnsExpected() + public async Task SendCallback_WithoutBaseUrl_ReturnsExpected() { // Arrange A.CallTo(() => _tenantRepositories.GetCallbackData(_tenantId)) - .Returns(("bpn123", (string?)null, (string?)null, (Guid?)null)); - async Task Act() => await _sut.SendCallback(_tenantId, CancellationToken.None).ConfigureAwait(false); + .Returns(("bpn123", null, _fixture.Create(), null, null)); + async Task Act() => await _sut.SendCallback(_tenantId, CancellationToken.None); // Act - var ex = await Assert.ThrowsAsync(Act); + var ex = await Assert.ThrowsAsync(Act); // Assert - ex.Message.Should().Be("DownloadUrl must not be null."); + ex.Message.Should().Be("BaseUrl must always be set"); } [Fact] - public async Task SendCallback_WithNoCompanyId_ReturnsExpected() + public async Task SendCallback_WithoutDid_ReturnsExpected() { // Arrange A.CallTo(() => _tenantRepositories.GetCallbackData(_tenantId)) - .Returns(("bpn123", "https://example.org/did", (string?)null, (Guid?)null)); - async Task Act() => await _sut.SendCallback(_tenantId, CancellationToken.None).ConfigureAwait(false); + .Returns(("bpn123", "https://example.org/base", _fixture.Create(), null, null)); + async Task Act() => await _sut.SendCallback(_tenantId, CancellationToken.None); // Act - var ex = await Assert.ThrowsAsync(Act); + var ex = await Assert.ThrowsAsync(Act); // Assert - ex.Message.Should().Be("Did must not be null."); + ex.Message.Should().Be("Did must always be set"); } [Fact] - public async Task SendCallback_WithNoDimInstanceId_ReturnsExpected() + public async Task SendCallback_WithoutDownloadUrl_ReturnsExpected() { // Arrange A.CallTo(() => _tenantRepositories.GetCallbackData(_tenantId)) - .Returns(("bpn123", "https://example.org/did", Guid.NewGuid().ToString(), (Guid?)null)); - async Task Act() => await _sut.SendCallback(_tenantId, CancellationToken.None).ConfigureAwait(false); + .Returns(("bpn123", "https://example.org/base", _fixture.Create(), "did:web:example:org:base", null)); + async Task Act() => await _sut.SendCallback(_tenantId, CancellationToken.None); // Act - var ex = await Assert.ThrowsAsync(Act); + var ex = await Assert.ThrowsAsync(Act); // Assert - ex.Message.Should().Be("DimInstanceId must not be null."); + ex.Message.Should().Be("DownloadUrl must always be set"); } [Fact] public async Task SendCallback_WithValidData_ReturnsExpected() { // Arrange - var serviceCrenentialBinding = _fixture.Create(); - var identityResponse = _fixture.Create(); - var dimInstanceId = Guid.NewGuid(); - var tenant = new Tenant(_tenantId, "test", "Corp", "https://example.org/did", false, _processId, _operatorId); - var did = Guid.NewGuid().ToString(); + var tenant = new Tenant(_tenantId, "test", "Corp", "https://example.org/did", false, _processId, _operatorId) + { + Did = "did:web:example:org:base", + DidDownloadUrl = "https://example.org/download", + }; + + var cryptoHelper = _settings.EncryptionConfigs.GetCryptoHelper(_settings.EncryptionConfigIndex); + var (encryptSecret, initializationVector) = cryptoHelper.Encrypt("test123"); + var walletData = new WalletData("https://example.org/token", "cl1", encryptSecret, initializationVector, _settings.EncryptionConfigIndex); A.CallTo(() => _tenantRepositories.GetCallbackData(_tenantId)) - .Returns(("bpn123", "https://example.org/did", did, dimInstanceId)); - A.CallTo(() => _cfClient.GetServiceBindingDetails(dimInstanceId, A._)) - .Returns(serviceCrenentialBinding); - A.CallTo(() => _dimClient.GetDidDocument(A._, A._)) - .Returns(JsonDocument.Parse("{}")); - A.CallTo(() => _tenantRepositories.AttachAndModifyTenant(_tenantId, A>._, A>._)) - .Invokes((Guid _, Action? initialize, Action modify) => - { - initialize?.Invoke(tenant); - modify(tenant); - }); - A.CallTo(() => _dimClient.CreateCompanyIdentity(A._, _tenantId, "https://example.org/hosting", A._, false, A._)) - .Returns(identityResponse); + .Returns(("bpn123", "https://example.org/base", walletData, tenant.Did, tenant.DidDownloadUrl)); // Act var result = await _sut.SendCallback(_tenantId, CancellationToken.None); // Assert - A.CallTo(() => _callbackService.SendCallback("bpn123", A._, A._, did, A._)) + A.CallTo(() => _callbackService.SendCallback(A._, A._, A._, A._, A._)) + .MustHaveHappenedOnceExactly(); + A.CallTo(() => _callbackService.SendCallback("bpn123", A._, A._, "did:web:example:org:base", A._)) .MustHaveHappenedOnceExactly(); result.modified.Should().BeFalse(); @@ -1037,4 +473,12 @@ public async Task SendCallback_WithValidData_ReturnsExpected() } #endregion + + private WalletData GetWalletData() + { + var cryptoHelper = _settings.EncryptionConfigs.GetCryptoHelper(_settings.EncryptionConfigIndex); + var (secret, initializationVector) = cryptoHelper.Encrypt("test123"); + + return new WalletData("https://example.org/token", "cl1", secret, initializationVector, _settings.EncryptionConfigIndex); + } } diff --git a/tests/processes/DimProcess.Library.Tests/TechnicalUserProcessHandlerTests.cs b/tests/processes/DimProcess.Library.Tests/TechnicalUserProcessHandlerTests.cs index 88cb19e..26d2660 100644 --- a/tests/processes/DimProcess.Library.Tests/TechnicalUserProcessHandlerTests.cs +++ b/tests/processes/DimProcess.Library.Tests/TechnicalUserProcessHandlerTests.cs @@ -18,8 +18,11 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -using Dim.Clients.Api.Cf; +using Dim.Clients.Api.Div; +using Dim.Clients.Api.Div.Models; +using Dim.Clients.Token; using Dim.DbAccess; +using Dim.DbAccess.Models; using Dim.DbAccess.Repositories; using Dim.Entities.Entities; using Dim.Entities.Enums; @@ -34,10 +37,11 @@ namespace DimProcess.Library.Tests; public class TechnicalUserProcessHandlerTests { - private readonly ITenantRepository _tenantRepositories; - private readonly ICfClient _cfClient; + private readonly ITechnicalUserRepository _technicalUserRepository; private readonly ICallbackService _callbackService; private readonly TechnicalUserProcessHandler _sut; + private readonly TechnicalUserSettings _settings; + private readonly IProvisioningClient _provisioningClient; public TechnicalUserProcessHandlerTests() { @@ -47,13 +51,13 @@ public TechnicalUserProcessHandlerTests() fixture.Behaviors.Add(new OmitOnRecursionBehavior()); var repositories = A.Fake(); - _tenantRepositories = A.Fake(); + _technicalUserRepository = A.Fake(); - A.CallTo(() => repositories.GetInstance()).Returns(_tenantRepositories); + A.CallTo(() => repositories.GetInstance()).Returns(_technicalUserRepository); - _cfClient = A.Fake(); + _provisioningClient = A.Fake(); _callbackService = A.Fake(); - var options = Options.Create(new TechnicalUserSettings + _settings = new TechnicalUserSettings { EncryptionConfigIndex = 0, EncryptionConfigs = new[] @@ -66,115 +70,336 @@ public TechnicalUserProcessHandlerTests() EncryptionKey = "2c68516f23467028602524534824437e417e253c29546c563c2f5e3d485e7667" } } - }); + }; + var options = Options.Create(_settings); - _sut = new TechnicalUserProcessHandler(repositories, _cfClient, _callbackService, options); + _sut = new TechnicalUserProcessHandler(repositories, _provisioningClient, _callbackService, options); } - #region CreateSubaccount + #region CreateServiceInstanceBindings + + [Fact] + public async Task CreateServiceInstanceBindings_WithValid_SavesOperationId() + { + // Arrange + var operationId = Guid.NewGuid(); + var technicalUser = new TechnicalUser(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), "saTest", Guid.NewGuid()); + A.CallTo(() => _technicalUserRepository.GetTechnicalUserNameAndWalletId(technicalUser.Id)) + .Returns((Guid.NewGuid(), technicalUser.TechnicalUserName)); + A.CallTo(() => _technicalUserRepository.AttachAndModifyTechnicalUser(technicalUser.Id, A>._, A>._)) + .Invokes((Guid _, Action? initialize, Action modify) => + { + initialize!.Invoke(technicalUser); + modify(technicalUser); + }); + A.CallTo(() => _provisioningClient.CreateServiceKey(technicalUser.TechnicalUserName, A._, A._)) + .Returns(operationId); + + // Act + var result = await _sut.CreateServiceInstanceBindings(technicalUser.Id, CancellationToken.None); + + // Assert + result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); + result.processMessage.Should().BeNull(); + result.modified.Should().BeFalse(); + result.nextStepTypeIds.Should().ContainSingle().And.Satisfy(x => x == ProcessStepTypeId.GET_TECHNICAL_USER_DATA); + technicalUser.OperationId.Should().Be(operationId); + } [Fact] - public async Task CreateSubaccount_WithValidData_ReturnsExpected() + public async Task CreateServiceInstanceBindings_WithWalletIdNotSet_ThrowsConflictException() { // Arrange var technicalUserId = Guid.NewGuid(); - var serviceBindingId = Guid.NewGuid(); - var technicalUser = new TechnicalUser(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), "test", Guid.NewGuid()); - A.CallTo(() => _tenantRepositories.GetSpaceIdAndTechnicalUserName(technicalUserId)) - .Returns(new ValueTuple(Guid.NewGuid(), "test")); - A.CallTo(() => _cfClient.GetServiceBinding("test", A._, A._, A._)) - .Returns(serviceBindingId); - A.CallTo(() => _cfClient.GetServiceBindingDetails(serviceBindingId, A._)) - .Returns(new ServiceCredentialBindingDetailResponse(new Credentials("https://example.org", new Uaa("cl1", "test123", "https://example.org/test", "https://example.org/api")))); - A.CallTo(() => _tenantRepositories.AttachAndModifyTechnicalUser(A._, A>._, A>._)) + Task Act() => _sut.CreateServiceInstanceBindings(technicalUserId, CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("WalletId must not be null"); + } + + #endregion + + #region GetTechnicalUserData + + [Fact] + public async Task GetTechnicalUserData_WithValid_SavesData() + { + // Arrange + var operationId = Guid.NewGuid(); + var responseData = new OperationResponseData( + Guid.NewGuid(), + Guid.NewGuid().ToString(), + "test name", + new ServiceKey( + new ServiceUaa("https://example.org/api", "https://example.org", "cl1", "test123"), + "https://example.org/test", + "test")); + var technicalUser = new TechnicalUser(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), "saTest", Guid.NewGuid()); + A.CallTo(() => _technicalUserRepository.GetOperationIdForTechnicalUser(technicalUser.Id)) + .Returns(operationId); + A.CallTo(() => _technicalUserRepository.AttachAndModifyTechnicalUser(technicalUser.Id, A>._, A>._)) .Invokes((Guid _, Action? initialize, Action modify) => { - initialize?.Invoke(technicalUser); + initialize!.Invoke(technicalUser); modify(technicalUser); }); + A.CallTo(() => _provisioningClient.GetOperation(A._, A._)) + .Returns(new OperationResponse(operationId, OperationResponseStatus.completed, null, null, responseData)); // Act - var result = await _sut.GetTechnicalUserData("test", technicalUserId, CancellationToken.None); + var result = await _sut.GetTechnicalUserData(technicalUser.Id, CancellationToken.None); // Assert + result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); + result.processMessage.Should().BeNull(); result.modified.Should().BeFalse(); + result.nextStepTypeIds.Should().ContainSingle().And.Satisfy(x => x == ProcessStepTypeId.GET_TECHNICAL_USER_SERVICE_KEY); + + technicalUser.TokenAddress.Should().Be(responseData.ServiceKey.Uaa.Url); + technicalUser.ClientId.Should().Be(responseData.ServiceKey.Uaa.ClientId); + } + + [Fact] + public async Task GetTechnicalUserData_WithPending_StaysInTodo() + { + // Arrange + var operationId = Guid.NewGuid(); + var technicalUserId = Guid.NewGuid(); + A.CallTo(() => _technicalUserRepository.GetOperationIdForTechnicalUser(technicalUserId)) + .Returns(operationId); + A.CallTo(() => _provisioningClient.GetOperation(A._, A._)) + .Returns(new OperationResponse(operationId, OperationResponseStatus.pending, null, null, null)); + + // Act + var result = await _sut.GetTechnicalUserData(technicalUserId, CancellationToken.None); + + // Assert + result.stepStatusId.Should().Be(ProcessStepStatusId.TODO); result.processMessage.Should().BeNull(); + result.modified.Should().BeTrue(); + result.nextStepTypeIds.Should().BeNull(); + } + + [Fact] + public async Task GetTechnicalUserData_WithCompletedAndNoData_ThrowsUnexpectedConditionException() + { + // Arrange + var operationId = Guid.NewGuid(); + var technicalUserId = Guid.NewGuid(); + A.CallTo(() => _technicalUserRepository.GetOperationIdForTechnicalUser(technicalUserId)) + .Returns(operationId); + A.CallTo(() => _provisioningClient.GetOperation(A._, A._)) + .Returns(new OperationResponse(operationId, OperationResponseStatus.completed, null, null, null)); + Task Act() => _sut.GetTechnicalUserData(technicalUserId, CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("Data should never be null when in status completed"); + } + + [Fact] + public async Task GetTechnicalUserData_WithOperationIdNotSet_ThrowsConflictException() + { + // Arrange + var technicalUserId = Guid.NewGuid(); + Task Act() => _sut.GetTechnicalUserData(technicalUserId, CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("OperationId must not be null"); + } + + #endregion + + #region GetTechnicalUserData + + [Fact] + public async Task GetTechnicalUserServiceKey_WithValid_SavesData() + { + // Arrange + var serviceKeyId = Guid.NewGuid(); + var walletId = Guid.NewGuid(); + var technicalUser = new TechnicalUser(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), "saTest", Guid.NewGuid()); + A.CallTo(() => _technicalUserRepository.GetWalletIdAndNameForTechnicalUser(technicalUser.Id)) + .Returns((walletId, "test")); + A.CallTo(() => _technicalUserRepository.AttachAndModifyTechnicalUser(technicalUser.Id, A>._, A>._)) + .Invokes((Guid _, Action? initialize, Action modify) => + { + initialize!.Invoke(technicalUser); + modify(technicalUser); + }); + A.CallTo(() => _provisioningClient.GetServiceKey(A._, A._, A._)) + .Returns(serviceKeyId); + + // Act + var result = await _sut.GetTechnicalUserServiceKey(technicalUser.Id, CancellationToken.None); + + // Assert result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); - result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK); - technicalUser.EncryptionMode.Should().NotBeNull().And.Be(0); - technicalUser.ClientId.Should().Be("cl1"); + result.processMessage.Should().BeNull(); + result.modified.Should().BeFalse(); + result.nextStepTypeIds.Should().ContainSingle().And.Satisfy(x => x == ProcessStepTypeId.SEND_TECHNICAL_USER_CREATION_CALLBACK); + + technicalUser.ServiceKeyId.Should().Be(serviceKeyId); + } + + [Fact] + public async Task GetTechnicalUserData_WithWalletIdNotSet_ThrowsConflictException() + { + // Arrange + var technicalUserId = Guid.NewGuid(); + Task Act() => _sut.GetTechnicalUserServiceKey(technicalUserId, CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("WalletId must be set"); } #endregion - #region DeleteServiceInstanceBindings + #region SendCreateCallback [Fact] - public async Task DeleteServiceInstanceBindings_WithValidData_ReturnsExpected() + public async Task SendCreateCallback_WithValidData_ReturnsExpected() { // Arrange var technicalUserId = Guid.NewGuid(); - var serviceBindingId = Guid.NewGuid(); - var spaceId = Guid.NewGuid(); - A.CallTo(() => _tenantRepositories.GetSpaceIdAndTechnicalUserName(technicalUserId)) - .Returns(new ValueTuple(spaceId, "test")); - A.CallTo(() => _cfClient.GetServiceBinding("test", spaceId, A._, A._)) - .Returns(serviceBindingId); + A.CallTo(() => _technicalUserRepository.GetTechnicalUserCallbackData(technicalUserId)) + .Returns((Guid.NewGuid(), GetWalletData())); // Act - var result = await _sut.DeleteServiceInstanceBindings("test", technicalUserId, CancellationToken.None); + var result = await _sut.SendCreateCallback(technicalUserId, CancellationToken.None); // Assert + A.CallTo(() => _callbackService.SendTechnicalUserCallback(A._, A._, A._, A._, A._)) + .MustHaveHappenedOnceExactly(); + result.modified.Should().BeFalse(); result.processMessage.Should().BeNull(); result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); - result.nextStepTypeIds.Should().ContainSingle().Which.Should().Be(ProcessStepTypeId.SEND_TECHNICAL_USER_DELETION_CALLBACK); - A.CallTo(() => _cfClient.DeleteServiceInstanceBindings(A._, A._)) - .MustHaveHappenedOnceExactly(); - A.CallTo(() => _cfClient.DeleteServiceInstanceBindings(serviceBindingId, A._)) - .MustHaveHappenedOnceExactly(); + result.nextStepTypeIds.Should().BeNull(); } + #endregion + + #region DeleteServiceInstanceBindings + [Fact] - public async Task DeleteServiceInstanceBindings_WithoutSpaceId_ThrowsConflictException() + public async Task DeleteServiceInstanceBindings_WithMissingServiceKeyId_ThrowsConflictException() { // Arrange var technicalUserId = Guid.NewGuid(); - A.CallTo(() => _tenantRepositories.GetSpaceIdAndTechnicalUserName(technicalUserId)) - .Returns(new ValueTuple(null, "test")); - async Task Act() => await _sut.DeleteServiceInstanceBindings("test", technicalUserId, CancellationToken.None); + A.CallTo(() => _technicalUserRepository.GetServiceKeyAndWalletId(technicalUserId)) + .Returns(new ValueTuple(null, Guid.NewGuid())); + Task Act() => _sut.DeleteServiceInstanceBindings(technicalUserId, CancellationToken.None); // Act var ex = await Assert.ThrowsAsync(Act); // Assert - ex.Message.Should().Be("SpaceId must not be null."); - A.CallTo(() => _cfClient.DeleteServiceInstanceBindings(A._, A._)) - .MustNotHaveHappened(); + ex.Message.Should().Be("ServiceKeyId must not be null"); + } + + [Fact] + public async Task DeleteServiceInstanceBindings_WithWalletIdNotSet_ThrowsConflictException() + { + // Arrange + var technicalUserId = Guid.NewGuid(); + A.CallTo(() => _technicalUserRepository.GetServiceKeyAndWalletId(technicalUserId)) + .Returns(new ValueTuple(Guid.NewGuid(), null)); + Task Act() => _sut.DeleteServiceInstanceBindings(technicalUserId, CancellationToken.None); + + // Act + var ex = await Assert.ThrowsAsync(Act); + + // Assert + ex.Message.Should().Be("WalletId must not be null"); + } + + [Fact] + public async Task DeleteServiceInstanceBindings_WithValidData_ReturnsExpected() + { + // Arrange + var technicalUserId = Guid.NewGuid(); + var walletId = Guid.NewGuid(); + var serviceKeyId = Guid.NewGuid(); + A.CallTo(() => _technicalUserRepository.GetServiceKeyAndWalletId(technicalUserId)) + .Returns(new ValueTuple(serviceKeyId, walletId)); + + // Act + var result = await _sut.DeleteServiceInstanceBindings(technicalUserId, CancellationToken.None); + + // Assert + A.CallTo(() => _provisioningClient.DeleteServiceKey(walletId, serviceKeyId, A._)) + .MustHaveHappenedOnceExactly(); + result.modified.Should().BeFalse(); + result.processMessage.Should().BeNull(); + result.stepStatusId.Should().Be(ProcessStepStatusId.DONE); + result.nextStepTypeIds.Should().ContainSingle() + .And.Satisfy(x => x == ProcessStepTypeId.SEND_TECHNICAL_USER_DELETION_CALLBACK); } #endregion #region SendCallback + [Fact] + public async Task SendCallback_WithOperationInPending_StaysInTodo() + { + // Arrange + var technicalUserId = Guid.NewGuid(); + var operationId = Guid.NewGuid(); + var externalId = Guid.NewGuid(); + A.CallTo(() => _technicalUserRepository.GetOperationAndExternalIdForTechnicalUser(technicalUserId)) + .Returns((operationId, externalId)); + A.CallTo(() => _provisioningClient.GetOperation(operationId, A._)) + .Returns(new OperationResponse(operationId, OperationResponseStatus.pending, null, null, null)); + + // Act + var result = await _sut.SendDeleteCallback(technicalUserId, CancellationToken.None); + + // Assert + result.modified.Should().BeTrue(); + result.processMessage.Should().BeNull(); + result.stepStatusId.Should().Be(ProcessStepStatusId.TODO); + result.nextStepTypeIds.Should().BeNull(); + + A.CallTo(() => _technicalUserRepository.RemoveTechnicalUser(A._)) + .MustNotHaveHappened(); + A.CallTo(() => _callbackService.SendTechnicalUserDeletionCallback(A._, A._)) + .MustNotHaveHappened(); + } + [Fact] public async Task SendCallback_WithValidData_ReturnsExpected() { // Arrange var technicalUserId = Guid.NewGuid(); + var operationId = Guid.NewGuid(); var externalId = Guid.NewGuid(); var technicalUsers = new List { new(technicalUserId, Guid.NewGuid(), Guid.NewGuid(), "sa-t", Guid.NewGuid()) }; - A.CallTo(() => _tenantRepositories.GetExternalIdForTechnicalUser(technicalUserId)) - .Returns(externalId); - A.CallTo(() => _tenantRepositories.RemoveTechnicalUser(A._)) + A.CallTo(() => _technicalUserRepository.GetOperationAndExternalIdForTechnicalUser(technicalUserId)) + .Returns((operationId, externalId)); + A.CallTo(() => _technicalUserRepository.RemoveTechnicalUser(A._)) .Invokes((Guid tuId) => { var user = technicalUsers.Single(x => x.Id == tuId); technicalUsers.Remove(user); }); + A.CallTo(() => _provisioningClient.GetOperation(operationId, A._)) + .Returns(new OperationResponse(operationId, OperationResponseStatus.completed, null, null, null)); // Act var result = await _sut.SendDeleteCallback(technicalUserId, CancellationToken.None); @@ -192,4 +417,12 @@ public async Task SendCallback_WithValidData_ReturnsExpected() } #endregion + + private WalletData GetWalletData() + { + var cryptoHelper = _settings.EncryptionConfigs.GetCryptoHelper(_settings.EncryptionConfigIndex); + var (secret, initializationVector) = cryptoHelper.Encrypt("test123"); + + return new WalletData("https://example.org/token", "cl1", secret, initializationVector, _settings.EncryptionConfigIndex); + } } diff --git a/tests/processes/Processes.Library.Tests/ManualProcessDataExtensionsTests.cs b/tests/processes/Processes.Library.Tests/ManualProcessDataExtensionsTests.cs index 45d5ac8..f0c213e 100644 --- a/tests/processes/Processes.Library.Tests/ManualProcessDataExtensionsTests.cs +++ b/tests/processes/Processes.Library.Tests/ManualProcessDataExtensionsTests.cs @@ -19,6 +19,7 @@ ********************************************************************************/ using Dim.DbAccess; +using Dim.DbAccess.Models; using Dim.DbAccess.Repositories; using Dim.Entities.Entities; using Dim.Entities.Enums; diff --git a/tests/processes/Processes.Worker.Library.Tests/ProcessExecutionServiceTests.cs b/tests/processes/Processes.Worker.Library.Tests/ProcessExecutionServiceTests.cs index 62b0bb0..d731e8e 100644 --- a/tests/processes/Processes.Worker.Library.Tests/ProcessExecutionServiceTests.cs +++ b/tests/processes/Processes.Worker.Library.Tests/ProcessExecutionServiceTests.cs @@ -27,7 +27,6 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider; -using Org.Eclipse.TractusX.Portal.Backend.Processes.Worker.Library; using System.Collections.Immutable; namespace Processes.Worker.Library.Tests; diff --git a/tests/processes/Processes.Worker.Library.Tests/ProcessExecutorTests.cs b/tests/processes/Processes.Worker.Library.Tests/ProcessExecutorTests.cs index 222fcf9..0e20982 100644 --- a/tests/processes/Processes.Worker.Library.Tests/ProcessExecutorTests.cs +++ b/tests/processes/Processes.Worker.Library.Tests/ProcessExecutorTests.cs @@ -24,7 +24,6 @@ using Dim.Entities.Enums; using Microsoft.Extensions.Logging; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; -using Org.Eclipse.TractusX.Portal.Backend.Processes.Worker.Library; using System.Collections.Immutable; using ProcessTypeId = Dim.Entities.Enums.ProcessTypeId; @@ -82,13 +81,13 @@ public void GetRegisteredProcessTypeIds_ReturnsExpected() public async Task ExecuteProcess_WithInvalidProcessTypeId_Throws() { // Arrange - var Act = async () => await _sut.ExecuteProcess(Guid.NewGuid(), (ProcessTypeId)default, CancellationToken.None).ToListAsync().ConfigureAwait(false); + var Act = async () => await _sut.ExecuteProcess(Guid.NewGuid(), default, CancellationToken.None).ToListAsync(); // Act var result = await Assert.ThrowsAsync(Act); // Assert - result.Message.Should().Be($"processType {(ProcessTypeId)default} is not a registered executable processType."); + result.Message.Should().Be("processType 0 is not a registered executable processType."); } [Theory] @@ -842,7 +841,7 @@ public async Task ExecuteProcess_ProcessThrowsSystemException_Throws(bool isLock var Act = async () => { - await foreach (var stepResult in _sut.ExecuteProcess(processId, ProcessTypeId.SETUP_DIM, CancellationToken.None).ConfigureAwait(false)) + await foreach (var stepResult in _sut.ExecuteProcess(processId, ProcessTypeId.SETUP_DIM, CancellationToken.None)) { stepResults.Add(stepResult); } diff --git a/tests/web/Dim.Web.Tests/Controllers/DimControllerTests.cs b/tests/web/Dim.Web.Tests/Controllers/DimControllerTests.cs new file mode 100644 index 0000000..1b275f9 --- /dev/null +++ b/tests/web/Dim.Web.Tests/Controllers/DimControllerTests.cs @@ -0,0 +1,62 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * 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 Dim.Web.Tests.Setup; +using System.Net; + +namespace Dim.Web.Tests.Controllers; + +public class DimControllerTests(IntegrationTestFactory factory) : IClassFixture +{ + private const string BaseUrl = "api/dim"; + private readonly HttpClient _client = factory.CreateClient(); + + #region SetupDim + + [Fact] + public async Task SetupDim_WithoutFilters_ReturnsExpected() + { + // Act + var companyName = $"test-{DateTime.UtcNow.Ticks}"; + var bpn = $"BPN-{DateTime.UtcNow.Ticks}"; + var didDocumentLocation = $"https://example.org/did/{bpn}/did.json"; + var response = await _client.PostAsync($"{BaseUrl}/setup-dim?companyName={companyName}&bpn={bpn}&didDocumentLocation={didDocumentLocation}", null, CancellationToken.None); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + } + + #endregion + + #region Swagger + + [Fact] + public async Task CheckSwagger_ReturnsExpected() + { + // Act + var response = await _client.GetAsync($"{BaseUrl}/swagger/v1/swagger.json"); + + // Assert + response.Should().NotBeNull(); + response.StatusCode.Should().Be(HttpStatusCode.OK); + } + + #endregion +} diff --git a/tests/web/Dim.Web.Tests/Dim.Web.Tests.csproj b/tests/web/Dim.Web.Tests/Dim.Web.Tests.csproj index dd2aff2..3bb2cc7 100644 --- a/tests/web/Dim.Web.Tests/Dim.Web.Tests.csproj +++ b/tests/web/Dim.Web.Tests/Dim.Web.Tests.csproj @@ -31,7 +31,9 @@ + + runtime; build; native; contentfiles; analyzers; buildtransitive @@ -45,7 +47,30 @@ + + + + Seeder/Data/ + Always + + + Seeder/Data/ + Always + + + Seeder/Data/ + Always + + + Seeder/Data/ + Always + + + + Always + + diff --git a/tests/web/Dim.Web.Tests/DimBusinessLogicTests.cs b/tests/web/Dim.Web.Tests/DimBusinessLogicTests.cs index 9d32d81..bf40d2a 100644 --- a/tests/web/Dim.Web.Tests/DimBusinessLogicTests.cs +++ b/tests/web/Dim.Web.Tests/DimBusinessLogicTests.cs @@ -18,15 +18,20 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -using Dim.Clients.Api.Cf; using Dim.Clients.Api.Dim; +using Dim.Clients.Token; using Dim.DbAccess; +using Dim.DbAccess.Models; using Dim.DbAccess.Repositories; using Dim.Entities.Entities; using Dim.Entities.Enums; using Dim.Web.BusinessLogic; +using Dim.Web.ErrorHandling; +using Dim.Web.Models; using Microsoft.Extensions.Options; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration; +using System.Security.Cryptography; namespace Dim.Web.Tests; @@ -34,34 +39,50 @@ public class DimBusinessLogicTests { private static readonly Guid OperatorId = Guid.NewGuid(); private readonly IDimBusinessLogic _sut; - private readonly ICfClient _cfClient; private readonly IDimClient _dimClient; private readonly ITenantRepository _tenantRepository; + private readonly ITechnicalUserRepository _technicalUserRepository; private readonly IProcessStepRepository _processStepRepository; + private readonly DimSettings _settings; + private readonly IFixture _fixture; public DimBusinessLogicTests() { - var fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); - fixture.Behaviors.OfType().ToList() - .ForEach(b => fixture.Behaviors.Remove(b)); - fixture.Behaviors.Add(new OmitOnRecursionBehavior()); + _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); + _fixture.Behaviors.OfType().ToList() + .ForEach(b => _fixture.Behaviors.Remove(b)); + _fixture.Behaviors.Add(new OmitOnRecursionBehavior()); var repositories = A.Fake(); _dimClient = A.Fake(); - _cfClient = A.Fake(); _tenantRepository = A.Fake(); + _technicalUserRepository = A.Fake(); _processStepRepository = A.Fake(); A.CallTo(() => repositories.GetInstance()).Returns(_tenantRepository); + A.CallTo(() => repositories.GetInstance()).Returns(_technicalUserRepository); A.CallTo(() => repositories.GetInstance()).Returns(_processStepRepository); - _sut = new DimBusinessLogic(repositories, _cfClient, _dimClient, Options.Create(new DimSettings + _settings = new DimSettings { - OperatorId = OperatorId - })); + OperatorId = OperatorId, + EncryptionConfigs = new[] + { + new EncryptionModeConfig + { + Index = 0, + CipherMode = CipherMode.CBC, + PaddingMode = PaddingMode.PKCS7, + EncryptionKey = "2c68516f23467028602524534824437e417e253c29546c563c2f5e3d485e7667" + } + } + }; + _sut = new DimBusinessLogic(repositories, _dimClient, Options.Create(_settings)); } + #region StartSetupDim + [Fact] public async Task StartSetupDim_WithExisting_ThrowsConflictException() { @@ -74,7 +95,7 @@ public async Task StartSetupDim_WithExisting_ThrowsConflictException() var result = await Assert.ThrowsAsync(Act); // Assert - result.Message.Should().Be($"Tenant testCompany with Bpn BPNL00000001TEST already exists"); + result.Message.Should().Be(DimErrors.TENANT_ALREADY_EXISTS.ToString()); } [Theory] @@ -97,11 +118,12 @@ public async Task StartSetupDim_WithNewData_CreatesExpected(string companyName, .Invokes((ProcessTypeId processTypeId) => { processes.Add(new Process(processId, processTypeId, Guid.NewGuid())); - }); + }) + .Returns(new Process(processId, ProcessTypeId.TECHNICAL_USER, Guid.NewGuid())); A.CallTo(() => _processStepRepository.CreateProcessStep(A._, A._, A._)) .Invokes((ProcessStepTypeId processStepTypeId, ProcessStepStatusId processStepStatusId, Guid pId) => { - processSteps.Add(new ProcessStep(Guid.NewGuid(), processStepTypeId, processStepStatusId, processId, DateTimeOffset.UtcNow)); + processSteps.Add(new ProcessStep(Guid.NewGuid(), processStepTypeId, processStepStatusId, pId, DateTimeOffset.UtcNow)); }); A.CallTo(() => _tenantRepository.CreateTenant(A._, A._, A._, A._, A._, A._)) @@ -118,8 +140,276 @@ public async Task StartSetupDim_WithNewData_CreatesExpected(string companyName, processes.Should().ContainSingle() .Which.ProcessTypeId.Should().Be(ProcessTypeId.SETUP_DIM); processSteps.Should().ContainSingle() - .And.Satisfy(x => x.ProcessId == processId && x.ProcessStepTypeId == ProcessStepTypeId.CREATE_SUBACCOUNT); + .And.Satisfy(x => x.ProcessId == processId && x.ProcessStepTypeId == ProcessStepTypeId.CREATE_WALLET); tenants.Should().ContainSingle() .And.Satisfy(x => x.CompanyName == expectedCompanyName && x.Bpn == "BPNL00000001TEST"); } + + #endregion + + #region GetStatusList + + [Fact] + public async Task GetStatusList_WithNotExisting_ThrowsNotFoundException() + { + // Arrange + var bpn = "BPNL00000001TEST"; + A.CallTo(() => _tenantRepository.GetCompanyAndWalletDataForBpn(bpn)) + .Returns((false, null, null, GetWalletData())); + Task Act() => _sut.GetStatusList(bpn, CancellationToken.None); + + // Act + var result = await Assert.ThrowsAsync(Act); + + // Assert + result.Message.Should().Be(DimErrors.NO_COMPANY_FOR_BPN.ToString()); + } + + [Fact] + public async Task GetStatusList_WithoutCompanyId_ThrowsConflictException() + { + // Arrange + const string Bpn = "BPNL00000001TEST"; + A.CallTo(() => _tenantRepository.GetCompanyAndWalletDataForBpn(Bpn)) + .Returns((true, null, null, GetWalletData())); + Task Act() => _sut.GetStatusList(Bpn, CancellationToken.None); + + // Act + var result = await Assert.ThrowsAsync(Act); + + // Assert + result.Message.Should().Be(DimErrors.NO_COMPANY_ID_SET.ToString()); + } + + [Fact] + public async Task GetStatusList_WithBaseUrlNotSet_ThrowsConflictException() + { + // Arrange + const string Bpn = "BPNL00000001TEST"; + var companyId = Guid.NewGuid(); + A.CallTo(() => _tenantRepository.GetCompanyAndWalletDataForBpn(Bpn)) + .Returns((true, companyId, null, GetWalletData())); + Task Act() => _sut.GetStatusList(Bpn, CancellationToken.None); + + // Act + var result = await Assert.ThrowsAsync(Act); + + // Assert + result.Message.Should().Be(DimErrors.NO_BASE_URL_SET.ToString()); + } + + [Fact] + public async Task GetStatusList_WithValid_ReturnsExpected() + { + // Arrange + const string Bpn = "BPNL00000001TEST"; + const string BaseUrl = "https://example.org/base"; + var companyId = Guid.NewGuid(); + A.CallTo(() => _tenantRepository.GetCompanyAndWalletDataForBpn(Bpn)) + .Returns((true, companyId, BaseUrl, GetWalletData())); + A.CallTo(() => _dimClient.GetStatusList(A._, BaseUrl, companyId, A._)) + .Returns("https://example.org/statuslist"); + + // Act + var result = await _sut.GetStatusList(Bpn, CancellationToken.None); + + // Assert + result.Should().Be("https://example.org/statuslist"); + } + + #endregion + + #region CreateStatusList + + [Fact] + public async Task CreateStatusList_WithNotExisting_ThrowsNotFoundException() + { + // Arrange + var bpn = "BPNL00000001TEST"; + A.CallTo(() => _tenantRepository.GetCompanyAndWalletDataForBpn(bpn)) + .Returns((false, null, null, GetWalletData())); + Task Act() => _sut.CreateStatusList(bpn, CancellationToken.None); + + // Act + var result = await Assert.ThrowsAsync(Act); + + // Assert + result.Message.Should().Be(DimErrors.NO_COMPANY_FOR_BPN.ToString()); + } + + [Fact] + public async Task CreateStatusList_WithoutCompanyId_ThrowsConflictException() + { + // Arrange + const string Bpn = "BPNL00000001TEST"; + A.CallTo(() => _tenantRepository.GetCompanyAndWalletDataForBpn(Bpn)) + .Returns((true, null, null, GetWalletData())); + Task Act() => _sut.CreateStatusList(Bpn, CancellationToken.None); + + // Act + var result = await Assert.ThrowsAsync(Act); + + // Assert + result.Message.Should().Be(DimErrors.NO_COMPANY_ID_SET.ToString()); + } + + [Fact] + public async Task CreateStatusList_WithBaseUrlNotSet_ThrowsConflictException() + { + // Arrange + const string Bpn = "BPNL00000001TEST"; + var companyId = Guid.NewGuid(); + A.CallTo(() => _tenantRepository.GetCompanyAndWalletDataForBpn(Bpn)) + .Returns((true, companyId, null, GetWalletData())); + Task Act() => _sut.CreateStatusList(Bpn, CancellationToken.None); + + // Act + var result = await Assert.ThrowsAsync(Act); + + // Assert + result.Message.Should().Be(DimErrors.NO_BASE_URL_SET.ToString()); + } + + [Fact] + public async Task CreateStatusList_WithValid_ReturnsExpected() + { + // Arrange + const string Bpn = "BPNL00000001TEST"; + const string BaseUrl = "https://example.org/base"; + var companyId = Guid.NewGuid(); + A.CallTo(() => _tenantRepository.GetCompanyAndWalletDataForBpn(Bpn)) + .Returns((true, companyId, BaseUrl, GetWalletData())); + A.CallTo(() => _dimClient.CreateStatusList(A._, BaseUrl, companyId, A._)) + .Returns("https://example.org/statuslist"); + + // Act + var result = await _sut.CreateStatusList(Bpn, CancellationToken.None); + + // Assert + result.Should().Be("https://example.org/statuslist"); + } + + #endregion + + #region StartSetupDim + + [Fact] + public async Task CreateTechnicalUser_WithExisting_ThrowsNotFoundException() + { + // Arrange + const string Bpn = "BPNL00000001TEST"; + A.CallTo(() => _tenantRepository.GetTenantForBpn(Bpn)) + .Returns((false, Guid.NewGuid())); + async Task Act() => await _sut.CreateTechnicalUser(Bpn, _fixture.Create()); + + // Act + var result = await Assert.ThrowsAsync(Act); + + // Assert + result.Message.Should().Be(DimErrors.NO_COMPANY_FOR_BPN.ToString()); + } + + [Fact] + public async Task CreateTechnicalUser_WithNewData_CreatesExpected() + { + // Arrange + const string Bpn = "BPNL00001Test"; + var processId = Guid.NewGuid(); + var processes = new List(); + var processSteps = new List(); + var technicalUsers = new List(); + A.CallTo(() => _tenantRepository.GetTenantForBpn(Bpn)) + .Returns((true, Guid.NewGuid())); + A.CallTo(() => _processStepRepository.CreateProcess(A._)) + .Invokes((ProcessTypeId processTypeId) => + { + processes.Add(new Process(processId, processTypeId, Guid.NewGuid())); + }) + .Returns(new Process(processId, ProcessTypeId.TECHNICAL_USER, Guid.NewGuid())); + A.CallTo(() => _processStepRepository.CreateProcessStep(A._, A._, A._)) + .Invokes((ProcessStepTypeId processStepTypeId, ProcessStepStatusId processStepStatusId, Guid pId) => + { + processSteps.Add(new ProcessStep(Guid.NewGuid(), processStepTypeId, processStepStatusId, pId, DateTimeOffset.UtcNow)); + }); + A.CallTo(() => + _technicalUserRepository.CreateTenantTechnicalUser(A._, A._, A._, A._)) + .Invokes((Guid tenantId, string name, Guid externalId, Guid pId) => + { + technicalUsers.Add(new TechnicalUser(Guid.NewGuid(), tenantId, externalId, name, pId)); + }); + + // Act + await _sut.CreateTechnicalUser(Bpn, _fixture.Create()); + + // Assert + processes.Should().ContainSingle() + .Which.ProcessTypeId.Should().Be(ProcessTypeId.TECHNICAL_USER); + processSteps.Should().ContainSingle() + .And.Satisfy(x => x.ProcessId == processId && x.ProcessStepTypeId == ProcessStepTypeId.CREATE_TECHNICAL_USER); + technicalUsers.Should().ContainSingle(); + } + + #endregion + + #region StartSetupDim + + [Fact] + public async Task DeleteTechnicalUser_WithExisting_ThrowsNotFoundException() + { + // Arrange + const string Bpn = "BPNL00000001TEST"; + var technicalUserData = new TechnicalUserData(Guid.NewGuid(), "test"); + A.CallTo(() => _technicalUserRepository.GetTechnicalUserForBpn(Bpn, technicalUserData.Name)) + .Returns((false, Guid.NewGuid(), Guid.NewGuid())); + async Task Act() => await _sut.DeleteTechnicalUser(Bpn, technicalUserData); + + // Act + var result = await Assert.ThrowsAsync(Act); + + // Assert + result.Message.Should().Be(DimErrors.NO_TECHNICAL_USER_FOUND.ToString()); + } + + [Fact] + public async Task DeleteTechnicalUser_WithValid_DeletesExpected() + { + // Arrange + const string Bpn = "BPNL00001Test"; + var processId = Guid.NewGuid(); + var processSteps = new List(); + var technicalUserData = new TechnicalUserData(Guid.NewGuid(), "test"); + var technicalUser = new TechnicalUser(Guid.NewGuid(), Guid.NewGuid(), Guid.NewGuid(), "test", processId); + A.CallTo(() => _technicalUserRepository.GetTechnicalUserForBpn(Bpn, technicalUserData.Name)) + .Returns((true, technicalUser.Id, technicalUser.ProcessId)); + A.CallTo(() => _processStepRepository.CreateProcessStep(A._, A._, A._)) + .Invokes((ProcessStepTypeId processStepTypeId, ProcessStepStatusId processStepStatusId, Guid pId) => + { + processSteps.Add(new ProcessStep(Guid.NewGuid(), processStepTypeId, processStepStatusId, pId, DateTimeOffset.UtcNow)); + }); + A.CallTo(() => _technicalUserRepository.AttachAndModifyTechnicalUser(A._, A>._, A>._)) + .Invokes((Guid _, Action? initialize, Action modify) => + { + initialize?.Invoke(technicalUser); + modify(technicalUser); + }); + + // Act + await _sut.DeleteTechnicalUser(Bpn, technicalUserData); + + // Assert + processSteps.Should().ContainSingle() + .And.Satisfy(x => x.ProcessId == processId && x.ProcessStepTypeId == ProcessStepTypeId.DELETE_TECHNICAL_USER); + technicalUser.ProcessId.Should().Be(processId); + technicalUser.ExternalId.Should().Be(technicalUserData.ExternalId); + } + + #endregion + + private WalletData GetWalletData() + { + var cryptoHelper = _settings.EncryptionConfigs.GetCryptoHelper(0); + var (secret, initializationVector) = cryptoHelper.Encrypt("test123"); + + return new WalletData("https://example.org/token", "cl1", secret, initializationVector, 0); + } } diff --git a/src/clients/Dim.Clients/Api/Services/DependencyInjection/ServiceClientServiceExtensions.cs b/tests/web/Dim.Web.Tests/Setup/FakePolicyEvaluator.cs similarity index 56% rename from src/clients/Dim.Clients/Api/Services/DependencyInjection/ServiceClientServiceExtensions.cs rename to tests/web/Dim.Web.Tests/Setup/FakePolicyEvaluator.cs index ac75302..59ad550 100644 --- a/src/clients/Dim.Clients/Api/Services/DependencyInjection/ServiceClientServiceExtensions.cs +++ b/tests/web/Dim.Web.Tests/Setup/FakePolicyEvaluator.cs @@ -18,19 +18,19 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ -using Dim.Clients.Extensions; -using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization.Policy; +using Microsoft.AspNetCore.Http; +using System.Security.Claims; -namespace Dim.Clients.Api.Services.DependencyInjection; +namespace Dim.Web.Tests.Setup; -public static class ServiceClientServiceExtensions +public class FakePolicyEvaluator : IPolicyEvaluator { - public static IServiceCollection AddServiceClient(this IServiceCollection services) - { - services - .AddCustomHttpClientWithAuthentication(null, null) - .AddTransient(); + public Task AuthenticateAsync(AuthorizationPolicy policy, HttpContext context) => + Task.FromResult(AuthenticateResult.Success(new AuthenticationTicket(new ClaimsPrincipal(), new AuthenticationProperties(), "FakeScheme"))); - return services; - } + public Task AuthorizeAsync(AuthorizationPolicy policy, AuthenticateResult authenticationResult, HttpContext context, object? resource) => + Task.FromResult(PolicyAuthorizationResult.Success()); } diff --git a/tests/web/Dim.Web.Tests/Setup/IntegrationTestFactory.cs b/tests/web/Dim.Web.Tests/Setup/IntegrationTestFactory.cs new file mode 100644 index 0000000..d4373a2 --- /dev/null +++ b/tests/web/Dim.Web.Tests/Setup/IntegrationTestFactory.cs @@ -0,0 +1,103 @@ +/******************************************************************************** + * Copyright (c) 2024 BMW Group AG + * Copyright 2024 SAP SE or an SAP affiliate company and ssi-dim-middle-layer contributors. + * + * 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 Dim.Entities; +using Dim.Migrations.Migrations; +using Dim.Web.BusinessLogic; +using Microsoft.AspNetCore.Authorization.Policy; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.TestHost; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Logging; +using System.Text.Json.Serialization; +using Testcontainers.PostgreSql; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] + +namespace Dim.Web.Tests.Setup; + +public class IntegrationTestFactory : WebApplicationFactory, IAsyncLifetime +{ + private readonly PostgreSqlContainer _container = new PostgreSqlBuilder() + .WithDatabase("test_db") + .WithImage("postgres") + .WithCleanUp(true) + .WithName(Guid.NewGuid().ToString()) + .Build(); + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + var projectDir = Directory.GetCurrentDirectory(); + var configPath = Path.Combine(projectDir, "appsettings.IntegrationTests.json"); + + var config = new ConfigurationBuilder().AddJsonFile(configPath, true).Build(); + builder.UseConfiguration(config); + builder.ConfigureTestServices(services => + { + services.ConfigureHttpJsonOptions(options => + { + options.SerializerOptions.Converters.Add(new JsonStringEnumConverter()); + }); + services.Configure(options => + { + options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + }); + + var descriptor = services.SingleOrDefault(d => d.ServiceType == typeof(DbContextOptions)); + if (descriptor != null) + services.Remove(descriptor); + + services.AddDbContext(options => + { + options.UseNpgsql(_container.GetConnectionString(), + x => x.MigrationsAssembly(typeof(Initial).Assembly.GetName().Name) + .MigrationsHistoryTable("__efmigrations_history_dim")); + }); + services.AddSingleton(); + }); + } + + /// + protected override IHost CreateHost(IHostBuilder builder) + { + builder.AddLogging(); + var host = base.CreateHost(builder); + + var optionsBuilder = new DbContextOptionsBuilder(); + + optionsBuilder.UseNpgsql( + _container.GetConnectionString(), + x => x.MigrationsAssembly(typeof(Initial).Assembly.GetName().Name) + .MigrationsHistoryTable("__efmigrations_history_dim", "public") + ); + var context = new DimDbContext(optionsBuilder.Options); + context.Database.Migrate(); + + return host; + } + + public async Task InitializeAsync() => await _container.StartAsync(); + + public new async Task DisposeAsync() => await _container.DisposeAsync(); +} diff --git a/tests/web/Dim.Web.Tests/appsettings.IntegrationTests.json b/tests/web/Dim.Web.Tests/appsettings.IntegrationTests.json new file mode 100644 index 0000000..3abb5a2 --- /dev/null +++ b/tests/web/Dim.Web.Tests/appsettings.IntegrationTests.json @@ -0,0 +1,38 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*", + "ConnectionStrings": { + "DimDb": "Server=placeholder;Database=placeholder;Port=5432;User Id=placeholder;Password=placeholder;Ssl Mode=Disable;" + }, + "Dim": { + "OperatorId": "8077d6b6-b765-4302-95b3-506e67a1dfb0", + "EncryptionConfigs": [ + { + "Index": 0, + "EncryptionKey": "", + "CipherMode": "", + "PaddingMode": "" + } + ] + }, + "SwaggerEnabled": true, + "JwtBearerOptions": { + "RequireHttpsMetadata": true, + "MetadataAddress": "", + "SaveToken": true, + "TokenValidationParameters": { + "ValidateIssuer": true, + "ValidIssuer": "", + "ValidateIssuerSigningKey": true, + "ValidAudience": "", + "ValidateAudience": true, + "ValidateLifetime": true, + "ClockSkew": 600000 + } + } +} \ No newline at end of file