diff --git a/.github/workflows/iam-seeding.yml b/.github/workflows/iam-seeding.yml new file mode 100644 index 0000000000..4659e343bc --- /dev/null +++ b/.github/workflows/iam-seeding.yml @@ -0,0 +1,85 @@ +############################################################### +# Copyright (c) 2021, 2023 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 +############################################################### + +name: IAM Seeding + +on: + push: + paths: + # service and transitive paths + - 'src/framework/**' + - 'src/keycloak/**' + # workflow file + - '.github/workflows/iam-seeding.yml' + # dockerfile + - 'docker/Dockerfile-iam-seeding' + + branches: + - 'dev' + workflow_dispatch: + +env: + IMAGE_NAMESPACE: "tractusx" + IMAGE_NAME: "portal-iam-seeding" + +jobs: + build-and-push-image: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Login to DockerHub + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME }} + tags: | + type=raw,value=dev + type=raw,value=${{ github.sha }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: docker/Dockerfile-iam-seeding + pull: true + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + # https://github.com/peter-evans/dockerhub-description + - name: Update Docker Hub description + if: github.event_name != 'pull_request' + uses: peter-evans/dockerhub-description@v3 + with: + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + repository: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME }} + readme-filepath: "./docker/notice-iam-seeding.md" diff --git a/.github/workflows/release_iam-seeding.yml b/.github/workflows/release_iam-seeding.yml new file mode 100644 index 0000000000..12b2b990e6 --- /dev/null +++ b/.github/workflows/release_iam-seeding.yml @@ -0,0 +1,76 @@ +############################################################### +# Copyright (c) 2021, 2023 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 +############################################################### + +name: Release IAM Seeding + +on: + push: + tags: + - 'iam-v*.*.*' + workflow_dispatch: + +env: + IMAGE_NAMESPACE: "tractusx" + IMAGE_NAME: "portal-iam-seeding" + +jobs: + iam-seeding-release: + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Login to DockerHub + if: github.event_name != 'pull_request' + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + + - name: Docker meta + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME}} + tags: | + type=raw,value=latest + type=raw,value=${{ github.ref_name }} + + - name: Build and push Docker image + uses: docker/build-push-action@v4 + with: + context: . + file: docker/Dockerfile-iam-seeding + pull: true + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + # https://github.com/peter-evans/dockerhub-description + - name: Update Docker Hub description + if: github.event_name != 'pull_request' + uses: peter-evans/dockerhub-description@v3 + with: + username: ${{ secrets.DOCKER_HUB_USER }} + password: ${{ secrets.DOCKER_HUB_TOKEN }} + repository: ${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME }} + readme-filepath: "./docker/notice-iam-seeding.md" diff --git a/.github/workflows/trivy-dev.yml b/.github/workflows/trivy-dev.yml index 521ee1b415..2eb4b3cfe6 100644 --- a/.github/workflows/trivy-dev.yml +++ b/.github/workflows/trivy-dev.yml @@ -368,3 +368,36 @@ jobs: uses: github/codeql-action/upload-sarif@v2 with: sarif_file: "trivy-results11.sarif" + + analyze-portal-iam_seeding: + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # It's also possible to scan your private registry with Trivy's built-in image scan. + # All you have to do is set ENV vars. + # Docker Hub needs TRIVY_USERNAME and TRIVY_PASSWORD. + # You don't need to set ENV vars when downloading from a public repository. + # For public images, no ENV vars must be set. + - name: Run Trivy vulnerability scanner + if: always() + uses: aquasecurity/trivy-action@master + with: + # Path to Docker image + image-ref: "${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME }}-iam-seeding:dev" + format: "sarif" + output: "trivy-results12.sarif" + exit-code: "1" + severity: "CRITICAL,HIGH" + + - name: Upload Trivy scan results to GitHub Security tab + if: always() + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: "trivy-results12.sarif" diff --git a/.github/workflows/trivy.yml b/.github/workflows/trivy.yml index 48c1dd18e7..ae4abb2415 100644 --- a/.github/workflows/trivy.yml +++ b/.github/workflows/trivy.yml @@ -368,3 +368,36 @@ jobs: uses: github/codeql-action/upload-sarif@v2 with: sarif_file: "trivy-results11.sarif" + + analyze-portal-iam_seeding: + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # It's also possible to scan your private registry with Trivy's built-in image scan. + # All you have to do is set ENV vars. + # Docker Hub needs TRIVY_USERNAME and TRIVY_PASSWORD. + # You don't need to set ENV vars when downloading from a public repository. + # For public images, no ENV vars must be set. + - name: Run Trivy vulnerability scanner + if: always() + uses: aquasecurity/trivy-action@master + with: + # Path to Docker image + image-ref: "${{ env.IMAGE_NAMESPACE }}/${{ env.IMAGE_NAME }}-iam-seeding:latest" + format: "sarif" + output: "trivy-results12.sarif" + exit-code: "1" + severity: "CRITICAL,HIGH" + + - name: Upload Trivy scan results to GitHub Security tab + if: always() + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: "trivy-results12.sarif" diff --git a/.github/workflows/veracode.yaml b/.github/workflows/veracode.yaml index 1012f62bfb..571af10a37 100644 --- a/.github/workflows/veracode.yaml +++ b/.github/workflows/veracode.yaml @@ -435,3 +435,46 @@ jobs: vid: "${{ secrets.ORG_VERACODE_API_ID }}" vkey: "${{ secrets.ORG_VERACODE_API_KEY }}" include: 'Org.Eclipse.TractusX.Portal.Backend.Processes.Worker.dll' + + analyze-iam-seeding: + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: ${{ env.DOTNET_VERSION }} + + - name: Install dependencies + run: dotnet restore src/keycloak/Keycloak.Seeding + + - name: Build and publish + run: | + cd src/keycloak/Keycloak.Seeding + dotnet build --no-restore + dotnet publish -c Debug -p:PublishDir=.\publish + + - name: "Bundle files to scan" + run: > + zip -r portal-iam-seeding.zip + src/keycloak/Keycloak.Seeding/.publish + + - name: Run Veracode Upload And Scan + uses: veracode/veracode-uploadandscan-action@0.2.1 + with: + # Specify Veracode application name + appname: "Portal-IAM_Seeding" + createprofile: true + teams: 'portal' + # Specify path to upload + filepath: "portal-iam-seeding.zip" + vid: "${{ secrets.ORG_VERACODE_API_ID }}" + vkey: "${{ secrets.ORG_VERACODE_API_KEY }}" + include: 'Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.dll' diff --git a/.tractusx b/.tractusx index 516454ab19..b52629dca6 100644 --- a/.tractusx +++ b/.tractusx @@ -1 +1,20 @@ +############################################################### +# Copyright (c) 2021, 2023 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 +############################################################### + leadingRepository: "https://github.com/eclipse-tractusx/portal-cd" diff --git a/CHANGELOG.md b/CHANGELOG.md index 20dc91a695..7a3832c3cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,45 @@ New features, fixed bugs, known defects and other noteworthy changes to each release of the Catena-X Portal Backend. +## 1.6.0-RC7 + +### Change +* All Services + * add an /api/info endpoint to retrieve specific api endpoints which can be used publicly for external services +* Keycloak Seeding + * add seeding for keycloak realm-data from a json-file +* Marketplace Service + * Removed PUT: /api/apps/appreleaseprocess/updateapp/{appId} +* Process Worker: + * add error handling for BPDM Pull Process Steps + +### Technical Support +* Logging + * removed machine name, processId, threadId from the logging message +* TRG + * changed license notice for images + * add second license + * add file header to .tractusx + +### Bugfix +* Administration Service + * fixed Get: api/administration/companydata/certificates when multiple certificates are in the database for a specific company + * add check for active offerSubscriptions when deleting a connector + * fixed api/administration/serviceaccount/owncompany/serviceaccounts +* Company Service Accounts + * set identityTypeId when creating service accounts to company service account instead of company user + * change client_id of service accounts in seeding data + * add service accounts from cx-central base + * remove service account for daps +* Marketplace Service + * fixed validation for /api/apps/AppReleaseProcess/instance-type/{appId} to only be executable for apps in state CREATED +* Mail Templates + * fixed bpn display in the welcome email +* Registration Service + * fixed Get: /api/registration/legalEntityAddress/{bpn} +* Seeding + * TestDataEnvironments set to optional + ## 1.6.0-RC6 ### Change diff --git a/LICENSES/CC-BY-4.0.txt b/LICENSES/CC-BY-4.0.txt new file mode 100644 index 0000000000..4ea99c213c --- /dev/null +++ b/LICENSES/CC-BY-4.0.txt @@ -0,0 +1,395 @@ +Attribution 4.0 International + +======================================================================= + +Creative Commons Corporation ("Creative Commons") is not a law firm and +does not provide legal services or legal advice. Distribution of +Creative Commons public licenses does not create a lawyer-client or +other relationship. Creative Commons makes its licenses and related +information available on an "as-is" basis. Creative Commons gives no +warranties regarding its licenses, any material licensed under their +terms and conditions, or any related information. Creative Commons +disclaims all liability for damages resulting from their use to the +fullest extent possible. + +Using Creative Commons Public Licenses + +Creative Commons public licenses provide a standard set of terms and +conditions that creators and other rights holders may use to share +original works of authorship and other material subject to copyright +and certain other rights specified in the public license below. The +following considerations are for informational purposes only, are not +exhaustive, and do not form part of our licenses. + + Considerations for licensors: Our public licenses are + intended for use by those authorized to give the public + permission to use material in ways otherwise restricted by + copyright and certain other rights. Our licenses are + irrevocable. Licensors should read and understand the terms + and conditions of the license they choose before applying it. + Licensors should also secure all rights necessary before + applying our licenses so that the public can reuse the + material as expected. Licensors should clearly mark any + material not subject to the license. This includes other CC- + licensed material, or material used under an exception or + limitation to copyright. More considerations for licensors: + wiki.creativecommons.org/Considerations_for_licensors + + Considerations for the public: By using one of our public + licenses, a licensor grants the public permission to use the + licensed material under specified terms and conditions. If + the licensor's permission is not necessary for any reason--for + example, because of any applicable exception or limitation to + copyright--then that use is not regulated by the license. Our + licenses grant only permissions under copyright and certain + other rights that a licensor has authority to grant. Use of + the licensed material may still be restricted for other + reasons, including because others have copyright or other + rights in the material. A licensor may make special requests, + such as asking that all changes be marked or described. + Although not required by our licenses, you are encouraged to + respect those requests where reasonable. More considerations + for the public: + wiki.creativecommons.org/Considerations_for_licensees + +======================================================================= + +Creative Commons Attribution 4.0 International Public License + +By exercising the Licensed Rights (defined below), You accept and agree +to be bound by the terms and conditions of this Creative Commons +Attribution 4.0 International Public License ("Public License"). To the +extent this Public License may be interpreted as a contract, You are +granted the Licensed Rights in consideration of Your acceptance of +these terms and conditions, and the Licensor grants You such rights in +consideration of benefits the Licensor receives from making the +Licensed Material available under these terms and conditions. + + +Section 1 -- Definitions. + + a. Adapted Material means material subject to Copyright and Similar + Rights that is derived from or based upon the Licensed Material + and in which the Licensed Material is translated, altered, + arranged, transformed, or otherwise modified in a manner requiring + permission under the Copyright and Similar Rights held by the + Licensor. For purposes of this Public License, where the Licensed + Material is a musical work, performance, or sound recording, + Adapted Material is always produced where the Licensed Material is + synched in timed relation with a moving image. + + b. Adapter's License means the license You apply to Your Copyright + and Similar Rights in Your contributions to Adapted Material in + accordance with the terms and conditions of this Public License. + + c. Copyright and Similar Rights means copyright and/or similar rights + closely related to copyright including, without limitation, + performance, broadcast, sound recording, and Sui Generis Database + Rights, without regard to how the rights are labeled or + categorized. For purposes of this Public License, the rights + specified in Section 2(b)(1)-(2) are not Copyright and Similar + Rights. + + d. Effective Technological Measures means those measures that, in the + absence of proper authority, may not be circumvented under laws + fulfilling obligations under Article 11 of the WIPO Copyright + Treaty adopted on December 20, 1996, and/or similar international + agreements. + + e. Exceptions and Limitations means fair use, fair dealing, and/or + any other exception or limitation to Copyright and Similar Rights + that applies to Your use of the Licensed Material. + + f. Licensed Material means the artistic or literary work, database, + or other material to which the Licensor applied this Public + License. + + g. Licensed Rights means the rights granted to You subject to the + terms and conditions of this Public License, which are limited to + all Copyright and Similar Rights that apply to Your use of the + Licensed Material and that the Licensor has authority to license. + + h. Licensor means the individual(s) or entity(ies) granting rights + under this Public License. + + i. Share means to provide material to the public by any means or + process that requires permission under the Licensed Rights, such + as reproduction, public display, public performance, distribution, + dissemination, communication, or importation, and to make material + available to the public including in ways that members of the + public may access the material from a place and at a time + individually chosen by them. + + j. Sui Generis Database Rights means rights other than copyright + resulting from Directive 96/9/EC of the European Parliament and of + the Council of 11 March 1996 on the legal protection of databases, + as amended and/or succeeded, as well as other essentially + equivalent rights anywhere in the world. + + k. You means the individual or entity exercising the Licensed Rights + under this Public License. Your has a corresponding meaning. + + +Section 2 -- Scope. + + a. License grant. + + 1. Subject to the terms and conditions of this Public License, + the Licensor hereby grants You a worldwide, royalty-free, + non-sublicensable, non-exclusive, irrevocable license to + exercise the Licensed Rights in the Licensed Material to: + + a. reproduce and Share the Licensed Material, in whole or + in part; and + + b. produce, reproduce, and Share Adapted Material. + + 2. Exceptions and Limitations. For the avoidance of doubt, where + Exceptions and Limitations apply to Your use, this Public + License does not apply, and You do not need to comply with + its terms and conditions. + + 3. Term. The term of this Public License is specified in Section + 6(a). + + 4. Media and formats; technical modifications allowed. The + Licensor authorizes You to exercise the Licensed Rights in + all media and formats whether now known or hereafter created, + and to make technical modifications necessary to do so. The + Licensor waives and/or agrees not to assert any right or + authority to forbid You from making technical modifications + necessary to exercise the Licensed Rights, including + technical modifications necessary to circumvent Effective + Technological Measures. For purposes of this Public License, + simply making modifications authorized by this Section 2(a) + (4) never produces Adapted Material. + + 5. Downstream recipients. + + a. Offer from the Licensor -- Licensed Material. Every + recipient of the Licensed Material automatically + receives an offer from the Licensor to exercise the + Licensed Rights under the terms and conditions of this + Public License. + + b. No downstream restrictions. You may not offer or impose + any additional or different terms or conditions on, or + apply any Effective Technological Measures to, the + Licensed Material if doing so restricts exercise of the + Licensed Rights by any recipient of the Licensed + Material. + + 6. No endorsement. Nothing in this Public License constitutes or + may be construed as permission to assert or imply that You + are, or that Your use of the Licensed Material is, connected + with, or sponsored, endorsed, or granted official status by, + the Licensor or others designated to receive attribution as + provided in Section 3(a)(1)(A)(i). + + b. Other rights. + + 1. Moral rights, such as the right of integrity, are not + licensed under this Public License, nor are publicity, + privacy, and/or other similar personality rights; however, to + the extent possible, the Licensor waives and/or agrees not to + assert any such rights held by the Licensor to the limited + extent necessary to allow You to exercise the Licensed + Rights, but not otherwise. + + 2. Patent and trademark rights are not licensed under this + Public License. + + 3. To the extent possible, the Licensor waives any right to + collect royalties from You for the exercise of the Licensed + Rights, whether directly or through a collecting society + under any voluntary or waivable statutory or compulsory + licensing scheme. In all other cases the Licensor expressly + reserves any right to collect such royalties. + + +Section 3 -- License Conditions. + +Your exercise of the Licensed Rights is expressly made subject to the +following conditions. + + a. Attribution. + + 1. If You Share the Licensed Material (including in modified + form), You must: + + a. retain the following if it is supplied by the Licensor + with the Licensed Material: + + i. identification of the creator(s) of the Licensed + Material and any others designated to receive + attribution, in any reasonable manner requested by + the Licensor (including by pseudonym if + designated); + + ii. a copyright notice; + + iii. a notice that refers to this Public License; + + iv. a notice that refers to the disclaimer of + warranties; + + v. a URI or hyperlink to the Licensed Material to the + extent reasonably practicable; + + b. indicate if You modified the Licensed Material and + retain an indication of any previous modifications; and + + c. indicate the Licensed Material is licensed under this + Public License, and include the text of, or the URI or + hyperlink to, this Public License. + + 2. You may satisfy the conditions in Section 3(a)(1) in any + reasonable manner based on the medium, means, and context in + which You Share the Licensed Material. For example, it may be + reasonable to satisfy the conditions by providing a URI or + hyperlink to a resource that includes the required + information. + + 3. If requested by the Licensor, You must remove any of the + information required by Section 3(a)(1)(A) to the extent + reasonably practicable. + + 4. If You Share Adapted Material You produce, the Adapter's + License You apply must not prevent recipients of the Adapted + Material from complying with this Public License. + + +Section 4 -- Sui Generis Database Rights. + +Where the Licensed Rights include Sui Generis Database Rights that +apply to Your use of the Licensed Material: + + a. for the avoidance of doubt, Section 2(a)(1) grants You the right + to extract, reuse, reproduce, and Share all or a substantial + portion of the contents of the database; + + b. if You include all or a substantial portion of the database + contents in a database in which You have Sui Generis Database + Rights, then the database in which You have Sui Generis Database + Rights (but not its individual contents) is Adapted Material; and + + c. You must comply with the conditions in Section 3(a) if You Share + all or a substantial portion of the contents of the database. + +For the avoidance of doubt, this Section 4 supplements and does not +replace Your obligations under this Public License where the Licensed +Rights include other Copyright and Similar Rights. + + +Section 5 -- Disclaimer of Warranties and Limitation of Liability. + + a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE + EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS + AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF + ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, + IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, + WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, + ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT + KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT + ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. + + b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE + TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, + NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, + INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, + COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR + USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN + ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR + DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR + IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. + + c. The disclaimer of warranties and limitation of liability provided + above shall be interpreted in a manner that, to the extent + possible, most closely approximates an absolute disclaimer and + waiver of all liability. + + +Section 6 -- Term and Termination. + + a. This Public License applies for the term of the Copyright and + Similar Rights licensed here. However, if You fail to comply with + this Public License, then Your rights under this Public License + terminate automatically. + + b. Where Your right to use the Licensed Material has terminated under + Section 6(a), it reinstates: + + 1. automatically as of the date the violation is cured, provided + it is cured within 30 days of Your discovery of the + violation; or + + 2. upon express reinstatement by the Licensor. + + For the avoidance of doubt, this Section 6(b) does not affect any + right the Licensor may have to seek remedies for Your violations + of this Public License. + + c. For the avoidance of doubt, the Licensor may also offer the + Licensed Material under separate terms or conditions or stop + distributing the Licensed Material at any time; however, doing so + will not terminate this Public License. + + d. Sections 1, 5, 6, 7, and 8 survive termination of this Public + License. + + +Section 7 -- Other Terms and Conditions. + + a. The Licensor shall not be bound by any additional or different + terms or conditions communicated by You unless expressly agreed. + + b. Any arrangements, understandings, or agreements regarding the + Licensed Material not stated herein are separate from and + independent of the terms and conditions of this Public License. + + +Section 8 -- Interpretation. + + a. For the avoidance of doubt, this Public License does not, and + shall not be interpreted to, reduce, limit, restrict, or impose + conditions on any use of the Licensed Material that could lawfully + be made without permission under this Public License. + + b. To the extent possible, if any provision of this Public License is + deemed unenforceable, it shall be automatically reformed to the + minimum extent necessary to make it enforceable. If the provision + cannot be reformed, it shall be severed from this Public License + without affecting the enforceability of the remaining terms and + conditions. + + c. No term or condition of this Public License will be waived and no + failure to comply consented to unless expressly agreed to by the + Licensor. + + d. Nothing in this Public License constitutes or may be interpreted + as a limitation upon, or waiver of, any privileges and immunities + that apply to the Licensor or You, including from the legal + processes of any jurisdiction or authority. + + +======================================================================= + +Creative Commons is not a party to its public +licenses. Notwithstanding, Creative Commons may elect to apply one of +its public licenses to material it publishes and in those instances +will be considered the “Licensor.” The text of the Creative Commons +public licenses is dedicated to the public domain under the CC0 Public +Domain Dedication. Except for the limited purpose of indicating that +material is shared under a Creative Commons public license or as +otherwise permitted by the Creative Commons policies published at +creativecommons.org/policies, Creative Commons does not authorize the +use of the trademark "Creative Commons" or any other trademark or logo +of Creative Commons without its prior written consent including, +without limitation, in connection with any unauthorized modifications +to any of its public licenses or any other arrangements, +understandings, or agreements concerning use of licensed material. For +the avoidance of doubt, this paragraph does not form part of the +public licenses. + +Creative Commons may be contacted at creativecommons.org. diff --git a/docker/Dockerfile-iam-seeding b/docker/Dockerfile-iam-seeding new file mode 100644 index 0000000000..a22f369aca --- /dev/null +++ b/docker/Dockerfile-iam-seeding @@ -0,0 +1,45 @@ +############################################################### +# Copyright (c) 2021, 2023 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 +############################################################### + +FROM mcr.microsoft.com/dotnet/aspnet:6.0-alpine AS base + +FROM mcr.microsoft.com/dotnet/sdk:6.0-alpine AS build +WORKDIR / +COPY LICENSE NOTICE.md DEPENDENCIES / +COPY /src/framework/Framework.Async /src/framework/Framework.Async +COPY /src/framework/Framework.ErrorHandling.Library /src/framework/Framework.ErrorHandling.Library +COPY /src/framework/Framework.Linq /src/framework/Framework.Linq +COPY /src/framework/Framework.Logging /src/framework/Framework.Logging +COPY /src/framework/Framework.Models /src/framework/Framework.Models +COPY /src/keycloak/Keycloak.ErrorHandling /src/keycloak/Keycloak.ErrorHandling +COPY /src/keycloak/Keycloak.Factory /src/keycloak/Keycloak.Factory +COPY /src/keycloak/Keycloak.Library /src/keycloak/Keycloak.Library +COPY /src/keycloak/Keycloak.Seeding /src/keycloak/Keycloak.Seeding +WORKDIR /src/keycloak/Keycloak.Seeding +RUN dotnet build "Keycloak.Seeding.csproj" -c Release -o /app/build + +FROM build AS publish +RUN dotnet publish "Keycloak.Seeding.csproj" -c Release -o /app/publish + +FROM base AS final +WORKDIR /app +COPY --from=publish /app/publish . +RUN chown -R 1000:3000 /app +USER 1000:3000 +ENTRYPOINT ["dotnet", "Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.dll"] diff --git a/docker/notice-iam-seeding.md b/docker/notice-iam-seeding.md new file mode 100644 index 0000000000..cf19ebf822 --- /dev/null +++ b/docker/notice-iam-seeding.md @@ -0,0 +1,22 @@ +## Notice for Docker image + +DockerHub: [https://hub.docker.com/r/tractusx/portal-iam-seeding](https://hub.docker.com/r/tractusx/portal-iam-seeding) + +Eclipse Tractus-X product(s) installed within the image: + +__Portal Checklist Worker__ + +- GitHub: https://github.com/eclipse-tractusx/portal-backend +- Project home: https://projects.eclipse.org/projects/automotive.tractusx +- Dockerfile: https://github.com/eclipse-tractusx/portal-backend/blob/main/docker/Dockerfile-iam-seeding +- Project license: [Apache License, Version 2.0](https://github.com/eclipse-tractusx/portal-backend/blob/main/LICENSE) + +__Used base images__ + +- Dockerfile: [mcr.microsoft.com/dotnet/runtime:6.0-alpine](https://github.com/dotnet/dotnet-docker/blob/main/src/runtime/6.0/alpine3.17/amd64/Dockerfile) +- GitHub project: [https://github.com/dotnet/dotnet-docker](https://github.com/dotnet/dotnet-docker) +- DockerHub: [https://hub.docker.com/_/microsoft-dotnet-runtime](https://hub.docker.com/_/microsoft-dotnet-runtime) + +As with all Docker images, these likely also contain other software which may be under other licenses (such as Bash, etc from the base distribution, along with any direct or indirect dependencies of the primary software being contained). + +As for any pre-built image usage, it is the image user's responsibility to ensure that any use of this image complies with any relevant licenses for all software contained within. diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 2efc185599..d0671e5510 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -21,6 +21,6 @@ 1.6.0 - RC6 + RC7 diff --git a/src/Portal.Backend.sln b/src/Portal.Backend.sln index 3e625d207a..1f7093b921 100644 --- a/src/Portal.Backend.sln +++ b/src/Portal.Backend.sln @@ -206,6 +206,14 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Framework.Models.Tests", ". EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Framework.ProcessIdentity", "framework\Framework.ProcessIdentity\Framework.ProcessIdentity.csproj", "{4CA307AB-A0F8-4AA5-A09D-91F47DA3054A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Framework.PublicInfos", "framework\Framework.PublicInfos\Framework.PublicInfos.csproj", "{47E089E3-E875-4045-9E58-C1223BE899E9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Framework.PublicInfos.Tests", "..\tests\framework\Framework.PublicInfos.Tests\Framework.PublicInfos.Tests.csproj", "{9D574E57-75A6-4965-AF23-ACE0BB9CD0B3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Keycloak.Seeding", "keycloak\Keycloak.Seeding\Keycloak.Seeding.csproj", "{E1D41A07-F468-4D13-8185-35F127230B17}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Keycloak.Seeding.Tests", "..\tests\keycloak\Keycloak.Seeding.Tests\Keycloak.Seeding.Tests.csproj", "{A5BEDD89-7280-466E-8D14-EC5E177AAD07}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1284,6 +1292,54 @@ Global {4CA307AB-A0F8-4AA5-A09D-91F47DA3054A}.Release|x64.Build.0 = Release|Any CPU {4CA307AB-A0F8-4AA5-A09D-91F47DA3054A}.Release|x86.ActiveCfg = Release|Any CPU {4CA307AB-A0F8-4AA5-A09D-91F47DA3054A}.Release|x86.Build.0 = Release|Any CPU + {47E089E3-E875-4045-9E58-C1223BE899E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47E089E3-E875-4045-9E58-C1223BE899E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47E089E3-E875-4045-9E58-C1223BE899E9}.Debug|x64.ActiveCfg = Debug|Any CPU + {47E089E3-E875-4045-9E58-C1223BE899E9}.Debug|x64.Build.0 = Debug|Any CPU + {47E089E3-E875-4045-9E58-C1223BE899E9}.Debug|x86.ActiveCfg = Debug|Any CPU + {47E089E3-E875-4045-9E58-C1223BE899E9}.Debug|x86.Build.0 = Debug|Any CPU + {47E089E3-E875-4045-9E58-C1223BE899E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47E089E3-E875-4045-9E58-C1223BE899E9}.Release|Any CPU.Build.0 = Release|Any CPU + {47E089E3-E875-4045-9E58-C1223BE899E9}.Release|x64.ActiveCfg = Release|Any CPU + {47E089E3-E875-4045-9E58-C1223BE899E9}.Release|x64.Build.0 = Release|Any CPU + {47E089E3-E875-4045-9E58-C1223BE899E9}.Release|x86.ActiveCfg = Release|Any CPU + {47E089E3-E875-4045-9E58-C1223BE899E9}.Release|x86.Build.0 = Release|Any CPU + {9D574E57-75A6-4965-AF23-ACE0BB9CD0B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D574E57-75A6-4965-AF23-ACE0BB9CD0B3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D574E57-75A6-4965-AF23-ACE0BB9CD0B3}.Debug|x64.ActiveCfg = Debug|Any CPU + {9D574E57-75A6-4965-AF23-ACE0BB9CD0B3}.Debug|x64.Build.0 = Debug|Any CPU + {9D574E57-75A6-4965-AF23-ACE0BB9CD0B3}.Debug|x86.ActiveCfg = Debug|Any CPU + {9D574E57-75A6-4965-AF23-ACE0BB9CD0B3}.Debug|x86.Build.0 = Debug|Any CPU + {9D574E57-75A6-4965-AF23-ACE0BB9CD0B3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D574E57-75A6-4965-AF23-ACE0BB9CD0B3}.Release|Any CPU.Build.0 = Release|Any CPU + {9D574E57-75A6-4965-AF23-ACE0BB9CD0B3}.Release|x64.ActiveCfg = Release|Any CPU + {9D574E57-75A6-4965-AF23-ACE0BB9CD0B3}.Release|x64.Build.0 = Release|Any CPU + {9D574E57-75A6-4965-AF23-ACE0BB9CD0B3}.Release|x86.ActiveCfg = Release|Any CPU + {9D574E57-75A6-4965-AF23-ACE0BB9CD0B3}.Release|x86.Build.0 = Release|Any CPU + {E1D41A07-F468-4D13-8185-35F127230B17}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1D41A07-F468-4D13-8185-35F127230B17}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1D41A07-F468-4D13-8185-35F127230B17}.Debug|x64.ActiveCfg = Debug|Any CPU + {E1D41A07-F468-4D13-8185-35F127230B17}.Debug|x64.Build.0 = Debug|Any CPU + {E1D41A07-F468-4D13-8185-35F127230B17}.Debug|x86.ActiveCfg = Debug|Any CPU + {E1D41A07-F468-4D13-8185-35F127230B17}.Debug|x86.Build.0 = Debug|Any CPU + {E1D41A07-F468-4D13-8185-35F127230B17}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1D41A07-F468-4D13-8185-35F127230B17}.Release|Any CPU.Build.0 = Release|Any CPU + {E1D41A07-F468-4D13-8185-35F127230B17}.Release|x64.ActiveCfg = Release|Any CPU + {E1D41A07-F468-4D13-8185-35F127230B17}.Release|x64.Build.0 = Release|Any CPU + {E1D41A07-F468-4D13-8185-35F127230B17}.Release|x86.ActiveCfg = Release|Any CPU + {E1D41A07-F468-4D13-8185-35F127230B17}.Release|x86.Build.0 = Release|Any CPU + {A5BEDD89-7280-466E-8D14-EC5E177AAD07}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5BEDD89-7280-466E-8D14-EC5E177AAD07}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A5BEDD89-7280-466E-8D14-EC5E177AAD07}.Debug|x64.ActiveCfg = Debug|Any CPU + {A5BEDD89-7280-466E-8D14-EC5E177AAD07}.Debug|x64.Build.0 = Debug|Any CPU + {A5BEDD89-7280-466E-8D14-EC5E177AAD07}.Debug|x86.ActiveCfg = Debug|Any CPU + {A5BEDD89-7280-466E-8D14-EC5E177AAD07}.Debug|x86.Build.0 = Debug|Any CPU + {A5BEDD89-7280-466E-8D14-EC5E177AAD07}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A5BEDD89-7280-466E-8D14-EC5E177AAD07}.Release|Any CPU.Build.0 = Release|Any CPU + {A5BEDD89-7280-466E-8D14-EC5E177AAD07}.Release|x64.ActiveCfg = Release|Any CPU + {A5BEDD89-7280-466E-8D14-EC5E177AAD07}.Release|x64.Build.0 = Release|Any CPU + {A5BEDD89-7280-466E-8D14-EC5E177AAD07}.Release|x86.ActiveCfg = Release|Any CPU + {A5BEDD89-7280-466E-8D14-EC5E177AAD07}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1292,6 +1348,7 @@ Global SolutionGuid = {2EB6265F-323A-4BF3-969E-003D64A14B64} EndGlobalSection GlobalSection(NestedProjects) = preSolution + {A5BEDD89-7280-466E-8D14-EC5E177AAD07} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {EA9BA26E-83F6-47C4-BA3B-880AF1AD6A82} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {4B40193E-2C67-4DC4-8EF4-3286DAA01D8B} = {323C198D-A8C6-4EB0-8B79-72624275E35F} {5E80DEEA-B254-425C-8220-27EEF47C10BD} = {323C198D-A8C6-4EB0-8B79-72624275E35F} @@ -1381,5 +1438,8 @@ Global {031237BF-7B2A-4B37-9E37-4D4C575FDD22} = {23500169-FC01-4D2B-A997-E7FAE2169FC0} {CB9FFDD9-7F41-44DA-BFA8-C157A96D51F6} = {23500169-FC01-4D2B-A997-E7FAE2169FC0} {4CA307AB-A0F8-4AA5-A09D-91F47DA3054A} = {23500169-FC01-4D2B-A997-E7FAE2169FC0} + {47E089E3-E875-4045-9E58-C1223BE899E9} = {23500169-FC01-4D2B-A997-E7FAE2169FC0} + {9D574E57-75A6-4965-AF23-ACE0BB9CD0B3} = {323C198D-A8C6-4EB0-8B79-72624275E35F} + {E1D41A07-F468-4D13-8185-35F127230B17} = {46383371-8252-4598-9350-A97692851408} EndGlobalSection EndGlobal diff --git a/src/administration/Administration.Service/BusinessLogic/CompanyDataBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/CompanyDataBusinessLogic.cs index 4e1d8ad0db..c99bc613b8 100644 --- a/src/administration/Administration.Service/BusinessLogic/CompanyDataBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/CompanyDataBusinessLogic.cs @@ -34,6 +34,7 @@ using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Identities; using System.Globalization; using System.Text.Json; @@ -47,6 +48,7 @@ public class CompanyDataBusinessLogic : ICompanyDataBusinessLogic private readonly IMailingService _mailingService; private readonly ICustodianService _custodianService; private readonly IDateTimeProvider _dateTimeProvider; + private readonly IIdentityService _identityService; private readonly CompanyDataSettings _settings; /// @@ -56,19 +58,22 @@ public class CompanyDataBusinessLogic : ICompanyDataBusinessLogic /// /// /// + /// /// - public CompanyDataBusinessLogic(IPortalRepositories portalRepositories, IMailingService mailingService, ICustodianService custodianService, IDateTimeProvider dateTimeProvider, IOptions options) + public CompanyDataBusinessLogic(IPortalRepositories portalRepositories, IMailingService mailingService, ICustodianService custodianService, IDateTimeProvider dateTimeProvider, IIdentityService identityService, IOptions options) { _portalRepositories = portalRepositories; _mailingService = mailingService; _custodianService = custodianService; _dateTimeProvider = dateTimeProvider; + _identityService = identityService; _settings = options.Value; } /// - public async Task GetCompanyDetailsAsync(Guid companyId) + public async Task GetCompanyDetailsAsync() { + var companyId = _identityService.IdentityData.CompanyId; var result = await _portalRepositories.GetInstance().GetCompanyDetailsAsync(companyId).ConfigureAwait(false); if (result == null) { @@ -78,12 +83,13 @@ public async Task GetCompanyDetailsAsync(Guid companyI } /// - public IAsyncEnumerable GetCompanyAssigendUseCaseDetailsAsync(Guid companyId) => - _portalRepositories.GetInstance().GetCompanyAssigendUseCaseDetailsAsync(companyId); + public IAsyncEnumerable GetCompanyAssigendUseCaseDetailsAsync() => + _portalRepositories.GetInstance().GetCompanyAssigendUseCaseDetailsAsync(_identityService.IdentityData.CompanyId); /// - public async Task CreateCompanyAssignedUseCaseDetailsAsync(Guid companyId, Guid useCaseId) + public async Task CreateCompanyAssignedUseCaseDetailsAsync(Guid useCaseId) { + var companyId = _identityService.IdentityData.CompanyId; var companyRepositories = _portalRepositories.GetInstance(); var useCaseDetails = await companyRepositories.GetCompanyStatusAndUseCaseIdAsync(companyId, useCaseId).ConfigureAwait(false); if (!useCaseDetails.IsActiveCompanyStatus) @@ -100,8 +106,9 @@ public async Task CreateCompanyAssignedUseCaseDetailsAsync(Guid companyId, } /// - public async Task RemoveCompanyAssignedUseCaseDetailsAsync(Guid companyId, Guid useCaseId) + public async Task RemoveCompanyAssignedUseCaseDetailsAsync(Guid useCaseId) { + var companyId = _identityService.IdentityData.CompanyId; var companyRepositories = _portalRepositories.GetInstance(); var useCaseDetails = await companyRepositories.GetCompanyStatusAndUseCaseIdAsync(companyId, useCaseId).ConfigureAwait(false); if (!useCaseDetails.IsActiveCompanyStatus) @@ -116,8 +123,9 @@ public async Task RemoveCompanyAssignedUseCaseDetailsAsync(Guid companyId, Guid await _portalRepositories.SaveAsync().ConfigureAwait(false); } - public async IAsyncEnumerable GetCompanyRoleAndConsentAgreementDetailsAsync(Guid companyId, string? languageShortName) + public async IAsyncEnumerable GetCompanyRoleAndConsentAgreementDetailsAsync(string? languageShortName) { + var companyId = _identityService.IdentityData.CompanyId; if (languageShortName != null && !await _portalRepositories.GetInstance().IsValidLanguageCode(languageShortName).ConfigureAwait(false)) { throw new ControllerArgumentException($"language {languageShortName} is not a valid languagecode"); @@ -152,12 +160,14 @@ public async IAsyncEnumerable GetCompanyRoleAndConse } /// - public async Task CreateCompanyRoleAndConsentAgreementDetailsAsync((Guid UserId, Guid CompanyId) identity, IEnumerable companyRoleConsentDetails) + public async Task CreateCompanyRoleAndConsentAgreementDetailsAsync(IEnumerable companyRoleConsentDetails) { if (!companyRoleConsentDetails.Any()) { return; } + + var identity = _identityService.IdentityData; var companyRepositories = _portalRepositories.GetInstance(); var result = await companyRepositories.GetCompanyRolesDataAsync(identity.CompanyId, companyRoleConsentDetails.Select(x => x.CompanyRole)).ConfigureAwait(false); if (!result.IsValidCompany) @@ -221,10 +231,10 @@ public async Task CreateCompanyRoleAndConsentAgreementDetailsAsync((Guid UserId, } /// - public async Task> GetUseCaseParticipationAsync(Guid companyId, string? language) => + public async Task> GetUseCaseParticipationAsync(string? language) => await _portalRepositories .GetInstance() - .GetUseCaseParticipationForCompany(companyId, language ?? Constants.DefaultLanguage) + .GetUseCaseParticipationForCompany(_identityService.IdentityData.CompanyId, language ?? Constants.DefaultLanguage) .Select(x => new UseCaseParticipationData( x.UseCase, x.Description, @@ -247,27 +257,23 @@ await _portalRepositories .ConfigureAwait(false); /// - public async Task> GetSsiCertificatesAsync(Guid companyId) => + public async Task> GetSsiCertificatesAsync() => await _portalRepositories .GetInstance() - .GetSsiCertificates(companyId) + .GetSsiCertificates(_identityService.IdentityData.CompanyId) .Select(x => new SsiCertificateData( x.CredentialType, - x.SsiDetailData.CatchingInto( - data => data - .Select(d => new CompanySsiDetailData( + x.SsiDetailData.Select(d => new CompanySsiDetailData( d.CredentialId, d.ParticipationStatus, d.ExpiryDate, - d.Document)) - .SingleOrDefault(), - (InvalidOperationException _) => new ConflictException("There should only be one pending or active ssi detail be assigned") + d.Document) ))) .ToListAsync() .ConfigureAwait(false); /// - public async Task CreateUseCaseParticipation((Guid UserId, Guid CompanyId) identity, UseCaseParticipationCreationData data, CancellationToken cancellationToken) + public async Task CreateUseCaseParticipation(UseCaseParticipationCreationData data, CancellationToken cancellationToken) { var (verifiedCredentialExternalTypeDetailId, credentialTypeId, document) = data; var documentContentType = document.ContentType.ParseMediaTypeId(); @@ -279,11 +285,11 @@ public async Task CreateUseCaseParticipation((Guid UserId, Guid CompanyId) ident throw new ControllerArgumentException($"VerifiedCredentialExternalTypeDetail {verifiedCredentialExternalTypeDetailId} does not exist"); } - await HandleSsiCreationAsync(identity, new(credentialTypeId, VerifiedCredentialTypeKindId.USE_CASE, verifiedCredentialExternalTypeDetailId, document, documentContentType), companyCredentialDetailsRepository, cancellationToken).ConfigureAwait(false); + await HandleSsiCreationAsync(credentialTypeId, VerifiedCredentialTypeKindId.USE_CASE, verifiedCredentialExternalTypeDetailId, document, documentContentType, companyCredentialDetailsRepository, cancellationToken).ConfigureAwait(false); } /// - public async Task CreateSsiCertificate((Guid UserId, Guid CompanyId) identity, SsiCertificateCreationData data, CancellationToken cancellationToken) + public async Task CreateSsiCertificate(SsiCertificateCreationData data, CancellationToken cancellationToken) { var (credentialTypeId, document) = data; var documentContentType = document.ContentType.ParseMediaTypeId(); @@ -295,16 +301,15 @@ public async Task CreateSsiCertificate((Guid UserId, Guid CompanyId) identity, S throw new ControllerArgumentException($"{credentialTypeId} is not assigned to a certificate"); } - await HandleSsiCreationAsync(identity, new(credentialTypeId, VerifiedCredentialTypeKindId.CERTIFICATE, null, document, documentContentType), companyCredentialDetailsRepository, cancellationToken).ConfigureAwait(false); + await HandleSsiCreationAsync(credentialTypeId, VerifiedCredentialTypeKindId.CERTIFICATE, null, document, documentContentType, companyCredentialDetailsRepository, cancellationToken).ConfigureAwait(false); } private async Task HandleSsiCreationAsync( - (Guid UserId, Guid CompanyId) identity, - (VerifiedCredentialTypeId CredentialTypeId, VerifiedCredentialTypeKindId kindId, Guid? VerifiedCredentialExternalTypeDetailId, IFormFile Document, MediaTypeId MediaTypeId) creationData, + VerifiedCredentialTypeId credentialTypeId, VerifiedCredentialTypeKindId kindId, Guid? verifiedCredentialExternalTypeDetailId, IFormFile document, MediaTypeId mediaTypeId, ICompanySsiDetailsRepository companyCredentialDetailsRepository, CancellationToken cancellationToken) { - var (credentialTypeId, kindId, verifiedCredentialExternalTypeDetailId, document, mediaTypeId) = creationData; + var identity = _identityService.IdentityData; if (await companyCredentialDetailsRepository.CheckSsiDetailsExistsForCompany(identity.CompanyId, credentialTypeId, kindId, verifiedCredentialExternalTypeDetailId).ConfigureAwait(false)) { throw new ControllerArgumentException("Credential request already existing"); @@ -372,9 +377,10 @@ private async Task HandleSsiCreationAsync( } /// - public async Task ApproveCredential(Guid userId, Guid credentialId, CancellationToken cancellationToken) + public async Task ApproveCredential(Guid credentialId, CancellationToken cancellationToken) { var companySsiRepository = _portalRepositories.GetInstance(); + var userId = _identityService.IdentityData.UserId; var (exists, data) = await companySsiRepository.GetSsiApprovalData(credentialId).ConfigureAwait(false); if (!exists) { @@ -445,9 +451,10 @@ public async Task ApproveCredential(Guid userId, Guid credentialId, Cancellation } /// - public async Task RejectCredential(Guid userId, Guid credentialId) + public async Task RejectCredential(Guid credentialId) { var companySsiRepository = _portalRepositories.GetInstance(); + var userId = _identityService.IdentityData.UserId; var (exists, status, type, requesterId, requesterEmail, requesterFirstname, requesterLastname) = await companySsiRepository.GetSsiRejectionData(credentialId).ConfigureAwait(false); if (!exists) { diff --git a/src/administration/Administration.Service/BusinessLogic/ConnectorsBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/ConnectorsBusinessLogic.cs index 4c3ed29a6e..6ee9f00dd1 100644 --- a/src/administration/Administration.Service/BusinessLogic/ConnectorsBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/ConnectorsBusinessLogic.cs @@ -272,41 +272,35 @@ public async Task DeleteConnectorAsync(Guid connectorId) { var companyId = _identityService.IdentityData.CompanyId; var connectorsRepository = _portalRepositories.GetInstance(); - var (isValidConnectorId, isProvidingOrHostCompany, selfDescriptionDocumentId, documentStatusId, connectorStatus, assignedOfferSubscriptions) = await connectorsRepository.GetConnectorDeleteDataAsync(connectorId, companyId).ConfigureAwait(false); - - if (!isValidConnectorId) - { - throw new NotFoundException($"Connector {connectorId} does not exist"); - } - - if (!isProvidingOrHostCompany) + var result = await connectorsRepository.GetConnectorDeleteDataAsync(connectorId, companyId).ConfigureAwait(false) ?? throw new NotFoundException($"Connector {connectorId} does not exist"); + if (!result.IsProvidingOrHostCompany) { throw new ForbiddenException($"company {companyId} is neither provider nor host-company of connector {connectorId}"); } - switch (connectorStatus) + switch (result.ConnectorStatus) { - case ConnectorStatusId.PENDING when selfDescriptionDocumentId == null: - await DeleteConnectorWithoutDocuments(connectorId, assignedOfferSubscriptions, connectorsRepository); + case ConnectorStatusId.PENDING when result.SelfDescriptionDocumentId == null: + await DeleteConnectorWithoutDocuments(connectorId, result.ConnectorOfferSubscriptions, connectorsRepository); break; case ConnectorStatusId.PENDING: - await DeleteConnectorWithDocuments(connectorId, assignedOfferSubscriptions, selfDescriptionDocumentId.Value, connectorsRepository); + await DeleteConnectorWithDocuments(connectorId, result.SelfDescriptionDocumentId.Value, result.ConnectorOfferSubscriptions, connectorsRepository); break; - case ConnectorStatusId.ACTIVE when selfDescriptionDocumentId != null && documentStatusId != null: - await DeleteConnector(connectorId, assignedOfferSubscriptions, selfDescriptionDocumentId.Value, documentStatusId.Value, connectorsRepository); + case ConnectorStatusId.ACTIVE when result.SelfDescriptionDocumentId != null && result.DocumentStatusId != null: + await DeleteConnector(connectorId, result.ConnectorOfferSubscriptions, result.SelfDescriptionDocumentId.Value, result.DocumentStatusId.Value, connectorsRepository); break; default: throw new ConflictException("Connector status does not match a deletion scenario. Deletion declined"); } } - private async Task DeleteConnector(Guid connectorId, IEnumerable assignedOfferSubscriptions, Guid selfDescriptionDocumentId, DocumentStatusId documentStatus, IConnectorsRepository connectorsRepository) + private async Task DeleteConnector(Guid connectorId, IEnumerable connectorOfferSubscriptions, Guid selfDescriptionDocumentId, DocumentStatusId documentStatus, IConnectorsRepository connectorsRepository) { _portalRepositories.GetInstance().AttachAndModifyDocument( selfDescriptionDocumentId, a => { a.DocumentStatusId = documentStatus; }, a => { a.DocumentStatusId = DocumentStatusId.INACTIVE; }); - RemoveConnectorAssignedOfferSubscriptions(connectorId, assignedOfferSubscriptions, connectorsRepository); + RemoveConnectorAssignedOfferSubscriptions(connectorId, connectorOfferSubscriptions, connectorsRepository); await DeleteUpdateConnectorDetail(connectorId, connectorsRepository); } @@ -320,23 +314,30 @@ private async Task DeleteUpdateConnectorDetail(Guid connectorId, IConnectorsRepo await _portalRepositories.SaveAsync(); } - private async Task DeleteConnectorWithDocuments(Guid connectorId, IEnumerable assignedOfferSubscriptions, Guid selfDescriptionDocumentId, IConnectorsRepository connectorsRepository) + private async Task DeleteConnectorWithDocuments(Guid connectorId, Guid selfDescriptionDocumentId, IEnumerable connectorOfferSubscriptions, IConnectorsRepository connectorsRepository) { _portalRepositories.GetInstance().RemoveDocument(selfDescriptionDocumentId); - RemoveConnectorAssignedOfferSubscriptions(connectorId, assignedOfferSubscriptions, connectorsRepository); + RemoveConnectorAssignedOfferSubscriptions(connectorId, connectorOfferSubscriptions, connectorsRepository); connectorsRepository.DeleteConnector(connectorId); await _portalRepositories.SaveAsync(); } - private async Task DeleteConnectorWithoutDocuments(Guid connectorId, IEnumerable assignedOfferSubscriptions, IConnectorsRepository connectorsRepository) + private async Task DeleteConnectorWithoutDocuments(Guid connectorId, IEnumerable connectorOfferSubscriptions, IConnectorsRepository connectorsRepository) { - RemoveConnectorAssignedOfferSubscriptions(connectorId, assignedOfferSubscriptions, connectorsRepository); + RemoveConnectorAssignedOfferSubscriptions(connectorId, connectorOfferSubscriptions, connectorsRepository); connectorsRepository.DeleteConnector(connectorId); await _portalRepositories.SaveAsync(); } - private static void RemoveConnectorAssignedOfferSubscriptions(Guid connectorId, IEnumerable assignedOfferSubscriptions, IConnectorsRepository connectorsRepository) + private static void RemoveConnectorAssignedOfferSubscriptions(Guid connectorId, IEnumerable connectorOfferSubscriptions, IConnectorsRepository connectorsRepository) { + var activeConnectorOfferSubscription = connectorOfferSubscriptions.Where(cos => cos.OfferSubscriptionStatus == OfferSubscriptionStatusId.ACTIVE) + .Select(cos => cos.AssignedOfferSubscriptionIds); + if (activeConnectorOfferSubscription.Any()) + { + throw new ForbiddenException($"Deletion Failed. Connector {connectorId} connected to an active offer subscription [{string.Join(",", activeConnectorOfferSubscription)}]"); + } + var assignedOfferSubscriptions = connectorOfferSubscriptions.Select(cos => cos.AssignedOfferSubscriptionIds); if (assignedOfferSubscriptions.Any()) { connectorsRepository.DeleteConnectorAssignedSubscriptions(connectorId, assignedOfferSubscriptions); diff --git a/src/administration/Administration.Service/BusinessLogic/ICompanyDataBusinessLogic.cs b/src/administration/Administration.Service/BusinessLogic/ICompanyDataBusinessLogic.cs index 270d7eac33..861e04ef51 100644 --- a/src/administration/Administration.Service/BusinessLogic/ICompanyDataBusinessLogic.cs +++ b/src/administration/Administration.Service/BusinessLogic/ICompanyDataBusinessLogic.cs @@ -27,29 +27,30 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.BusinessLog public interface ICompanyDataBusinessLogic { - Task GetCompanyDetailsAsync(Guid companyId); + Task GetCompanyDetailsAsync(); - IAsyncEnumerable GetCompanyAssigendUseCaseDetailsAsync(Guid companyId); + IAsyncEnumerable GetCompanyAssigendUseCaseDetailsAsync(); - Task CreateCompanyAssignedUseCaseDetailsAsync(Guid companyId, Guid useCaseId); + Task CreateCompanyAssignedUseCaseDetailsAsync(Guid useCaseId); - Task RemoveCompanyAssignedUseCaseDetailsAsync(Guid companyId, Guid useCaseId); + Task RemoveCompanyAssignedUseCaseDetailsAsync(Guid useCaseId); - IAsyncEnumerable GetCompanyRoleAndConsentAgreementDetailsAsync(Guid companyId, string? languageShortName); + IAsyncEnumerable GetCompanyRoleAndConsentAgreementDetailsAsync(string? languageShortName); - Task CreateCompanyRoleAndConsentAgreementDetailsAsync((Guid UserId, Guid CompanyId) identity, IEnumerable companyRoleConsentDetails); + Task CreateCompanyRoleAndConsentAgreementDetailsAsync(IEnumerable companyRoleConsentDetails); - Task> GetUseCaseParticipationAsync(Guid companyId, string? language); + Task> GetUseCaseParticipationAsync(string? language); - Task> GetSsiCertificatesAsync(Guid companyId); + Task> GetSsiCertificatesAsync(); - Task CreateUseCaseParticipation((Guid UserId, Guid CompanyId) identity, UseCaseParticipationCreationData data, CancellationToken cancellationToken); - Task CreateSsiCertificate((Guid UserId, Guid CompanyId) identity, SsiCertificateCreationData data, CancellationToken cancellationToken); + Task CreateUseCaseParticipation(UseCaseParticipationCreationData data, CancellationToken cancellationToken); + Task CreateSsiCertificate(SsiCertificateCreationData data, CancellationToken cancellationToken); Task> GetCredentials(int page, int size, CompanySsiDetailStatusId? companySsiDetailStatusId, VerifiedCredentialTypeId? credentialTypeId, string? companyName, CompanySsiDetailSorting? sorting); - Task ApproveCredential(Guid userId, Guid credentialId, CancellationToken cancellationToken); + Task ApproveCredential(Guid credentialId, CancellationToken cancellationToken); + + Task RejectCredential(Guid credentialId); - Task RejectCredential(Guid userId, Guid credentialId); IAsyncEnumerable GetCertificateTypes(); } diff --git a/src/administration/Administration.Service/Controllers/CompanyDataController.cs b/src/administration/Administration.Service/Controllers/CompanyDataController.cs index 251db9a4bc..e7b8ac8280 100644 --- a/src/administration/Administration.Service/Controllers/CompanyDataController.cs +++ b/src/administration/Administration.Service/Controllers/CompanyDataController.cs @@ -65,7 +65,7 @@ public CompanyDataController(ICompanyDataBusinessLogic logic) [ProducesResponseType(typeof(CompanyAddressDetailData), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status409Conflict)] public Task GetOwnCompanyDetailsAsync() => - this.WithCompanyId(companyId => _logic.GetCompanyDetailsAsync(companyId)); + _logic.GetCompanyDetailsAsync(); /// /// Gets the CompanyAssigned UseCase details @@ -79,7 +79,7 @@ public Task GetOwnCompanyDetailsAsync() => [Route("preferredUseCases")] [ProducesResponseType(typeof(CompanyAssignedUseCaseData), StatusCodes.Status200OK)] public IAsyncEnumerable GetCompanyAssigendUseCaseDetailsAsync() => - this.WithCompanyId(companyId => _logic.GetCompanyAssigendUseCaseDetailsAsync(companyId)); + _logic.GetCompanyAssigendUseCaseDetailsAsync(); /// /// Create the CompanyAssigned UseCase details @@ -96,7 +96,7 @@ public IAsyncEnumerable GetCompanyAssigendUseCaseDet [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status409Conflict)] public async Task CreateCompanyAssignedUseCaseDetailsAsync([FromBody] UseCaseIdDetails data) => - await this.WithCompanyId(companyId => _logic.CreateCompanyAssignedUseCaseDetailsAsync(companyId, data.useCaseId)).ConfigureAwait(false) + await _logic.CreateCompanyAssignedUseCaseDetailsAsync(data.useCaseId).ConfigureAwait(false) ? StatusCode((int)HttpStatusCode.Created) : NoContent(); @@ -115,7 +115,7 @@ await this.WithCompanyId(companyId => _logic.CreateCompanyAssignedUseCaseDetails [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status409Conflict)] public async Task RemoveCompanyAssignedUseCaseDetailsAsync([FromBody] UseCaseIdDetails data) { - await this.WithCompanyId(companyId => _logic.RemoveCompanyAssignedUseCaseDetailsAsync(companyId, data.useCaseId)).ConfigureAwait(false); + await _logic.RemoveCompanyAssignedUseCaseDetailsAsync(data.useCaseId).ConfigureAwait(false); return NoContent(); } @@ -134,7 +134,7 @@ public async Task RemoveCompanyAssignedUseCaseDetailsAsync([Fro [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status409Conflict)] public IAsyncEnumerable GetCompanyRoleAndConsentAgreementDetailsAsync([FromQuery] string? languageShortName = null) => - this.WithCompanyId(companyId => _logic.GetCompanyRoleAndConsentAgreementDetailsAsync(companyId, languageShortName)); + _logic.GetCompanyRoleAndConsentAgreementDetailsAsync(languageShortName); /// /// Post the companyrole and Consent Details @@ -153,7 +153,7 @@ public IAsyncEnumerable GetCompanyRoleAndConsentAgre [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status409Conflict)] public async Task CreateCompanyRoleAndConsentAgreementDetailsAsync([FromBody] IEnumerable companyRoleConsentDetails) { - await this.WithUserIdAndCompanyId(identity => _logic.CreateCompanyRoleAndConsentAgreementDetailsAsync(identity, companyRoleConsentDetails)); + await _logic.CreateCompanyRoleAndConsentAgreementDetailsAsync(companyRoleConsentDetails); return NoContent(); } @@ -169,7 +169,7 @@ public async Task CreateCompanyRoleAndConsentAgreementDetailsAs [Route("useCaseParticipation")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public Task> GetUseCaseParticipation([FromQuery] string? language) => - this.WithCompanyId(companyId => _logic.GetUseCaseParticipationAsync(companyId, language)); + _logic.GetUseCaseParticipationAsync(language); /// /// Gets the Ssi certifications for the own company @@ -183,7 +183,7 @@ public Task> GetUseCaseParticipation([From [Route("certificates")] [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] public Task> GetSsiCertificationData() => - this.WithCompanyId(companyId => _logic.GetSsiCertificatesAsync(companyId)); + _logic.GetSsiCertificatesAsync(); /// /// Gets the Ssi certifications for the own company @@ -214,7 +214,7 @@ public IAsyncEnumerable GetCertificateTypes() => [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task CreateUseCaseParticipation([FromForm] UseCaseParticipationCreationData data, CancellationToken cancellationToken) { - await this.WithUserIdAndCompanyId(identity => _logic.CreateUseCaseParticipation(identity, data, cancellationToken)).ConfigureAwait(false); + await _logic.CreateUseCaseParticipation(data, cancellationToken).ConfigureAwait(false); return NoContent(); } @@ -234,7 +234,7 @@ public async Task CreateUseCaseParticipation([FromForm] UseCase [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task CreateSsiCertificate([FromForm] SsiCertificateCreationData data, CancellationToken cancellationToken) { - await this.WithUserIdAndCompanyId(identity => _logic.CreateSsiCertificate(identity, data, cancellationToken)).ConfigureAwait(false); + await _logic.CreateSsiCertificate(data, cancellationToken).ConfigureAwait(false); return NoContent(); } @@ -278,7 +278,7 @@ public async Task CreateSsiCertificate([FromForm] SsiCertificat [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task ApproveCredential([FromRoute] Guid credentialId, CancellationToken cts) { - await this.WithUserId(userId => _logic.ApproveCredential(userId, credentialId, cts)).ConfigureAwait(false); + await _logic.ApproveCredential(credentialId, cts).ConfigureAwait(false); return NoContent(); } @@ -296,7 +296,7 @@ public async Task ApproveCredential([FromRoute] Guid credential [ProducesResponseType(StatusCodes.Status204NoContent)] public async Task RejectCredential([FromRoute] Guid credentialId) { - await this.WithUserId(userId => _logic.RejectCredential(userId, credentialId)).ConfigureAwait(false); + await _logic.RejectCredential(credentialId).ConfigureAwait(false); return NoContent(); } } diff --git a/src/administration/Administration.Service/Controllers/ConnectorsController.cs b/src/administration/Administration.Service/Controllers/ConnectorsController.cs index d7cb8d9ad8..cc75341eab 100644 --- a/src/administration/Administration.Service/Controllers/ConnectorsController.cs +++ b/src/administration/Administration.Service/Controllers/ConnectorsController.cs @@ -24,8 +24,10 @@ using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Models; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling.Library; using Org.Eclipse.TractusX.Portal.Backend.Framework.Models; +using Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Authentication; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; using Org.Eclipse.TractusX.Portal.Backend.SdFactory.Library.Models; namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Controllers; @@ -84,6 +86,7 @@ public ConnectorsController(IConnectorsBusinessLogic connectorsBusinessLogic) [Authorize(Roles = "view_connectors")] [Authorize(Policy = PolicyTypes.ValidCompany)] [ProducesResponseType(typeof(Pagination.Response), StatusCodes.Status200OK)] + [PublicUrl(CompanyRoleId.APP_PROVIDER, CompanyRoleId.SERVICE_PROVIDER)] public Task> GetManagedConnectorsForCurrentUserAsync([FromQuery] int page = 0, [FromQuery] int size = 15) => _businessLogic.GetManagedConnectorForCompany(page, size); @@ -145,6 +148,7 @@ public async Task CreateConnectorAsync([FromForm] Connecto [ProducesResponseType(typeof(Guid), StatusCodes.Status201Created)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status503ServiceUnavailable)] + [PublicUrl(CompanyRoleId.APP_PROVIDER, CompanyRoleId.SERVICE_PROVIDER)] public async Task CreateManagedConnectorAsync([FromForm] ManagedConnectorInputModel connectorInputModel, CancellationToken cancellationToken) { var connectorId = await _businessLogic.CreateManagedConnectorAsync(connectorInputModel, cancellationToken).ConfigureAwait(false); @@ -182,6 +186,7 @@ public async Task DeleteConnectorAsync([FromRoute] Guid connector [Route("discovery")] [Authorize(Roles = "view_connectors")] [ProducesResponseType(typeof(IAsyncEnumerable), StatusCodes.Status200OK)] + [PublicUrl(CompanyRoleId.APP_PROVIDER, CompanyRoleId.SERVICE_PROVIDER, CompanyRoleId.ACTIVE_PARTICIPANT)] public IAsyncEnumerable GetCompanyConnectorEndPointAsync([FromBody] IEnumerable bpns) => _businessLogic.GetCompanyConnectorEndPointAsync(bpns); diff --git a/src/administration/Administration.Service/Controllers/DocumentsController.cs b/src/administration/Administration.Service/Controllers/DocumentsController.cs index 05bea4d640..9e3c564d90 100644 --- a/src/administration/Administration.Service/Controllers/DocumentsController.cs +++ b/src/administration/Administration.Service/Controllers/DocumentsController.cs @@ -23,8 +23,10 @@ using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.BusinessLogic; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling.Library; using Org.Eclipse.TractusX.Portal.Backend.Framework.Models; +using Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Authentication; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Controllers; @@ -85,6 +87,7 @@ public async Task GetDocumentContentFileAsync([FromRoute] Guid doc [Authorize(Roles = "view_documents")] [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)] + [PublicUrl(CompanyRoleId.SERVICE_PROVIDER, CompanyRoleId.APP_PROVIDER)] public async Task GetSelfDescriptionDocumentsAsync([FromRoute] Guid documentId) { var (fileName, content, mediaType) = await _businessLogic.GetSelfDescriptionDocumentAsync(documentId).ConfigureAwait(false); diff --git a/src/administration/Administration.Service/Controllers/PartnerNetworkController.cs b/src/administration/Administration.Service/Controllers/PartnerNetworkController.cs index a48e7b13aa..b4d38b4b42 100644 --- a/src/administration/Administration.Service/Controllers/PartnerNetworkController.cs +++ b/src/administration/Administration.Service/Controllers/PartnerNetworkController.cs @@ -21,6 +21,8 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.BusinessLogic; +using Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Controllers; @@ -53,6 +55,7 @@ public PartnerNetworkController(IPartnerNetworkBusinessLogic logic) [Authorize(Roles = "view_membership")] [Route("memberCompanies")] [ProducesResponseType(StatusCodes.Status200OK)] + [PublicUrl(CompanyRoleId.ACTIVE_PARTICIPANT, CompanyRoleId.SERVICE_PROVIDER, CompanyRoleId.APP_PROVIDER)] public IAsyncEnumerable GetAllMemberCompaniesBPNAsync() => _logic.GetAllMemberCompaniesBPNAsync(); } diff --git a/src/administration/Administration.Service/Controllers/SubscriptionConfigurationController.cs b/src/administration/Administration.Service/Controllers/SubscriptionConfigurationController.cs index ed5fc4b456..dba22e0a8c 100644 --- a/src/administration/Administration.Service/Controllers/SubscriptionConfigurationController.cs +++ b/src/administration/Administration.Service/Controllers/SubscriptionConfigurationController.cs @@ -23,8 +23,10 @@ using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.BusinessLogic; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling.Library; using Org.Eclipse.TractusX.Portal.Backend.Framework.Models; +using Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Authentication; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Controllers; @@ -65,6 +67,7 @@ public SubscriptionConfigurationController(ISubscriptionConfigurationBusinessLog [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status403Forbidden)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status409Conflict)] + [PublicUrl(CompanyRoleId.SERVICE_PROVIDER, CompanyRoleId.APP_PROVIDER)] public Task GetServiceProviderCompanyDetail() => this.WithCompanyId(companyId => _businessLogic.GetProviderCompanyDetailsAsync(companyId)); @@ -107,6 +110,7 @@ public async Task SetProviderCompanyDetail([FromBody] ProviderD [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)] + [PublicUrl(CompanyRoleId.SERVICE_PROVIDER, CompanyRoleId.APP_PROVIDER)] public IAsyncEnumerable GetProcessStepsForSubscription([FromRoute] Guid offerSubscriptionId) => _businessLogic.GetProcessStepsForSubscription(offerSubscriptionId); @@ -188,6 +192,7 @@ public async Task RetriggerCreateTechnicalUser([FromRoute] Guid [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)] + [PublicUrl(CompanyRoleId.SERVICE_PROVIDER, CompanyRoleId.APP_PROVIDER)] public async Task RetriggerProviderCallback([FromRoute] Guid offerSubscriptionId) { await _businessLogic.RetriggerProviderCallback(offerSubscriptionId).ConfigureAwait(false); diff --git a/src/administration/Administration.Service/Controllers/UserController.cs b/src/administration/Administration.Service/Controllers/UserController.cs index 5fc2f4e2ca..d456c42f2b 100644 --- a/src/administration/Administration.Service/Controllers/UserController.cs +++ b/src/administration/Administration.Service/Controllers/UserController.cs @@ -36,8 +36,6 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Controllers [Consumes("application/json")] public class UserController : ControllerBase { - - private readonly ILogger _logger; private readonly IUserBusinessLogic _logic; private readonly IUserUploadBusinessLogic _uploadLogic; private readonly IUserRolesBusinessLogic _rolesLogic; @@ -45,13 +43,11 @@ public class UserController : ControllerBase /// /// Creates a new instance of /// - /// The logger /// The User Business Logic /// The User Upload Business Logic /// The User Roles Management Business Logic - public UserController(ILogger logger, IUserBusinessLogic logic, IUserUploadBusinessLogic uploadLogic, IUserRolesBusinessLogic rolesLogic) + public UserController(IUserBusinessLogic logic, IUserUploadBusinessLogic uploadLogic, IUserRolesBusinessLogic rolesLogic) { - _logger = logger; _logic = logic; _uploadLogic = uploadLogic; _rolesLogic = rolesLogic; diff --git a/src/administration/Administration.Service/Models/SsiCertificateData.cs b/src/administration/Administration.Service/Models/SsiCertificateData.cs index 83163ffdf8..6678259b73 100644 --- a/src/administration/Administration.Service/Models/SsiCertificateData.cs +++ b/src/administration/Administration.Service/Models/SsiCertificateData.cs @@ -5,5 +5,5 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Models; public record SsiCertificateData ( VerifiedCredentialTypeId CredentialType, - CompanySsiDetailData? SsiDetailData + IEnumerable SsiDetailData ); diff --git a/src/administration/Administration.Service/appsettings.json b/src/administration/Administration.Service/appsettings.json index 303206eff5..bd9933c541 100644 --- a/src/administration/Administration.Service/appsettings.json +++ b/src/administration/Administration.Service/appsettings.json @@ -5,6 +5,7 @@ "Default": "Information", "Override": { "Microsoft": "Warning", + "Microsoft.AspNetCore.HttpLogging.HttpLoggingMiddleware": "Warning", "System": "Information", "Microsoft.Hosting.Lifetime": "Information", "Org.Eclipse.TractusX.Portal.Backend": "Information" @@ -14,10 +15,6 @@ { "Name": "Console" } ], "Enrich": [ - "FromLogContext", - "WithMachineName", - "WithProcessId", - "WithThreadId", "WithCorrelationId" ], "Properties": { diff --git a/src/externalsystems/Bpdm.Library/BpdmService.cs b/src/externalsystems/Bpdm.Library/BpdmService.cs index 391a68373c..54ef384863 100644 --- a/src/externalsystems/Bpdm.Library/BpdmService.cs +++ b/src/externalsystems/Bpdm.Library/BpdmService.cs @@ -77,7 +77,7 @@ public async Task PutInputLegalEntity(BpdmTransferData data, CancellationT data.AlphaCode2, // Country data.ZipCode, // PostalCode data.City, // City - new BpdmStreet( + new BpdmLegalEntityStreet( null, // NamePrefix null, // AdditionalNamePrefix data.StreetName, // Name @@ -132,4 +132,29 @@ public async Task FetchInputLegalEntity(string extern throw new ServiceException($"Access to external system bpdm did not return a valid json response: {je.Message}"); } } + + public async Task GetSharingState(Guid applicationId, CancellationToken cancellationToken) + { + var httpClient = await _tokenService.GetAuthorizedClient(_settings, cancellationToken).ConfigureAwait(false); + + var url = $"/companies/test-company/api/catena/sharing-state?externalIds={applicationId}&businessPartnerType={BpdmSharingStateBusinessPartnerType.LEGAL_ENTITY}"; + var result = await httpClient.GetAsync(url, cancellationToken) + .CatchingIntoServiceExceptionFor("bpdm-sharing-state", HttpAsyncResponseMessageExtension.RecoverOptions.INFRASTRUCTURE).ConfigureAwait(false); + try + { + var response = await result.Content + .ReadFromJsonAsync(Options, cancellationToken) + .ConfigureAwait(false); + if (response?.Content?.Count() != 1) + { + throw new ServiceException("Access to sharing state did not return a valid legal entity response", true); + } + + return response.Content.Single(); + } + catch (JsonException je) + { + throw new ServiceException($"Access to sharing state did not return a valid json response: {je.Message}"); + } + } } diff --git a/src/externalsystems/Bpdm.Library/BusinessLogic/BpdmBusinessLogic.cs b/src/externalsystems/Bpdm.Library/BusinessLogic/BpdmBusinessLogic.cs index b16f21fbd0..a197ddf291 100644 --- a/src/externalsystems/Bpdm.Library/BusinessLogic/BpdmBusinessLogic.cs +++ b/src/externalsystems/Bpdm.Library/BusinessLogic/BpdmBusinessLogic.cs @@ -21,6 +21,7 @@ using Org.Eclipse.TractusX.Portal.Backend.Bpdm.Library.Models; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; using Org.Eclipse.TractusX.Portal.Backend.Processes.ApplicationChecklist.Library; @@ -98,16 +99,33 @@ public BpdmBusinessLogic(IPortalRepositories portalRepositories, IBpdmService bp public async Task HandlePullLegalEntity(IApplicationChecklistService.WorkerChecklistProcessStepData context, CancellationToken cancellationToken) { - var result = await _portalRepositories.GetInstance().GetBpdmDataForApplicationAsync(context.ApplicationId).ConfigureAwait(false); + var result = await _portalRepositories.GetInstance() + .GetBpdmDataForApplicationAsync(context.ApplicationId).ConfigureAwait(false); if (result == default) { throw new UnexpectedConditionException($"CompanyApplication {context.ApplicationId} does not exist"); } - var (companyId, data) = result; + var sharingState = await _bpdmService.GetSharingState(context.ApplicationId, cancellationToken).ConfigureAwait(false); + return sharingState.SharingStateType switch + { + BpdmSharingStateType.Success => + await HandlePullLegalEntityInternal(context, result.CompanyId, result.BpdmData, cancellationToken), + BpdmSharingStateType.Error => + throw new ServiceException($"ErrorCode: {sharingState.SharingErrorCode}, ErrorMessage: {sharingState.SharingErrorMessage}"), + _ => new IApplicationChecklistService.WorkerChecklistProcessStepExecutionResult(ProcessStepStatusId.TODO, null, null, null, false, null) + }; + } - var legalEntity = await _bpdmService.FetchInputLegalEntity(context.ApplicationId.ToString(), cancellationToken).ConfigureAwait(false); + private async Task HandlePullLegalEntityInternal( + IApplicationChecklistService.WorkerChecklistProcessStepData context, + Guid companyId, + BpdmData data, + CancellationToken cancellationToken) + { + var legalEntity = await _bpdmService.FetchInputLegalEntity(context.ApplicationId.ToString(), cancellationToken) + .ConfigureAwait(false); if (string.IsNullOrEmpty(legalEntity.Bpn)) { diff --git a/src/externalsystems/Bpdm.Library/IBpdmService.cs b/src/externalsystems/Bpdm.Library/IBpdmService.cs index f7e451cf85..b79ae9028a 100644 --- a/src/externalsystems/Bpdm.Library/IBpdmService.cs +++ b/src/externalsystems/Bpdm.Library/IBpdmService.cs @@ -35,4 +35,5 @@ public interface IBpdmService /// Returns true if the service call was successful, otherwise false Task PutInputLegalEntity(BpdmTransferData data, CancellationToken cancellationToken); Task FetchInputLegalEntity(string externalId, CancellationToken cancellationToken); + Task GetSharingState(Guid applicationId, CancellationToken cancellationToken); } diff --git a/src/externalsystems/Bpdm.Library/Models/BpdmLegalEntityData.cs b/src/externalsystems/Bpdm.Library/Models/BpdmLegalEntityData.cs index 28274d2a15..7f8c4797b6 100644 --- a/src/externalsystems/Bpdm.Library/Models/BpdmLegalEntityData.cs +++ b/src/externalsystems/Bpdm.Library/Models/BpdmLegalEntityData.cs @@ -79,7 +79,7 @@ public record BpdmAddressPhysicalPostalAddress( string? Country, string? PostalCode, string? City, - BpdmStreet? Street, + BpdmLegalEntityStreet? Street, string? AdministrativeAreaLevel1, string? AdministrativeAreaLevel2, string? AdministrativeAreaLevel3, @@ -101,3 +101,14 @@ public record BpdmAddressAlternativePostalAddress( string? DeliveryServiceType, string? DeliveryServiceQualifier ); + +public record BpdmLegalEntityStreet( + string? NamePrefix, + string? AdditionalNamePrefix, + string Name, + string? NameSuffix, + string? AdditionalNameSuffix, + string? HouseNumber, + string? Milestone, + string? Direction +); diff --git a/src/externalsystems/Bpdm.Library/Models/BpdmLegalEntityDto.cs b/src/externalsystems/Bpdm.Library/Models/BpdmLegalEntityDto.cs index cc337c73c6..65b190edfe 100644 --- a/src/externalsystems/Bpdm.Library/Models/BpdmLegalEntityDto.cs +++ b/src/externalsystems/Bpdm.Library/Models/BpdmLegalEntityDto.cs @@ -29,37 +29,37 @@ public record BpdmLegalEntityDto( [property: JsonPropertyName("currentness")] DateTimeOffset Currentness, [property: JsonPropertyName("createdAt")] DateTimeOffset CreatedAt, [property: JsonPropertyName("updatedAt")] DateTimeOffset UpdatedAt, - IEnumerable Identifiers, - BpdmLegalFormDto? LegalForm, - IEnumerable Status, - IEnumerable ProfileClassifications, - IEnumerable Relations, - BpdmLegalEntityAddress LegalEntityAddress + [property: JsonPropertyName("identifiers")] IEnumerable Identifiers, + [property: JsonPropertyName("legalForm")] BpdmLegalFormDto? LegalForm, + [property: JsonPropertyName("states")] IEnumerable States, + [property: JsonPropertyName("classifications")] IEnumerable Classifications, + [property: JsonPropertyName("relations")] IEnumerable Relations, + [property: JsonPropertyName("legalAddress")] BpdmLegalEntityAddress? LegalEntityAddress ); public record BpdmIdentifierDto( string Value, BpdmTechnicalKey Type, - string IssuingBody + string? IssuingBody ); public record BpdmLegalFormDto( - string TechnicalKey, - string Name, - string Abbreviation + string? TechnicalKey, + string? Name, + string? Abbreviation ); public record BpdmStatusDto( - string OfficialDenotation, + string? OfficialDenotation, DateTimeOffset ValidFrom, DateTimeOffset ValidUntil, BpdmTechnicalKey Type ); public record BpdmProfileClassificationDto( - string Value, - string Code, - BpdmTechnicalKey Type + string? Value, + string? Code, + BpdmTechnicalKey? Type ); public record BpdmDataDto( @@ -74,18 +74,18 @@ string Name public record BpdmRelationDto( BpdmTechnicalKey Type, - string StartBpn, - string EndBpn, + string? StartBpn, + string? EndBpn, DateTimeOffset ValidFrom, DateTimeOffset ValidTo ); public record BpdmLegalEntityAddress ( - string Bpnl, - string Name, - string BpnLegalEntity, - string BpnSite, + string? Bpna, + string? Name, + string? BpnLegalEntity, + string? BpnSite, DateTimeOffset CreatedAt, DateTimeOffset UpdatedAt, bool IsLegalAddress, @@ -98,7 +98,7 @@ public record BpdmLegalEntityAddress public record BpdmLegalEntityAddressState ( - string Description, + string? Description, DateTimeOffset ValidFrom, DateTimeOffset ValidTo, BpdmTechnicalKey Type @@ -106,7 +106,7 @@ BpdmTechnicalKey Type public record BpdmLegalEntityAddressIdentifier ( - string Value, + string? Value, BpdmTechnicalKey Type ); @@ -139,16 +139,13 @@ public record BpdmAlternativePostalAddress( ); public record BpdmAdministrativeAreaLevel( - string? Name, + string? CountryCode, + string? RegionName, string? RegionCode ); public record BpdmStreet( - string? NamePrefix, - string? AdditionalNamePrefix, - string Name, - string? NameSuffix, - string? AdditionalNameSuffix, + string? Name, string? HouseNumber, string? Milestone, string? Direction diff --git a/src/externalsystems/Bpdm.Library/Models/BpdmSharingState.cs b/src/externalsystems/Bpdm.Library/Models/BpdmSharingState.cs new file mode 100644 index 0000000000..78da8a3c0c --- /dev/null +++ b/src/externalsystems/Bpdm.Library/Models/BpdmSharingState.cs @@ -0,0 +1,30 @@ +namespace Org.Eclipse.TractusX.Portal.Backend.Bpdm.Library.Models; + +public record BpdmPaginationSharingStateOutput( + IEnumerable? Content +); + +public record BpdmSharingState( + BpdmSharingStateBusinessPartnerType BusinessPartnerType, + Guid ExternalId, + BpdmSharingStateType SharingStateType, + string? SharingErrorCode, + string? SharingErrorMessage, + string? Bpn, + DateTimeOffset SharingProcessStarted +); + +public enum BpdmSharingStateType +{ + Pending = 1, + Success = 2, + Error = 3, + Initial = 4 +} + +public enum BpdmSharingStateBusinessPartnerType +{ + LEGAL_ENTITY = 1, + SITE = 2, + ADDRESS = 3 +} diff --git a/src/framework/Framework.Linq/NullOrSequenceEqualExtension.cs b/src/framework/Framework.Linq/NullOrSequenceEqualExtension.cs new file mode 100644 index 0000000000..b7a5412bbc --- /dev/null +++ b/src/framework/Framework.Linq/NullOrSequenceEqualExtension.cs @@ -0,0 +1,58 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 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.Diagnostics.CodeAnalysis; + +namespace Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; + +public static class NullOrSequenceEqualExtensions +{ + public static bool NullOrContentEqual(this IEnumerable? items, IEnumerable? others, IEqualityComparer? comparer = null) where T : IComparable => + items == null && others == null || + items != null && others != null && + items.OrderBy(x => x).SequenceEqual(others.OrderBy(x => x), comparer); + + public static bool NullOrContentEqual(this IEnumerable>? items, IEnumerable>? others, IEqualityComparer>? comparer = null) where K : IComparable where V : IComparable => + items == null && others == null || + items != null && others != null && + items.OrderBy(x => x.Key).SequenceEqual(others.OrderBy(x => x.Key), comparer ?? new KeyValuePairEqualityComparer()); + + public static bool NullOrContentEqual(this IEnumerable>>? items, IEnumerable>>? others, IEqualityComparer>>? comparer = null) where V : IComparable => + items == null && others == null || + items != null && others != null && + items.OrderBy(x => x.Key).SequenceEqual(others.OrderBy(x => x.Key), comparer ?? new EnumerableValueKeyValuePairEqualityComparer()); +} + +public class KeyValuePairEqualityComparer : IEqualityComparer> where K : IComparable where V : IComparable +{ + public bool Equals(KeyValuePair x, KeyValuePair y) => + x.Key.Equals(y.Key) && x.Value.Equals(y.Value); + public int GetHashCode([DisallowNull] KeyValuePair obj) => throw new NotImplementedException(); +} + +public class EnumerableValueKeyValuePairEqualityComparer : IEqualityComparer>> where V : IComparable +{ + public bool Equals(KeyValuePair> source, KeyValuePair> other) => + (source.Key == null && other.Key == null || + source.Key != null && source.Key.Equals(other.Key)) && + source.Value.NullOrContentEqual(other.Value); + + public int GetHashCode([DisallowNull] KeyValuePair> obj) => throw new NotImplementedException(); +} diff --git a/src/framework/Framework.Logging/LoggingExtensions.cs b/src/framework/Framework.Logging/LoggingExtensions.cs index f401a0511f..060e636493 100644 --- a/src/framework/Framework.Logging/LoggingExtensions.cs +++ b/src/framework/Framework.Logging/LoggingExtensions.cs @@ -38,9 +38,9 @@ public static IHostBuilder AddLogging(this IHostBuilder host, Action { configuration - .WriteTo.Console(new JsonFormatter()) .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) - .ReadFrom.Configuration(context.Configuration); + .ReadFrom.Configuration(context.Configuration) + .WriteTo.Console(new JsonFormatter(renderMessage: true)); extendLogging?.Invoke(configuration, context.Configuration); }); @@ -58,8 +58,7 @@ public static void EnsureInitialized() Log.Logger = new LoggerConfiguration() .MinimumLevel.Override("Microsoft.AspNetCore", LogEventLevel.Warning) - .Enrich.FromLogContext() - .WriteTo.Console(new JsonFormatter()) + .WriteTo.Console(new JsonFormatter(renderMessage: true)) .CreateBootstrapLogger(); } } diff --git a/src/framework/Framework.Models/HasNextEnumeratorExtensions.cs b/src/framework/Framework.Models/HasNextEnumeratorExtensions.cs new file mode 100644 index 0000000000..a878e9ae48 --- /dev/null +++ b/src/framework/Framework.Models/HasNextEnumeratorExtensions.cs @@ -0,0 +1,64 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.Portal.Backend.Framework.Models; + +public static class HasNextEnumeratorExtensions +{ + private sealed class HasNextEnumeratorWrapper : IHasNextEnumerator + { + private readonly IEnumerator _enumerator; + private bool _hasNext; + + public HasNextEnumeratorWrapper(IEnumerable enumerable) + { + _enumerator = enumerable.GetEnumerator(); + _hasNext = _enumerator.MoveNext(); + } + + public void Advance() + { + _hasNext = _enumerator.MoveNext(); + } + + public bool HasNext + { + get => _hasNext; + } + + public T Current + { + get => _enumerator.Current; + } + + public void Dispose() => _enumerator.Dispose(); + } + + public static IHasNextEnumerator GetHasNextEnumerator(this IEnumerable source) => new HasNextEnumeratorWrapper(source); +} + +public interface IHasNextEnumerator : IDisposable +{ + void Advance(); + + bool HasNext { get; } + + T Current { get; } +} diff --git a/src/framework/Framework.PublicInfos/DependencyInjection/PublicInfosServiceCollectionExtensions.cs b/src/framework/Framework.PublicInfos/DependencyInjection/PublicInfosServiceCollectionExtensions.cs new file mode 100644 index 0000000000..f6287cd1b7 --- /dev/null +++ b/src/framework/Framework.PublicInfos/DependencyInjection/PublicInfosServiceCollectionExtensions.cs @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Microsoft.Extensions.DependencyInjection; + +namespace Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos.DependencyInjection; + +public static class PublicInfosServiceCollectionExtensions +{ + public static IServiceCollection AddPublicInfos(this IServiceCollection services) + { + services + .AddTransient(); + + return services; + } +} diff --git a/src/framework/Framework.PublicInfos/Framework.PublicInfos.csproj b/src/framework/Framework.PublicInfos/Framework.PublicInfos.csproj new file mode 100644 index 0000000000..b9e249136d --- /dev/null +++ b/src/framework/Framework.PublicInfos/Framework.PublicInfos.csproj @@ -0,0 +1,44 @@ + + + + Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos + Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos + net6.0 + enable + enable + + + + + + + + + + + + + + + + + + diff --git a/src/framework/Framework.PublicInfos/IPublicInformationBusinessLogic.cs b/src/framework/Framework.PublicInfos/IPublicInformationBusinessLogic.cs new file mode 100644 index 0000000000..e0d118c4a4 --- /dev/null +++ b/src/framework/Framework.PublicInfos/IPublicInformationBusinessLogic.cs @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos; + +public interface IPublicInformationBusinessLogic +{ + Task> GetPublicUrls(); +} diff --git a/src/framework/Framework.PublicInfos/OpenInformationController.cs b/src/framework/Framework.PublicInfos/OpenInformationController.cs new file mode 100644 index 0000000000..3720b11135 --- /dev/null +++ b/src/framework/Framework.PublicInfos/OpenInformationController.cs @@ -0,0 +1,56 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Authentication; + +namespace Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos; + +[ApiController] +[Route("api/info")] +public class OpenInformationController : ControllerBase +{ + private readonly IPublicInformationBusinessLogic _publicInformationBusinessLogic; + + /// + /// Creates a new instance of + /// + /// The business logic + public OpenInformationController(IPublicInformationBusinessLogic publicInformationBusinessLogic) + { + _publicInformationBusinessLogic = publicInformationBusinessLogic; + } + + /// + /// Gets all open information based on the + /// + /// + /// Example: GET: api/info + /// + /// Successfully executed the invitation. + [HttpGet] + [Authorize(Policy = PolicyTypes.ValidCompany)] + [ProducesResponseType(typeof(IEnumerable), StatusCodes.Status200OK)] + public Task> GetOpenUrls() => + _publicInformationBusinessLogic.GetPublicUrls(); +} diff --git a/src/framework/Framework.PublicInfos/PublicInformationBusinessLogic.cs b/src/framework/Framework.PublicInfos/PublicInformationBusinessLogic.cs new file mode 100644 index 0000000000..378d317431 --- /dev/null +++ b/src/framework/Framework.PublicInfos/PublicInformationBusinessLogic.cs @@ -0,0 +1,63 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Identities; +using System.Reflection; + +namespace Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos; + +public class PublicInformationBusinessLogic : IPublicInformationBusinessLogic +{ + private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider; + private readonly IPortalRepositories _portalRepositories; + private readonly IIdentityService _identityService; + + /// + /// Creates a new instance of + /// + /// The actionDescriptorCollectionProvider + /// + /// + public PublicInformationBusinessLogic(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider, IPortalRepositories portalRepositories, IIdentityService identityService) + { + _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; + _portalRepositories = portalRepositories; + _identityService = identityService; + } + + public async Task> GetPublicUrls() + { + var companyRoleIds = await _portalRepositories.GetInstance().GetOwnCompanyRolesAsync(_identityService.IdentityData.CompanyId).ToArrayAsync().ConfigureAwait(false); + return _actionDescriptorCollectionProvider.ActionDescriptors.Items + .Where(item => item.ActionConstraints != null && item.ActionConstraints.OfType().Any()) + .OfType() + .Where(item => item.MethodInfo.GetCustomAttribute()?.CompanyRoleIds.Intersect(companyRoleIds).Any() ?? false) + .Select(item => + new UrlInformation( + string.Join(", ", item.ActionConstraints!.OfType().SelectMany(x => x.HttpMethods).Distinct()), + (item.AttributeRouteInfo?.Template ?? throw new ConflictException($"There must be an url for {item.DisplayName}")).ToLower())); + } +} diff --git a/src/framework/Framework.PublicInfos/PublicUrlAttribute.cs b/src/framework/Framework.PublicInfos/PublicUrlAttribute.cs new file mode 100644 index 0000000000..7c16bde307 --- /dev/null +++ b/src/framework/Framework.PublicInfos/PublicUrlAttribute.cs @@ -0,0 +1,34 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; + +namespace Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos; + +[AttributeUsage(AttributeTargets.Method)] +public class PublicUrlAttribute : Attribute +{ + public PublicUrlAttribute(params CompanyRoleId[] companyRoleIds) + { + CompanyRoleIds = companyRoleIds; + } + + public IEnumerable CompanyRoleIds { get; set; } +} diff --git a/src/framework/Framework.PublicInfos/UrlInformation.cs b/src/framework/Framework.PublicInfos/UrlInformation.cs new file mode 100644 index 0000000000..e1d4e5b2f6 --- /dev/null +++ b/src/framework/Framework.PublicInfos/UrlInformation.cs @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 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 Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos; + +public record UrlInformation( + [property: JsonPropertyName("httpMethods")] string HttpMethods, + [property: JsonPropertyName("url")] string Url +); diff --git a/src/framework/Framework.Seeding/SeederSettings.cs b/src/framework/Framework.Seeding/SeederSettings.cs index 60cc165ea2..8b5b8c1770 100644 --- a/src/framework/Framework.Seeding/SeederSettings.cs +++ b/src/framework/Framework.Seeding/SeederSettings.cs @@ -27,23 +27,16 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Framework.Seeding; public class SeederSettings { - public SeederSettings() - { - TestDataEnvironments = null!; - DataPaths = null!; - } - /// /// Configurable paths where to check for the seeding data /// [Required] - public IEnumerable DataPaths { get; set; } + public IEnumerable DataPaths { get; set; } = null!; /// /// Configurable environments for the testdata /// - [Required] - public IEnumerable TestDataEnvironments { get; set; } + public IEnumerable? TestDataEnvironments { get; set; } } public static class SeederSettingsExtensions diff --git a/src/framework/Framework.Web/Framework.Web.csproj b/src/framework/Framework.Web/Framework.Web.csproj index d1b6ffea9b..daa8adbaab 100644 --- a/src/framework/Framework.Web/Framework.Web.csproj +++ b/src/framework/Framework.Web/Framework.Web.csproj @@ -32,6 +32,7 @@ + diff --git a/src/framework/Framework.Web/StartupServiceExtensions.cs b/src/framework/Framework.Web/StartupServiceExtensions.cs index ed8bcf868b..5cde1fb78f 100644 --- a/src/framework/Framework.Web/StartupServiceExtensions.cs +++ b/src/framework/Framework.Web/StartupServiceExtensions.cs @@ -27,6 +27,7 @@ using Org.Eclipse.TractusX.Portal.Backend.Framework.Cors; using Org.Eclipse.TractusX.Portal.Backend.Framework.DateTimeProvider.DependencyInjection; using Org.Eclipse.TractusX.Portal.Backend.Framework.Models; +using Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos.DependencyInjection; using Org.Eclipse.TractusX.Portal.Backend.Framework.Swagger; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Authentication; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; @@ -104,6 +105,7 @@ public static IServiceCollection AddDefaultServices(this IServiceColle services.AddScoped(); services.AddDateTimeProvider(); + services.AddPublicInfos(); return services; } } diff --git a/src/keycloak/Keycloak.Library/AuthenticationManagement/KeycloakClient.cs b/src/keycloak/Keycloak.Library/AuthenticationManagement/KeycloakClient.cs index 920b840d0a..aeaed96a15 100644 --- a/src/keycloak/Keycloak.Library/AuthenticationManagement/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/AuthenticationManagement/KeycloakClient.cs @@ -53,31 +53,31 @@ public async Task GetAuthenticatorProviderConfiguration .GetJsonAsync() .ConfigureAwait(false); - [Obsolete("Not working yet")] - public async Task GetAuthenticatorConfigurationAsync(string realm, string configurationId) => await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) - .AppendPathSegment("/admin/realms/") - .AppendPathSegment(realm, true) - .AppendPathSegment("/authentication/config/") - .AppendPathSegment(configurationId, true) - .GetJsonAsync() - .ConfigureAwait(false); + public async Task GetAuthenticatorConfigurationAsync(string realm, string configurationId, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) + .AppendPathSegment("/admin/realms/") + .AppendPathSegment(realm, true) + .AppendPathSegment("/authentication/config/") + .AppendPathSegment(configurationId, true) + .GetJsonAsync(cancellationToken) + .ConfigureAwait(false); - public async Task UpdateAuthenticatorConfigurationAsync(string realm, string configurationId, AuthenticatorConfig authenticatorConfig) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task UpdateAuthenticatorConfigurationAsync(string realm, string configurationId, AuthenticatorConfig authenticatorConfig, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/authentication/config/") .AppendPathSegment(configurationId, true) - .PutJsonAsync(authenticatorConfig) + .PutJsonAsync(authenticatorConfig, cancellationToken) .ConfigureAwait(false); - public async Task DeleteAuthenticatorConfigurationAsync(string realm, string configurationId) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task DeleteAuthenticatorConfigurationAsync(string realm, string configurationId, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/authentication/config/") .AppendPathSegment(configurationId, true) - .DeleteAsync() + .DeleteAsync(cancellationToken) .ConfigureAwait(false); public async Task AddAuthenticationExecutionAsync(string realm, AuthenticationExecution authenticationExecution) => @@ -97,23 +97,23 @@ public async Task GetAuthenticationExecutionAsync(s .GetJsonAsync() .ConfigureAwait(false); - public async Task DeleteAuthenticationExecutionAsync(string realm, string executionId) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task DeleteAuthenticationExecutionAsync(string realm, string executionId, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/authentication/executions/") .AppendPathSegment(executionId, true) - .DeleteAsync() + .DeleteAsync(cancellationToken) .ConfigureAwait(false); - public async Task UpdateAuthenticationExecutionConfigurationAsync(string realm, string executionId, AuthenticatorConfig authenticatorConfig) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task CreateAuthenticationExecutionConfigurationAsync(string realm, string executionId, AuthenticatorConfig authenticatorConfig, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/authentication/executions/") .AppendPathSegment(executionId, true) .AppendPathSegment("/config") - .PostJsonAsync(authenticatorConfig) + .PostJsonAsync(authenticatorConfig, cancellationToken) .ConfigureAwait(false); public async Task LowerAuthenticationExecutionPriorityAsync(string realm, string executionId) => @@ -136,20 +136,20 @@ public async Task RaiseAuthenticationExecutionPriorityAsync(string realm, string .PostAsync(new StringContent("")) .ConfigureAwait(false); - public async Task CreateAuthenticationFlowAsync(string realm, AuthenticationFlow authenticationFlow) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task CreateAuthenticationFlowAsync(string realm, AuthenticationFlow authenticationFlow, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/authentication/flows") - .PostJsonAsync(authenticationFlow) + .PostJsonAsync(authenticationFlow, cancellationToken) .ConfigureAwait(false); - public async Task> GetAuthenticationFlowsAsync(string realm) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task> GetAuthenticationFlowsAsync(string realm, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/authentication/flows") - .GetJsonAsync>() + .GetJsonAsync>(cancellationToken) .ConfigureAwait(false); public async Task DuplicateAuthenticationFlowAsync(string realm, string flowAlias, string newName) => @@ -162,44 +162,64 @@ public async Task DuplicateAuthenticationFlowAsync(string realm, string flowAlia .PostJsonAsync(new Dictionary { [nameof(newName)] = newName }) .ConfigureAwait(false); - public async Task> GetAuthenticationFlowExecutionsAsync(string realm, string flowAlias) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task> GetAuthenticationFlowExecutionsAsync(string realm, string flowAlias, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/authentication/flows/") .AppendPathSegment(flowAlias, true) .AppendPathSegment("/executions") - .GetJsonAsync>() + .GetJsonAsync>(cancellationToken) .ConfigureAwait(false); - public async Task UpdateAuthenticationFlowExecutionsAsync(string realm, string flowAlias, AuthenticationExecutionInfo authenticationExecutionInfo) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task UpdateAuthenticationFlowExecutionsAsync(string realm, string flowAlias, AuthenticationExecutionInfo authenticationExecutionInfo, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/authentication/flows/") .AppendPathSegment(flowAlias, true) .AppendPathSegment("/executions") - .PutJsonAsync(authenticationExecutionInfo) + .PutJsonAsync(authenticationExecutionInfo, cancellationToken) .ConfigureAwait(false); - public async Task AddAuthenticationFlowExecutionAsync(string realm, string flowAlias, IDictionary dataWithProvider) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public Task AddAuthenticationFlowExecutionAsync(string realm, string flowAlias, IDictionary dataWithProvider, CancellationToken cancellationToken = default) => + InternalAddAuthenticationFlowExecutionAsync(realm, flowAlias, dataWithProvider, cancellationToken); + + public async Task AddAndRetrieveAuthenticationFlowExecutionIdAsync(string realm, string flowAlias, IDictionary dataWithProvider, CancellationToken cancellationToken = default) + { + var response = await InternalAddAuthenticationFlowExecutionAsync(realm, flowAlias, dataWithProvider, cancellationToken).ConfigureAwait(false); + var locationPathAndQuery = response.ResponseMessage.Headers.Location?.PathAndQuery; + return locationPathAndQuery != null ? locationPathAndQuery.Substring(locationPathAndQuery.LastIndexOf("/", StringComparison.Ordinal) + 1) : null; + } + + private async Task InternalAddAuthenticationFlowExecutionAsync(string realm, string flowAlias, IDictionary dataWithProvider, CancellationToken cancellationToken) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/authentication/flows/") .AppendPathSegment(flowAlias, true) .AppendPathSegment("/executions/execution") - .PostJsonAsync(dataWithProvider) + .PostJsonAsync(dataWithProvider, cancellationToken) .ConfigureAwait(false); - public async Task AddAuthenticationFlowAndExecutionToAuthenticationFlowAsync(string realm, string flowAlias, IDictionary dataWithAliasTypeProviderDescription) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public Task AddAuthenticationFlowAndExecutionToAuthenticationFlowAsync(string realm, string flowAlias, IDictionary dataWithAliasTypeProviderDescription, CancellationToken cancellationToken = default) => + InternalAddAuthenticationFlowAndExecutionToAuthenticationFlowAsync(realm, flowAlias, dataWithAliasTypeProviderDescription, cancellationToken); + + public async Task AddAndRetrieveAuthenticationFlowAndExecutionToAuthenticationFlowIdAsync(string realm, string flowAlias, IDictionary dataWithAliasTypeProviderDescription, CancellationToken cancellationToken) + { + var response = await InternalAddAuthenticationFlowAndExecutionToAuthenticationFlowAsync(realm, flowAlias, dataWithAliasTypeProviderDescription, cancellationToken).ConfigureAwait(false); + var locationPathAndQuery = response.ResponseMessage.Headers.Location?.PathAndQuery; + return locationPathAndQuery != null ? locationPathAndQuery.Substring(locationPathAndQuery.LastIndexOf("/", StringComparison.Ordinal) + 1) : null; + } + + private async Task InternalAddAuthenticationFlowAndExecutionToAuthenticationFlowAsync(string realm, string flowAlias, IDictionary dataWithAliasTypeProviderDescription, CancellationToken cancellationToken) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/authentication/flows/") .AppendPathSegment(flowAlias, true) .AppendPathSegment("/executions/flow") - .PostJsonAsync(dataWithAliasTypeProviderDescription) + .PostJsonAsync(dataWithAliasTypeProviderDescription, cancellationToken) .ConfigureAwait(false); public async Task GetAuthenticationFlowByIdAsync(string realm, string flowId) => @@ -211,22 +231,22 @@ public async Task GetAuthenticationFlowByIdAsync(string real .GetJsonAsync() .ConfigureAwait(false); - public async Task UpdateAuthenticationFlowAsync(string realm, string flowId, AuthenticationFlow authenticationFlow) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task UpdateAuthenticationFlowAsync(string realm, string flowId, AuthenticationFlow authenticationFlow, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/authentication/flows/") .AppendPathSegment(flowId, true) - .PutJsonAsync(authenticationFlow) + .PutJsonAsync(authenticationFlow, cancellationToken) .ConfigureAwait(false); - public async Task DeleteAuthenticationFlowAsync(string realm, string flowId) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task DeleteAuthenticationFlowAsync(string realm, string flowId, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/authentication/flows/") .AppendPathSegment(flowId, true) - .DeleteAsync() + .DeleteAsync(cancellationToken) .ConfigureAwait(false); public async Task>> GetFormActionProvidersAsync(string realm) => diff --git a/src/keycloak/Keycloak.Library/ClientRoleMappings/KeycloakClient.cs b/src/keycloak/Keycloak.Library/ClientRoleMappings/KeycloakClient.cs index 4f8dc57e7a..f66cb98c5b 100644 --- a/src/keycloak/Keycloak.Library/ClientRoleMappings/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/ClientRoleMappings/KeycloakClient.cs @@ -88,37 +88,37 @@ public async Task> GetEffectiveClientRoleMappingsForGroupAsync .GetJsonAsync>() .ConfigureAwait(false); - public async Task AddClientRoleMappingsToUserAsync(string realm, string userId, string clientId, IEnumerable roles) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task AddClientRoleMappingsToUserAsync(string realm, string userId, string clientId, IEnumerable roles, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/users/") .AppendPathSegment(userId, true) .AppendPathSegment("/role-mappings/clients/") .AppendPathSegment(clientId, true) - .PostJsonAsync(roles) + .PostJsonAsync(roles, cancellationToken) .ConfigureAwait(false); - public async Task> GetClientRoleMappingsForUserAsync(string realm, string userId, string clientId) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task> GetClientRoleMappingsForUserAsync(string realm, string userId, string clientId, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/users/") .AppendPathSegment(userId, true) .AppendPathSegment("/role-mappings/clients/") .AppendPathSegment(clientId, true) - .GetJsonAsync>() + .GetJsonAsync>(cancellationToken) .ConfigureAwait(false); - public async Task DeleteClientRoleMappingsFromUserAsync(string realm, string userId, string clientId, IEnumerable roles) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task DeleteClientRoleMappingsFromUserAsync(string realm, string userId, string clientId, IEnumerable roles, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/users/") .AppendPathSegment(userId, true) .AppendPathSegment("/role-mappings/clients/") .AppendPathSegment(clientId, true) - .SendJsonAsync(HttpMethod.Delete, roles) + .SendJsonAsync(HttpMethod.Delete, roles, cancellationToken) .ConfigureAwait(false); public async Task> GetAvailableClientRoleMappingsForUserAsync(string realm, string userId, string clientId) => diff --git a/src/keycloak/Keycloak.Library/ClientScopes/KeycloakClient.cs b/src/keycloak/Keycloak.Library/ClientScopes/KeycloakClient.cs index 690a308ac6..9fcba4326b 100644 --- a/src/keycloak/Keycloak.Library/ClientScopes/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/ClientScopes/KeycloakClient.cs @@ -31,20 +31,20 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library; public partial class KeycloakClient { - public async Task CreateClientScopeAsync(string realm, ClientScope clientScope) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task CreateClientScopeAsync(string realm, ClientScope clientScope, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/client-scopes") - .PostJsonAsync(clientScope) + .PostJsonAsync(clientScope, cancellationToken) .ConfigureAwait(false); - public async Task> GetClientScopesAsync(string realm) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task> GetClientScopesAsync(string realm, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/client-scopes") - .GetJsonAsync>() + .GetJsonAsync>(cancellationToken) .ConfigureAwait(false); public async Task GetClientScopeAsync(string realm, string clientScopeId) => @@ -56,21 +56,21 @@ public async Task GetClientScopeAsync(string realm, string clientSc .GetJsonAsync() .ConfigureAwait(false); - public async Task UpdateClientScopeAsync(string realm, string clientScopeId, ClientScope clientScope) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task UpdateClientScopeAsync(string realm, string clientScopeId, ClientScope clientScope, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/client-scopes/") .AppendPathSegment(clientScopeId, true) - .PutJsonAsync(clientScope) + .PutJsonAsync(clientScope, cancellationToken) .ConfigureAwait(false); - public async Task DeleteClientScopeAsync(string realm, string clientScopeId) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task DeleteClientScopeAsync(string realm, string clientScopeId, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/client-scopes/") .AppendPathSegment(clientScopeId, true) - .DeleteAsync() + .DeleteAsync(cancellationToken) .ConfigureAwait(false); } diff --git a/src/keycloak/Keycloak.Library/Clients/KeycloakClient.cs b/src/keycloak/Keycloak.Library/Clients/KeycloakClient.cs index e22f490f24..47f5306eed 100644 --- a/src/keycloak/Keycloak.Library/Clients/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/Clients/KeycloakClient.cs @@ -36,26 +36,26 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library; public partial class KeycloakClient { - public async Task CreateClientAsync(string realm, Client client) => - await InternalCreateClientAsync(realm, client).ConfigureAwait(false); + public async Task CreateClientAsync(string realm, Client client, CancellationToken cancellationToken = default) => + await InternalCreateClientAsync(realm, client, cancellationToken).ConfigureAwait(false); - public async Task CreateClientAndRetrieveClientIdAsync(string realm, Client client) + public async Task CreateClientAndRetrieveClientIdAsync(string realm, Client client, CancellationToken cancellationToken = default) { - var response = await InternalCreateClientAsync(realm, client).ConfigureAwait(false); + var response = await InternalCreateClientAsync(realm, client, cancellationToken).ConfigureAwait(false); var locationPathAndQuery = response.ResponseMessage.Headers.Location?.PathAndQuery; return locationPathAndQuery?.Substring(locationPathAndQuery.LastIndexOf("/", StringComparison.Ordinal) + 1); } - private async Task InternalCreateClientAsync(string realm, Client client) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + private async Task InternalCreateClientAsync(string realm, Client client, CancellationToken cancellationToken) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/clients") - .PostJsonAsync(client) + .PostJsonAsync(client, cancellationToken) .ConfigureAwait(false); - public async Task> GetClientsAsync(string realm, string? clientId = null, bool? viewableOnly = null) + public async Task> GetClientsAsync(string realm, string? clientId = null, bool? viewableOnly = null, CancellationToken cancellationToken = default) { var queryParams = new Dictionary { @@ -63,12 +63,12 @@ public async Task> GetClientsAsync(string realm, string? cli [nameof(viewableOnly)] = viewableOnly }; - return await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + return await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/clients") .SetQueryParams(queryParams) - .GetJsonAsync>() + .GetJsonAsync>(cancellationToken) .ConfigureAwait(false); } @@ -81,13 +81,13 @@ public async Task GetClientAsync(string realm, string clientId) => .GetJsonAsync() .ConfigureAwait(false); - public async Task UpdateClientAsync(string realm, string clientId, Client client) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task UpdateClientAsync(string realm, string clientId, Client client, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/clients/") .AppendPathSegment(clientId, true) - .PutJsonAsync(client) + .PutJsonAsync(client, cancellationToken) .ConfigureAwait(false); public async Task DeleteClientAsync(string realm, string clientId) => diff --git a/src/keycloak/Keycloak.Library/Common/Extensions/FlurlRequestExtensions.cs b/src/keycloak/Keycloak.Library/Common/Extensions/FlurlRequestExtensions.cs index f9b193da5b..016362b766 100644 --- a/src/keycloak/Keycloak.Library/Common/Extensions/FlurlRequestExtensions.cs +++ b/src/keycloak/Keycloak.Library/Common/Extensions/FlurlRequestExtensions.cs @@ -31,7 +31,7 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Common.Extensions public static class FlurlRequestExtensions { - private static async Task GetAccessTokenAsync(string url, string realm, string userName, string password) + private static async Task GetAccessTokenAsync(string url, string realm, string userName, string password, CancellationToken cancellationToken) { var result = await url .AppendPathSegment($"/auth/realms/{realm}/protocol/openid-connect/token") @@ -42,7 +42,8 @@ private static async Task GetAccessTokenAsync(string url, string realm, new KeyValuePair("username", userName), new KeyValuePair("password", password), new KeyValuePair("client_id", "admin-cli") - }) + }, + cancellationToken) .ReceiveJson().ConfigureAwait(false); string accessToken = result @@ -51,7 +52,7 @@ private static async Task GetAccessTokenAsync(string url, string realm, return accessToken; } - private static async Task GetAccessTokenWithClientIdAsync(string url, string realm, string clientSecret, string? clientId) + private static async Task GetAccessTokenWithClientIdAsync(string url, string realm, string clientSecret, string? clientId, CancellationToken cancellationToken) { var result = await url .AppendPathSegment($"/auth/realms/{realm}/protocol/openid-connect/token") @@ -61,7 +62,8 @@ private static async Task GetAccessTokenWithClientIdAsync(string url, st new KeyValuePair("grant_type", "client_credentials"), new KeyValuePair("client_secret", clientSecret), new KeyValuePair("client_id", clientId ?? "admin-cli") - }) + }, + cancellationToken) .ReceiveJson().ConfigureAwait(false); string accessToken = result @@ -70,20 +72,20 @@ private static async Task GetAccessTokenWithClientIdAsync(string url, st return accessToken; } - public static async Task WithAuthenticationAsync(this IFlurlRequest request, Func>? getTokenAsync, string url, string realm, string? userName, string? password, string? clientSecret, string? clientId) + public static async Task WithAuthenticationAsync(this IFlurlRequest request, Func>? getTokenAsync, string url, string realm, string? userName, string? password, string? clientSecret, string? clientId, CancellationToken cancellationToken) { string? token; if (getTokenAsync != null) { - token = await getTokenAsync().ConfigureAwait(false); + token = await getTokenAsync(cancellationToken).ConfigureAwait(false); } else if (clientSecret != null) { - token = await GetAccessTokenWithClientIdAsync(url, realm, clientSecret, clientId).ConfigureAwait(false); + token = await GetAccessTokenWithClientIdAsync(url, realm, clientSecret, clientId, cancellationToken).ConfigureAwait(false); } else if (userName != null) { - token = await GetAccessTokenAsync(url, realm, userName, password ?? "").ConfigureAwait(false); + token = await GetAccessTokenAsync(url, realm, userName, password ?? "", cancellationToken).ConfigureAwait(false); } else { diff --git a/src/keycloak/Keycloak.Library/IdentityProviders/KeycloakClient.cs b/src/keycloak/Keycloak.Library/IdentityProviders/KeycloakClient.cs index 2fa6266aed..9f60c6e4d9 100644 --- a/src/keycloak/Keycloak.Library/IdentityProviders/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/IdentityProviders/KeycloakClient.cs @@ -54,12 +54,12 @@ public async Task> ImportIdentityProviderFromUrlAsyn .ReceiveJson>() .ConfigureAwait(false); - public async Task CreateIdentityProviderAsync(string realm, IdentityProvider identityProvider) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task CreateIdentityProviderAsync(string realm, IdentityProvider identityProvider, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/identity-provider/instances") - .PostJsonAsync(identityProvider) + .PostJsonAsync(identityProvider, cancellationToken) .ConfigureAwait(false); public async Task> GetIdentityProviderInstancesAsync(string realm) => @@ -70,13 +70,13 @@ public async Task> GetIdentityProviderInstancesAsy .GetJsonAsync>() .ConfigureAwait(false); - public async Task GetIdentityProviderAsync(string realm, string identityProviderAlias) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task GetIdentityProviderAsync(string realm, string identityProviderAlias, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/identity-provider/instances/") .AppendPathSegment(identityProviderAlias, true) - .GetJsonAsync() + .GetJsonAsync(cancellationToken) .ConfigureAwait(false); /// @@ -95,13 +95,13 @@ public async Task GetIdentityProviderTokenAsync(string re .GetJsonAsync() .ConfigureAwait(false); - public async Task UpdateIdentityProviderAsync(string realm, string identityProviderAlias, IdentityProvider identityProvider) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task UpdateIdentityProviderAsync(string realm, string identityProviderAlias, IdentityProvider identityProvider, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/identity-provider/instances/") .AppendPathSegment(identityProviderAlias, true) - .PutJsonAsync(identityProvider) + .PutJsonAsync(identityProvider, cancellationToken) .ConfigureAwait(false); public async Task DeleteIdentityProviderAsync(string realm, string identityProviderAlias) => @@ -154,24 +154,24 @@ public async Task> GetIdentityProviderMapperTypesAsy .GetJsonAsync>() .ConfigureAwait(false); - public async Task AddIdentityProviderMapperAsync(string realm, string identityProviderAlias, IdentityProviderMapper identityProviderMapper) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task AddIdentityProviderMapperAsync(string realm, string identityProviderAlias, IdentityProviderMapper identityProviderMapper, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/identity-provider/instances/") .AppendPathSegment(identityProviderAlias, true) .AppendPathSegment("/mappers") - .PostJsonAsync(identityProviderMapper) + .PostJsonAsync(identityProviderMapper, cancellationToken) .ConfigureAwait(false); - public async Task> GetIdentityProviderMappersAsync(string realm, string identityProviderAlias) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task> GetIdentityProviderMappersAsync(string realm, string identityProviderAlias, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/identity-provider/instances/") .AppendPathSegment(identityProviderAlias, true) .AppendPathSegment("/mappers") - .GetJsonAsync>() + .GetJsonAsync>(cancellationToken) .ConfigureAwait(false); public async Task GetIdentityProviderMapperByIdAsync(string realm, string identityProviderAlias, string mapperId) => @@ -185,26 +185,26 @@ public async Task GetIdentityProviderMapperByIdAsync(str .GetJsonAsync() .ConfigureAwait(false); - public async Task UpdateIdentityProviderMapperAsync(string realm, string identityProviderAlias, string mapperId, IdentityProviderMapper identityProviderMapper) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task UpdateIdentityProviderMapperAsync(string realm, string identityProviderAlias, string mapperId, IdentityProviderMapper identityProviderMapper, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/identity-provider/instances/") .AppendPathSegment(identityProviderAlias, true) .AppendPathSegment("/mappers/") .AppendPathSegment(mapperId, true) - .PutJsonAsync(identityProviderMapper) + .PutJsonAsync(identityProviderMapper, cancellationToken) .ConfigureAwait(false); - public async Task DeleteIdentityProviderMapperAsync(string realm, string identityProviderAlias, string mapperId) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task DeleteIdentityProviderMapperAsync(string realm, string identityProviderAlias, string mapperId, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/identity-provider/instances/") .AppendPathSegment(identityProviderAlias, true) .AppendPathSegment("/mappers/") .AppendPathSegment(mapperId, true) - .DeleteAsync() + .DeleteAsync(cancellationToken) .ConfigureAwait(false); public async Task GetIdentityProviderByProviderIdAsync(string realm, string providerId) => diff --git a/src/keycloak/Keycloak.Library/KeycloakClient.cs b/src/keycloak/Keycloak.Library/KeycloakClient.cs index 2120f92509..c34f224e7f 100644 --- a/src/keycloak/Keycloak.Library/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/KeycloakClient.cs @@ -45,7 +45,7 @@ public partial class KeycloakClient private readonly string? _userName; private readonly string? _password; private readonly string? _clientSecret; - private readonly Func>? _getTokenAsync; + private readonly Func>? _getTokenAsync; private readonly string? _authRealm; private readonly string? _clientId; @@ -75,11 +75,11 @@ private KeycloakClient(string url, string? userName, string? password, string? a public KeycloakClient(string url, Func getToken, string? authRealm = null) : this(url) { - _getTokenAsync = () => Task.FromResult(getToken()); + _getTokenAsync = (_) => Task.FromResult(getToken()); _authRealm = authRealm; } - public KeycloakClient(string url, Func> getTokenAsync, string? authRealm = null) + public KeycloakClient(string url, Func> getTokenAsync, string? authRealm = null) : this(url) { _getTokenAsync = getTokenAsync; @@ -96,8 +96,8 @@ public void SetSerializer(ISerializer serializer) _serializer = serializer ?? throw new ArgumentNullException(nameof(serializer)); } - private Task GetBaseUrlAsync(string targetRealm) => new Url(_url) + private Task GetBaseUrlAsync(string targetRealm, CancellationToken cancellationToken = default) => new Url(_url) .AppendPathSegment("/auth") .ConfigureRequest(settings => settings.JsonSerializer = _serializer) - .WithAuthenticationAsync(_getTokenAsync, _url, _authRealm ?? targetRealm, _userName, _password, _clientSecret, _clientId); + .WithAuthenticationAsync(_getTokenAsync, _url, _authRealm ?? targetRealm, _userName, _password, _clientSecret, _clientId, cancellationToken); } diff --git a/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationExecutionBase.cs b/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationExecutionBase.cs index c7fd4a0c46..7d81597946 100644 --- a/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationExecutionBase.cs +++ b/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationExecutionBase.cs @@ -31,9 +31,9 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Authentica public abstract class AuthenticationExecutionBase { [JsonProperty("authenticator")] - public string Authenticator { get; set; } + public string? Authenticator { get; set; } [JsonProperty("authenticatorConfig")] - public string AuthenticatorConfig { get; set; } + public string? AuthenticatorConfig { get; set; } [JsonProperty("authenticatorFlow")] public bool? AuthenticatorFlow { get; set; } [JsonProperty("autheticatorFlow")] @@ -41,5 +41,5 @@ public abstract class AuthenticationExecutionBase [JsonProperty("priority")] public int? Priority { get; set; } [JsonProperty("requirement")] - public string Requirement { get; set; } + public string? Requirement { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationExecutionById.cs b/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationExecutionById.cs index c350e67ec1..d0d676e20c 100644 --- a/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationExecutionById.cs +++ b/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationExecutionById.cs @@ -31,17 +31,17 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Authentica public class AuthenticationExecutionById { [JsonProperty("id")] - public string Id { get; set; } + public string? Id { get; set; } [JsonProperty("authenticator")] - public string Authenticator { get; set; } + public string? Authenticator { get; set; } [JsonProperty("authenticatorFlow")] public bool? AuthenticatorFlow { get; set; } [JsonProperty("requirement")] - public string Requirement { get; set; } + public string? Requirement { get; set; } [JsonProperty("priority")] public int? Priority { get; set; } [JsonProperty("parentFlow")] - public string ParentFlow { get; set; } + public string? ParentFlow { get; set; } [JsonProperty("optional")] public bool? Optional { get; set; } [JsonProperty("enabled")] diff --git a/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationExecutionExport.cs b/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationExecutionExport.cs index 8e45249597..1576623621 100644 --- a/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationExecutionExport.cs +++ b/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationExecutionExport.cs @@ -31,7 +31,7 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Authentica public class AuthenticationExecutionExport : AuthenticationExecutionBase { [JsonProperty("flowAlias")] - public string FlowAlias { get; set; } + public string? FlowAlias { get; set; } [JsonProperty("userSetupAllowed")] public bool? UserSetupAllowed { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationExecutionInfo.cs b/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationExecutionInfo.cs index a6095fffd1..8497ba2504 100644 --- a/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationExecutionInfo.cs +++ b/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationExecutionInfo.cs @@ -31,27 +31,29 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Authentica public class AuthenticationExecutionInfo { [JsonProperty("alias")] - public string Alias { get; set; } + public string? Alias { get; set; } [JsonProperty("authenticationConfig")] - public string AuthenticationConfig { get; set; } + public string? AuthenticationConfig { get; set; } [JsonProperty("authenticationFlow")] public bool? AuthenticationFlow { get; set; } [JsonProperty("configurable")] public bool? Configurable { get; set; } + [JsonProperty("description")] + public string? Description { get; set; } [JsonProperty("displayName")] - public string DisplayName { get; set; } + public string? DisplayName { get; set; } [JsonProperty("flowId")] - public string FlowId { get; set; } + public string? FlowId { get; set; } [JsonProperty("id")] - public string Id { get; set; } + public string? Id { get; set; } [JsonProperty("index")] public int? Index { get; set; } [JsonProperty("level")] public int? Level { get; set; } [JsonProperty("providerId")] - public string ProviderId { get; set; } + public string? ProviderId { get; set; } [JsonProperty("requirement")] - public string Requirement { get; set; } + public string? Requirement { get; set; } [JsonProperty("requirementChoices")] - public IEnumerable RequirementChoices { get; set; } + public IEnumerable? RequirementChoices { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationFlow.cs b/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationFlow.cs index 68ea55caf8..737d605190 100644 --- a/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationFlow.cs +++ b/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationFlow.cs @@ -31,17 +31,17 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Authentica public class AuthenticationFlow { [JsonProperty("alias")] - public string Alias { get; set; } + public string? Alias { get; set; } [JsonProperty("authenticationExecutions")] - public IEnumerable AuthenticationExecutions { get; set; } + public IEnumerable? AuthenticationExecutions { get; set; } [JsonProperty("builtIn")] public bool? BuiltIn { get; set; } [JsonProperty("description")] - public string Description { get; set; } + public string? Description { get; set; } [JsonProperty("id")] - public string Id { get; set; } + public string? Id { get; set; } [JsonProperty("providerId")] - public string ProviderId { get; set; } + public string? ProviderId { get; set; } [JsonProperty("topLevel")] public bool? TopLevel { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationFlowExecution.cs b/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationFlowExecution.cs index 63f142baa7..63caddea43 100644 --- a/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationFlowExecution.cs +++ b/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticationFlowExecution.cs @@ -31,17 +31,19 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Authentica public class AuthenticationFlowExecution { [JsonProperty("id")] - public string Id { get; set; } + public string? Id { get; set; } [JsonProperty("requirement")] - public string Requirement { get; set; } + public string? Requirement { get; set; } [JsonProperty("displayName")] - public string DisplayName { get; set; } + public string? DisplayName { get; set; } + [JsonProperty("description")] + public string? Description { get; set; } [JsonProperty("requirementChoices")] - public IEnumerable RequirementChoices { get; set; } + public IEnumerable? RequirementChoices { get; set; } [JsonProperty("configurable")] public bool? Configurable { get; set; } [JsonProperty("providerId")] - public string ProviderId { get; set; } + public string? ProviderId { get; set; } [JsonProperty("level")] public int? Level { get; set; } [JsonProperty("index")] @@ -49,5 +51,7 @@ public class AuthenticationFlowExecution [JsonProperty("authenticationFlow")] public bool? AuthenticationFlow { get; set; } [JsonProperty("flowId")] - public string FlowId { get; set; } + public string? FlowId { get; set; } + [JsonProperty("authenticationConfig")] + public string? AuthenticationConfig { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticatorConfig.cs b/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticatorConfig.cs index 8f1c9bf2d3..dd20c91022 100644 --- a/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticatorConfig.cs +++ b/src/keycloak/Keycloak.Library/Models/AuthenticationManagement/AuthenticatorConfig.cs @@ -31,9 +31,9 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Authentica public class AuthenticatorConfig { [JsonProperty("alias")] - public string Alias { get; set; } + public string? Alias { get; set; } [JsonProperty("config")] - public IDictionary Config { get; set; } + public IDictionary? Config { get; set; } [JsonProperty("id")] - public string Id { get; set; } + public string? Id { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/ClientScopes/Attributes.cs b/src/keycloak/Keycloak.Library/Models/ClientScopes/Attributes.cs index 9a31cef664..201978245c 100644 --- a/src/keycloak/Keycloak.Library/Models/ClientScopes/Attributes.cs +++ b/src/keycloak/Keycloak.Library/Models/ClientScopes/Attributes.cs @@ -30,10 +30,10 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.ClientScop public class Attributes { - [JsonProperty("consentscreentext")] - public string ConsentScreenText { get; set; } - [JsonProperty("displayonconsentscreen")] - public string DisplayOnConsentScreen { get; set; } - [JsonProperty("includeintokenscope")] - public string IncludeInTokenScope { get; set; } + [JsonProperty("consent.screen.text")] + public string? ConsentScreenText { get; set; } + [JsonProperty("display.on.consent.screen")] + public string? DisplayOnConsentScreen { get; set; } + [JsonProperty("include.in.token.scope")] + public string? IncludeInTokenScope { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/ClientScopes/ClientScope.cs b/src/keycloak/Keycloak.Library/Models/ClientScopes/ClientScope.cs index d260ff8ab7..69a47d049c 100644 --- a/src/keycloak/Keycloak.Library/Models/ClientScopes/ClientScope.cs +++ b/src/keycloak/Keycloak.Library/Models/ClientScopes/ClientScope.cs @@ -32,15 +32,15 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.ClientScop public class ClientScope { [JsonProperty("id")] - public string Id { get; set; } + public string? Id { get; set; } [JsonProperty("name")] - public string Name { get; set; } + public string? Name { get; set; } [JsonProperty("description")] - public string Description { get; set; } + public string? Description { get; set; } [JsonProperty("protocol")] - public string Protocol { get; set; } + public string? Protocol { get; set; } [JsonProperty("attributes")] - public Attributes Attributes { get; set; } + public Attributes? Attributes { get; set; } [JsonProperty("protocolMappers")] - public IEnumerable ProtocolMappers { get; set; } + public IEnumerable? ProtocolMappers { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/Clients/Client.cs b/src/keycloak/Keycloak.Library/Models/Clients/Client.cs index 3b381f09b7..a58606ad8c 100644 --- a/src/keycloak/Keycloak.Library/Models/Clients/Client.cs +++ b/src/keycloak/Keycloak.Library/Models/Clients/Client.cs @@ -31,15 +31,17 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Clients; public class Client { [JsonProperty("id")] - public string Id { get; set; } + public string? Id { get; set; } [JsonProperty("clientId")] - public string ClientId { get; set; } + public string? ClientId { get; set; } [JsonProperty("rootUrl")] - public string RootUrl { get; set; } + public string? RootUrl { get; set; } [JsonProperty("name")] - public string Name { get; set; } + public string? Name { get; set; } + [JsonProperty("description")] + public string? Description { get; set; } [JsonProperty("baseUrl")] - public string BaseUrl { get; set; } + public string? BaseUrl { get; set; } [JsonProperty("surrogateAuthRequired")] public bool? SurrogateAuthRequired { get; set; } [JsonProperty("enabled")] @@ -47,11 +49,11 @@ public class Client [JsonProperty("alwaysDisplayInConsole")] public bool? AlwaysDisplayInConsole { get; set; } [JsonProperty("clientAuthenticatorType")] - public string ClientAuthenticatorType { get; set; } + public string? ClientAuthenticatorType { get; set; } [JsonProperty("redirectUris")] - public IEnumerable RedirectUris { get; set; } + public IEnumerable? RedirectUris { get; set; } [JsonProperty("webOrigins")] - public IEnumerable WebOrigins { get; set; } + public IEnumerable? WebOrigins { get; set; } [JsonProperty("notBefore")] public int? NotBefore { get; set; } [JsonProperty("bearerOnly")] @@ -71,25 +73,25 @@ public class Client [JsonProperty("frontchannelLogout")] public bool? FrontChannelLogout { get; set; } [JsonProperty("protocol")] - public string Protocol { get; set; } + public string? Protocol { get; set; } [JsonProperty("attributes")] - public IDictionary Attributes { get; set; } + public IDictionary? Attributes { get; set; } [JsonProperty("authenticationFlowBindingOverrides")] - public IDictionary AuthenticationFlowBindingOverrides { get; set; } + public IDictionary? AuthenticationFlowBindingOverrides { get; set; } [JsonProperty("fullScopeAllowed")] public bool? FullScopeAllowed { get; set; } [JsonProperty("nodeReRegistrationTimeout")] public int? NodeReregistrationTimeout { get; set; } [JsonProperty("protocolMappers")] - public IEnumerable ProtocolMappers { get; set; } + public IEnumerable? ProtocolMappers { get; set; } [JsonProperty("defaultClientScopes")] - public IEnumerable DefaultClientScopes { get; set; } + public IEnumerable? DefaultClientScopes { get; set; } [JsonProperty("optionalClientScopes")] - public IEnumerable OptionalClientScopes { get; set; } + public IEnumerable? OptionalClientScopes { get; set; } [JsonProperty("access")] - public ClientAccess Access { get; set; } + public ClientAccess? Access { get; set; } [JsonProperty("secret")] - public string Secret { get; set; } + public string? Secret { get; set; } [JsonProperty("authorizationServicesEnabled")] public bool? AuthorizationServicesEnabled { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/Clients/ClientConfig.cs b/src/keycloak/Keycloak.Library/Models/Clients/ClientConfig.cs index 5a72062a67..ab200e302e 100644 --- a/src/keycloak/Keycloak.Library/Models/Clients/ClientConfig.cs +++ b/src/keycloak/Keycloak.Library/Models/Clients/ClientConfig.cs @@ -30,20 +30,20 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Clients; public class ClientConfig { - [JsonProperty("userinfotokenclaim")] - public string UserInfoTokenClaim { get; set; } + [JsonProperty("userinfo.token.claim")] + public string? UserInfoTokenClaim { get; set; } [JsonProperty("user.attribute")] - public string UserAttribute { get; set; } - [JsonProperty("idtokenclaim")] - public string IdTokenClaim { get; set; } - [JsonProperty("accesstokenclaim")] - public string AccessTokenClaim { get; set; } - [JsonProperty("claimname")] - public string ClaimName { get; set; } - [JsonProperty("jsonTypelabel")] - public string JsonTypelabel { get; set; } + public string? UserAttribute { get; set; } + [JsonProperty("id.token.claim")] + public string? IdTokenClaim { get; set; } + [JsonProperty("access.token.claim")] + public string? AccessTokenClaim { get; set; } + [JsonProperty("claim.name")] + public string? ClaimName { get; set; } + [JsonProperty("jsonType.label")] + public string? JsonTypelabel { get; set; } [JsonProperty("friendly.name")] - public string FriendlyName { get; set; } + public string? FriendlyName { get; set; } [JsonProperty("attribute.name")] - public string AttributeName { get; set; } + public string? AttributeName { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/Clients/ClientProtocolMapper.cs b/src/keycloak/Keycloak.Library/Models/Clients/ClientProtocolMapper.cs index ab2e13a58a..4f2c70eb9e 100644 --- a/src/keycloak/Keycloak.Library/Models/Clients/ClientProtocolMapper.cs +++ b/src/keycloak/Keycloak.Library/Models/Clients/ClientProtocolMapper.cs @@ -31,15 +31,15 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Clients; public class ClientProtocolMapper { [JsonProperty("id")] - public string Id { get; set; } + public string? Id { get; set; } [JsonProperty("name")] - public string Name { get; set; } + public string? Name { get; set; } [JsonProperty("protocol")] - public string Protocol { get; set; } + public string? Protocol { get; set; } [JsonProperty("protocolMapper")] - public string ProtocolMapper { get; set; } + public string? ProtocolMapper { get; set; } [JsonProperty("consentRequired")] public bool? ConsentRequired { get; set; } [JsonProperty("config")] - public ClientConfig Config { get; set; } + public ClientConfig? Config { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/IdentityProviders/Config.cs b/src/keycloak/Keycloak.Library/Models/IdentityProviders/Config.cs index b53017c454..bed7327c0a 100644 --- a/src/keycloak/Keycloak.Library/Models/IdentityProviders/Config.cs +++ b/src/keycloak/Keycloak.Library/Models/IdentityProviders/Config.cs @@ -31,75 +31,75 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.IdentityPr public class Config { [JsonProperty("hideOnLoginPage")] - public string HideOnLoginPage { get; set; } + public string? HideOnLoginPage { get; set; } [JsonProperty("clientSecret")] public string? ClientSecret { get; set; } [JsonProperty("clientId")] - public string ClientId { get; set; } + public string? ClientId { get; set; } [JsonProperty("disableUserInfo")] - public string DisableUserInfo { get; set; } + public string? DisableUserInfo { get; set; } [JsonProperty("useJwksUrl")] - public string UseJwksUrl { get; set; } + public string? UseJwksUrl { get; set; } [JsonProperty("tokenUrl")] - public string TokenUrl { get; set; } + public string? TokenUrl { get; set; } [JsonProperty("authorizationUrl")] - public string AuthorizationUrl { get; set; } + public string? AuthorizationUrl { get; set; } [JsonProperty("logoutUrl")] - public string LogoutUrl { get; set; } + public string? LogoutUrl { get; set; } [JsonProperty("jwksUrl")] - public string JwksUrl { get; set; } + public string? JwksUrl { get; set; } [JsonProperty("clientAuthMethod")] - public string ClientAuthMethod { get; set; } + public string? ClientAuthMethod { get; set; } [JsonProperty("clientAssertionSigningAlg")] public string? ClientAssertionSigningAlg { get; set; } [JsonProperty("syncMode")] - public string SyncMode { get; set; } + public string? SyncMode { get; set; } [JsonProperty("validateSignature")] - public string ValidateSignature { get; set; } + public string? ValidateSignature { get; set; } [JsonProperty("userInfoUrl")] - public string UserInfoUrl { get; set; } + public string? UserInfoUrl { get; set; } [JsonProperty("issuer")] - public string Issuer { get; set; } + public string? Issuer { get; set; } // for SAML: [JsonProperty("nameIDPolicyFormat")] - public string NameIDPolicyFormat { get; set; } + public string? NameIDPolicyFormat { get; set; } [JsonProperty("principalType")] - public string PrincipalType { get; set; } + public string? PrincipalType { get; set; } [JsonProperty("signatureAlgorithm")] - public string SignatureAlgorithm { get; set; } + public string? SignatureAlgorithm { get; set; } [JsonProperty("xmlSigKeyInfoKeyNameTransformer")] - public string XmlSigKeyInfoKeyNameTransformer { get; set; } + public string? XmlSigKeyInfoKeyNameTransformer { get; set; } [JsonProperty("allowCreate")] - public string AllowCreate { get; set; } + public string? AllowCreate { get; set; } [JsonProperty("entityId")] - public string EntityId { get; set; } + public string? EntityId { get; set; } [JsonProperty("authnContextComparisonType")] - public string AuthnContextComparisonType { get; set; } + public string? AuthnContextComparisonType { get; set; } [JsonProperty("backchannelSupported")] - public string BackchannelSupported { get; set; } + public string? BackchannelSupported { get; set; } [JsonProperty("postBindingResponse")] - public string PostBindingResponse { get; set; } + public string? PostBindingResponse { get; set; } [JsonProperty("postBindingAuthnRequest")] - public string PostBindingAuthnRequest { get; set; } + public string? PostBindingAuthnRequest { get; set; } [JsonProperty("postBindingLogout")] - public string PostBindingLogout { get; set; } + public string? PostBindingLogout { get; set; } [JsonProperty("wantAuthnRequestsSigned")] - public string WantAuthnRequestsSigned { get; set; } + public string? WantAuthnRequestsSigned { get; set; } [JsonProperty("wantAssertionsSigned")] - public string WantAssertionsSigned { get; set; } + public string? WantAssertionsSigned { get; set; } [JsonProperty("wantAssertionsEncrypted")] - public string WantAssertionsEncrypted { get; set; } + public string? WantAssertionsEncrypted { get; set; } [JsonProperty("forceAuthn")] - public string ForceAuthn { get; set; } + public string? ForceAuthn { get; set; } [JsonProperty("signSpMetadata")] - public string SignSpMetadata { get; set; } + public string? SignSpMetadata { get; set; } [JsonProperty("loginHint")] - public string LoginHint { get; set; } + public string? LoginHint { get; set; } [JsonProperty("singleSignOnServiceUrl")] - public string SingleSignOnServiceUrl { get; set; } + public string? SingleSignOnServiceUrl { get; set; } [JsonProperty("allowedClockSkew")] - public string AllowedClockSkew { get; set; } + public string? AllowedClockSkew { get; set; } [JsonProperty("attributeConsumingServiceIndex")] - public string AttributeConsumingServiceIndex { get; set; } + public string? AttributeConsumingServiceIndex { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/IdentityProviders/IdentityProvider.cs b/src/keycloak/Keycloak.Library/Models/IdentityProviders/IdentityProvider.cs index 0154942063..228642fee5 100644 --- a/src/keycloak/Keycloak.Library/Models/IdentityProviders/IdentityProvider.cs +++ b/src/keycloak/Keycloak.Library/Models/IdentityProviders/IdentityProvider.cs @@ -31,15 +31,15 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.IdentityPr public class IdentityProvider { [JsonProperty("alias")] - public string Alias { get; set; } + public string? Alias { get; set; } [JsonProperty("internalId")] - public string InternalId { get; set; } + public string? InternalId { get; set; } [JsonProperty("providerId")] - public string ProviderId { get; set; } + public string? ProviderId { get; set; } [JsonProperty("enabled")] public bool? Enabled { get; set; } [JsonProperty("updateProfileFirstLoginMode")] - public string UpdateProfileFirstLoginMode { get; set; } + public string? UpdateProfileFirstLoginMode { get; set; } [JsonProperty("trustEmail")] public bool? TrustEmail { get; set; } [JsonProperty("storeToken")] @@ -51,11 +51,11 @@ public class IdentityProvider [JsonProperty("linkOnly")] public bool? LinkOnly { get; set; } [JsonProperty("firstBrokerLoginFlowAlias")] - public string FirstBrokerLoginFlowAlias { get; set; } + public string? FirstBrokerLoginFlowAlias { get; set; } [JsonProperty("postBrokerLoginFlowAlias")] - public string PostBrokerLoginFlowAlias { get; set; } + public string? PostBrokerLoginFlowAlias { get; set; } [JsonProperty("displayName")] - public string DisplayName { get; set; } + public string? DisplayName { get; set; } [JsonProperty("config")] - public Config Config { get; set; } + public Config? Config { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/IdentityProviders/IdentityProviderMapper.cs b/src/keycloak/Keycloak.Library/Models/IdentityProviders/IdentityProviderMapper.cs index 63b5db5683..1796b9026d 100644 --- a/src/keycloak/Keycloak.Library/Models/IdentityProviders/IdentityProviderMapper.cs +++ b/src/keycloak/Keycloak.Library/Models/IdentityProviders/IdentityProviderMapper.cs @@ -31,14 +31,14 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.IdentityPr public class IdentityProviderMapper { [JsonProperty("config")] - public IDictionary Config { get; set; } + public IDictionary? Config { get; set; } [JsonProperty("id")] - public string Id { get; set; } + public string? Id { get; set; } [JsonProperty("identityProviderAlias")] - public string IdentityProviderAlias { get; set; } + public string? IdentityProviderAlias { get; set; } [JsonProperty("identityProviderMapper")] // ReSharper disable once InconsistentNaming - public string _IdentityProviderMapper { get; set; } + public string? _IdentityProviderMapper { get; set; } [JsonProperty("name")] - public string Name { get; set; } + public string? Name { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/ProtocolMappers/Config.cs b/src/keycloak/Keycloak.Library/Models/ProtocolMappers/Config.cs index 29bbef972c..692b21878b 100644 --- a/src/keycloak/Keycloak.Library/Models/ProtocolMappers/Config.cs +++ b/src/keycloak/Keycloak.Library/Models/ProtocolMappers/Config.cs @@ -31,37 +31,37 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.ProtocolMa public class Config { [JsonProperty("single")] - public string Single { get; set; } - [JsonProperty("attributenameformat")] - public string AttributeNameFormat { get; set; } - [JsonProperty("attributename")] - public string AttributeName { get; set; } + public string? Single { get; set; } + [JsonProperty("attribute.nameformat")] + public string? AttributeNameFormat { get; set; } + [JsonProperty("attribute.name")] + public string? AttributeName { get; set; } [JsonProperty("userinfo.token.claim")] - public string UserInfoTokenClaim { get; set; } + public string? UserInfoTokenClaim { get; set; } [JsonProperty("user.attribute")] - public string UserAttribute { get; set; } + public string? UserAttribute { get; set; } [JsonProperty("id.token.claim")] - public string IdTokenClaim { get; set; } + public string? IdTokenClaim { get; set; } [JsonProperty("access.token.claim")] - public string AccessTokenClaim { get; set; } + public string? AccessTokenClaim { get; set; } [JsonProperty("claim.name")] - public string ClaimName { get; set; } + public string? ClaimName { get; set; } [JsonProperty("jsonType.label")] - public string JsonTypelabel { get; set; } - [JsonProperty("userattributeformatted")] - public string UserAttributeFormatted { get; set; } - [JsonProperty("userattributecountry")] - public string UserAttributeCountry { get; set; } - [JsonProperty("userattributepostal_code")] - public string UserAttributePostalCode { get; set; } - [JsonProperty("userattributestreet")] - public string UserAttributeStreet { get; set; } - [JsonProperty("userattributeregion")] - public string UserAttributeRegion { get; set; } - [JsonProperty("userattributelocality")] - public string UserAttributeLocality { get; set; } + public string? JsonTypelabel { get; set; } + [JsonProperty("user.attribute.formatted")] + public string? UserAttributeFormatted { get; set; } + [JsonProperty("user.attribute.country")] + public string? UserAttributeCountry { get; set; } + [JsonProperty("user.attribute.postal_code")] + public string? UserAttributePostalCode { get; set; } + [JsonProperty("user.attribute.street")] + public string? UserAttributeStreet { get; set; } + [JsonProperty("user.attribute.region")] + public string? UserAttributeRegion { get; set; } + [JsonProperty("user.attribute.locality")] + public string? UserAttributeLocality { get; set; } [JsonProperty("included.client.audience")] - public string IncludedClientAudience { get; set; } + public string? IncludedClientAudience { get; set; } [JsonProperty("multivalued")] - public string Multivalued { get; set; } + public string? Multivalued { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/ProtocolMappers/ProtocolMapper.cs b/src/keycloak/Keycloak.Library/Models/ProtocolMappers/ProtocolMapper.cs index bb39e41fa6..305a6f10d3 100644 --- a/src/keycloak/Keycloak.Library/Models/ProtocolMappers/ProtocolMapper.cs +++ b/src/keycloak/Keycloak.Library/Models/ProtocolMappers/ProtocolMapper.cs @@ -31,16 +31,16 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.ProtocolMa public class ProtocolMapper { [JsonProperty("id")] - public string Id { get; set; } + public string? Id { get; set; } [JsonProperty("name")] - public string Name { get; set; } + public string? Name { get; set; } [JsonProperty("protocol")] - public string Protocol { get; set; } + public string? Protocol { get; set; } [JsonProperty("protocolMapper")] // ReSharper disable once InconsistentNaming - public string _ProtocolMapper { get; set; } + public string? _ProtocolMapper { get; set; } [JsonProperty("consentRequired")] public bool? ConsentRequired { get; set; } [JsonProperty("config")] - public Config Config { get; set; } + public Config? Config { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/RealmsAdmin/BrowserSecurityHeaders.cs b/src/keycloak/Keycloak.Library/Models/RealmsAdmin/BrowserSecurityHeaders.cs index a791301ff9..8cab2bd84a 100644 --- a/src/keycloak/Keycloak.Library/Models/RealmsAdmin/BrowserSecurityHeaders.cs +++ b/src/keycloak/Keycloak.Library/Models/RealmsAdmin/BrowserSecurityHeaders.cs @@ -31,17 +31,17 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.RealmsAdmi public class BrowserSecurityHeaders { [JsonProperty("contentSecurityPolicyReportOnly")] - public string ContentSecurityPolicyReportOnly { get; set; } + public string? ContentSecurityPolicyReportOnly { get; set; } [JsonProperty("xContentTypeOptions")] - public string XContentTypeOptions { get; set; } + public string? XContentTypeOptions { get; set; } [JsonProperty("xRobotsTag")] - public string XRobotsTag { get; set; } + public string? XRobotsTag { get; set; } [JsonProperty("xFrameOptions")] - public string XFrameOptions { get; set; } + public string? XFrameOptions { get; set; } [JsonProperty("xXSSProtection")] - public string XXssProtection { get; set; } + public string? XXssProtection { get; set; } [JsonProperty("contentSecurityPolicy")] - public string ContentSecurityPolicy { get; set; } + public string? ContentSecurityPolicy { get; set; } [JsonProperty("strictTransportSecurity")] - public string StrictTransportSecurity { get; set; } + public string? StrictTransportSecurity { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/RealmsAdmin/Config.cs b/src/keycloak/Keycloak.Library/Models/RealmsAdmin/Config.cs index e4af7d9c53..8c2513cb86 100644 --- a/src/keycloak/Keycloak.Library/Models/RealmsAdmin/Config.cs +++ b/src/keycloak/Keycloak.Library/Models/RealmsAdmin/Config.cs @@ -31,13 +31,13 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.RealmsAdmi public class Config { [JsonProperty("hideOnLoginPage")] - public string HideOnLoginPage { get; set; } + public string? HideOnLoginPage { get; set; } [JsonProperty("clientSecret")] - public string ClientSecret { get; set; } + public string? ClientSecret { get; set; } [JsonProperty("clientId")] - public string ClientId { get; set; } + public string? ClientId { get; set; } [JsonProperty("disableUserInfo")] - public string DisableUserInfo { get; set; } + public string? DisableUserInfo { get; set; } [JsonProperty("useJwksUrl")] - public string UseJwksUrl { get; set; } + public string? UseJwksUrl { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/RealmsAdmin/IdentityProvider.cs b/src/keycloak/Keycloak.Library/Models/RealmsAdmin/IdentityProvider.cs index 56531b8107..35cdc6778e 100644 --- a/src/keycloak/Keycloak.Library/Models/RealmsAdmin/IdentityProvider.cs +++ b/src/keycloak/Keycloak.Library/Models/RealmsAdmin/IdentityProvider.cs @@ -31,15 +31,17 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.RealmsAdmi public class IdentityProvider { [JsonProperty("alias")] - public string Alias { get; set; } + public string? Alias { get; set; } + [JsonProperty("displayName")] + public string? DisplayName { get; set; } [JsonProperty("internalId")] - public string InternalId { get; set; } + public string? InternalId { get; set; } [JsonProperty("providerId")] - public string ProviderId { get; set; } + public string? ProviderId { get; set; } [JsonProperty("enabled")] public bool? Enabled { get; set; } [JsonProperty("updateProfileFirstLoginMode")] - public string UpdateProfileFirstLoginMode { get; set; } + public string? UpdateProfileFirstLoginMode { get; set; } [JsonProperty("trustEmail")] public bool? TrustEmail { get; set; } [JsonProperty("storeToken")] @@ -51,7 +53,7 @@ public class IdentityProvider [JsonProperty("linkOnly")] public bool? LinkOnly { get; set; } [JsonProperty("firstBrokerLoginFlowAlias")] - public string FirstBrokerLoginFlowAlias { get; set; } + public string? FirstBrokerLoginFlowAlias { get; set; } [JsonProperty("config")] - public Config Config { get; set; } + public Config? Config { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/RealmsAdmin/Realm.cs b/src/keycloak/Keycloak.Library/Models/RealmsAdmin/Realm.cs index d89ca062a8..e86142df32 100644 --- a/src/keycloak/Keycloak.Library/Models/RealmsAdmin/Realm.cs +++ b/src/keycloak/Keycloak.Library/Models/RealmsAdmin/Realm.cs @@ -25,20 +25,25 @@ ********************************************************************************/ using Newtonsoft.Json; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Roles; namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.RealmsAdmin; public class Realm { [JsonProperty("id")] - public string Id { get; set; } + public string? Id { get; set; } [JsonProperty("realm")] // ReSharper disable once InconsistentNaming - public string _Realm { get; set; } + public string? _Realm { get; set; } [JsonProperty("displayName")] - public string DisplayName { get; set; } + public string? DisplayName { get; set; } + [JsonProperty("displayNameHtml")] + public string? DisplayNameHtml { get; set; } [JsonProperty("notBefore")] public int? NotBefore { get; set; } + [JsonProperty("defaultSignatureAlgorithm")] + public string? DefaultSignatureAlgorithm { get; set; } [JsonProperty("revokeRefreshToken")] public bool? RevokeRefreshToken { get; set; } [JsonProperty("refreshTokenMaxReuse")] @@ -74,7 +79,7 @@ public class Realm [JsonProperty("enabled")] public bool? Enabled { get; set; } [JsonProperty("sslRequired")] - public string SslRequired { get; set; } + public string? SslRequired { get; set; } [JsonProperty("registrationAllowed")] public bool? RegistrationAllowed { get; set; } [JsonProperty("registrationEmailAsUsername")] @@ -107,14 +112,14 @@ public class Realm public int? MaxDeltaTimeSeconds { get; set; } [JsonProperty("failureFactor")] public int? FailureFactor { get; set; } - [JsonProperty("defaultRoles")] - public IEnumerable DefaultRoles { get; set; } + [JsonProperty("defaultRole")] + public Role? DefaultRole { get; set; } [JsonProperty("requiredCredentials")] - public IEnumerable RequiredCredentials { get; set; } + public IEnumerable? RequiredCredentials { get; set; } [JsonProperty("otpPolicyType")] - public string OtpPolicyType { get; set; } + public string? OtpPolicyType { get; set; } [JsonProperty("otpPolicyAlgorithm")] - public string OtpPolicyAlgorithm { get; set; } + public string? OtpPolicyAlgorithm { get; set; } [JsonProperty("otpPolicyInitialCounter")] public int? OtpPolicyInitialCounter { get; set; } [JsonProperty("otpPolicyDigits")] @@ -124,46 +129,51 @@ public class Realm [JsonProperty("otpPolicyPeriod")] public int? OtpPolicyPeriod { get; set; } [JsonProperty("otpSupportedApplications")] - public IEnumerable OtpSupportedApplications { get; set; } + public IEnumerable? OtpSupportedApplications { get; set; } [JsonProperty("browserSecurityHeaders")] - public BrowserSecurityHeaders BrowserSecurityHeaders { get; set; } + public BrowserSecurityHeaders? BrowserSecurityHeaders { get; set; } [JsonProperty("smtpServer")] - public SmtpServer SmtpServer { get; set; } + public SmtpServer? SmtpServer { get; set; } + [JsonProperty("loginTheme")] + public string? LoginTheme { get; set; } + [JsonProperty("accountTheme")] + public string? AccountTheme { get; set; } + [JsonProperty("adminTheme")] + public string? AdminTheme { get; set; } + [JsonProperty("emailTheme")] + public string? EmailTheme { get; set; } [JsonProperty("eventsEnabled")] public bool? EventsEnabled { get; set; } [JsonProperty("eventsListeners")] - public IEnumerable EventsListeners { get; set; } + public IEnumerable? EventsListeners { get; set; } [JsonProperty("enabledEventTypes")] - public IEnumerable EnabledEventTypes { get; set; } + public IEnumerable? EnabledEventTypes { get; set; } [JsonProperty("adminEventsEnabled")] public bool? AdminEventsEnabled { get; set; } [JsonProperty("adminEventsDetailsEnabled")] public bool? AdminEventsDetailsEnabled { get; set; } [JsonProperty("identityProviders")] - public IEnumerable IdentityProviders { get; set; } + public IEnumerable? IdentityProviders { get; set; } [JsonProperty("internationalizationEnabled")] public bool? InternationalizationEnabled { get; set; } [JsonProperty("supportedLocales")] - public IEnumerable SupportedLocales { get; set; } + public IEnumerable? SupportedLocales { get; set; } [JsonProperty("browserFlow")] - public string BrowserFlow { get; set; } + public string? BrowserFlow { get; set; } [JsonProperty("registrationFlow")] - public string RegistrationFlow { get; set; } + public string? RegistrationFlow { get; set; } [JsonProperty("directGrantFlow")] - public string DirectGrantFlow { get; set; } + public string? DirectGrantFlow { get; set; } [JsonProperty("resetCredentialsFlow")] - public string ResetCredentialsFlow { get; set; } + public string? ResetCredentialsFlow { get; set; } [JsonProperty("clientAuthenticationFlow")] - public string ClientAuthenticationFlow { get; set; } + public string? ClientAuthenticationFlow { get; set; } [JsonProperty("dockerAuthenticationFlow")] - public string DockerAuthenticationFlow { get; set; } + public string? DockerAuthenticationFlow { get; set; } [JsonProperty("attributes")] - public Attributes Attributes { get; set; } + public IDictionary? Attributes { get; set; } [JsonProperty("userManagedAccessAllowed")] public bool? UserManagedAccessAllowed { get; set; } [JsonProperty("passwordPolicy")] - public string PasswordPolicy { get; set; } - - [JsonProperty("loginTheme")] - public string? LoginTheme { get; set; } + public string? PasswordPolicy { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/RealmsAdmin/SmtpServer.cs b/src/keycloak/Keycloak.Library/Models/RealmsAdmin/SmtpServer.cs index b7de666cb1..cc194a0138 100644 --- a/src/keycloak/Keycloak.Library/Models/RealmsAdmin/SmtpServer.cs +++ b/src/keycloak/Keycloak.Library/Models/RealmsAdmin/SmtpServer.cs @@ -31,27 +31,27 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.RealmsAdmi public class SmtpServer { [JsonProperty("host")] - public string Host { get; set; } + public string? Host { get; set; } [JsonProperty("ssl")] - public string Ssl { get; set; } + public string? Ssl { get; set; } [JsonProperty("starttls")] - public string StartTls { get; set; } + public string? StartTls { get; set; } [JsonProperty("user")] - public string User { get; set; } + public string? User { get; set; } [JsonProperty("password")] - public string Password { get; set; } + public string? Password { get; set; } [JsonProperty("auth")] - public string Auth { get; set; } + public string? Auth { get; set; } [JsonProperty("from")] - public string From { get; set; } + public string? From { get; set; } [JsonProperty("fromDisplayName")] - public string FromDisplayName { get; set; } + public string? FromDisplayName { get; set; } [JsonProperty("replyTo")] - public string ReplyTo { get; set; } + public string? ReplyTo { get; set; } [JsonProperty("replyToDisplayName")] - public string ReplyToDisplayName { get; set; } + public string? ReplyToDisplayName { get; set; } [JsonProperty("envelopeFrom")] - public string EnvelopeFrom { get; set; } + public string? EnvelopeFrom { get; set; } [JsonProperty("port")] - public string Port { get; set; } + public string? Port { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/Roles/Role.cs b/src/keycloak/Keycloak.Library/Models/Roles/Role.cs index b32e3714c6..b261cd2a3a 100644 --- a/src/keycloak/Keycloak.Library/Models/Roles/Role.cs +++ b/src/keycloak/Keycloak.Library/Models/Roles/Role.cs @@ -31,19 +31,19 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Roles; public class Role { [JsonProperty("id")] - public string Id { get; set; } + public string? Id { get; set; } [JsonProperty("name")] - public string Name { get; set; } + public string? Name { get; set; } [JsonProperty("description")] - public string Description { get; set; } + public string? Description { get; set; } [JsonProperty("composite")] public bool? Composite { get; set; } [JsonProperty("composites")] - public RoleComposite Composites { get; set; } + public RoleComposite? Composites { get; set; } [JsonProperty("clientRole")] public bool? ClientRole { get; set; } [JsonProperty("containerId")] - public string ContainerId { get; set; } + public string? ContainerId { get; set; } [JsonProperty("attributes")] - public IDictionary Attributes { get; set; } + public IDictionary>? Attributes { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/Roles/RoleComposite.cs b/src/keycloak/Keycloak.Library/Models/Roles/RoleComposite.cs index 5dadf54149..3bdc59e098 100644 --- a/src/keycloak/Keycloak.Library/Models/Roles/RoleComposite.cs +++ b/src/keycloak/Keycloak.Library/Models/Roles/RoleComposite.cs @@ -31,7 +31,7 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Roles; public class RoleComposite { [JsonProperty("client")] - public IDictionary Client { get; set; } + public IDictionary>? Client { get; set; } [JsonProperty("realm")] - public IEnumerable Realm { get; set; } + public IEnumerable? Realm { get; set; } } diff --git a/src/keycloak/Keycloak.Library/Models/Users/FederatedIdentity.cs b/src/keycloak/Keycloak.Library/Models/Users/FederatedIdentity.cs index 44b92d3667..df17fbc797 100644 --- a/src/keycloak/Keycloak.Library/Models/Users/FederatedIdentity.cs +++ b/src/keycloak/Keycloak.Library/Models/Users/FederatedIdentity.cs @@ -31,9 +31,9 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Users; public class FederatedIdentity { [JsonProperty("identityProvider")] - public string IdentityProvider { get; set; } = default!; + public string? IdentityProvider { get; set; } = default!; [JsonProperty("userId")] - public string UserId { get; set; } = default!; + public string? UserId { get; set; } = default!; [JsonProperty("userName")] - public string UserName { get; set; } = default!; + public string? UserName { get; set; } = default!; } diff --git a/src/keycloak/Keycloak.Library/Models/Users/User.cs b/src/keycloak/Keycloak.Library/Models/Users/User.cs index f1042b7b96..6c65ddf155 100644 --- a/src/keycloak/Keycloak.Library/Models/Users/User.cs +++ b/src/keycloak/Keycloak.Library/Models/Users/User.cs @@ -35,7 +35,7 @@ public class User [JsonProperty("createdTimestamp")] public long? CreatedTimestamp { get; set; } [JsonProperty("username")] - public string UserName { get; set; } = default!; + public string? UserName { get; set; } = default!; [JsonProperty("enabled")] public bool? Enabled { get; set; } [JsonProperty("totp")] @@ -61,7 +61,7 @@ public class User [JsonProperty("clientConsents")] public IEnumerable? ClientConsents { get; set; } [JsonProperty("clientRoles")] - public IDictionary? ClientRoles { get; set; } + public IDictionary>? ClientRoles { get; set; } [JsonProperty("credentials")] public IEnumerable? Credentials { get; set; } [JsonProperty("federatedIdentities")] diff --git a/src/keycloak/Keycloak.Library/ProtocolMappers/KeycloakClient.cs b/src/keycloak/Keycloak.Library/ProtocolMappers/KeycloakClient.cs index 6caec9544d..ddbd50e0f1 100644 --- a/src/keycloak/Keycloak.Library/ProtocolMappers/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/ProtocolMappers/KeycloakClient.cs @@ -41,14 +41,14 @@ public async Task CreateMultipleProtocolMappersAsync(string realm, string client .PostJsonAsync(protocolMapperRepresentations) .ConfigureAwait(false); - public async Task CreateProtocolMapperAsync(string realm, string clientScopeId, ProtocolMapper protocolMapperRepresentation) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task CreateProtocolMapperAsync(string realm, string clientScopeId, ProtocolMapper protocolMapperRepresentation, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/client-scopes/") .AppendPathSegment(clientScopeId, true) .AppendPathSegment("/protocol-mappers/models") - .PostJsonAsync(protocolMapperRepresentation) + .PostJsonAsync(protocolMapperRepresentation, cancellationToken) .ConfigureAwait(false); public async Task> GetProtocolMappersAsync(string realm, string clientScopeId) => @@ -72,26 +72,26 @@ public async Task GetProtocolMapperAsync(string realm, string cl .GetJsonAsync() .ConfigureAwait(false); - public async Task UpdateProtocolMapperAsync(string realm, string clientScopeId, string protocolMapperId, ProtocolMapper protocolMapperRepresentation) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task UpdateProtocolMapperAsync(string realm, string clientScopeId, string protocolMapperId, ProtocolMapper protocolMapperRepresentation, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/client-scopes/") .AppendPathSegment(clientScopeId, true) .AppendPathSegment("/protocol-mappers/models/") .AppendPathSegment(protocolMapperId, true) - .PutJsonAsync(protocolMapperRepresentation) + .PutJsonAsync(protocolMapperRepresentation, cancellationToken) .ConfigureAwait(false); - public async Task DeleteProtocolMapperAsync(string realm, string clientScopeId, string protocolMapperId) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task DeleteProtocolMapperAsync(string realm, string clientScopeId, string protocolMapperId, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/client-scopes/") .AppendPathSegment(clientScopeId, true) .AppendPathSegment("/protocol-mappers/models/") .AppendPathSegment(protocolMapperId, true) - .DeleteAsync() + .DeleteAsync(cancellationToken) .ConfigureAwait(false); public async Task> GetProtocolMappersByNameAsync(string realm, string clientScopeId, string protocol) => @@ -105,13 +105,35 @@ public async Task> GetProtocolMappersByNameAsync(str .GetJsonAsync>() .ConfigureAwait(false); - public async Task CreateClientProtocolMapperAsync(string realm, string clientId, ProtocolMapper protocolMapperRepresentation) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task CreateClientProtocolMapperAsync(string realm, string clientId, ProtocolMapper protocolMapperRepresentation, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/clients/") .AppendPathSegment(clientId, true) .AppendPathSegment("/protocol-mappers/models") - .PostJsonAsync(protocolMapperRepresentation) + .PostJsonAsync(protocolMapperRepresentation, cancellationToken) + .ConfigureAwait(false); + + public async Task UpdateClientProtocolMapperAsync(string realm, string clientId, string protocolMapperId, ProtocolMapper protocolMapperRepresentation, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) + .AppendPathSegment("/admin/realms/") + .AppendPathSegment(realm, true) + .AppendPathSegment("/clients/") + .AppendPathSegment(clientId, true) + .AppendPathSegment("/protocol-mappers/models/") + .AppendPathSegment(protocolMapperId, true) + .PutJsonAsync(protocolMapperRepresentation, cancellationToken) + .ConfigureAwait(false); + + public async Task DeleteClientProtocolMapperAsync(string realm, string clientId, string protocolMapperId, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) + .AppendPathSegment("/admin/realms/") + .AppendPathSegment(realm, true) + .AppendPathSegment("/clients/") + .AppendPathSegment(clientId, true) + .AppendPathSegment("/protocol-mappers/models/") + .AppendPathSegment(protocolMapperId, true) + .DeleteAsync(cancellationToken) .ConfigureAwait(false); } diff --git a/src/keycloak/Keycloak.Library/RealmsAdmin/KeycloakClient.cs b/src/keycloak/Keycloak.Library/RealmsAdmin/KeycloakClient.cs index b052cb5f8a..8e8b00e345 100644 --- a/src/keycloak/Keycloak.Library/RealmsAdmin/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/RealmsAdmin/KeycloakClient.cs @@ -35,10 +35,10 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library; public partial class KeycloakClient { - public async Task ImportRealmAsync(string realm, Realm rep) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task ImportRealmAsync(string realm, Realm rep, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms") - .PostJsonAsync(rep) + .PostJsonAsync(rep, cancellationToken) .ConfigureAwait(false); public async Task> GetRealmsAsync(string realm) => @@ -47,18 +47,18 @@ public async Task> GetRealmsAsync(string realm) => .GetJsonAsync>() .ConfigureAwait(false); - public async Task GetRealmAsync(string realm) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task GetRealmAsync(string realm, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) - .GetJsonAsync() + .GetJsonAsync(cancellationToken) .ConfigureAwait(false); - public async Task UpdateRealmAsync(string realm, Realm rep) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task UpdateRealmAsync(string realm, Realm rep, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) - .PutJsonAsync(rep) + .PutJsonAsync(rep, cancellationToken) .ConfigureAwait(false); public async Task DeleteRealmAsync(string realm) => diff --git a/src/keycloak/Keycloak.Library/RoleMapper/KeycloakClient.cs b/src/keycloak/Keycloak.Library/RoleMapper/KeycloakClient.cs index 45205abe77..22fa491b17 100644 --- a/src/keycloak/Keycloak.Library/RoleMapper/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/RoleMapper/KeycloakClient.cs @@ -102,34 +102,34 @@ public async Task GetRoleMappingsForUserAsync(string realm, string user .GetJsonAsync() .ConfigureAwait(false); - public async Task AddRealmRoleMappingsToUserAsync(string realm, string userId, IEnumerable roles) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task AddRealmRoleMappingsToUserAsync(string realm, string userId, IEnumerable roles, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/users/") .AppendPathSegment(userId, true) .AppendPathSegment("/role-mappings/realm") - .PostJsonAsync(roles) + .PostJsonAsync(roles, cancellationToken) .ConfigureAwait(false); - public async Task> GetRealmRoleMappingsForUserAsync(string realm, string userId) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task> GetRealmRoleMappingsForUserAsync(string realm, string userId, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/users/") .AppendPathSegment(userId, true) .AppendPathSegment("/role-mappings/realm") - .GetJsonAsync>() + .GetJsonAsync>(cancellationToken) .ConfigureAwait(false); - public async Task DeleteRealmRoleMappingsFromUserAsync(string realm, string userId, IEnumerable roles) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task DeleteRealmRoleMappingsFromUserAsync(string realm, string userId, IEnumerable roles, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/users/") .AppendPathSegment(userId, true) .AppendPathSegment("/role-mappings/realm") - .SendJsonAsync(HttpMethod.Delete, roles) + .SendJsonAsync(HttpMethod.Delete, roles, cancellationToken) .ConfigureAwait(false); public async Task> GetAvailableRealmRoleMappingsForUserAsync(string realm, string userId) => diff --git a/src/keycloak/Keycloak.Library/Roles/KeycloakClient.cs b/src/keycloak/Keycloak.Library/Roles/KeycloakClient.cs index e0bdc20c57..d521357d8b 100644 --- a/src/keycloak/Keycloak.Library/Roles/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/Roles/KeycloakClient.cs @@ -34,25 +34,25 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library; public partial class KeycloakClient { - public async Task CreateRoleAsync(string realm, string clientId, Role role) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task CreateRoleAsync(string realm, string clientId, Role role, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/clients/") .AppendPathSegment(clientId, true) .AppendPathSegment("/roles") - .PostJsonAsync(role) + .PostJsonAsync(role, cancellationToken) .ConfigureAwait(false); - public async Task CreateRoleAsync(string realm, Role role) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task CreateRoleAsync(string realm, Role role, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/roles") - .PostJsonAsync(role) + .PostJsonAsync(role, cancellationToken) .ConfigureAwait(false); - public async Task> GetRolesAsync(string realm, string clientId, int? first = null, int? max = null, string? search = null) + public async Task> GetRolesAsync(string realm, string clientId, int? first = null, int? max = null, string? search = null, CancellationToken cancellationToken = default) { var queryParams = new Dictionary { @@ -61,18 +61,18 @@ public async Task> GetRolesAsync(string realm, string clientId [nameof(search)] = search }; - return await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + return await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/clients/") .AppendPathSegment(clientId, true) .AppendPathSegment("/roles") .SetQueryParams(queryParams) - .GetJsonAsync>() + .GetJsonAsync>(cancellationToken) .ConfigureAwait(false); } - public async Task> GetRolesAsync(string realm, int? first = null, int? max = null, string? search = null) + public async Task> GetRolesAsync(string realm, int? first = null, int? max = null, string? search = null, CancellationToken cancellationToken = default) { var queryParams = new Dictionary { @@ -81,33 +81,33 @@ public async Task> GetRolesAsync(string realm, int? first = nu [nameof(search)] = search }; - return await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + return await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/roles") .SetQueryParams(queryParams) - .GetJsonAsync>() + .GetJsonAsync>(cancellationToken) .ConfigureAwait(false); } - public async Task GetRoleByNameAsync(string realm, string clientId, string roleName) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task GetRoleByNameAsync(string realm, string clientId, string roleName, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/clients/") .AppendPathSegment(clientId, true) .AppendPathSegment("/roles/") .AppendPathSegment(roleName, true) - .GetJsonAsync() + .GetJsonAsync(cancellationToken) .ConfigureAwait(false); - public async Task GetRoleByNameAsync(string realm, string roleName) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task GetRoleByNameAsync(string realm, string roleName, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/roles/") .AppendPathSegment(roleName, true) - .GetJsonAsync() + .GetJsonAsync(cancellationToken) .ConfigureAwait(false); public async Task UpdateRoleByNameAsync(string realm, string clientId, string roleName, Role role) => @@ -150,8 +150,8 @@ public async Task DeleteRoleByNameAsync(string realm, string roleName) => .DeleteAsync() .ConfigureAwait(false); - public async Task AddCompositesToRoleAsync(string realm, string clientId, string roleName, IEnumerable roles) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task AddCompositesToRoleAsync(string realm, string clientId, string roleName, IEnumerable roles, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/clients/") @@ -159,17 +159,17 @@ public async Task AddCompositesToRoleAsync(string realm, string clientId, string .AppendPathSegment("/roles/") .AppendPathSegment(roleName, true) .AppendPathSegment("/composites") - .PostJsonAsync(roles) + .PostJsonAsync(roles, cancellationToken) .ConfigureAwait(false); - public async Task AddCompositesToRoleAsync(string realm, string roleName, IEnumerable roles) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task AddCompositesToRoleAsync(string realm, string roleName, IEnumerable roles, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/roles/") .AppendPathSegment(roleName, true) .AppendPathSegment("/composites") - .PostJsonAsync(roles) + .PostJsonAsync(roles, cancellationToken) .ConfigureAwait(false); public async Task> GetRoleCompositesAsync(string realm, string clientId, string roleName) => @@ -194,8 +194,8 @@ public async Task> GetRoleCompositesAsync(string realm, string .GetJsonAsync>() .ConfigureAwait(false); - public async Task RemoveCompositesFromRoleAsync(string realm, string clientId, string roleName, IEnumerable roles) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task RemoveCompositesFromRoleAsync(string realm, string clientId, string roleName, IEnumerable roles, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/clients/") @@ -203,17 +203,17 @@ public async Task RemoveCompositesFromRoleAsync(string realm, string clientId, s .AppendPathSegment("/roles/") .AppendPathSegment(roleName, true) .AppendPathSegment("/composites") - .SendJsonAsync(HttpMethod.Delete, roles) + .SendJsonAsync(HttpMethod.Delete, roles, cancellationToken) .ConfigureAwait(false); - public async Task RemoveCompositesFromRoleAsync(string realm, string roleName, IEnumerable roles) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task RemoveCompositesFromRoleAsync(string realm, string roleName, IEnumerable roles, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/roles/") .AppendPathSegment(roleName, true) .AppendPathSegment("/composites") - .SendJsonAsync(HttpMethod.Delete, roles) + .SendJsonAsync(HttpMethod.Delete, roles, cancellationToken) .ConfigureAwait(false); public async Task> GetApplicationRolesForCompositeAsync(string realm, string clientId, string roleName, string forClientId) => diff --git a/src/keycloak/Keycloak.Library/RolesById/KeycloakClient.cs b/src/keycloak/Keycloak.Library/RolesById/KeycloakClient.cs index 5cf06dbfb1..220f186b04 100755 --- a/src/keycloak/Keycloak.Library/RolesById/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/RolesById/KeycloakClient.cs @@ -42,22 +42,22 @@ public async Task GetRoleByIdAsync(string realm, string roleId) => .GetJsonAsync() .ConfigureAwait(false); - public async Task UpdateRoleByIdAsync(string realm, string roleId, Role role) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task UpdateRoleByIdAsync(string realm, string roleId, Role role, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/roles-by-id/") .AppendPathSegment(roleId, true) - .PutJsonAsync(role) + .PutJsonAsync(role, cancellationToken) .ConfigureAwait(false); - public async Task DeleteRoleByIdAsync(string realm, string roleId) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task DeleteRoleByIdAsync(string realm, string roleId, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/roles-by-id/") .AppendPathSegment(roleId, true) - .DeleteAsync() + .DeleteAsync(cancellationToken) .ConfigureAwait(false); public async Task MakeRoleCompositeAsync(string realm, string roleId, IEnumerable roles) => @@ -70,14 +70,14 @@ public async Task MakeRoleCompositeAsync(string realm, string roleId, IEnumerabl .PostJsonAsync(roles) .ConfigureAwait(false); - public async Task> GetRoleChildrenAsync(string realm, string roleId) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task> GetRoleChildrenAsync(string realm, string roleId, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/roles-by-id/") .AppendPathSegment(roleId, true) .AppendPathSegment("/composites") - .GetJsonAsync>() + .GetJsonAsync>(cancellationToken) .ConfigureAwait(false); public async Task RemoveRolesFromCompositeAsync(string realm, string roleId, IEnumerable roles) => diff --git a/src/keycloak/Keycloak.Library/Users/KeycloakClient.cs b/src/keycloak/Keycloak.Library/Users/KeycloakClient.cs index d38d5d698a..0bd8f7d805 100644 --- a/src/keycloak/Keycloak.Library/Users/KeycloakClient.cs +++ b/src/keycloak/Keycloak.Library/Users/KeycloakClient.cs @@ -32,25 +32,25 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library; public partial class KeycloakClient { - public async Task CreateUserAsync(string realm, User user) => - await InternalCreateUserAsync(realm, user).ConfigureAwait(false); + public async Task CreateUserAsync(string realm, User user, CancellationToken cancellationToken = default) => + await InternalCreateUserAsync(realm, user, cancellationToken).ConfigureAwait(false); - private async Task InternalCreateUserAsync(string realm, User user) => await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + private async Task InternalCreateUserAsync(string realm, User user, CancellationToken cancellationToken) => await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/users") - .PostJsonAsync(user) + .PostJsonAsync(user, cancellationToken) .ConfigureAwait(false); - public async Task CreateAndRetrieveUserIdAsync(string realm, User user) + public async Task CreateAndRetrieveUserIdAsync(string realm, User user, CancellationToken cancellationToken = default) { - var response = await InternalCreateUserAsync(realm, user).ConfigureAwait(false); + var response = await InternalCreateUserAsync(realm, user, cancellationToken).ConfigureAwait(false); var locationPathAndQuery = response.ResponseMessage.Headers.Location?.PathAndQuery; return locationPathAndQuery != null ? locationPathAndQuery.Substring(locationPathAndQuery.LastIndexOf("/", StringComparison.Ordinal) + 1) : null; } public async Task> GetUsersAsync(string realm, bool? briefRepresentation = null, string? email = null, int? first = null, - string? firstName = null, string? lastName = null, int? max = null, string? search = null, string? username = null) + string? firstName = null, string? lastName = null, int? max = null, string? search = null, string? username = null, CancellationToken cancellationToken = default) { var queryParams = new Dictionary { @@ -64,12 +64,12 @@ public async Task> GetUsersAsync(string realm, bool? briefRepr [nameof(username)] = username }; - return await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + return await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/users") .SetQueryParams(queryParams) - .GetJsonAsync>() + .GetJsonAsync>(cancellationToken) .ConfigureAwait(false); } @@ -90,13 +90,13 @@ public async Task GetUserAsync(string realm, string userId) => .GetJsonAsync() .ConfigureAwait(false); - public async Task UpdateUserAsync(string realm, string userId, User user) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task UpdateUserAsync(string realm, string userId, User user, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/users/") .AppendPathSegment(userId, true) - .PutJsonAsync(user) + .PutJsonAsync(user, cancellationToken) .ConfigureAwait(false); public async Task DeleteUserAsync(string realm, string userId) => @@ -169,26 +169,26 @@ public async Task> GetUserSocialLoginsAsync(strin .GetJsonAsync>() .ConfigureAwait(false); - public async Task AddUserSocialLoginProviderAsync(string realm, string userId, string provider, FederatedIdentity federatedIdentity) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task AddUserSocialLoginProviderAsync(string realm, string userId, string provider, FederatedIdentity federatedIdentity, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/users/") .AppendPathSegment(userId, true) .AppendPathSegment("/federated-identity/") .AppendPathSegment(provider, true) - .PostJsonAsync(federatedIdentity) + .PostJsonAsync(federatedIdentity, cancellationToken) .ConfigureAwait(false); - public async Task RemoveUserSocialLoginProviderAsync(string realm, string userId, string provider) => - await (await GetBaseUrlAsync(realm).ConfigureAwait(false)) + public async Task RemoveUserSocialLoginProviderAsync(string realm, string userId, string provider, CancellationToken cancellationToken = default) => + await (await GetBaseUrlAsync(realm, cancellationToken).ConfigureAwait(false)) .AppendPathSegment("/admin/realms/") .AppendPathSegment(realm, true) .AppendPathSegment("/users/") .AppendPathSegment(userId, true) .AppendPathSegment("/federated-identity/") .AppendPathSegment(provider, true) - .DeleteAsync() + .DeleteAsync(cancellationToken) .ConfigureAwait(false); public async Task> GetUserGroupsAsync(string realm, string userId) => diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs new file mode 100644 index 0000000000..097e84d59c --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/AuthenticationFlowsUpdater.cs @@ -0,0 +1,401 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Factory; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.AuthenticationManagement; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; +using System.Collections.Immutable; + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public class AuthenticationFlowsUpdater : IAuthenticationFlowsUpdater +{ + private readonly IKeycloakFactory _keycloakFactory; + private readonly ISeedDataHandler _seedData; + + public AuthenticationFlowsUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) + { + _keycloakFactory = keycloakFactory; + _seedData = seedDataHandler; + } + + public Task UpdateAuthenticationFlows(string keycloakInstanceName, CancellationToken cancellationToken) + { + var keycloak = _keycloakFactory.CreateKeycloakClient(keycloakInstanceName); + + var handler = new AuthenticationFlowHandler(keycloak, _seedData); + + return handler.UpdateAuthenticationFlows(cancellationToken); + } + + private sealed class AuthenticationFlowHandler + { + private readonly string _realm; + private readonly KeycloakClient _keycloak; + private readonly ISeedDataHandler _seedData; + + public AuthenticationFlowHandler(KeycloakClient keycloak, ISeedDataHandler seedData) + { + _keycloak = keycloak; + _seedData = seedData; + _realm = seedData.Realm; + } + + public async Task UpdateAuthenticationFlows(CancellationToken cancellationToken) + { + var flows = (await _keycloak.GetAuthenticationFlowsAsync(_realm, cancellationToken).ConfigureAwait(false)); + var seedFlows = _seedData.TopLevelCustomAuthenticationFlows; + var topLevelCustomFlows = flows.Where(flow => !(flow.BuiltIn ?? false) && (flow.TopLevel ?? false)); + + await DeleteRedundantAuthenticationFlows(topLevelCustomFlows, seedFlows, cancellationToken).ConfigureAwait(false); + await AddMissingAuthenticationFlows(topLevelCustomFlows, seedFlows, cancellationToken).ConfigureAwait(false); + await UpdateExistingAuthenticationFlows(topLevelCustomFlows, seedFlows, cancellationToken).ConfigureAwait(false); + } + + private async Task DeleteRedundantAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, CancellationToken cancellationToken) + { + foreach (var delete in topLevelCustomFlows.ExceptBy(seedFlows.Select(x => x.Alias), x => x.Alias)) + { + if (delete.Id == null) + throw new ConflictException($"authenticationFlow.id is null {delete.Alias} {delete.Description}"); + await _keycloak.DeleteAuthenticationFlowAsync(_realm, delete.Id, cancellationToken).ConfigureAwait(false); + } + } + + private async Task AddMissingAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, CancellationToken cancellationToken) + { + foreach (var addFlow in seedFlows.ExceptBy(topLevelCustomFlows.Select(x => x.Alias), x => x.Alias)) + { + if (addFlow.Alias == null) + throw new ConflictException($"authenticationFlow.Alias is null {addFlow.Id} {addFlow.Description}"); + if (addFlow.BuiltIn ?? false) + throw new ConflictException($"authenticationFlow.buildIn is true. flow cannot be added: {addFlow.Alias}"); + await _keycloak.CreateAuthenticationFlowAsync(_realm, CreateUpdateAuthenticationFlow(null, addFlow), cancellationToken).ConfigureAwait(false); + await UpdateAuthenticationFlowExecutions(addFlow.Alias, cancellationToken); + } + } + + private async Task UpdateExistingAuthenticationFlows(IEnumerable topLevelCustomFlows, IEnumerable seedFlows, CancellationToken cancellationToken) + { + foreach (var (flow, seed) in topLevelCustomFlows + .Join( + seedFlows, + x => x.Alias, + x => x.Alias, + (flow, seed) => (Flow: flow, Seed: seed))) + { + if (flow.Id == null) + throw new ConflictException($"authenticationFlow.id is null {flow.Alias} {flow.Description}"); + if (flow.Alias == null) + throw new ConflictException($"authenticationFlow.Alias is null {flow.Id} {flow.Description}"); + if (!CompareAuthenticationFlow(flow, seed)) + { + await _keycloak.UpdateAuthenticationFlowAsync(_realm, flow.Id, CreateUpdateAuthenticationFlow(flow.Id, seed), cancellationToken).ConfigureAwait(false); + } + await UpdateAuthenticationFlowExecutions(flow.Alias, cancellationToken); + } + } + + private static AuthenticationFlow CreateUpdateAuthenticationFlow(string? id, AuthenticationFlowModel update) => new AuthenticationFlow + { + Id = id, + Alias = update.Alias, + BuiltIn = update.BuiltIn, + Description = update.Description, + ProviderId = update.ProviderId, + TopLevel = update.TopLevel + }; + + private static bool CompareAuthenticationFlow(AuthenticationFlow flow, AuthenticationFlowModel update) => + flow.BuiltIn == update.BuiltIn && + flow.Description == update.Description && + flow.ProviderId == update.ProviderId && + flow.TopLevel == update.TopLevel; + + private async Task UpdateAuthenticationFlowExecutions(string alias, CancellationToken cancellationToken) + { + var updateExecutions = _seedData.GetAuthenticationExecutions(alias); + var executionNodes = ExecutionNode.Parse(await GetExecutions(alias, cancellationToken).ConfigureAwait(false)); + + if (!CompareStructureRecursive(executionNodes, updateExecutions)) + { + await DeleteExecutionsRecursive(executionNodes, cancellationToken).ConfigureAwait(false); + await AddExecutionsRecursive(alias, updateExecutions, cancellationToken).ConfigureAwait(false); + executionNodes = ExecutionNode.Parse(await GetExecutions(alias, cancellationToken).ConfigureAwait(false)); + } + await UpdateExecutionsRecursive(alias, executionNodes, updateExecutions, cancellationToken).ConfigureAwait(false); + } + + private bool CompareStructureRecursive(IReadOnlyList executions, IEnumerable updateExecutions) => + executions.Count == updateExecutions.Count() && + executions.Zip( + updateExecutions.OrderBy(x => x.Priority), + (execution, update) => (Node: execution, Update: update)).All( + x => + (x.Node.Execution.AuthenticationFlow ?? false) == (x.Update.AuthenticatorFlow ?? false) && + (!(x.Node.Execution.AuthenticationFlow ?? false) || CompareStructureRecursive(x.Node.Children, _seedData.GetAuthenticationExecutions(x.Update.FlowAlias)))); + + private async Task DeleteExecutionsRecursive(IEnumerable executionNodes, CancellationToken cancellationToken) + { + foreach (var executionNode in executionNodes) + { + if (executionNode.Execution.AuthenticationFlow ?? false) + { + await DeleteExecutionsRecursive(executionNode.Children, cancellationToken).ConfigureAwait(false); + } + await _keycloak.DeleteAuthenticationExecutionAsync(_realm, executionNode.Execution.Id ?? throw new ConflictException("authenticationFlow.Id is null"), cancellationToken).ConfigureAwait(false); + } + } + + private async Task AddExecutionsRecursive(string? alias, IEnumerable seedExecutions, CancellationToken cancellationToken) + { + foreach (var execution in seedExecutions) + { + await (execution.AuthenticatorFlow switch + { + true => AddAuthenticationFlowExecutionRecursive(alias!, execution, cancellationToken), + _ => _keycloak.AddAuthenticationFlowExecutionAsync(_realm, alias!, CreateDataWithProvider(execution), cancellationToken) + }).ConfigureAwait(false); + } + + async Task AddAuthenticationFlowExecutionRecursive(string alias, AuthenticationExecutionModel execution, CancellationToken cancellationToken) + { + await _keycloak.AddAuthenticationFlowAndExecutionToAuthenticationFlowAsync(_realm, alias, CreateDataWithAliasTypeProviderDescription(execution), cancellationToken).ConfigureAwait(false); + await AddExecutionsRecursive(execution.FlowAlias, _seedData.GetAuthenticationExecutions(execution.FlowAlias), cancellationToken).ConfigureAwait(false); + } + } + + private async Task UpdateExecutionsRecursive(string alias, IReadOnlyList executionNodes, IEnumerable seedExecutions, CancellationToken cancellationToken) + { + if (executionNodes.Count != seedExecutions.Count()) + throw new ArgumentException("number of elements in executionNodes doesn't match seedData"); + + foreach (var (executionNode, update) in executionNodes.Zip(seedExecutions)) + { + if ((executionNode.Execution.AuthenticationFlow ?? false) != (update.AuthenticatorFlow ?? false)) + throw new ArgumentException("execution.AuthenticatorFlow doesn't match seedData"); + + await (executionNode.Execution.AuthenticationFlow switch + { + true => UpdateAuthenticationFlowExecutionRecursive(alias, executionNode, update, cancellationToken), + _ => UpdateAuthenticationExecution(executionNode, update, cancellationToken) + }).ConfigureAwait(false); + } + + async Task UpdateAuthenticationFlowExecutionRecursive(string alias, ExecutionNode executionNode, AuthenticationExecutionModel update, CancellationToken cancellationToken) + { + if (!CompareFlowExecutions(executionNode.Execution, update)) + { + await _keycloak.UpdateAuthenticationFlowExecutionsAsync( + _realm, + alias, + new AuthenticationExecutionInfo + { + Alias = update.FlowAlias, + AuthenticationFlow = true, + Configurable = executionNode.Execution.Configurable, + Description = _seedData.GetAuthenticationFlow(update.FlowAlias).Description, + DisplayName = update.FlowAlias, + FlowId = executionNode.Execution.FlowId, + Id = executionNode.Execution.Id, + Index = executionNode.Execution.Index, + Level = executionNode.Execution.Level, + Requirement = update.Requirement, + RequirementChoices = executionNode.Execution.RequirementChoices + }, + cancellationToken).ConfigureAwait(false); + } + + var seedExecutions = _seedData.GetAuthenticationExecutions(update.FlowAlias); + + await UpdateExecutionsRecursive( + update.FlowAlias!, + executionNode.Children, + seedExecutions, + cancellationToken).ConfigureAwait(false); + } + + async Task UpdateAuthenticationExecution(ExecutionNode executionNode, AuthenticationExecutionModel update, CancellationToken cancellationToken) + { + var (isEqual, authenticatorConfig) = await CompareExecutions(executionNode.Execution, update, cancellationToken).ConfigureAwait(false); + if (!isEqual) + { + await _keycloak.UpdateAuthenticationFlowExecutionsAsync( + _realm, + alias, + new AuthenticationExecutionInfo + { + Configurable = executionNode.Execution.Configurable, + DisplayName = executionNode.Execution.Description, + Id = executionNode.Execution.Id, + Index = executionNode.Execution.Index, + Level = executionNode.Execution.Level, + ProviderId = executionNode.Execution.ProviderId, + Requirement = update.Requirement, + RequirementChoices = executionNode.Execution.RequirementChoices + }, + cancellationToken).ConfigureAwait(false); + + await UpdateAuthenticatorConfig(executionNode.Execution, update, authenticatorConfig, cancellationToken).ConfigureAwait(false); + } + } + } + + private async Task UpdateAuthenticatorConfig(AuthenticationFlowExecution execution, AuthenticationExecutionModel update, AuthenticatorConfig? config, CancellationToken cancellationToken) + { + switch ((execution.AuthenticationConfig, update.AuthenticatorConfig)) + { + case (null, null): + break; + + case (null, _): + await _keycloak.CreateAuthenticationExecutionConfigurationAsync( + _realm, + execution.Id!, + new AuthenticatorConfig + { + Alias = update.AuthenticatorConfig, + Config = _seedData.GetAuthenticatorConfig(update.AuthenticatorConfig).Config?.ToDictionary(x => x.Key, x => x.Value) + }, + cancellationToken).ConfigureAwait(false); + break; + + case (_, null): + await _keycloak.DeleteAuthenticatorConfigurationAsync( + _realm, + execution.AuthenticationConfig, + cancellationToken).ConfigureAwait(false); + break; + + case (_, _): + var updateConfig = _seedData.GetAuthenticatorConfig(update.AuthenticatorConfig); + if (config == null) + throw new UnexpectedConditionException("authenticatorConfig is null"); + config.Alias = update.AuthenticatorConfig; + config.Config = updateConfig.Config?.ToDictionary(x => x.Key, x => x.Value); + await _keycloak.UpdateAuthenticatorConfigurationAsync(_realm, execution.AuthenticationConfig, config, cancellationToken).ConfigureAwait(false); + break; + } + } + + private bool CompareFlowExecutions(AuthenticationFlowExecution execution, AuthenticationExecutionModel update) => + execution.Description == _seedData.GetAuthenticationFlow(update.FlowAlias).Description && + execution.DisplayName == update.FlowAlias && + execution.Requirement == update.Requirement; + + private Task<(bool IsEqual, AuthenticatorConfig? AuthenticatorConfig)> CompareExecutions(AuthenticationFlowExecution execution, AuthenticationExecutionModel update, CancellationToken cancellationToken) => + (execution.ProviderId != update.Authenticator || + execution.Requirement != update.Requirement) + ? Task.FromResult((false, (AuthenticatorConfig?)null)) + : ((execution.AuthenticationConfig, update.AuthenticatorConfig) switch + { + (null, null) => Task.FromResult((true, (AuthenticatorConfig?)null)), + (null, _) => Task.FromResult((false, (AuthenticatorConfig?)null)), + (_, null) => Task.FromResult((false, (AuthenticatorConfig?)null)), + (_, _) => CompareAuthenticationConfig(execution.AuthenticationConfig, update.AuthenticatorConfig, cancellationToken) + }); + + private async Task<(bool, AuthenticatorConfig?)> CompareAuthenticationConfig(string authenticatorConfigId, string authenticatorConfigAlias, CancellationToken cancellationToken) + { + var config = await _keycloak.GetAuthenticatorConfigurationAsync(_realm, authenticatorConfigId, cancellationToken).ConfigureAwait(false); + var update = _seedData.GetAuthenticatorConfig(authenticatorConfigAlias); + return (CompareAuthenticatorConfig(config, update), config); + } + + private static bool CompareAuthenticatorConfig(AuthenticatorConfig config, AuthenticatorConfigModel update) => + config.Alias == update.Alias && + config.Config.NullOrContentEqual(update.Config); + + private Task> GetExecutions(string alias, CancellationToken cancellationToken) => + _keycloak.GetAuthenticationFlowExecutionsAsync(_realm, alias, cancellationToken); + + private IDictionary CreateDataWithAliasTypeProviderDescription(AuthenticationExecutionModel execution) + { + var seedFlow = _seedData.GetAuthenticationFlow(execution.FlowAlias); + return new Dictionary { + { "alias", execution.FlowAlias ?? throw new ConflictException($"authenticationExecution.FlowAlias is null: {seedFlow.Alias}")}, + { "description", seedFlow.Description ?? throw new ConflictException($"authenticationFlow.ProviderId is null: {seedFlow.Alias}")}, + { "provider", "registration-page-form" }, + { "type", seedFlow.ProviderId ?? throw new ConflictException($"authenticationFlow.ProviderId is null: {seedFlow.Alias}")} + }; + } + + private static IDictionary CreateDataWithProvider(AuthenticationExecutionModel execution) => + new Dictionary + { + { "provider", execution.Authenticator ?? throw new ConflictException("authenticationExecution.Authenticator is null")} + }; + + private sealed class ExecutionNode + { + private readonly AuthenticationFlowExecution _execution; + private readonly IReadOnlyList? _children; + + private ExecutionNode(AuthenticationFlowExecution execution, IReadOnlyList? children = null) + { + _execution = execution; + _children = children; + } + + public AuthenticationFlowExecution Execution + { + get => _execution; + } + + public IReadOnlyList Children + { + get => _children ?? throw new InvalidOperationException($"execution is not a flow: {_execution.DisplayName}"); + } + + public static IReadOnlyList Parse(IEnumerable executions) + { + return ParseInternal(executions.GetHasNextEnumerator(), 0).ToImmutableList(); + } + + private static IEnumerable ParseInternal(IHasNextEnumerator executions, int level) + { + while (executions.HasNext) + { + var execution = executions.Current; + if (execution.Level < level) + { + yield break; + } + + if (execution.Level > level) + { + throw new ConflictException($"unexpected raise of level to {executions.Current.Level}, {executions.Current.DisplayName}"); + } + + executions.Advance(); + yield return execution.AuthenticationFlow switch + { + true => new ExecutionNode(execution, ParseInternal(executions, level + 1).ToImmutableList()), + _ => new ExecutionNode(execution) + }; + } + } + } + } +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs new file mode 100644 index 0000000000..0fab2fb82a --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientScopesUpdater.cs @@ -0,0 +1,181 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Factory; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.ClientScopes; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.ProtocolMappers; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public class ClientScopesUpdater : IClientScopesUpdater +{ + private readonly IKeycloakFactory _keycloakFactory; + private readonly ISeedDataHandler _seedData; + + public ClientScopesUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) + { + _keycloakFactory = keycloakFactory; + _seedData = seedDataHandler; + } + + public async Task UpdateClientScopes(string instanceName, CancellationToken cancellationToken) + { + var keycloak = _keycloakFactory.CreateKeycloakClient(instanceName); + var realm = _seedData.Realm; + + var clientScopes = await keycloak.GetClientScopesAsync(realm, cancellationToken).ConfigureAwait(false); + var seedClientScopes = _seedData.ClientScopes; + + await RemoveObsoleteClientScopes(keycloak, realm, clientScopes, seedClientScopes, cancellationToken).ConfigureAwait(false); + await CreateMissingClientScopes(keycloak, realm, clientScopes, seedClientScopes, cancellationToken).ConfigureAwait(false); + await UpdateExistingClientScopes(keycloak, realm, clientScopes, seedClientScopes, cancellationToken).ConfigureAwait(false); + } + + private static async Task RemoveObsoleteClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, CancellationToken cancellationToken) + { + foreach (var deleteScope in clientScopes.ExceptBy(seedClientScopes.Select(x => x.Name), x => x.Name)) + { + await keycloak.DeleteClientScopeAsync( + realm, + deleteScope.Id ?? throw new ConflictException($"clientScope.Id is null: {deleteScope.Name}"), + cancellationToken).ConfigureAwait(false); + } + } + + private static async Task CreateMissingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, CancellationToken cancellationToken) + { + foreach (var addScope in seedClientScopes.ExceptBy(clientScopes.Select(x => x.Name), x => x.Name)) + { + await keycloak.CreateClientScopeAsync(realm, CreateClientScope(null, addScope, true), cancellationToken).ConfigureAwait(false); + } + } + + private static async Task UpdateExistingClientScopes(KeycloakClient keycloak, string realm, IEnumerable clientScopes, IEnumerable seedClientScopes, CancellationToken cancellationToken) + { + foreach (var (clientScope, update) in clientScopes + .Join( + seedClientScopes, + x => x.Name, + x => x.Name, + (clientScope, update) => (ClientScope: clientScope, Update: update))) + { + await UpdateClientScopeWithProtocolMappers(keycloak, realm, clientScope, update, cancellationToken).ConfigureAwait(false); + } + } + + private static async Task UpdateClientScopeWithProtocolMappers(KeycloakClient keycloak, string realm, ClientScope clientScope, ClientScopeModel update, CancellationToken cancellationToken) + { + if (clientScope.Id == null) + throw new ConflictException($"clientScope.Id is null: {clientScope.Name}"); + + if (!CompareClientScope(clientScope, update)) + { + await keycloak.UpdateClientScopeAsync( + realm, + clientScope.Id, + CreateClientScope(clientScope.Id, update, false), + cancellationToken).ConfigureAwait(false); + } + + var mappers = clientScope.ProtocolMappers ?? Enumerable.Empty(); + var updateMappers = update.ProtocolMappers ?? Enumerable.Empty(); + + await DeleteObsoleteProtocolMappers(keycloak, realm, clientScope.Id, mappers, updateMappers, cancellationToken).ConfigureAwait(false); + await CreateMissingProtocolMappers(keycloak, realm, clientScope.Id, mappers, updateMappers, cancellationToken).ConfigureAwait(false); + await UpdateExistingProtocolMappers(keycloak, realm, clientScope.Id, mappers, updateMappers, cancellationToken).ConfigureAwait(false); + } + + private static async Task DeleteObsoleteProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, CancellationToken cancellationToken) + { + foreach (var mapper in mappers.ExceptBy(updateMappers.Select(x => x.Name), x => x.Name)) + { + await keycloak.DeleteProtocolMapperAsync( + realm, + clientScopeId, + mapper.Id ?? throw new ConflictException($"protocolMapper.Id is null {mapper.Name}"), + cancellationToken).ConfigureAwait(false); + } + } + + private static async Task CreateMissingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, CancellationToken cancellationToken) + { + foreach (var update in updateMappers.ExceptBy(mappers.Select(x => x.Name), x => x.Name)) + { + await keycloak.CreateProtocolMapperAsync( + realm, + clientScopeId, + ProtocolMappersUpdater.CreateProtocolMapper(null, update), + cancellationToken).ConfigureAwait(false); + } + } + + private static async Task UpdateExistingProtocolMappers(KeycloakClient keycloak, string realm, string clientScopeId, IEnumerable mappers, IEnumerable updateMappers, CancellationToken cancellationToken) + { + foreach (var (mapper, update) in mappers.Join( + updateMappers, + x => x.Name, + x => x.Name, + (mapper, update) => (Mapper: mapper, Update: update)) + .Where(x => !ProtocolMappersUpdater.CompareProtocolMapper(x.Mapper, x.Update))) + { + await keycloak.UpdateProtocolMapperAsync( + realm, + clientScopeId, + mapper.Id ?? throw new ConflictException($"protocolMapper.Id is null {mapper.Name}"), + ProtocolMappersUpdater.CreateProtocolMapper(mapper.Id, update), + cancellationToken).ConfigureAwait(false); + } + } + + private static ClientScope CreateClientScope(string? id, ClientScopeModel clientScope, bool includeProtocolMappers) => + new ClientScope + { + Id = id, + Name = clientScope.Name, + Description = clientScope.Description, + Protocol = clientScope.Protocol, + Attributes = clientScope.Attributes == null ? null : CreateClientScopeAttributes(clientScope.Attributes), + ProtocolMappers = includeProtocolMappers ? clientScope?.ProtocolMappers?.Select(x => ProtocolMappersUpdater.CreateProtocolMapper(x.Id, x)) : null + }; + + private static bool CompareClientScope(ClientScope scope, ClientScopeModel update) => + scope.Name == update.Name && + scope.Description == update.Description && + scope.Protocol == update.Protocol && + (scope.Attributes == null && update.Attributes == null || + scope.Attributes != null && update.Attributes != null && + CompareClientScopeAttributes(scope.Attributes, update.Attributes)); + + private static Attributes CreateClientScopeAttributes(IReadOnlyDictionary update) => + new Attributes + { + ConsentScreenText = update.GetValueOrDefault("consent.screen.text"), + DisplayOnConsentScreen = update.GetValueOrDefault("display.on.consent.screen"), + IncludeInTokenScope = update.GetValueOrDefault("include.in.token.scope") + }; + + private static bool CompareClientScopeAttributes(Attributes attributes, IReadOnlyDictionary update) => + attributes.ConsentScreenText == update.GetValueOrDefault("consent.screen.text") && + attributes.DisplayOnConsentScreen == update.GetValueOrDefault("display.on.consent.screen") && + attributes.IncludeInTokenScope == update.GetValueOrDefault("include.in.token.scope"); +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs new file mode 100644 index 0000000000..1435117972 --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ClientsUpdater.cs @@ -0,0 +1,237 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Factory; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Clients; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; +using System.Runtime.CompilerServices; + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public class ClientsUpdater : IClientsUpdater +{ + private readonly IKeycloakFactory _keycloakFactory; + private readonly ISeedDataHandler _seedData; + + public ClientsUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) + { + _keycloakFactory = keycloakFactory; + _seedData = seedDataHandler; + } + + public Task UpdateClients(string keycloakInstanceName, CancellationToken cancellationToken) + { + var realm = _seedData.Realm; + var keycloak = _keycloakFactory.CreateKeycloakClient(keycloakInstanceName); + return _seedData.SetClientInternalIds(UpdateClientsInternal(keycloak, realm, cancellationToken)); + } + + private async IAsyncEnumerable<(string ClientId, string Id)> UpdateClientsInternal(KeycloakClient keycloak, string realm, [EnumeratorCancellation] CancellationToken cancellationToken) + { + foreach (var update in _seedData.Clients) + { + if (update.ClientId == null) + throw new ConflictException($"clientId must not be null {update.Id}"); + var client = (await keycloak.GetClientsAsync(realm, clientId: update.ClientId, cancellationToken: cancellationToken).ConfigureAwait(false)).SingleOrDefault(x => x.ClientId == update.ClientId); + if (client == null) + { + var id = await keycloak.CreateClientAndRetrieveClientIdAsync(realm, CreateUpdateClient(null, update), cancellationToken).ConfigureAwait(false); + if (id == null) + throw new KeycloakNoSuccessException($"creation of client {update.ClientId} did not return the expected result"); + + // load newly created client as keycloak may create default protocolmappers on client-creation + client = await keycloak.GetClientAsync(realm, id).ConfigureAwait(false); + } + + if (client.Id == null) + throw new ConflictException($"client.Id must not be null: clientId {update.ClientId}"); + + if (!CompareClient(client, update)) + { + var updateClient = CreateUpdateClient(client.Id, update); + await keycloak.UpdateClientAsync( + realm, + client.Id, + updateClient, + cancellationToken).ConfigureAwait(false); + } + + await UpdateClientProtocollMappers(keycloak, realm, client.Id, client, update, cancellationToken).ConfigureAwait(false); + + yield return (update.ClientId, client.Id); + } + } + + private static async Task UpdateClientProtocollMappers(KeycloakClient keycloak, string realm, string clientId, Client client, ClientModel update, CancellationToken cancellationToken) + { + var clientProtocolMappers = client.ProtocolMappers ?? Enumerable.Empty(); + var updateProtocolMappers = update.ProtocolMappers ?? Enumerable.Empty(); + + await DeleteObsoleteClientProtocolMappers(keycloak, realm, clientId, clientProtocolMappers, updateProtocolMappers, cancellationToken).ConfigureAwait(false); + await CreateMissingClientProtocolMappers(keycloak, realm, clientId, clientProtocolMappers, updateProtocolMappers, cancellationToken).ConfigureAwait(false); + await UpdateExistingClientProtocolMappers(keycloak, realm, clientId, clientProtocolMappers, updateProtocolMappers, cancellationToken).ConfigureAwait(false); + } + + private static async Task DeleteObsoleteClientProtocolMappers(KeycloakClient keycloak, string realm, string clientId, IEnumerable clientProtocolMappers, IEnumerable updateProtocolMappers, CancellationToken cancellationToken) + { + foreach (var mapper in clientProtocolMappers.ExceptBy(updateProtocolMappers.Select(x => x.Name), x => x.Name)) + { + await keycloak.DeleteClientProtocolMapperAsync( + realm, + clientId, + mapper.Id ?? throw new ConflictException($"protocolMapper.Id is null {mapper.Name}"), + cancellationToken).ConfigureAwait(false); + } + } + + private static async Task CreateMissingClientProtocolMappers(KeycloakClient keycloak, string realm, string clientId, IEnumerable clientProtocolMappers, IEnumerable updateProtocolMappers, CancellationToken cancellationToken) + { + foreach (var update in updateProtocolMappers.ExceptBy(clientProtocolMappers.Select(x => x.Name), x => x.Name)) + { + await keycloak.CreateClientProtocolMapperAsync( + realm, + clientId, + ProtocolMappersUpdater.CreateProtocolMapper(null, update), + cancellationToken).ConfigureAwait(false); + } + } + + private static async Task UpdateExistingClientProtocolMappers(KeycloakClient keycloak, string realm, string clientId, IEnumerable clientProtocolMappers, IEnumerable updateProtocolMappers, CancellationToken cancellationToken) + { + foreach (var (mapper, update) in clientProtocolMappers + .Join( + updateProtocolMappers, + x => x.Name, + x => x.Name, + (mapper, update) => (Mapper: mapper, Update: update)) + .Where( + x => !CompareClientProtocolMapper(x.Mapper, x.Update))) + { + await keycloak.UpdateClientProtocolMapperAsync( + realm, + clientId, + mapper.Id ?? throw new ConflictException($"protocolMapper.Id is null {mapper.Name}"), + ProtocolMappersUpdater.CreateProtocolMapper(mapper.Id, update), + cancellationToken).ConfigureAwait(false); + } + } + + private static Client CreateUpdateClient(string? id, ClientModel update) => new Client // secret is not updated as it cannot be read via the keycloak api + { + Id = id, + ClientId = update.ClientId, + RootUrl = update.RootUrl, + Name = update.Name, + Description = update.Description, + BaseUrl = update.BaseUrl, + SurrogateAuthRequired = update.SurrogateAuthRequired, + Enabled = update.Enabled, + AlwaysDisplayInConsole = update.AlwaysDisplayInConsole, + ClientAuthenticatorType = update.ClientAuthenticatorType, + RedirectUris = update.RedirectUris, + WebOrigins = update.WebOrigins, + NotBefore = update.NotBefore, + BearerOnly = update.BearerOnly, + ConsentRequired = update.ConsentRequired, + StandardFlowEnabled = update.StandardFlowEnabled, + ImplicitFlowEnabled = update.ImplicitFlowEnabled, + DirectAccessGrantsEnabled = update.DirectAccessGrantsEnabled, + ServiceAccountsEnabled = update.ServiceAccountsEnabled, + PublicClient = update.PublicClient, + FrontChannelLogout = update.FrontchannelLogout, + Protocol = update.Protocol, + Attributes = update.Attributes?.ToDictionary(x => x.Key, x => x.Value), + AuthenticationFlowBindingOverrides = update.AuthenticationFlowBindingOverrides?.ToDictionary(x => x.Key, x => x.Value), + FullScopeAllowed = update.FullScopeAllowed, + NodeReregistrationTimeout = update.NodeReRegistrationTimeout, + DefaultClientScopes = update.DefaultClientScopes, + OptionalClientScopes = update.OptionalClientScopes, + Access = update.Access == null + ? null + : new ClientAccess + { + View = update.Access.View, + Configure = update.Access.Configure, + Manage = update.Access.Manage + }, + AuthorizationServicesEnabled = update.AuthorizationServicesEnabled + }; + + private static bool CompareClient(Client client, ClientModel update) => // secret is not compared as it cannot be read via the keycloak api + client.ClientId == update.ClientId && + client.RootUrl == update.RootUrl && + client.Name == update.Name && + client.Description == update.Description && + client.BaseUrl == update.BaseUrl && + client.SurrogateAuthRequired == update.SurrogateAuthRequired && + client.Enabled == update.Enabled && + client.AlwaysDisplayInConsole == update.AlwaysDisplayInConsole && + client.ClientAuthenticatorType == update.ClientAuthenticatorType && + client.RedirectUris.NullOrContentEqual(update.RedirectUris) && + client.WebOrigins.NullOrContentEqual(update.WebOrigins) && + client.NotBefore == update.NotBefore && + client.BearerOnly == update.BearerOnly && + client.ConsentRequired == update.ConsentRequired && + client.StandardFlowEnabled == update.StandardFlowEnabled && + client.ImplicitFlowEnabled == update.ImplicitFlowEnabled && + client.DirectAccessGrantsEnabled == update.DirectAccessGrantsEnabled && + client.ServiceAccountsEnabled == update.ServiceAccountsEnabled && + client.PublicClient == update.PublicClient && + client.FrontChannelLogout == update.FrontchannelLogout && + client.Protocol == update.Protocol && + client.Attributes.NullOrContentEqual(update.Attributes) && + client.AuthenticationFlowBindingOverrides.NullOrContentEqual(update.AuthenticationFlowBindingOverrides) && + client.FullScopeAllowed == update.FullScopeAllowed && + client.NodeReregistrationTimeout == update.NodeReRegistrationTimeout && + client.DefaultClientScopes.NullOrContentEqual(update.DefaultClientScopes) && + client.OptionalClientScopes.NullOrContentEqual(update.OptionalClientScopes) && + CompareClientAccess(client.Access, update.Access) && + client.AuthorizationServicesEnabled == update.AuthorizationServicesEnabled; + + private static bool CompareClientAccess(ClientAccess? access, ClientAccessModel? updateAccess) => + access == null && updateAccess == null || + access != null && updateAccess != null && + access.Configure == updateAccess.Configure && + access.Manage == updateAccess.Manage && + access.View == updateAccess.View; + + private static bool CompareClientProtocolMapper(ClientProtocolMapper mapper, ProtocolMapperModel update) => + mapper.Name == update.Name && + mapper.Protocol == update.Protocol && + mapper.ProtocolMapper == update.ProtocolMapper && + mapper.ConsentRequired == update.ConsentRequired && + (mapper.Config == null && update.Config == null || + mapper.Config != null && update.Config != null && + CompareClientProtocolMapperConfig(mapper.Config, update.Config)); + + private static bool CompareClientProtocolMapperConfig(ClientConfig config, IReadOnlyDictionary update) => + config.UserInfoTokenClaim == update.GetValueOrDefault("userinfo.token.claim") && + config.UserAttribute == update.GetValueOrDefault("user.attribute") && + config.IdTokenClaim == update.GetValueOrDefault("id.token.claim") && + config.AccessTokenClaim == update.GetValueOrDefault("access.token.claim") && + config.ClaimName == update.GetValueOrDefault("claim.name") && + config.JsonTypelabel == update.GetValueOrDefault("jsonType.label") && + config.FriendlyName == update.GetValueOrDefault("friendly.name") && + config.AttributeName == update.GetValueOrDefault("attribute.name"); +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/IAuthenticationFlowsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/IAuthenticationFlowsUpdater.cs new file mode 100644 index 0000000000..44e034f6f9 --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/IAuthenticationFlowsUpdater.cs @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public interface IAuthenticationFlowsUpdater +{ + Task UpdateAuthenticationFlows(string keycloakInstanceName, CancellationToken cancellationToken); +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/IClientScopesUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/IClientScopesUpdater.cs new file mode 100644 index 0000000000..85e26d3a8e --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/IClientScopesUpdater.cs @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public interface IClientScopesUpdater +{ + Task UpdateClientScopes(string instanceName, CancellationToken cancellationToken); +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/IClientsUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/IClientsUpdater.cs new file mode 100644 index 0000000000..b17ceb72f4 --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/IClientsUpdater.cs @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public interface IClientsUpdater +{ + Task UpdateClients(string keycloakInstanceName, CancellationToken cancellationToken); +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/IIdentityProvidersUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/IIdentityProvidersUpdater.cs new file mode 100644 index 0000000000..383810989c --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/IIdentityProvidersUpdater.cs @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public interface IIdentityProvidersUpdater +{ + Task UpdateIdentityProviders(string keycloakInstanceName, CancellationToken cancellationToken); +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/IKeecloakSeeder.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/IKeecloakSeeder.cs new file mode 100644 index 0000000000..ded8039f05 --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/IKeecloakSeeder.cs @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public interface IKeycloakSeeder +{ + Task Seed(CancellationToken cancellationToken); +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/IRealmUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/IRealmUpdater.cs new file mode 100644 index 0000000000..c1dd057ea2 --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/IRealmUpdater.cs @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public interface IRealmUpdater +{ + public Task UpdateRealm(string keycloakInstanceName, CancellationToken cancellationToken); +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/IRolesUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/IRolesUpdater.cs new file mode 100644 index 0000000000..2adbaee7bc --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/IRolesUpdater.cs @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public interface IRolesUpdater +{ + Task UpdateClientRoles(string keycloakInstanceName, CancellationToken cancellationToken); + Task UpdateRealmRoles(string keycloakInstanceName, CancellationToken cancellationToken); + Task UpdateCompositeRoles(string keycloakInstanceName, CancellationToken cancellationToken); +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs new file mode 100644 index 0000000000..b996c3cbc4 --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ISeedDataHandler.cs @@ -0,0 +1,60 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public interface ISeedDataHandler +{ + Task Import(string path, CancellationToken cancellationToken); + + string Realm { get; } + + KeycloakRealm KeycloakRealm { get; } + + IEnumerable Clients { get; } + + IReadOnlyDictionary> ClientRoles { get; } + + IEnumerable RealmRoles { get; } + + IEnumerable IdentityProviders { get; } + + IEnumerable IdentityProviderMappers { get; } + + IEnumerable Users { get; } + + IEnumerable TopLevelCustomAuthenticationFlows { get; } + + IEnumerable ClientScopes { get; } + + IReadOnlyDictionary ClientsDictionary { get; } + + Task SetClientInternalIds(IAsyncEnumerable<(string ClientId, string Id)> clientInternalIds); + + string GetIdOfClient(string clientId); + + AuthenticationFlowModel GetAuthenticationFlow(string? alias); + + IEnumerable GetAuthenticationExecutions(string? alias); + + AuthenticatorConfigModel GetAuthenticatorConfig(string? alias); +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/IUsersUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/IUsersUpdater.cs new file mode 100644 index 0000000000..c07ebbf7c9 --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/IUsersUpdater.cs @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public interface IUsersUpdater +{ + Task UpdateUsers(string keycloakInstanceName, CancellationToken cancellationToken); +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs new file mode 100644 index 0000000000..a18842aa5b --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/IdentityProvidersUpdater.cs @@ -0,0 +1,256 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Factory; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.IdentityProviders; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public class IdentityProvidersUpdater : IIdentityProvidersUpdater +{ + private readonly IKeycloakFactory _keycloakFactory; + private readonly ISeedDataHandler _seedData; + + public IdentityProvidersUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) + { + _keycloakFactory = keycloakFactory; + _seedData = seedDataHandler; + } + + public async Task UpdateIdentityProviders(string keycloakInstanceName, CancellationToken cancellationToken) + { + var keycloak = _keycloakFactory.CreateKeycloakClient(keycloakInstanceName); + var realm = _seedData.Realm; + + foreach (var updateIdentityProvider in _seedData.IdentityProviders) + { + if (updateIdentityProvider.Alias == null) + throw new ConflictException($"identityProvider alias must not be null: {updateIdentityProvider.InternalId} {updateIdentityProvider.DisplayName}"); + + try + { + var identityProvider = await keycloak.GetIdentityProviderAsync(realm, updateIdentityProvider.Alias, cancellationToken).ConfigureAwait(false); + if (!CompareIdentityProvider(identityProvider, updateIdentityProvider)) + { + UpdateIdentityProvider(identityProvider, updateIdentityProvider); + await keycloak.UpdateIdentityProviderAsync(realm, updateIdentityProvider.Alias, identityProvider, cancellationToken).ConfigureAwait(false); + } + } + catch (KeycloakEntityNotFoundException) + { + var identityProvider = new IdentityProvider(); + UpdateIdentityProvider(identityProvider, updateIdentityProvider); + await keycloak.CreateIdentityProviderAsync(realm, identityProvider, cancellationToken).ConfigureAwait(false); + } + + var updateMappers = _seedData.IdentityProviderMappers.Where(x => x.IdentityProviderAlias == updateIdentityProvider.Alias); + var mappers = await keycloak.GetIdentityProviderMappersAsync(realm, updateIdentityProvider.Alias, cancellationToken).ConfigureAwait(false); + + await DeleteObsoleteIdentityProviderMappers(keycloak, realm, updateIdentityProvider.Alias, mappers, updateMappers, cancellationToken).ConfigureAwait(false); + await CreateMissingIdentityProviderMappers(keycloak, realm, updateIdentityProvider.Alias, mappers, updateMappers, cancellationToken).ConfigureAwait(false); + await UpdateExistingIdentityProviderMappers(keycloak, realm, updateIdentityProvider.Alias, mappers, updateMappers, cancellationToken).ConfigureAwait(false); + } + } + + private static async Task CreateMissingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, CancellationToken cancellationToken) + { + foreach (var mapper in updateMappers.ExceptBy(mappers.Select(x => x.Name), x => x.Name)) + { + await keycloak.AddIdentityProviderMapperAsync( + realm, + alias, + UpdateIdentityProviderMapper( + new IdentityProviderMapper + { + Name = mapper.Name, + IdentityProviderAlias = mapper.IdentityProviderAlias + }, + mapper), + cancellationToken).ConfigureAwait(false); + } + } + + private static async Task UpdateExistingIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, CancellationToken cancellationToken) + { + foreach (var (mapper, update) in mappers + .Join( + updateMappers, + x => x.Name, + x => x.Name, + (mapper, update) => (Mapper: mapper, Update: update)) + .Where( + x => !CompareIdentityProviderMapper(x.Mapper, x.Update))) + { + await keycloak.UpdateIdentityProviderMapperAsync( + realm, + alias, + mapper.Id ?? throw new ConflictException($"identityProviderMapper.id must never be null {mapper.Name} {mapper.IdentityProviderAlias}"), + UpdateIdentityProviderMapper(mapper, update), + cancellationToken).ConfigureAwait(false); + } + } + + private static async Task DeleteObsoleteIdentityProviderMappers(KeycloakClient keycloak, string realm, string alias, IEnumerable mappers, IEnumerable updateMappers, CancellationToken cancellationToken) + { + if (mappers.ExceptBy(updateMappers.Select(x => x.Name), x => x.Name).IfAny( + async mappers => + { + foreach (var mapper in mappers) + { + await keycloak.DeleteIdentityProviderMapperAsync( + realm, + alias, + mapper.Id ?? throw new ConflictException($"identityProviderMapper.id must never be null {mapper.Name} {mapper.IdentityProviderAlias}"), + cancellationToken).ConfigureAwait(false); + } + }, + out var deleteMappersTask)) + { + await deleteMappersTask!.ConfigureAwait(false); + } + } + + private static void UpdateIdentityProvider(IdentityProvider provider, IdentityProviderModel update) + { + provider.Alias = update.Alias; + provider.DisplayName = update.DisplayName; + provider.ProviderId = update.ProviderId; + provider.Enabled = update.Enabled; + provider.UpdateProfileFirstLoginMode = update.UpdateProfileFirstLoginMode; + provider.TrustEmail = update.TrustEmail; + provider.StoreToken = update.StoreToken; + provider.AddReadTokenRoleOnCreate = update.AddReadTokenRoleOnCreate; + provider.AuthenticateByDefault = update.AuthenticateByDefault; + provider.LinkOnly = update.LinkOnly; + provider.FirstBrokerLoginFlowAlias = update.FirstBrokerLoginFlowAlias; + provider.Config = update.Config == null + ? null + : new Config + { + HideOnLoginPage = update.Config.HideOnLoginPage, + //ClientSecret = update.Config.ClientSecret, + DisableUserInfo = update.Config.DisableUserInfo, + ValidateSignature = update.Config.ValidateSignature, + ClientId = update.Config.ClientId, + TokenUrl = update.Config.TokenUrl, + AuthorizationUrl = update.Config.AuthorizationUrl, + ClientAuthMethod = update.Config.ClientAuthMethod, + JwksUrl = update.Config.JwksUrl, + LogoutUrl = update.Config.LogoutUrl, + ClientAssertionSigningAlg = update.Config.ClientAssertionSigningAlg, + SyncMode = update.Config.SyncMode, + UseJwksUrl = update.Config.UseJwksUrl, + UserInfoUrl = update.Config.UserInfoUrl, + Issuer = update.Config.Issuer, + // for Saml: + NameIDPolicyFormat = update.Config.NameIDPolicyFormat, + PrincipalType = update.Config.PrincipalType, + SignatureAlgorithm = update.Config.SignatureAlgorithm, + XmlSigKeyInfoKeyNameTransformer = update.Config.XmlSigKeyInfoKeyNameTransformer, + AllowCreate = update.Config.AllowCreate, + EntityId = update.Config.EntityId, + AuthnContextComparisonType = update.Config.AuthnContextComparisonType, + BackchannelSupported = update.Config.BackchannelSupported, + PostBindingResponse = update.Config.PostBindingResponse, + PostBindingAuthnRequest = update.Config.PostBindingAuthnRequest, + PostBindingLogout = update.Config.PostBindingLogout, + WantAuthnRequestsSigned = update.Config.WantAuthnRequestsSigned, + WantAssertionsSigned = update.Config.WantAssertionsSigned, + WantAssertionsEncrypted = update.Config.WantAssertionsEncrypted, + ForceAuthn = update.Config.ForceAuthn, + SignSpMetadata = update.Config.SignSpMetadata, + LoginHint = update.Config.LoginHint, + SingleSignOnServiceUrl = update.Config.SingleSignOnServiceUrl, + AllowedClockSkew = update.Config.AllowedClockSkew, + AttributeConsumingServiceIndex = update.Config.AttributeConsumingServiceIndex + }; + } + + private static bool CompareIdentityProvider(IdentityProvider provider, IdentityProviderModel update) => + provider.Alias == update.Alias && + provider.DisplayName == update.DisplayName && + provider.ProviderId == update.ProviderId && + provider.Enabled == update.Enabled && + provider.UpdateProfileFirstLoginMode == update.UpdateProfileFirstLoginMode && + provider.TrustEmail == update.TrustEmail && + provider.StoreToken == update.StoreToken && + provider.AddReadTokenRoleOnCreate == update.AddReadTokenRoleOnCreate && + provider.AuthenticateByDefault == update.AuthenticateByDefault && + provider.LinkOnly == update.LinkOnly && + provider.FirstBrokerLoginFlowAlias == update.FirstBrokerLoginFlowAlias && + CompareIdentityProviderConfig(provider.Config, update.Config); + + private static bool CompareIdentityProviderConfig(Config? config, IdentityProviderConfigModel? update) => + config == null && update == null || + config != null && update != null && + config.HideOnLoginPage == update.HideOnLoginPage && + //ClientSecret = update.ClientSecret && + config.DisableUserInfo == update.DisableUserInfo && + config.ValidateSignature == update.ValidateSignature && + config.ClientId == update.ClientId && + config.TokenUrl == update.TokenUrl && + config.AuthorizationUrl == update.AuthorizationUrl && + config.ClientAuthMethod == update.ClientAuthMethod && + config.JwksUrl == update.JwksUrl && + config.LogoutUrl == update.LogoutUrl && + config.ClientAssertionSigningAlg == update.ClientAssertionSigningAlg && + config.SyncMode == update.SyncMode && + config.UseJwksUrl == update.UseJwksUrl && + config.UserInfoUrl == update.UserInfoUrl && + config.Issuer == update.Issuer && + // for Saml: + config.NameIDPolicyFormat == update.NameIDPolicyFormat && + config.PrincipalType == update.PrincipalType && + config.SignatureAlgorithm == update.SignatureAlgorithm && + config.XmlSigKeyInfoKeyNameTransformer == update.XmlSigKeyInfoKeyNameTransformer && + config.AllowCreate == update.AllowCreate && + config.EntityId == update.EntityId && + config.AuthnContextComparisonType == update.AuthnContextComparisonType && + config.BackchannelSupported == update.BackchannelSupported && + config.PostBindingResponse == update.PostBindingResponse && + config.PostBindingAuthnRequest == update.PostBindingAuthnRequest && + config.PostBindingLogout == update.PostBindingLogout && + config.WantAuthnRequestsSigned == update.WantAuthnRequestsSigned && + config.WantAssertionsSigned == update.WantAssertionsSigned && + config.WantAssertionsEncrypted == update.WantAssertionsEncrypted && + config.ForceAuthn == update.ForceAuthn && + config.SignSpMetadata == update.SignSpMetadata && + config.LoginHint == update.LoginHint && + config.SingleSignOnServiceUrl == update.SingleSignOnServiceUrl && + config.AllowedClockSkew == update.AllowedClockSkew && + config.AttributeConsumingServiceIndex == update.AttributeConsumingServiceIndex; + + private static IdentityProviderMapper UpdateIdentityProviderMapper(IdentityProviderMapper mapper, IdentityProviderMapperModel updateMapper) + { + mapper._IdentityProviderMapper = updateMapper.IdentityProviderMapper; + mapper.Config = updateMapper.Config?.ToDictionary(x => x.Key, x => x.Value); + return mapper; + } + + private static bool CompareIdentityProviderMapper(IdentityProviderMapper mapper, IdentityProviderMapperModel updateMapper) => + mapper.IdentityProviderAlias == updateMapper.IdentityProviderAlias && + mapper._IdentityProviderMapper == updateMapper.IdentityProviderMapper && + mapper.Config.NullOrContentEqual(updateMapper.Config); +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs new file mode 100644 index 0000000000..7436d71ac8 --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/KeecloakSeeder.cs @@ -0,0 +1,65 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Microsoft.Extensions.Options; + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public class KeycloakSeeder : IKeycloakSeeder +{ + private readonly KeycloakSeederSettings _settings; + private readonly ISeedDataHandler _seedData; + private readonly IRealmUpdater _realmUpdater; + private readonly IRolesUpdater _rolesUpdater; + private readonly IClientsUpdater _clientsUpdater; + private readonly IIdentityProvidersUpdater _identityProvidersUpdater; + private readonly IUsersUpdater _usersUpdater; + private readonly IClientScopesUpdater _clientScopesUpdater; + private readonly IAuthenticationFlowsUpdater _authenticationFlowsUpdater; + public KeycloakSeeder(ISeedDataHandler seedDataHandler, IRealmUpdater realmUpdater, IRolesUpdater rolesUpdater, IClientsUpdater clientsUpdater, IIdentityProvidersUpdater identityProvidersUpdater, IUsersUpdater usersUpdater, IClientScopesUpdater clientScopesUpdater, IAuthenticationFlowsUpdater authenticationFlowsUpdater, IOptions options) + { + _seedData = seedDataHandler; + _realmUpdater = realmUpdater; + _rolesUpdater = rolesUpdater; + _clientsUpdater = clientsUpdater; + _identityProvidersUpdater = identityProvidersUpdater; + _usersUpdater = usersUpdater; + _clientScopesUpdater = clientScopesUpdater; + _authenticationFlowsUpdater = authenticationFlowsUpdater; + _settings = options.Value; + } + + public async Task Seed(CancellationToken cancellationToken) + { + foreach (var dataPath in _settings.DataPathes) + { + await _seedData.Import(dataPath, cancellationToken).ConfigureAwait(false); + await _realmUpdater.UpdateRealm(_settings.InstanceName, cancellationToken).ConfigureAwait(false); + await _rolesUpdater.UpdateRealmRoles(_settings.InstanceName, cancellationToken).ConfigureAwait(false); + await _clientsUpdater.UpdateClients(_settings.InstanceName, cancellationToken).ConfigureAwait(false); + await _rolesUpdater.UpdateClientRoles(_settings.InstanceName, cancellationToken).ConfigureAwait(false); + await _rolesUpdater.UpdateCompositeRoles(_settings.InstanceName, cancellationToken).ConfigureAwait(false); + await _identityProvidersUpdater.UpdateIdentityProviders(_settings.InstanceName, cancellationToken).ConfigureAwait(false); + await _usersUpdater.UpdateUsers(_settings.InstanceName, cancellationToken).ConfigureAwait(false); + await _clientScopesUpdater.UpdateClientScopes(_settings.InstanceName, cancellationToken).ConfigureAwait(false); + await _authenticationFlowsUpdater.UpdateAuthenticationFlows(_settings.InstanceName, cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/KeycloakSeederSettings.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/KeycloakSeederSettings.cs new file mode 100644 index 0000000000..b426c4fc0c --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/KeycloakSeederSettings.cs @@ -0,0 +1,51 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Validation; +using System.ComponentModel.DataAnnotations; + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public class KeycloakSeederSettings +{ + [Required] + [DistinctValues] + public IEnumerable DataPathes { get; set; } = null!; + + [Required] + public string InstanceName { get; set; } = null!; +} + +public static class KeycloakSeederSettingsExtensions +{ + public static IServiceCollection ConfigureKeycloakSeederSettings( + this IServiceCollection services, + IConfigurationSection section + ) + { + services.AddOptions() + .Bind(section) + .ValidateDistinctValues(section) + .ValidateOnStart(); + return services; + } +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/ProtocolMappersUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/ProtocolMappersUpdater.cs new file mode 100644 index 0000000000..556c2e8e55 --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/ProtocolMappersUpdater.cs @@ -0,0 +1,87 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.ProtocolMappers; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; +public static class ProtocolMappersUpdater +{ + public static ProtocolMapper CreateProtocolMapper(string? id, ProtocolMapperModel x) => + new ProtocolMapper() + { + Id = id, + Name = x.Name, + Protocol = x.Protocol, + _ProtocolMapper = x.ProtocolMapper, + ConsentRequired = x.ConsentRequired, + Config = x.Config == null ? null : CreateProtocolMapperConfig(x.Config) + }; + + public static bool CompareProtocolMapper(ProtocolMapper mapper, ProtocolMapperModel update) => + mapper.Name == update.Name && + mapper.Protocol == update.Protocol && + mapper._ProtocolMapper == update.ProtocolMapper && + mapper.ConsentRequired == update.ConsentRequired && + (mapper.Config == null && update.Config == null || + mapper.Config != null && update.Config != null && + CompareProtocolMapperConfig(mapper.Config, update.Config)); + + private static Config CreateProtocolMapperConfig(IReadOnlyDictionary update) => + new Config + { + Single = update.GetValueOrDefault("single"), + AttributeNameFormat = update.GetValueOrDefault("attribute.nameformat"), + AttributeName = update.GetValueOrDefault("attribute.name"), + UserInfoTokenClaim = update.GetValueOrDefault("userinfo.token.claim"), + UserAttribute = update.GetValueOrDefault("user.attribute"), + IdTokenClaim = update.GetValueOrDefault("id.token.claim"), + AccessTokenClaim = update.GetValueOrDefault("access.token.claim"), + ClaimName = update.GetValueOrDefault("claim.name"), + JsonTypelabel = update.GetValueOrDefault("jsonType.label"), + UserAttributeFormatted = update.GetValueOrDefault("user.attribute.formated"), + UserAttributeCountry = update.GetValueOrDefault("user.attribute.country"), + UserAttributePostalCode = update.GetValueOrDefault("user.attribute.postal_code"), + UserAttributeStreet = update.GetValueOrDefault("user.attribute.street"), + UserAttributeRegion = update.GetValueOrDefault("user.attribute.region"), + UserAttributeLocality = update.GetValueOrDefault("user.attribute.locality"), + IncludedClientAudience = update.GetValueOrDefault("included.client.audience"), + Multivalued = update.GetValueOrDefault("multivalued") + }; + + private static bool CompareProtocolMapperConfig(Config config, IReadOnlyDictionary update) => + config.Single == update.GetValueOrDefault("single") && + config.AttributeNameFormat == update.GetValueOrDefault("attribute.nameformat") && + config.AttributeName == update.GetValueOrDefault("attribute.name") && + config.UserInfoTokenClaim == update.GetValueOrDefault("userinfo.token.claim") && + config.UserAttribute == update.GetValueOrDefault("user.attribute") && + config.IdTokenClaim == update.GetValueOrDefault("id.token.claim") && + config.AccessTokenClaim == update.GetValueOrDefault("access.token.claim") && + config.ClaimName == update.GetValueOrDefault("claim.name") && + config.JsonTypelabel == update.GetValueOrDefault("jsonType.label") && + config.UserAttributeFormatted == update.GetValueOrDefault("user.attribute.formated") && + config.UserAttributeCountry == update.GetValueOrDefault("user.attribute.country") && + config.UserAttributePostalCode == update.GetValueOrDefault("user.attribute.postal_code") && + config.UserAttributeStreet == update.GetValueOrDefault("user.attribute.street") && + config.UserAttributeRegion == update.GetValueOrDefault("user.attribute.region") && + config.UserAttributeLocality == update.GetValueOrDefault("user.attribute.locality") && + config.IncludedClientAudience == update.GetValueOrDefault("included.client.audience") && + config.Multivalued == update.GetValueOrDefault("multivalued"); +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/RealmUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/RealmUpdater.cs new file mode 100644 index 0000000000..8e571e42a7 --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/RealmUpdater.cs @@ -0,0 +1,272 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Factory; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.RealmsAdmin; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public class RealmUpdater : IRealmUpdater +{ + private readonly IKeycloakFactory _keycloakFactory; + private readonly ISeedDataHandler _seedData; + + public RealmUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) + { + _keycloakFactory = keycloakFactory; + _seedData = seedDataHandler; + } + + public async Task UpdateRealm(string keycloakInstanceName, CancellationToken cancellationToken) + { + var keycloak = _keycloakFactory.CreateKeycloakClient(keycloakInstanceName); + var realm = _seedData.Realm; + var seedRealm = _seedData.KeycloakRealm; + + Realm keycloakRealm; + try + { + keycloakRealm = await keycloak.GetRealmAsync(realm, cancellationToken).ConfigureAwait(false); + } + catch (KeycloakEntityNotFoundException) + { + keycloakRealm = new Realm + { + Id = seedRealm.Id, + _Realm = seedRealm.Realm + }; + await keycloak.ImportRealmAsync(realm, keycloakRealm, cancellationToken).ConfigureAwait(false); + } + + if (!CompareRealm(keycloakRealm, seedRealm)) // defaultRole and IdentityProviders are not compared as they cannot be updated through realm + { + keycloakRealm._Realm = seedRealm.Realm; + keycloakRealm.DisplayName = seedRealm.DisplayName; + keycloakRealm.DisplayNameHtml = seedRealm.DisplayNameHtml; + keycloakRealm.NotBefore = seedRealm.NotBefore; + keycloakRealm.DefaultSignatureAlgorithm = seedRealm.DefaultSignatureAlgorithm; + keycloakRealm.RevokeRefreshToken = seedRealm.RevokeRefreshToken; + keycloakRealm.RefreshTokenMaxReuse = seedRealm.RefreshTokenMaxReuse; + keycloakRealm.AccessTokenLifespan = seedRealm.AccessTokenLifespan; + keycloakRealm.AccessTokenLifespanForImplicitFlow = seedRealm.AccessTokenLifespanForImplicitFlow; + keycloakRealm.SsoSessionIdleTimeout = seedRealm.SsoSessionIdleTimeout; + keycloakRealm.SsoSessionMaxLifespan = seedRealm.SsoSessionMaxLifespan; + keycloakRealm.SsoSessionIdleTimeoutRememberMe = seedRealm.SsoSessionIdleTimeoutRememberMe; + keycloakRealm.SsoSessionMaxLifespanRememberMe = seedRealm.SsoSessionMaxLifespanRememberMe; + keycloakRealm.OfflineSessionIdleTimeout = seedRealm.OfflineSessionIdleTimeout; + keycloakRealm.OfflineSessionMaxLifespanEnabled = seedRealm.OfflineSessionMaxLifespanEnabled; + keycloakRealm.OfflineSessionMaxLifespan = seedRealm.OfflineSessionMaxLifespan; + keycloakRealm.AccessCodeLifespan = seedRealm.AccessCodeLifespan; + keycloakRealm.AccessCodeLifespanUserAction = seedRealm.AccessCodeLifespanUserAction; + keycloakRealm.AccessCodeLifespanLogin = seedRealm.AccessCodeLifespanLogin; + keycloakRealm.ActionTokenGeneratedByAdminLifespan = seedRealm.ActionTokenGeneratedByAdminLifespan; + keycloakRealm.ActionTokenGeneratedByUserLifespan = seedRealm.ActionTokenGeneratedByUserLifespan; + keycloakRealm.Enabled = seedRealm.Enabled; + keycloakRealm.SslRequired = seedRealm.SslRequired; + keycloakRealm.RegistrationAllowed = seedRealm.RegistrationAllowed; + keycloakRealm.RegistrationEmailAsUsername = seedRealm.RegistrationEmailAsUsername; + keycloakRealm.RememberMe = seedRealm.RememberMe; + keycloakRealm.VerifyEmail = seedRealm.VerifyEmail; + keycloakRealm.LoginWithEmailAllowed = seedRealm.LoginWithEmailAllowed; + keycloakRealm.DuplicateEmailsAllowed = seedRealm.DuplicateEmailsAllowed; + keycloakRealm.ResetPasswordAllowed = seedRealm.ResetPasswordAllowed; + keycloakRealm.EditUsernameAllowed = seedRealm.EditUsernameAllowed; + keycloakRealm.BruteForceProtected = seedRealm.BruteForceProtected; + keycloakRealm.PermanentLockout = seedRealm.PermanentLockout; + keycloakRealm.MaxFailureWaitSeconds = seedRealm.MaxFailureWaitSeconds; + keycloakRealm.MinimumQuickLoginWaitSeconds = seedRealm.MinimumQuickLoginWaitSeconds; + keycloakRealm.WaitIncrementSeconds = seedRealm.WaitIncrementSeconds; + keycloakRealm.QuickLoginCheckMilliSeconds = seedRealm.QuickLoginCheckMilliSeconds; + keycloakRealm.MaxDeltaTimeSeconds = seedRealm.MaxDeltaTimeSeconds; + keycloakRealm.FailureFactor = seedRealm.FailureFactor; + keycloakRealm.RequiredCredentials = seedRealm.RequiredCredentials; + keycloakRealm.OtpPolicyType = seedRealm.OtpPolicyType; + keycloakRealm.OtpPolicyAlgorithm = seedRealm.OtpPolicyAlgorithm; + keycloakRealm.OtpPolicyInitialCounter = seedRealm.OtpPolicyInitialCounter; + keycloakRealm.OtpPolicyDigits = seedRealm.OtpPolicyDigits; + keycloakRealm.OtpPolicyLookAheadWindow = seedRealm.OtpPolicyLookAheadWindow; + keycloakRealm.OtpPolicyPeriod = seedRealm.OtpPolicyPeriod; + keycloakRealm.OtpSupportedApplications = seedRealm.OtpSupportedApplications; + keycloakRealm.BrowserSecurityHeaders = UpdateBrowserSecurityHeaders(seedRealm.BrowserSecurityHeaders); + keycloakRealm.SmtpServer = UpdateSmtpServer(seedRealm.SmtpServer); + keycloakRealm.LoginTheme = seedRealm.LoginTheme; + keycloakRealm.AccountTheme = seedRealm.AccountTheme; + keycloakRealm.AdminTheme = seedRealm.AdminTheme; + keycloakRealm.EmailTheme = seedRealm.EmailTheme; + keycloakRealm.EventsEnabled = seedRealm.EventsEnabled; + keycloakRealm.EventsListeners = seedRealm.EventsListeners; + keycloakRealm.EnabledEventTypes = seedRealm.EnabledEventTypes; + keycloakRealm.AdminEventsEnabled = seedRealm.AdminEventsEnabled; + keycloakRealm.AdminEventsDetailsEnabled = seedRealm.AdminEventsDetailsEnabled; + keycloakRealm.InternationalizationEnabled = seedRealm.InternationalizationEnabled; + keycloakRealm.SupportedLocales = seedRealm.SupportedLocales; + keycloakRealm.BrowserFlow = seedRealm.BrowserFlow; + keycloakRealm.RegistrationFlow = seedRealm.RegistrationFlow; + keycloakRealm.DirectGrantFlow = seedRealm.DirectGrantFlow; + keycloakRealm.ResetCredentialsFlow = seedRealm.ResetCredentialsFlow; + keycloakRealm.ClientAuthenticationFlow = seedRealm.ClientAuthenticationFlow; + keycloakRealm.DockerAuthenticationFlow = seedRealm.DockerAuthenticationFlow; + keycloakRealm.Attributes = seedRealm.Attributes?.ToDictionary(x => x.Key, x => x.Value); + keycloakRealm.UserManagedAccessAllowed = seedRealm.UserManagedAccessAllowed; + keycloakRealm.PasswordPolicy = seedRealm.PasswordPolicy; + + await keycloak.UpdateRealmAsync(realm, keycloakRealm, cancellationToken).ConfigureAwait(false); + } + } + + private static bool CompareRealm(Realm keycloakRealm, KeycloakRealm seedRealm) => + keycloakRealm._Realm == seedRealm.Realm && + keycloakRealm.DisplayName == seedRealm.DisplayName && + keycloakRealm.NotBefore == seedRealm.NotBefore && + keycloakRealm.DefaultSignatureAlgorithm == seedRealm.DefaultSignatureAlgorithm && + keycloakRealm.RevokeRefreshToken == seedRealm.RevokeRefreshToken && + keycloakRealm.RefreshTokenMaxReuse == seedRealm.RefreshTokenMaxReuse && + keycloakRealm.AccessTokenLifespan == seedRealm.AccessTokenLifespan && + keycloakRealm.AccessTokenLifespanForImplicitFlow == seedRealm.AccessTokenLifespanForImplicitFlow && + keycloakRealm.SsoSessionIdleTimeout == seedRealm.SsoSessionIdleTimeout && + keycloakRealm.SsoSessionMaxLifespan == seedRealm.SsoSessionMaxLifespan && + keycloakRealm.SsoSessionIdleTimeoutRememberMe == seedRealm.SsoSessionIdleTimeoutRememberMe && + keycloakRealm.SsoSessionMaxLifespanRememberMe == seedRealm.SsoSessionMaxLifespanRememberMe && + keycloakRealm.OfflineSessionIdleTimeout == seedRealm.OfflineSessionIdleTimeout && + keycloakRealm.OfflineSessionMaxLifespanEnabled == seedRealm.OfflineSessionMaxLifespanEnabled && + keycloakRealm.OfflineSessionMaxLifespan == seedRealm.OfflineSessionMaxLifespan && + keycloakRealm.AccessCodeLifespan == seedRealm.AccessCodeLifespan && + keycloakRealm.AccessCodeLifespanUserAction == seedRealm.AccessCodeLifespanUserAction && + keycloakRealm.AccessCodeLifespanLogin == seedRealm.AccessCodeLifespanLogin && + keycloakRealm.ActionTokenGeneratedByAdminLifespan == seedRealm.ActionTokenGeneratedByAdminLifespan && + keycloakRealm.ActionTokenGeneratedByUserLifespan == seedRealm.ActionTokenGeneratedByUserLifespan && + keycloakRealm.Enabled == seedRealm.Enabled && + keycloakRealm.SslRequired == seedRealm.SslRequired && + keycloakRealm.RegistrationAllowed == seedRealm.RegistrationAllowed && + keycloakRealm.RegistrationEmailAsUsername == seedRealm.RegistrationEmailAsUsername && + keycloakRealm.RememberMe == seedRealm.RememberMe && + keycloakRealm.VerifyEmail == seedRealm.VerifyEmail && + keycloakRealm.LoginWithEmailAllowed == seedRealm.LoginWithEmailAllowed && + keycloakRealm.DuplicateEmailsAllowed == seedRealm.DuplicateEmailsAllowed && + keycloakRealm.ResetPasswordAllowed == seedRealm.ResetPasswordAllowed && + keycloakRealm.EditUsernameAllowed == seedRealm.EditUsernameAllowed && + keycloakRealm.BruteForceProtected == seedRealm.BruteForceProtected && + keycloakRealm.PermanentLockout == seedRealm.PermanentLockout && + keycloakRealm.MaxFailureWaitSeconds == seedRealm.MaxFailureWaitSeconds && + keycloakRealm.MinimumQuickLoginWaitSeconds == seedRealm.MinimumQuickLoginWaitSeconds && + keycloakRealm.WaitIncrementSeconds == seedRealm.WaitIncrementSeconds && + keycloakRealm.QuickLoginCheckMilliSeconds == seedRealm.QuickLoginCheckMilliSeconds && + keycloakRealm.MaxDeltaTimeSeconds == seedRealm.MaxDeltaTimeSeconds && + keycloakRealm.FailureFactor == seedRealm.FailureFactor && + keycloakRealm.RequiredCredentials.NullOrContentEqual(seedRealm.RequiredCredentials) && + keycloakRealm.OtpPolicyType == seedRealm.OtpPolicyType && + keycloakRealm.OtpPolicyAlgorithm == seedRealm.OtpPolicyAlgorithm && + keycloakRealm.OtpPolicyInitialCounter == seedRealm.OtpPolicyInitialCounter && + keycloakRealm.OtpPolicyDigits == seedRealm.OtpPolicyDigits && + keycloakRealm.OtpPolicyLookAheadWindow == seedRealm.OtpPolicyLookAheadWindow && + keycloakRealm.OtpPolicyPeriod == seedRealm.OtpPolicyPeriod && + keycloakRealm.OtpSupportedApplications.NullOrContentEqual(seedRealm.OtpSupportedApplications) && + keycloakRealm.PasswordPolicy == seedRealm.PasswordPolicy && + CompareBrowserSecurityHeaders(keycloakRealm.BrowserSecurityHeaders, seedRealm.BrowserSecurityHeaders) && + CompareSmtpServer(keycloakRealm.SmtpServer, seedRealm.SmtpServer) && + keycloakRealm.LoginTheme == seedRealm.LoginTheme && + keycloakRealm.AccountTheme == seedRealm.AccountTheme && + keycloakRealm.AdminTheme == seedRealm.AdminTheme && + keycloakRealm.EmailTheme == seedRealm.EmailTheme && + keycloakRealm.EventsEnabled == seedRealm.EventsEnabled && + keycloakRealm.EventsListeners.NullOrContentEqual(seedRealm.EventsListeners) && + keycloakRealm.EnabledEventTypes.NullOrContentEqual(seedRealm.EnabledEventTypes) && + keycloakRealm.AdminEventsEnabled == seedRealm.AdminEventsEnabled && + keycloakRealm.AdminEventsDetailsEnabled == seedRealm.AdminEventsDetailsEnabled && + keycloakRealm.InternationalizationEnabled == seedRealm.InternationalizationEnabled && + keycloakRealm.SupportedLocales.NullOrContentEqual(seedRealm.SupportedLocales) && + keycloakRealm.BrowserFlow == seedRealm.BrowserFlow && + keycloakRealm.RegistrationFlow == seedRealm.RegistrationFlow && + keycloakRealm.DirectGrantFlow == seedRealm.DirectGrantFlow && + keycloakRealm.ResetCredentialsFlow == seedRealm.ResetCredentialsFlow && + keycloakRealm.ClientAuthenticationFlow == seedRealm.ClientAuthenticationFlow && + keycloakRealm.DockerAuthenticationFlow == seedRealm.DockerAuthenticationFlow && + CompareRealmAttributes(keycloakRealm.Attributes, seedRealm.Attributes) && + keycloakRealm.UserManagedAccessAllowed == seedRealm.UserManagedAccessAllowed && + keycloakRealm.PasswordPolicy == seedRealm.PasswordPolicy; + + private static bool CompareRealmAttributes(IDictionary? attributes, IReadOnlyDictionary? updateAttributes) => + attributes == null && updateAttributes == null || + attributes != null && updateAttributes != null && + attributes.OrderBy(x => x.Key).SequenceEqual(updateAttributes.OrderBy(x => x.Key)); + + private static bool CompareBrowserSecurityHeaders(BrowserSecurityHeaders? securityHeaders, BrowserSecurityHeadersModel? updateSecurityHeaders) => + securityHeaders == null && updateSecurityHeaders == null || + securityHeaders != null && updateSecurityHeaders != null && + securityHeaders.ContentSecurityPolicyReportOnly == updateSecurityHeaders.ContentSecurityPolicyReportOnly && + securityHeaders.XContentTypeOptions == updateSecurityHeaders.XContentTypeOptions && + securityHeaders.XRobotsTag == updateSecurityHeaders.XRobotsTag && + securityHeaders.XFrameOptions == updateSecurityHeaders.XFrameOptions && + securityHeaders.XXssProtection == updateSecurityHeaders.XXSSProtection && + securityHeaders.ContentSecurityPolicy == updateSecurityHeaders.ContentSecurityPolicy && + securityHeaders.StrictTransportSecurity == updateSecurityHeaders.StrictTransportSecurity; + + private static BrowserSecurityHeaders? UpdateBrowserSecurityHeaders(BrowserSecurityHeadersModel? updateSecurityHeaders) => + updateSecurityHeaders == null + ? null + : new BrowserSecurityHeaders + { + ContentSecurityPolicyReportOnly = updateSecurityHeaders.ContentSecurityPolicyReportOnly, + XContentTypeOptions = updateSecurityHeaders.XContentTypeOptions, + XRobotsTag = updateSecurityHeaders.XRobotsTag, + XFrameOptions = updateSecurityHeaders.XFrameOptions, + XXssProtection = updateSecurityHeaders.XXSSProtection, + ContentSecurityPolicy = updateSecurityHeaders.ContentSecurityPolicy, + StrictTransportSecurity = updateSecurityHeaders.StrictTransportSecurity + }; + + private static bool CompareSmtpServer(SmtpServer? smtpServer, SmtpServerModel? updateSmtpServer) => + smtpServer == null && updateSmtpServer == null || + smtpServer != null && updateSmtpServer != null && + smtpServer.Host == updateSmtpServer.Host && + smtpServer.Ssl == updateSmtpServer.Ssl && + smtpServer.StartTls == updateSmtpServer.Starttls && + smtpServer.User == updateSmtpServer.User && + smtpServer.Password == updateSmtpServer.Password && + smtpServer.Auth == updateSmtpServer.Auth && + smtpServer.From == updateSmtpServer.From && + smtpServer.FromDisplayName == updateSmtpServer.FromDisplayName && + smtpServer.ReplyTo == updateSmtpServer.ReplyTo && + smtpServer.ReplyToDisplayName == updateSmtpServer.ReplyToDisplayName && + smtpServer.EnvelopeFrom == updateSmtpServer.EnvelopeFrom && + smtpServer.Port == updateSmtpServer.Port; + + private static SmtpServer? UpdateSmtpServer(SmtpServerModel? updateSmtpServer) => + updateSmtpServer == null + ? null + : new SmtpServer + { + Host = updateSmtpServer.Host, + Ssl = updateSmtpServer.Ssl, + StartTls = updateSmtpServer.Starttls, + User = updateSmtpServer.User, + Password = updateSmtpServer.Password, + Auth = updateSmtpServer.Auth, + From = updateSmtpServer.From, + FromDisplayName = updateSmtpServer.FromDisplayName, + ReplyTo = updateSmtpServer.ReplyTo, + ReplyToDisplayName = updateSmtpServer.ReplyToDisplayName, + EnvelopeFrom = updateSmtpServer.EnvelopeFrom, + Port = updateSmtpServer.Port + }; +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs new file mode 100644 index 0000000000..9d456868be --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/RolesUpdater.cs @@ -0,0 +1,231 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Framework.Async; +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Factory; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Roles; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public class RolesUpdater : IRolesUpdater +{ + private readonly IKeycloakFactory _keycloakFactory; + private readonly ISeedDataHandler _seedData; + + public RolesUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) + { + _keycloakFactory = keycloakFactory; + _seedData = seedDataHandler; + } + + public async Task UpdateClientRoles(string keycloakInstanceName, CancellationToken cancellationToken) + { + var keycloak = _keycloakFactory.CreateKeycloakClient(keycloakInstanceName); + var realm = _seedData.Realm; + + foreach (var (clientId, updateRoles) in _seedData.ClientRoles) + { + var id = _seedData.GetIdOfClient(clientId); + var roles = await keycloak.GetRolesAsync(realm, id, cancellationToken: cancellationToken).ConfigureAwait(false); + + foreach (var newRole in updateRoles.ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name)) + { + await keycloak.CreateRoleAsync(realm, id, CreateRole(newRole), cancellationToken).ConfigureAwait(false); + } + + await UpdateAndDeleteRoles(keycloak, realm, roles, updateRoles, cancellationToken).ConfigureAwait(false); + } + } + + public async Task UpdateRealmRoles(string keycloakInstanceName, CancellationToken cancellationToken) + { + var keycloak = _keycloakFactory.CreateKeycloakClient(keycloakInstanceName); + var realm = _seedData.Realm; + var roles = await keycloak.GetRolesAsync(realm, cancellationToken: cancellationToken).ConfigureAwait(false); + var updateRealmRoles = _seedData.RealmRoles; + + foreach (var newRole in updateRealmRoles.ExceptBy(roles.Select(role => role.Name), roleModel => roleModel.Name)) + { + await keycloak.CreateRoleAsync(realm, CreateRole(newRole), cancellationToken).ConfigureAwait(false); + } + await UpdateAndDeleteRoles(keycloak, realm, roles, updateRealmRoles, cancellationToken).ConfigureAwait(false); + } + + private static async Task UpdateAndDeleteRoles(KeycloakClient keycloak, string realm, IEnumerable roles, IEnumerable updateRoles, CancellationToken cancellationToken) + { + foreach (var (role, update) in + roles.Join( + updateRoles, + role => role.Name, + roleModel => roleModel.Name, + (role, roleModel) => (Role: role, Update: roleModel))) + { + if (!CompareRole(role, update)) + { + if (role.Id == null) + throw new ConflictException($"role id must not be null: {role.Name}"); + if (role.ContainerId == null) + throw new ConflictException($"role containerId must not be null: {role.Name}"); + + await keycloak.UpdateRoleByIdAsync(realm, role.Id, CreateUpdateRole(role.Id, role.ContainerId, update), cancellationToken).ConfigureAwait(false); + } + } + + foreach (var deleteRole in + roles.ExceptBy(updateRoles.Select(roleModel => roleModel.Name), role => role.Name)) + { + if (deleteRole.Id == null) + throw new ConflictException($"role id must not be null: {deleteRole.Name}"); + + await keycloak.DeleteRoleByIdAsync(realm, deleteRole.Id, cancellationToken).ConfigureAwait(false); + } + } + + public async Task UpdateCompositeRoles(string keycloakInstanceName, CancellationToken cancellationToken) + { + var keycloak = _keycloakFactory.CreateKeycloakClient(keycloakInstanceName); + var realm = _seedData.Realm; + + foreach (var (clientId, updateRoles) in _seedData.ClientRoles) + { + var id = _seedData.GetIdOfClient(clientId); + + await UpdateCompositeRolesInner( + () => keycloak.GetRolesAsync(realm, id, cancellationToken: cancellationToken), + updateRoles, + (name, roles) => keycloak.RemoveCompositesFromRoleAsync(realm, id, name, roles, cancellationToken), + (name, roles) => keycloak.AddCompositesToRoleAsync(realm, id, name, roles, cancellationToken)).ConfigureAwait(false); + } + + await UpdateCompositeRolesInner( + () => keycloak.GetRolesAsync(realm, cancellationToken: cancellationToken), + _seedData.RealmRoles, + (name, roles) => keycloak.RemoveCompositesFromRoleAsync(realm, name, roles, cancellationToken), + (name, roles) => keycloak.AddCompositesToRoleAsync(realm, name, roles, cancellationToken)).ConfigureAwait(false); + + async Task UpdateCompositeRolesInner( + Func>> getRoles, + IEnumerable updateRoles, + Func, Task> removeCompositeRoles, + Func, Task> addCompositeRoles) + { + var roles = await getRoles().ConfigureAwait(false); + + await RemoveAddCompositeRolesInner<(string ContainerId, string Name)>( + roleModel => roleModel.Composites?.Client?.Any() ?? false, + role => role.Composites?.Client?.Any() ?? false, + role => role.ClientRole ?? false, + roleModel => roleModel.Composites?.Client? + .Select(x => ( + Id: _seedData.GetIdOfClient(x.Key), + Names: x.Value)) + .SelectMany(x => x.Names.Select(name => (x.Id, name))) ?? throw new ConflictException($"roleModel.Composites.Client is null: {roleModel.Id} {roleModel.Name}"), + role => ( + role.ContainerId ?? throw new ConflictException($"role.ContainerId is null: {role.Id} {role.Name}"), + role.Name ?? throw new ConflictException($"role.Name is null: {role.Id}")), + async x => await keycloak.GetRoleByNameAsync(realm, x.ContainerId, x.Name, cancellationToken).ConfigureAwait(false) + ).ConfigureAwait(false); + + await RemoveAddCompositeRolesInner( + roleModel => roleModel.Composites?.Realm?.Any() ?? false, + role => role.Composites?.Realm?.Any() ?? false, + role => !(role.ClientRole ?? false), + roleModel => roleModel.Composites?.Realm ?? throw new ConflictException($"roleModel.Composites.Realm is null: {roleModel.Id} {roleModel.Name}"), + role => role.Name ?? throw new ConflictException($"role.Name is null: {role.Id}"), + async name => await keycloak.GetRoleByNameAsync(realm, name, cancellationToken).ConfigureAwait(false) + ).ConfigureAwait(false); + + async Task RemoveAddCompositeRolesInner( + Func compositeRolesUpdatePredicate, + Func compositeRolesPredicate, + Func rolePredicate, + Func> joinUpdateSelector, + Func joinUpdateKey, + Func> getRoleByName) + { + var updateComposites = updateRoles.Where(x => compositeRolesUpdatePredicate(x)); + var removeComposites = roles.Where(x => compositeRolesPredicate(x)).ExceptBy(updateComposites.Select(roleModel => roleModel.Name), role => role.Name); + + foreach (var remove in removeComposites) + { + if (remove.Id == null || remove.Name == null) + throw new ConflictException($"role.id or role.name must not be null {remove.Id} {remove.Name}"); + + var composites = (await keycloak.GetRoleChildrenAsync(realm, remove.Id, cancellationToken).ConfigureAwait(false)).Where(role => rolePredicate(role)); + await removeCompositeRoles(remove.Name, composites).ConfigureAwait(false); + } + + var joinedComposites = roles.Join( + updateComposites, + role => role.Name, + roleModel => roleModel.Name, + (role, roleModel) => ( + Role: role, + Update: joinUpdateSelector(roleModel))); + + foreach (var (role, updates) in joinedComposites) + { + if (role.Id == null || role.Name == null) + throw new ConflictException($"role.id or role.name must not be null {role.Id} {role.Name}"); + var composites = (await keycloak.GetRoleChildrenAsync(realm, role.Id, cancellationToken).ConfigureAwait(false)).Where(role => rolePredicate(role)); + composites.Where(role => role.ContainerId == null || role.Name == null).IfAny( + invalid => throw new ConflictException($"composites roles containerId or name must not be null: {string.Join(" ", invalid.Select(x => $"[{string.Join(",", x.Id, x.Name, x.Description, x.ContainerId)}]"))}")); + + var remove = composites.ExceptBy(updates, role => joinUpdateKey(role)); + await removeCompositeRoles(role.Name, remove).ConfigureAwait(false); + + var add = await updates.Except(composites.Select(role => joinUpdateKey(role))) + .ToAsyncEnumerable() + .SelectAwait(x => getRoleByName(x)) + .ToListAsync(cancellationToken) + .ConfigureAwait(false); + await addCompositeRoles(role.Name, add).ConfigureAwait(false); + } + } + } + } + + private static bool CompareRole(Role role, RoleModel update) => + role.Name == update.Name && + role.Description == update.Description && + role.Attributes.NullOrContentEqual(update.Attributes); + + private static Role CreateRole(RoleModel update) => + new Role + { + Name = update.Name, + Description = update.Description, + Composite = update.Composite, + ClientRole = update.ClientRole, + Attributes = update.Attributes?.ToDictionary(x => x.Key, x => x.Value) + }; + + private static Role CreateUpdateRole(string id, string containerId, RoleModel update) + { + var role = CreateRole(update); + role.Id = id; + role.ContainerId = containerId; + return role; + } +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs new file mode 100644 index 0000000000..2f85e3023a --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/SeedDataHandler.cs @@ -0,0 +1,120 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; +using System.Collections.Immutable; +using System.Text.Json; + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public class SeedDataHandler : ISeedDataHandler +{ + private static readonly JsonSerializerOptions Options = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, IncludeFields = true, PropertyNameCaseInsensitive = false }; + private KeycloakRealm? jsonRealm; + private IReadOnlyDictionary? _idOfClients; + + public async Task Import(string path, CancellationToken cancellationToken) + { + using (var stream = File.OpenRead(path)) + { + jsonRealm = await JsonSerializer.DeserializeAsync(stream, Options, cancellationToken).ConfigureAwait(false) ?? throw new ConfigurationException($"cannot deserialize realm from {path}"); + } + _idOfClients = null; + } + + public string Realm + { + get => jsonRealm?.Realm ?? throw new ConflictException("realm must not be null"); + } + + public KeycloakRealm KeycloakRealm + { + get => jsonRealm ?? throw new InvalidOperationException("Import has not been called"); + } + + public IEnumerable Clients + { + get => jsonRealm?.Clients ?? Enumerable.Empty(); + } + + public IReadOnlyDictionary> ClientRoles + { + get => jsonRealm?.Roles?.Client ?? Enumerable.Empty<(string, IEnumerable)>().ToImmutableDictionary(x => x.Item1, x => x.Item2); + } + + public IEnumerable RealmRoles + { + get => jsonRealm?.Roles?.Realm ?? Enumerable.Empty(); + } + + public IEnumerable IdentityProviders + { + get => jsonRealm?.IdentityProviders ?? Enumerable.Empty(); + } + + public IEnumerable IdentityProviderMappers + { + get => jsonRealm?.IdentityProviderMappers ?? Enumerable.Empty(); + } + + public IEnumerable Users + { + get => jsonRealm?.Users ?? Enumerable.Empty(); + } + + public IEnumerable TopLevelCustomAuthenticationFlows + { + get => jsonRealm?.AuthenticationFlows?.Where(x => (x.TopLevel ?? false) && !(x.BuiltIn ?? false)) ?? Enumerable.Empty(); + } + + public IEnumerable ClientScopes + { + get => jsonRealm?.ClientScopes ?? Enumerable.Empty(); + } + + public IReadOnlyDictionary ClientsDictionary + { + get => _idOfClients ?? throw new InvalidOperationException("ClientInternalIds have not been set"); + } + + public async Task SetClientInternalIds(IAsyncEnumerable<(string ClientId, string Id)> clientInternalIds) + { + var clientIds = new Dictionary(); + await foreach (var (clientId, id) in clientInternalIds.ConfigureAwait(false)) + { + clientIds[clientId] = id; + } + _idOfClients = clientIds.ToImmutableDictionary(); + } + + public string GetIdOfClient(string clientId) => + (_idOfClients ?? throw new InvalidOperationException("ClientInternalIds have not been set")) + .GetValueOrDefault(clientId) ?? throw new ConflictException($"clientId is unknown or id of client is null {clientId}"); + + public AuthenticationFlowModel GetAuthenticationFlow(string? alias) => + jsonRealm?.AuthenticationFlows?.SingleOrDefault(x => x.Alias == (alias ?? throw new ConflictException("alias is null"))) ?? throw new ConflictException($"authenticationFlow {alias} does not exist in seeding-data"); + + public IEnumerable GetAuthenticationExecutions(string? alias) => + GetAuthenticationFlow(alias).AuthenticationExecutions ?? Enumerable.Empty(); + + public AuthenticatorConfigModel GetAuthenticatorConfig(string? alias) => + jsonRealm?.AuthenticatorConfig?.SingleOrDefault(x => x.Alias == (alias ?? throw new ConflictException("alias is null"))) ?? throw new ConflictException($"authenticatorConfig {alias} does not exist"); +} diff --git a/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs b/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs new file mode 100644 index 0000000000..dcda97e84c --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/BusinessLogic/UsersUpdater.cs @@ -0,0 +1,263 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Factory; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Roles; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Library.Models.Users; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; + +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; + +public class UsersUpdater : IUsersUpdater +{ + private readonly IKeycloakFactory _keycloakFactory; + private readonly ISeedDataHandler _seedData; + + public UsersUpdater(IKeycloakFactory keycloakFactory, ISeedDataHandler seedDataHandler) + { + _keycloakFactory = keycloakFactory; + _seedData = seedDataHandler; + } + + public async Task UpdateUsers(string keycloakInstanceName, CancellationToken cancellationToken) + { + var realm = _seedData.Realm; + var keycloak = _keycloakFactory.CreateKeycloakClient(keycloakInstanceName); + var clientsDictionary = _seedData.ClientsDictionary; + + foreach (var seedUser in _seedData.Users) + { + if (seedUser.Username == null) + throw new ConflictException($"username must not be null {seedUser.Id}"); + + var userId = await CreateOrUpdateUserReturningId( + keycloak, + realm, + seedUser, + cancellationToken).ConfigureAwait(false); + + await UpdateClientAndRealmRoles( + keycloak, + realm, + userId, + seedUser, + clientsDictionary, + cancellationToken).ConfigureAwait(false); + + await UpdateFederatedIdentities( + keycloak, + realm, + userId, + seedUser.FederatedIdentities ?? Enumerable.Empty(), + cancellationToken).ConfigureAwait(false); + } + } + + private static async Task CreateOrUpdateUserReturningId(KeycloakClient keycloak, string realm, UserModel seedUser, CancellationToken cancellationToken) + { + var user = (await keycloak.GetUsersAsync(realm, username: seedUser.Username, cancellationToken: cancellationToken).ConfigureAwait(false)).SingleOrDefault(x => x.UserName == seedUser.Username); + + if (user == null) + { + return await keycloak.CreateAndRetrieveUserIdAsync( + realm, + CreateUpdateUser(null, seedUser), + cancellationToken).ConfigureAwait(false) ?? throw new KeycloakNoSuccessException($"failed to retrieve id of newly created user {seedUser.Username}"); + } + else + { + if (user.Id == null) + throw new ConflictException($"user.Id must not be null: userName {seedUser.Username}"); + if (!CompareUser(user, seedUser)) + { + await keycloak.UpdateUserAsync( + realm, + user.Id, + CreateUpdateUser(user.Id, seedUser), + cancellationToken).ConfigureAwait(false); + } + return user.Id; + } + } + + private static async Task UpdateClientAndRealmRoles(KeycloakClient keycloak, string realm, string userId, UserModel seedUser, IReadOnlyDictionary clientsDictionary, CancellationToken cancellationToken) + { + foreach (var (clientId, id) in clientsDictionary) + { + await UpdateUserRoles( + () => keycloak.GetClientRoleMappingsForUserAsync(realm, userId, id, cancellationToken), + () => seedUser.ClientRoles?.GetValueOrDefault(clientId) ?? Enumerable.Empty(), + () => keycloak.GetRolesAsync(realm, id, cancellationToken: cancellationToken), + delete => keycloak.DeleteClientRoleMappingsFromUserAsync(realm, userId, id, delete, cancellationToken), + add => keycloak.AddClientRoleMappingsToUserAsync(realm, userId, id, add, cancellationToken)).ConfigureAwait(false); + } + + await UpdateUserRoles( + () => keycloak.GetRealmRoleMappingsForUserAsync(realm, userId, cancellationToken), + () => seedUser.RealmRoles ?? Enumerable.Empty(), + () => keycloak.GetRolesAsync(realm, cancellationToken: cancellationToken), + delete => keycloak.DeleteRealmRoleMappingsFromUserAsync(realm, userId, delete, cancellationToken), + add => keycloak.AddRealmRoleMappingsToUserAsync(realm, userId, add, cancellationToken)).ConfigureAwait(false); + } + + private static async Task UpdateUserRoles(Func>> getUserRoles, Func> getSeedRoles, Func>> getAllRoles, Func, Task> deleteRoles, Func, Task> addRoles) + { + var userRoles = await getUserRoles().ConfigureAwait(false); + var seedRoles = getSeedRoles(); + + if (userRoles.ExceptBy(seedRoles, x => x.Name).IfAny( + delete => deleteRoles(delete), + out var deleteRolesTask)) + { + await deleteRolesTask!.ConfigureAwait(false); + } + + if (seedRoles.IfAny( + async seed => + { + var allRoles = await getAllRoles().ConfigureAwait(false); + seed.Except(allRoles.Select(x => x.Name)).IfAny(nonexisting => throw new ConflictException($"roles {string.Join(",", nonexisting)} does not exist")); + if (seed.Except(userRoles.Select(x => x.Name)).IfAny( + add => addRoles(allRoles.IntersectBy(add, x => x.Name)), + out var addRolesTask)) + { + await addRolesTask!.ConfigureAwait(false); + } + }, + out var updateRolesTask)) + { + await updateRolesTask!.ConfigureAwait(false); + } + } + + private static User CreateUpdateUser(string? id, UserModel update) => new User + { + // Access, ClientConsents, Credentials, FederatedIdentities, FederationLink, Origin, Self are not in scope + Id = id, + CreatedTimestamp = update.CreatedTimestamp, + UserName = update.Username, + Enabled = update.Enabled, + Totp = update.Totp, + EmailVerified = update.EmailVerified, + FirstName = update.FirstName, + LastName = update.LastName, + Email = update.Email, + DisableableCredentialTypes = update.DisableableCredentialTypes, + RequiredActions = update.RequiredActions, + NotBefore = update.NotBefore, + Attributes = update.Attributes?.ToDictionary(x => x.Key, x => x.Value), + Groups = update.Groups, + ServiceAccountClientId = update.ServiceAccountClientId + }; + + private static bool CompareUser(User user, UserModel update) => + // Access, ClientConsents, Credentials, FederatedIdentities, FederationLink, Origin, Self are not in scope + user.CreatedTimestamp == update.CreatedTimestamp && + user.UserName == update.Username && + user.Enabled == update.Enabled && + user.Totp == update.Totp && + user.EmailVerified == update.EmailVerified && + user.FirstName == update.FirstName && + user.LastName == update.LastName && + user.Email == update.Email && + user.DisableableCredentialTypes.NullOrContentEqual(update.DisableableCredentialTypes) && + user.RequiredActions.NullOrContentEqual(update.RequiredActions) && + user.NotBefore == update.NotBefore && + user.Attributes.NullOrContentEqual(update.Attributes) && + user.Groups.NullOrContentEqual(update.Groups) && + user.ServiceAccountClientId == update.ServiceAccountClientId; + + private static bool CompareFederatedIdentity(FederatedIdentity identity, FederatedIdentityModel update) => + identity.IdentityProvider == update.IdentityProvider && + identity.UserId == update.UserId && + identity.UserName == update.UserName; + + private static async Task UpdateFederatedIdentities(KeycloakClient keycloak, string realm, string userId, IEnumerable updates, CancellationToken cancellationToken) + { + var identities = await keycloak.GetUserSocialLoginsAsync(realm, userId).ConfigureAwait(false); + await DeleteObsoleteFederatedIdentities(keycloak, realm, userId, identities, updates, cancellationToken).ConfigureAwait(false); + await CreateMissingFederatedIdentities(keycloak, realm, userId, identities, updates, cancellationToken).ConfigureAwait(false); + await UpdateExistingFederatedIdentities(keycloak, realm, userId, identities, updates, cancellationToken).ConfigureAwait(false); + } + + private static async Task DeleteObsoleteFederatedIdentities(KeycloakClient keycloak, string realm, string userId, IEnumerable identities, IEnumerable updates, CancellationToken cancellationToken) + { + foreach (var identity in identities.ExceptBy(updates.Select(x => x.IdentityProvider), x => x.IdentityProvider)) + { + await keycloak.RemoveUserSocialLoginProviderAsync( + realm, + userId, + identity.IdentityProvider ?? throw new ConflictException($"federatedIdentity.IdentityProvider is null {userId}"), + cancellationToken).ConfigureAwait(false); + } + } + + private static async Task CreateMissingFederatedIdentities(KeycloakClient keycloak, string realm, string userId, IEnumerable identities, IEnumerable updates, CancellationToken cancellationToken) + { + foreach (var update in updates.ExceptBy(identities.Select(x => x.IdentityProvider), x => x.IdentityProvider)) + { + await keycloak.AddUserSocialLoginProviderAsync( + realm, + userId, + update.IdentityProvider ?? throw new ConflictException($"federatedIdentity.IdentityProvider is null {userId}"), + new() + { + IdentityProvider = update.IdentityProvider, + UserId = update.UserId ?? throw new ConflictException($"federatedIdentity.UserId is null {userId}, {update.IdentityProvider}"), + UserName = update.UserName ?? throw new ConflictException($"federatedIdentity.UserName is null {userId}, {update.IdentityProvider}") + }, + cancellationToken).ConfigureAwait(false); + } + } + + private static async Task UpdateExistingFederatedIdentities(KeycloakClient keycloak, string realm, string userId, IEnumerable identities, IEnumerable updates, CancellationToken cancellationToken) + { + foreach (var (identity, update) in identities + .Join( + updates, + x => x.IdentityProvider, + x => x.IdentityProvider, + (identity, update) => (Identity: identity, Update: update)) + .Where(x => !CompareFederatedIdentity(x.Identity, x.Update))) + { + await keycloak.RemoveUserSocialLoginProviderAsync( + realm, + userId, + identity.IdentityProvider ?? throw new ConflictException($"federatedIdentity.IdentityProvider is null {userId}"), + cancellationToken).ConfigureAwait(false); + + await keycloak.AddUserSocialLoginProviderAsync( + realm, + userId, + update.IdentityProvider ?? throw new ConflictException($"federatedIdentity.IdentityProvider is null {userId}"), + new() + { + IdentityProvider = update.IdentityProvider, + UserId = update.UserId ?? throw new ConflictException($"federatedIdentity.UserId is null {userId}, {update.IdentityProvider}"), + UserName = update.UserName ?? throw new ConflictException($"federatedIdentity.UserName is null {userId}, {update.IdentityProvider}") + }, + cancellationToken).ConfigureAwait(false); + } + } +} diff --git a/src/keycloak/Keycloak.Seeding/Keycloak.Seeding.csproj b/src/keycloak/Keycloak.Seeding/Keycloak.Seeding.csproj new file mode 100644 index 0000000000..16bafca4cf --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/Keycloak.Seeding.csproj @@ -0,0 +1,74 @@ + + + + + + Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding + Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding + net6.0 + enable + enable + b9d59db4-40b8-4722-8263-a61a1ef3155c + Linux + ..\..\.. + True + CS1591 + Exe + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + diff --git a/src/keycloak/Keycloak.Seeding/Models/KeycloakRealm.cs b/src/keycloak/Keycloak.Seeding/Models/KeycloakRealm.cs new file mode 100644 index 0000000000..1d4924c6ff --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/Models/KeycloakRealm.cs @@ -0,0 +1,417 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 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 Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; + +public class KeycloakRealm +{ + public string? Id { get; set; } + public string? Realm { get; set; } + public string? DisplayName { get; set; } + public string? DisplayNameHtml { get; set; } + public int? NotBefore { get; set; } + public string? DefaultSignatureAlgorithm { get; set; } + public bool? RevokeRefreshToken { get; set; } + public int? RefreshTokenMaxReuse { get; set; } + public int? AccessTokenLifespan { get; set; } + public int? AccessTokenLifespanForImplicitFlow { get; set; } + public int? SsoSessionIdleTimeout { get; set; } + public int? SsoSessionMaxLifespan { get; set; } + public int? SsoSessionIdleTimeoutRememberMe { get; set; } + public int? SsoSessionMaxLifespanRememberMe { get; set; } + public int? OfflineSessionIdleTimeout { get; set; } + public bool? OfflineSessionMaxLifespanEnabled { get; set; } + public int? OfflineSessionMaxLifespan { get; set; } + public int? ClientSessionIdleTimeout { get; set; } + public int? ClientSessionMaxLifespan { get; set; } + public int? ClientOfflineSessionIdleTimeout { get; set; } + public int? ClientOfflineSessionMaxLifespan { get; set; } + public int? AccessCodeLifespan { get; set; } + public int? AccessCodeLifespanUserAction { get; set; } + public int? AccessCodeLifespanLogin { get; set; } + public int? ActionTokenGeneratedByAdminLifespan { get; set; } + public int? ActionTokenGeneratedByUserLifespan { get; set; } + public int? Oauth2DeviceCodeLifespan { get; set; } + public int? Oauth2DevicePollingInterval { get; set; } + public bool? Enabled { get; set; } + public string? SslRequired { get; set; } + public bool? RegistrationAllowed { get; set; } + public bool? RegistrationEmailAsUsername { get; set; } + public bool? RememberMe { get; set; } + public bool? VerifyEmail { get; set; } + public bool? LoginWithEmailAllowed { get; set; } + public bool? DuplicateEmailsAllowed { get; set; } + public bool? ResetPasswordAllowed { get; set; } + public bool? EditUsernameAllowed { get; set; } + public bool? BruteForceProtected { get; set; } + public bool? PermanentLockout { get; set; } + public int? MaxFailureWaitSeconds { get; set; } + public int? MinimumQuickLoginWaitSeconds { get; set; } + public int? WaitIncrementSeconds { get; set; } + public int? QuickLoginCheckMilliSeconds { get; set; } + public int? MaxDeltaTimeSeconds { get; set; } + public int? FailureFactor { get; set; } + public RolesModel? Roles { get; set; } + public IEnumerable? Groups { get; set; } + public RoleModel? DefaultRole { get; set; } + public IEnumerable? DefaultGroups { get; set; } + public IEnumerable? RequiredCredentials { get; set; } + public string? OtpPolicyType { get; set; } + public string? OtpPolicyAlgorithm { get; set; } + public int? OtpPolicyInitialCounter { get; set; } + public int? OtpPolicyDigits { get; set; } + public int? OtpPolicyLookAheadWindow { get; set; } + public int? OtpPolicyPeriod { get; set; } + public IEnumerable? OtpSupportedApplications { get; set; } + public string? PasswordPolicy { get; set; } + public string? WebAuthnPolicyRpEntityName { get; set; } + public IEnumerable? WebAuthnPolicySignatureAlgorithms { get; set; } + public string? WebAuthnPolicyRpId { get; set; } + public string? WebAuthnPolicyAttestationConveyancePreference { get; set; } + public string? WebAuthnPolicyAuthenticatorAttachment { get; set; } + public string? WebAuthnPolicyRequireResidentKey { get; set; } + public string? WebAuthnPolicyUserVerificationRequirement { get; set; } + public int? WebAuthnPolicyCreateTimeout { get; set; } + public bool? WebAuthnPolicyAvoidSameAuthenticatorRegister { get; set; } + public IEnumerable? WebAuthnPolicyAcceptableAaguids { get; set; } + public string? WebAuthnPolicyPasswordlessRpEntityName { get; set; } + public IEnumerable? WebAuthnPolicyPasswordlessSignatureAlgorithms { get; set; } + public string? WebAuthnPolicyPasswordlessRpId { get; set; } + public string? WebAuthnPolicyPasswordlessAttestationConveyancePreference { get; set; } + public string? WebAuthnPolicyPasswordlessAuthenticatorAttachment { get; set; } + public string? WebAuthnPolicyPasswordlessRequireResidentKey { get; set; } + public string? WebAuthnPolicyPasswordlessUserVerificationRequirement { get; set; } + public int? WebAuthnPolicyPasswordlessCreateTimeout { get; set; } + public bool? WebAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister { get; set; } + public IEnumerable? WebAuthnPolicyPasswordlessAcceptableAaguids { get; set; } + public IEnumerable? Users { get; set; } + public IEnumerable? ScopeMappings { get; set; } + public IReadOnlyDictionary>? ClientScopeMappings { get; set; } + public IEnumerable? Clients { get; set; } + public IEnumerable? ClientScopes { get; set; } + public IEnumerable? DefaultDefaultClientScopes { get; set; } + public IEnumerable? DefaultOptionalClientScopes { get; set; } + public BrowserSecurityHeadersModel? BrowserSecurityHeaders { get; set; } + public SmtpServerModel? SmtpServer { get; set; } + public string? LoginTheme { get; set; } + public string? AccountTheme { get; set; } + public string? AdminTheme { get; set; } + public string? EmailTheme { get; set; } + public bool? EventsEnabled { get; set; } + public IEnumerable? EventsListeners { get; set; } + public IEnumerable? EnabledEventTypes { get; set; } + public bool? AdminEventsEnabled { get; set; } + public bool? AdminEventsDetailsEnabled { get; set; } + public IEnumerable? IdentityProviders { get; set; } + public IEnumerable? IdentityProviderMappers { get; set; } + public IReadOnlyDictionary>? Components { get; set; } + public bool? InternationalizationEnabled { get; set; } + public IEnumerable? SupportedLocales { get; set; } + public string? DefaultLocale { get; set; } + public IEnumerable? AuthenticationFlows { get; set; } + public IEnumerable? AuthenticatorConfig { get; set; } + public IEnumerable? RequiredActions { get; set; } + public string? BrowserFlow { get; set; } + public string? RegistrationFlow { get; set; } + public string? DirectGrantFlow { get; set; } + public string? ResetCredentialsFlow { get; set; } + public string? ClientAuthenticationFlow { get; set; } + public string? DockerAuthenticationFlow { get; set; } + public IReadOnlyDictionary? Attributes { get; set; } + public string? KeycloakVersion { get; set; } + public bool? UserManagedAccessAllowed { get; set; } + public ClientProfilesModel? ClientProfiles { get; set; } + public ClientPoliciesModel? ClientPolicies { get; set; } +} + +public record RolesModel( + IEnumerable? Realm, + IReadOnlyDictionary>? Client +); + +public record CompositeRolesModel( + IEnumerable? Realm, + IReadOnlyDictionary>? Client +); + +public record RoleModel( + string? Id, + string? Name, + string? Description, + bool? Composite, + bool? ClientRole, + string? ContainerId, + IReadOnlyDictionary>? Attributes, + CompositeRolesModel? Composites +); + +public record UserModel( + string? Id, + long? CreatedTimestamp, + string? Username, + bool? Enabled, + bool? Totp, + bool? EmailVerified, + string? FirstName, + string? LastName, + string? Email, + IReadOnlyDictionary>? Attributes, + IEnumerable? Credentials, + IEnumerable? DisableableCredentialTypes, + IEnumerable? RequiredActions, + IEnumerable? FederatedIdentities, + IEnumerable? RealmRoles, + IReadOnlyDictionary>? ClientRoles, + int? NotBefore, + IEnumerable? Groups, + string? ServiceAccountClientId +); + +public record FederatedIdentityModel( + string? IdentityProvider, + string? UserId, + string? UserName +); + +public record GroupModel( + string? Id, + string? Name, + string? Path, + IReadOnlyDictionary>? Attributes, + IEnumerable? RealmRoles, + IReadOnlyDictionary>? ClientRoles, + IEnumerable? SubGroups +); + +public record ScopeMappingModel( + string? ClientScope, + IEnumerable? Roles +); + +public record ClientScopeMappingModel( + string? Client, + IEnumerable? Roles +); + +public record ClientModel( + string? Id, + string? ClientId, + string? Name, + string? RootUrl, + string? BaseUrl, + bool? SurrogateAuthRequired, + bool? Enabled, + bool? AlwaysDisplayInConsole, + string? ClientAuthenticatorType, + IEnumerable? RedirectUris, + IEnumerable? WebOrigins, + int? NotBefore, + bool? BearerOnly, + bool? ConsentRequired, + bool? StandardFlowEnabled, + bool? ImplicitFlowEnabled, + bool? DirectAccessGrantsEnabled, + bool? ServiceAccountsEnabled, + bool? PublicClient, + bool? FrontchannelLogout, + string? Protocol, + IReadOnlyDictionary? Attributes, + IReadOnlyDictionary? AuthenticationFlowBindingOverrides, + bool? FullScopeAllowed, + int? NodeReRegistrationTimeout, + IEnumerable? DefaultClientScopes, + IEnumerable? OptionalClientScopes, + IEnumerable? ProtocolMappers, + ClientAccessModel? Access, + string? Secret, + string? AdminUrl, + string? Description, + bool? AuthorizationServicesEnabled +); + +public record ClientAccessModel( + bool? Configure, + bool? Manage, + bool? View +); + +public record ProtocolMapperModel( + string? Id, + string? Name, + string? Protocol, + string? ProtocolMapper, + bool? ConsentRequired, + IReadOnlyDictionary? Config +); + +public record ClientScopeModel( + string? Id, + string? Name, + string? Protocol, + IReadOnlyDictionary? Attributes, + IEnumerable? ProtocolMappers, + string? Description +); + +public record BrowserSecurityHeadersModel( + string? ContentSecurityPolicyReportOnly, + string? XContentTypeOptions, + string? XRobotsTag, + string? XFrameOptions, + string? ContentSecurityPolicy, + [property: JsonPropertyName("xXSSProtection")] + string? XXSSProtection, + string? StrictTransportSecurity +); + +public record SmtpServerModel( + string? Password, + string? Starttls, + string? Auth, + string? Port, + string? Host, + string? ReplyToDisplayName, + string? ReplyTo, + string? FromDisplayName, + string? From, + string? EnvelopeFrom, + string? Ssl, + string? User +); + +public record IdentityProviderModel( + string? Alias, + string? DisplayName, + string? InternalId, + string? ProviderId, + bool? Enabled, + string? UpdateProfileFirstLoginMode, + bool? TrustEmail, + bool? StoreToken, + bool? AddReadTokenRoleOnCreate, + bool? AuthenticateByDefault, + bool? LinkOnly, + string? FirstBrokerLoginFlowAlias, + string? PostBrokerLoginFlowAlias, + IdentityProviderConfigModel? Config +); + +public record IdentityProviderConfigModel( + string? HideOnLoginPage, + string? ClientSecret, + string? DisableUserInfo, + string? ValidateSignature, + string? ClientId, + string? TokenUrl, + string? AuthorizationUrl, + string? ClientAuthMethod, + string? JwksUrl, + string? LogoutUrl, + string? ClientAssertionSigningAlg, + string? SyncMode, + string? UseJwksUrl, + string? UserInfoUrl, + string? Issuer, + // for Saml: + string? NameIDPolicyFormat, + string? PrincipalType, + string? SignatureAlgorithm, + string? XmlSigKeyInfoKeyNameTransformer, + string? AllowCreate, + string? EntityId, + string? AuthnContextComparisonType, + string? BackchannelSupported, + string? PostBindingResponse, + string? PostBindingAuthnRequest, + string? PostBindingLogout, + string? WantAuthnRequestsSigned, + string? WantAssertionsSigned, + string? WantAssertionsEncrypted, + string? ForceAuthn, + string? SignSpMetadata, + string? LoginHint, + string? SingleSignOnServiceUrl, + string? AllowedClockSkew, + string? AttributeConsumingServiceIndex +); + +public record IdentityProviderMapperModel( + string? Id, + string? Name, + string? IdentityProviderAlias, + string? IdentityProviderMapper, + IReadOnlyDictionary? Config +); + +public record ComponentModel( + string? Id, + string? Name, + string? ProviderId, + string? SubType, + object? SubComponents, + IReadOnlyDictionary>? Config +); + +public record AuthenticationFlowModel( + string? Id, + string? Alias, + string? Description, + string? ProviderId, + bool? TopLevel, + bool? BuiltIn, + IEnumerable? AuthenticationExecutions +); + +public record AuthenticationExecutionModel( + string? Authenticator, + bool? AuthenticatorFlow, + string? Requirement, + int? Priority, + bool? UserSetupAllowed, + bool? AutheticatorFlow, + string? FlowAlias, + string? AuthenticatorConfig +); + +public record AuthenticatorConfigModel( + string? Id, + string? Alias, + IReadOnlyDictionary? Config +); + +public record RequiredActionModel( + string? Alias, + string? Name, + string? ProviderId, + bool? Enabled, + bool? DefaultAction, + int? Priority, + object? Config +); + +public record ClientProfilesModel( + IEnumerable? Profiles +); + +public record ClientPoliciesModel( + IEnumerable? Policies +); diff --git a/src/keycloak/Keycloak.Seeding/Program.cs b/src/keycloak/Keycloak.Seeding/Program.cs new file mode 100644 index 0000000000..58c208f330 --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/Program.cs @@ -0,0 +1,96 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Org.Eclipse.TractusX.Portal.Backend.Framework.Logging; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.ErrorHandling; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Factory; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; +using Serilog; + +LoggingExtensions.EnsureInitialized(); +Log.Information("Building keycloak-seeder"); +var isDevelopment = false; +try +{ + var host = Host + .CreateDefaultBuilder(args) + .ConfigureServices((hostContext, services) => + { + services + .AddLogging() + .AddScoped() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .AddTransient() + .ConfigureKeycloakSettingsMap(hostContext.Configuration.GetSection("Keycloak")) + .AddTransient() + .ConfigureKeycloakSeederSettings(hostContext.Configuration.GetSection("KeycloakSeeding")); + + if (hostContext.HostingEnvironment.IsDevelopment()) + { + var urlsToTrust = hostContext.Configuration.GetSection("Keycloak").Get().Values + .Where(config => config.ConnectionString.StartsWith("https://")) + .Select(config => config.ConnectionString) + .Distinct(); + FlurlUntrustedCertExceptionHandler.ConfigureExceptions(urlsToTrust); + isDevelopment = true; + } + }) + .UseSerilog() + .Build(); + + FlurlErrorHandler.ConfigureErrorHandler(host.Services.GetRequiredService>(), isDevelopment); + + Log.Information("Building keycloak-seeder completed"); + + var tokenSource = new CancellationTokenSource(); + Console.CancelKeyPress += (s, e) => + { + Log.Information("Canceling..."); + tokenSource.Cancel(); + e.Cancel = true; + }; + + using var scope = host.Services.CreateScope(); + Log.Information("Start seeding"); + var seederInstance = scope.ServiceProvider.GetRequiredService(); + await seederInstance.Seed(tokenSource.Token).ConfigureAwait(false); + Log.Information("Execution finished shutting down"); +} +catch (Exception ex) when (!ex.GetType().Name.Equals("StopTheHostException", StringComparison.Ordinal)) +{ + Log.Fatal(ex, "Unhandled exception"); + Log.CloseAndFlush(); + Environment.ExitCode = 1; + throw; +} +finally +{ + Log.Information("Server Shutting down"); + Log.CloseAndFlush(); +} diff --git a/src/keycloak/Keycloak.Seeding/Properties/launchSettings.json b/src/keycloak/Keycloak.Seeding/Properties/launchSettings.json new file mode 100644 index 0000000000..73c9924212 --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/Properties/launchSettings.json @@ -0,0 +1,10 @@ +{ + "profiles": { + "Keycloak.Seeding": { + "commandName": "Project", + "environmentVariables": { + "DOTNET_ENVIRONMENT": "Development" + } + } + } +} \ No newline at end of file diff --git a/src/keycloak/Keycloak.Seeding/appsettings.json b/src/keycloak/Keycloak.Seeding/appsettings.json new file mode 100644 index 0000000000..c0f18ff741 --- /dev/null +++ b/src/keycloak/Keycloak.Seeding/appsettings.json @@ -0,0 +1,37 @@ +{ + "Serilog": { + "Using": [ "Serilog.Sinks.Console" ], +"MinimumLevel": { + "Default": "Information", + "Override": { + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } +}, + "WriteTo": [ + { "Name": "Console" } + ], + "Enrich": [ + "FromLogContext", + "WithMachineName", + "WithProcessId", + "WithThreadId", + "WithCorrelationId" + ], + "Properties": { + "Application": "Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding" + } + }, + "Keycloak": { + "central": { + "ConnectionString": "", + "User": "", + "Password": "", + "AuthRealm": "" + } + }, + "KeycloakSeeding": { + "DataPathes": [], + "InstanceName": "" + } +} diff --git a/src/mailing/Mailing.Template/EmailTemplates/portal_welcome_email.html b/src/mailing/Mailing.Template/EmailTemplates/portal_welcome_email.html index ea4c00a658..7cc298c8d4 100644 --- a/src/mailing/Mailing.Template/EmailTemplates/portal_welcome_email.html +++ b/src/mailing/Mailing.Template/EmailTemplates/portal_welcome_email.html @@ -92,7 +92,7 @@ style="Margin:0;padding-top:20px;padding-bottom:20px;padding-left:30px;padding-right:30px;text-align: left;">

- Dear {userName} {bpn}
Your company registration application for {companyName} has + Dear {userName}
Your company registration application for {companyName} {bpn} has been successfully approved.
You can now start to participate in the Catena-X Network.

- [Obsolete("This Method is not used anymore, Planning to delete it with release 3.1")] - public Task UpdateAppAsync(Guid appId, AppEditableDetail updateModel, Guid companyId) - { - if (appId == Guid.Empty) - { - throw new ControllerArgumentException($"AppId must not be empty"); - } - if (updateModel.Descriptions.Any(item => string.IsNullOrWhiteSpace(item.LanguageCode))) - { - throw new ControllerArgumentException("Language Code must not be empty"); - } - return EditAppAsync(appId, updateModel, companyId); - } - - [Obsolete("This Method is not used anymore, Planning to delete it with release 3.1")] - private async Task EditAppAsync(Guid appId, AppEditableDetail updateModel, Guid companyId) - { - var appRepository = _portalRepositories.GetInstance(); - var appResult = await appRepository.GetOfferDetailsForUpdateAsync(appId, companyId, OfferTypeId.APP).ConfigureAwait(false); - if (appResult == default) - { - throw new NotFoundException($"app {appId} does not exist"); - } - if (!appResult.IsProviderUser) - { - throw new ForbiddenException($"Company {companyId} is not the providing company"); - } - if (!appResult.IsAppCreated) - { - throw new ConflictException($"app {appId} is not in status CREATED"); - } - appRepository.AttachAndModifyOffer(appId, app => - { - if (appResult.ContactEmail != updateModel.ContactEmail) - { - app.ContactEmail = updateModel.ContactEmail; - } - if (appResult.ContactNumber != updateModel.ContactNumber) - { - app.ContactNumber = updateModel.ContactNumber; - } - if (appResult.MarketingUrl != updateModel.ProviderUri) - { - app.MarketingUrl = updateModel.ProviderUri; - } - }); - - _offerService.UpsertRemoveOfferDescription(appId, updateModel.Descriptions, appResult.Descriptions); - await _portalRepositories.SaveAsync().ConfigureAwait(false); - } - /// public Task CreateAppDocumentAsync(Guid appId, DocumentTypeId documentTypeId, IFormFile document, (Guid UserId, Guid CompanyId) identity, CancellationToken cancellationToken) => UploadAppDoc(appId, documentTypeId, document, identity, OfferTypeId.APP, cancellationToken); @@ -500,8 +448,8 @@ private async Task SetInstanceTypeInternal(Guid appId, AppInstanceSetupData data if (!result.IsUserOfProvidingCompany) throw new ForbiddenException($"Company {companyId} is not the provider company"); - if (result.OfferStatus is not (OfferStatusId.CREATED or OfferStatusId.IN_REVIEW)) - throw new ConflictException($"App {appId} is not in Status {OfferStatusId.CREATED} or {OfferStatusId.IN_REVIEW}"); + if (result.OfferStatus is not OfferStatusId.CREATED) + throw new ConflictException($"App {appId} is not in Status {OfferStatusId.CREATED}"); await (result.SetupTransferData == null ? HandleAppInstanceCreation(appId, data) diff --git a/src/marketplace/Apps.Service/BusinessLogic/IAppReleaseBusinessLogic.cs b/src/marketplace/Apps.Service/BusinessLogic/IAppReleaseBusinessLogic.cs index a5a29eb53c..84899b1257 100644 --- a/src/marketplace/Apps.Service/BusinessLogic/IAppReleaseBusinessLogic.cs +++ b/src/marketplace/Apps.Service/BusinessLogic/IAppReleaseBusinessLogic.cs @@ -31,16 +31,6 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Apps.Service.BusinessLogic; /// public interface IAppReleaseBusinessLogic { - ///

- /// Update an App - /// - /// - /// - /// - /// - [Obsolete("This Method is not used anymore, Planning to delete it with release 3.1")] - Task UpdateAppAsync(Guid appId, AppEditableDetail updateModel, Guid companyId); - /// /// Upload document for given company user for appId /// diff --git a/src/marketplace/Apps.Service/Controllers/AppReleaseProcessController.cs b/src/marketplace/Apps.Service/Controllers/AppReleaseProcessController.cs index 2f37099152..e773d5f385 100644 --- a/src/marketplace/Apps.Service/Controllers/AppReleaseProcessController.cs +++ b/src/marketplace/Apps.Service/Controllers/AppReleaseProcessController.cs @@ -51,33 +51,6 @@ public AppReleaseProcessController(IAppReleaseBusinessLogic appReleaseBusinessLo _appReleaseBusinessLogic = appReleaseBusinessLogic; } - /// - /// Add app details to a newly created owned app under the app release/publishing process. - /// - /// - /// - /// Example: PUT: /api/apps/appreleaseprocess/updateapp/74BA5AEF-1CC7-495F-ABAA-CF87840FA6E2 - /// App was successfully updated. - /// If sub claim is empty/invalid or user does not exist, or any other parameters are invalid. - /// App does not exist. - /// User does not have edit permission. - /// App is in incorrect state. - [Obsolete("This endpoint is not used anymore, Planning to delete it with release 3.1")] - [HttpPut] - [Route("updateapp/{appId}")] - [Authorize(Roles = "app_management")] - [Authorize(Policy = PolicyTypes.ValidCompany)] - [ProducesResponseType(StatusCodes.Status204NoContent)] - [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)] - [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)] - [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status403Forbidden)] - [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status409Conflict)] - public async Task UpdateApp([FromRoute] Guid appId, [FromBody] AppEditableDetail updateModel) - { - await this.WithCompanyId(companyId => _appReleaseBusinessLogic.UpdateAppAsync(appId, updateModel, companyId)).ConfigureAwait(false); - return NoContent(); - } - /// /// Upload document for apps in status CREATED and document in status PENDING in the marketplace for given appId for same company as user /// diff --git a/src/marketplace/Apps.Service/Controllers/AppsController.cs b/src/marketplace/Apps.Service/Controllers/AppsController.cs index 1510a42eac..f1e2f9a49c 100644 --- a/src/marketplace/Apps.Service/Controllers/AppsController.cs +++ b/src/marketplace/Apps.Service/Controllers/AppsController.cs @@ -24,6 +24,7 @@ using Org.Eclipse.TractusX.Portal.Backend.Apps.Service.ViewModels; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling.Library; using Org.Eclipse.TractusX.Portal.Backend.Framework.Models; +using Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Authentication; using Org.Eclipse.TractusX.Portal.Backend.Offers.Library.Models; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models; @@ -314,6 +315,7 @@ public Task AutoSetupApp([FromBody] OfferAutoSetupDa [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status400BadRequest)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)] + [PublicUrl(CompanyRoleId.APP_PROVIDER)] public async Task StartAutoSetupAppProcess([FromBody] OfferAutoSetupData data) { await this.WithCompanyId(companyId => _appsBusinessLogic.StartAutoSetupAsync(data, companyId)).ConfigureAwait(false); @@ -381,6 +383,7 @@ public async Task GetAppDocumentContentAsync([FromRoute] Guid appId, [ProducesResponseType(typeof(AppProviderSubscriptionDetailData), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status403Forbidden)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)] + [PublicUrl(CompanyRoleId.APP_PROVIDER)] public Task GetSubscriptionDetailForProvider([FromRoute] Guid appId, [FromRoute] Guid subscriptionId) => this.WithCompanyId(companyId => _appsBusinessLogic.GetSubscriptionDetailForProvider(appId, subscriptionId, companyId)); diff --git a/src/marketplace/Apps.Service/Program.cs b/src/marketplace/Apps.Service/Program.cs index 85d69a752b..c19512de6a 100644 --- a/src/marketplace/Apps.Service/Program.cs +++ b/src/marketplace/Apps.Service/Program.cs @@ -26,6 +26,7 @@ using Org.Eclipse.TractusX.Portal.Backend.Offers.Library.Service; using Org.Eclipse.TractusX.Portal.Backend.Offers.Library.Web.DependencyInjection; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess; +using Org.Eclipse.TractusX.Portal.Backend.Provisioning.DBAccess; using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library; var VERSION = "v2"; @@ -49,5 +50,6 @@ .AddOfferDocumentServices(); builder.Services - .AddOfferServices(); + .AddOfferServices() + .AddProvisioningDBAccess(builder.Configuration); }); diff --git a/src/marketplace/Apps.Service/appsettings.json b/src/marketplace/Apps.Service/appsettings.json index 15f09085f7..332b66ff43 100644 --- a/src/marketplace/Apps.Service/appsettings.json +++ b/src/marketplace/Apps.Service/appsettings.json @@ -16,9 +16,6 @@ ], "Enrich": [ "FromLogContext", - "WithMachineName", - "WithProcessId", - "WithThreadId", "WithCorrelationId" ], "Properties": { diff --git a/src/marketplace/Offers.Library/Service/OfferSetupService.cs b/src/marketplace/Offers.Library/Service/OfferSetupService.cs index 427c8af850..36a38b0199 100644 --- a/src/marketplace/Offers.Library/Service/OfferSetupService.cs +++ b/src/marketplace/Offers.Library/Service/OfferSetupService.cs @@ -18,6 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Microsoft.Extensions.Logging; using Org.Eclipse.TractusX.Portal.Backend.Framework.Async; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; using Org.Eclipse.TractusX.Portal.Backend.Framework.IO; @@ -46,6 +47,7 @@ public class OfferSetupService : IOfferSetupService private readonly IOfferSubscriptionProcessService _offerSubscriptionProcessService; private readonly IMailingService _mailingService; private readonly ITechnicalUserProfileService _technicalUserProfileService; + private readonly ILogger _logger; /// /// Constructor. @@ -57,6 +59,7 @@ public class OfferSetupService : IOfferSetupService /// Access to offer subscription process service /// Mailing service to send mails to the user /// Access to the technical user profile service + /// Access to the logger public OfferSetupService( IPortalRepositories portalRepositories, IProvisioningManager provisioningManager, @@ -64,7 +67,8 @@ public OfferSetupService( INotificationService notificationService, IOfferSubscriptionProcessService offerSubscriptionProcessService, IMailingService mailingService, - ITechnicalUserProfileService technicalUserProfileService) + ITechnicalUserProfileService technicalUserProfileService, + ILogger logger) { _portalRepositories = portalRepositories; _provisioningManager = provisioningManager; @@ -73,10 +77,12 @@ public OfferSetupService( _offerSubscriptionProcessService = offerSubscriptionProcessService; _mailingService = mailingService; _technicalUserProfileService = technicalUserProfileService; + _logger = logger; } public async Task AutoSetupOfferAsync(OfferAutoSetupData data, IEnumerable itAdminRoles, (Guid UserId, Guid CompanyId) identity, OfferTypeId offerTypeId, string basePortalAddress, IEnumerable serviceManagerRoles) { + _logger.LogDebug("AutoSetup started from Company {CompanyId} for {RequestId} with OfferUrl: {OfferUrl}", identity.CompanyId, data.RequestId, data.OfferUrl); var offerSubscriptionsRepository = _portalRepositories.GetInstance(); var offerDetails = await GetAndValidateOfferDetails(data.RequestId, identity.CompanyId, offerTypeId, offerSubscriptionsRepository).ConfigureAwait(false); @@ -384,6 +390,7 @@ await _mailingService /// public async Task StartAutoSetupAsync(OfferAutoSetupData data, Guid companyId, OfferTypeId offerTypeId) { + _logger.LogDebug("AutoSetup Process started from Company {CompanyId} for {RequestId} with OfferUrl: {OfferUrl}", companyId, data.RequestId, data.OfferUrl); var offerSubscriptionRepository = _portalRepositories.GetInstance(); var details = await GetAndValidateOfferDetails(data.RequestId, companyId, offerTypeId, offerSubscriptionRepository).ConfigureAwait(false); if (details.InstanceData.IsSingleInstance) diff --git a/src/marketplace/Services.Service/Controllers/ServicesController.cs b/src/marketplace/Services.Service/Controllers/ServicesController.cs index a250c08200..db2bd28dcb 100644 --- a/src/marketplace/Services.Service/Controllers/ServicesController.cs +++ b/src/marketplace/Services.Service/Controllers/ServicesController.cs @@ -22,6 +22,7 @@ using Microsoft.AspNetCore.Mvc; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling.Library; using Org.Eclipse.TractusX.Portal.Backend.Framework.Models; +using Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos; using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Authentication; using Org.Eclipse.TractusX.Portal.Backend.Offers.Library.Models; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models; @@ -264,6 +265,7 @@ public async Task GetServiceDocumentContentAsync([FromRoute] Guid se [ProducesResponseType(typeof(ProviderSubscriptionDetailData), StatusCodes.Status200OK)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status403Forbidden)] [ProducesResponseType(typeof(ErrorResponse), StatusCodes.Status404NotFound)] + [PublicUrl(CompanyRoleId.SERVICE_PROVIDER)] public Task GetSubscriptionDetailForProvider([FromRoute] Guid serviceId, [FromRoute] Guid subscriptionId) => this.WithCompanyId(companyId => _serviceBusinessLogic.GetSubscriptionDetailForProvider(serviceId, subscriptionId, companyId)); diff --git a/src/marketplace/Services.Service/Program.cs b/src/marketplace/Services.Service/Program.cs index a8a76363ce..fc11e2d56c 100644 --- a/src/marketplace/Services.Service/Program.cs +++ b/src/marketplace/Services.Service/Program.cs @@ -24,6 +24,7 @@ using Org.Eclipse.TractusX.Portal.Backend.Offers.Library.DependencyInjection; using Org.Eclipse.TractusX.Portal.Backend.Offers.Library.Web.DependencyInjection; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess; +using Org.Eclipse.TractusX.Portal.Backend.Provisioning.DBAccess; using Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library; using Org.Eclipse.TractusX.Portal.Backend.Services.Service.BusinessLogic; using Org.Eclipse.TractusX.Portal.Backend.Services.Service.DependencyInjection; @@ -46,5 +47,7 @@ .AddTechnicalUserProfile() .AddOfferDocumentServices(); - builder.Services.AddOfferServices(); + builder.Services + .AddOfferServices() + .AddProvisioningDBAccess(builder.Configuration); }); diff --git a/src/marketplace/Services.Service/appsettings.json b/src/marketplace/Services.Service/appsettings.json index 9f6eaaae6e..8d5fc4b45a 100644 --- a/src/marketplace/Services.Service/appsettings.json +++ b/src/marketplace/Services.Service/appsettings.json @@ -16,9 +16,6 @@ ], "Enrich": [ "FromLogContext", - "WithMachineName", - "WithProcessId", - "WithThreadId", "WithCorrelationId" ], "Properties": { diff --git a/src/notifications/Notifications.Service/appsettings.json b/src/notifications/Notifications.Service/appsettings.json index b87402fb7d..39258a7d19 100644 --- a/src/notifications/Notifications.Service/appsettings.json +++ b/src/notifications/Notifications.Service/appsettings.json @@ -15,9 +15,6 @@ ], "Enrich": [ "FromLogContext", - "WithMachineName", - "WithProcessId", - "WithThreadId", "WithCorrelationId" ], "Properties": { diff --git a/src/portalbackend/PortalBackend.DBAccess/Models/ConnectorData.cs b/src/portalbackend/PortalBackend.DBAccess/Models/ConnectorData.cs index 9f112adff7..49bcb6b611 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Models/ConnectorData.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Models/ConnectorData.cs @@ -59,3 +59,15 @@ public record ManagedConnectorData( ConnectorStatusId Status, string? ProviderCompanyName, Guid? SelfDescriptionDocumentId); + +/// +/// connector information to delete +/// +public record DeleteConnectorData( + bool IsProvidingOrHostCompany, + Guid? SelfDescriptionDocumentId, + DocumentStatusId? DocumentStatusId, + ConnectorStatusId ConnectorStatus, + IEnumerable ConnectorOfferSubscriptions +); +public record ConnectorOfferSubscription(Guid AssignedOfferSubscriptionIds, OfferSubscriptionStatusId OfferSubscriptionStatus); diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs index 5067c21f7a..6ffda7fbe3 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanyRepository.cs @@ -290,6 +290,13 @@ public IAsyncEnumerable GetCompanyRoleAndConsentAgreemen )) .SingleOrDefaultAsync(); + /// + public IAsyncEnumerable GetOwnCompanyRolesAsync(Guid companyId) => + _context.CompanyAssignedRoles + .Where(x => x.CompanyId == companyId) + .Select(x => x.CompanyRoleId) + .AsAsyncEnumerable(); + /// public IAsyncEnumerable GetOperatorBpns() => _context.Companies @@ -299,5 +306,5 @@ public IAsyncEnumerable GetOperatorBpns() => .Select(x => new OperatorBpnData( x.Name, x.BusinessPartnerNumber!)) - .ToAsyncEnumerable(); + .AsAsyncEnumerable(); } diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanySsiDetailsRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanySsiDetailsRepository.cs index 9e19c28645..159325339b 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanySsiDetailsRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/CompanySsiDetailsRepository.cs @@ -111,7 +111,6 @@ public IAsyncEnumerable GetSsiCertificates(Guid comp : new DocumentData( ssi.Document!.Id, ssi.Document.DocumentName))) - .Take(2) )) .ToAsyncEnumerable(); diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/ConnectorsRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/ConnectorsRepository.cs index b37a5449fa..e83e7ae4b7 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/ConnectorsRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/ConnectorsRepository.cs @@ -137,16 +137,18 @@ public Connector AttachAndModifyConnector(Guid connectorId, Action? i .SingleOrDefaultAsync(); /// - public Task<(bool IsValidConnectorId, bool IsProvidingOrHostCompany, Guid? SelfDescriptionDocumentId, DocumentStatusId? DocumentStatusId, ConnectorStatusId ConnectorStatus, IEnumerable AssignedOfferSubscriptions)> GetConnectorDeleteDataAsync(Guid connectorId, Guid companyId) => + public Task GetConnectorDeleteDataAsync(Guid connectorId, Guid companyId) => _context.Connectors .Where(x => x.Id == connectorId) - .Select(connector => new ValueTuple>( - true, + .Select(connector => new DeleteConnectorData( connector.ProviderId == companyId || connector.HostId == companyId, connector.SelfDescriptionDocumentId, connector.SelfDescriptionDocument!.DocumentStatusId, connector.StatusId, - connector.ConnectorAssignedOfferSubscriptions.Select(x => x.OfferSubscriptionId) + connector.ConnectorAssignedOfferSubscriptions.Select(x => new ConnectorOfferSubscription( + x.OfferSubscriptionId, + x.OfferSubscription!.OfferSubscriptionStatusId + )) )).SingleOrDefaultAsync(); /// diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/ICompanyRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/ICompanyRepository.cs index 81f24bd09d..0ed13f60d3 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/ICompanyRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/ICompanyRepository.cs @@ -157,6 +157,7 @@ public interface ICompanyRepository Task<(bool IsActive, bool IsValid)> GetCompanyStatusDataAsync(Guid companyId); Task GetOwnCompanyInformationAsync(Guid companyId); + IAsyncEnumerable GetOwnCompanyRolesAsync(Guid companyId); /// /// Gets all bpns of companies with role operator diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/IConnectorsRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/IConnectorsRepository.cs index c797c6cdf2..af8e5db504 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/IConnectorsRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/IConnectorsRepository.cs @@ -87,7 +87,7 @@ public interface IConnectorsRepository /// Id of the connector /// Id of the company /// returns SelfDescriptionDocument Data/c> - Task<(bool IsValidConnectorId, bool IsProvidingOrHostCompany, Guid? SelfDescriptionDocumentId, DocumentStatusId? DocumentStatusId, ConnectorStatusId ConnectorStatus, IEnumerable AssignedOfferSubscriptions)> GetConnectorDeleteDataAsync(Guid connectorId, Guid companyId); + Task GetConnectorDeleteDataAsync(Guid connectorId, Guid companyId); /// /// Gets the data required for the connector update diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/IOfferRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/IOfferRepository.cs index 2984b1ec4d..5d1e8f1a0c 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/IOfferRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/IOfferRepository.cs @@ -144,18 +144,6 @@ public interface IOfferRepository [Obsolete("only referenced by code that is marked as obsolte")] IAsyncEnumerable GetClientRolesAsync(Guid appId, string languageShortName); - /// - /// Check whether the app is in status created and whether the - /// loggedin user belongs to the apps provider company - /// - /// - /// - /// - /// - /// ValueTuple, first item is true if the app is in status CREATED, - /// second item is true if the user is eligible to edit it - Task<(bool IsAppCreated, bool IsProviderUser, string? ContactEmail, string? ContactNumber, string? MarketingUrl, IEnumerable Descriptions)> GetOfferDetailsForUpdateAsync(Guid appId, Guid companyId, OfferTypeId offerTypeId); - /// Get Offer Release data by Offer Id /// /// Id of the offer diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/IUserRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/IUserRepository.cs index 855a3152b7..f4a03ef386 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/IUserRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/IUserRepository.cs @@ -33,7 +33,7 @@ public interface IUserRepository { IAsyncEnumerable GetApplicationsWithStatusUntrackedAsync(Guid companyId); CompanyUser CreateCompanyUser(Guid identityId, string? firstName, string? lastName, string email); - Identity CreateIdentity(Guid companyId, UserStatusId userStatusId); + Identity CreateIdentity(Guid companyId, UserStatusId userStatusId, IdentityTypeId identityTypeId); void AttachAndModifyCompanyUser(Guid companyUserId, Action? initialize, Action setOptionalParameters); IQueryable GetOwnCompanyUserQuery(Guid companyId, Guid? companyUserId = null, string? userEntityId = null, string? firstName = null, string? lastName = null, string? email = null, IEnumerable? statusIds = null); Task<(string UserEntityId, string? FirstName, string? LastName, string? Email)> GetUserEntityDataAsync(Guid companyUserId, Guid companyId); diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/OfferRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/OfferRepository.cs index 8523cc31b1..5ce8fca49d 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/OfferRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/OfferRepository.cs @@ -216,23 +216,6 @@ public IAsyncEnumerable GetProvidedOffersData(OfferTypeId offerTyp )) .AsAsyncEnumerable(); - /// - [Obsolete("only referenced by code that is marked as obsolte")] - public Task<(bool IsAppCreated, bool IsProviderUser, string? ContactEmail, string? ContactNumber, string? MarketingUrl, IEnumerable Descriptions)> GetOfferDetailsForUpdateAsync(Guid appId, Guid companyId, OfferTypeId offerTypeId) => - _context.Offers - .AsNoTracking() - .Where(a => a.Id == appId && a.OfferTypeId == offerTypeId) - .Select(a => - new ValueTuple>( - a.OfferStatusId == OfferStatusId.CREATED, - a.ProviderCompanyId == companyId, - a.ContactEmail, - a.ContactNumber, - a.MarketingUrl, - a.OfferDescriptions.Select(description => new LocalizedDescription(description.LanguageShortName, description.DescriptionLong, description.DescriptionShort)) - )) - .SingleOrDefaultAsync(); - /// [Obsolete("only referenced by code that is marked as obsolte")] public IAsyncEnumerable GetClientRolesAsync(Guid appId, string languageShortName) => diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/ServiceAccountRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/ServiceAccountRepository.cs index 0e754f0873..66557e3d95 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/ServiceAccountRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/ServiceAccountRepository.cs @@ -153,11 +153,11 @@ public void AttachAndModifyCompanyServiceAccount( _dbContext.CompanyServiceAccounts .AsNoTracking() .Where(serviceAccount => - serviceAccount.Identity!.CompanyId == userCompanyId && - serviceAccount.Identity.UserStatusId == UserStatusId.ACTIVE && - (!isOwner.HasValue || (isOwner.Value && serviceAccount.CompaniesLinkedServiceAccount!.Provider == null) || (!isOwner.Value && serviceAccount.CompaniesLinkedServiceAccount!.Provider != null)) && + (!isOwner.HasValue && (serviceAccount.CompaniesLinkedServiceAccount!.Owners == userCompanyId || serviceAccount.CompaniesLinkedServiceAccount!.Provider == userCompanyId) || + isOwner.HasValue && (isOwner.Value && serviceAccount.CompaniesLinkedServiceAccount!.Owners == userCompanyId || !isOwner.Value && serviceAccount.CompaniesLinkedServiceAccount!.Provider == userCompanyId)) && + serviceAccount.Identity!.UserStatusId == UserStatusId.ACTIVE && (clientId == null || EF.Functions.ILike(serviceAccount.ClientClientId!, $"%{clientId.EscapeForILike()}%"))) - .GroupBy(serviceAccount => serviceAccount.Identity!.CompanyId), + .GroupBy(serviceAccount => serviceAccount.Identity!.UserStatusId), serviceAccounts => serviceAccounts.OrderBy(serviceAccount => serviceAccount.Name), serviceAccount => new CompanyServiceAccountData( serviceAccount.Id, diff --git a/src/portalbackend/PortalBackend.DBAccess/Repositories/UserRepository.cs b/src/portalbackend/PortalBackend.DBAccess/Repositories/UserRepository.cs index 9fcb030f03..e76fcf9c4f 100644 --- a/src/portalbackend/PortalBackend.DBAccess/Repositories/UserRepository.cs +++ b/src/portalbackend/PortalBackend.DBAccess/Repositories/UserRepository.cs @@ -63,14 +63,14 @@ public CompanyUser CreateCompanyUser(Guid identityId, string? firstName, string? }).Entity; /// - public Identity CreateIdentity(Guid companyId, UserStatusId userStatusId) => + public Identity CreateIdentity(Guid companyId, UserStatusId userStatusId, IdentityTypeId identityTypeId) => _dbContext.Identities.Add( new Identity( Guid.NewGuid(), DateTimeOffset.UtcNow, companyId, userStatusId, - IdentityTypeId.COMPANY_USER)).Entity; + identityTypeId)).Entity; public void AttachAndModifyCompanyUser(Guid companyUserId, Action? initialize, Action setOptionalParameters) { diff --git a/src/portalbackend/PortalBackend.Migrations/PortalBackend.Migrations.csproj b/src/portalbackend/PortalBackend.Migrations/PortalBackend.Migrations.csproj index 7da3465242..aa0ff05665 100644 --- a/src/portalbackend/PortalBackend.Migrations/PortalBackend.Migrations.csproj +++ b/src/portalbackend/PortalBackend.Migrations/PortalBackend.Migrations.csproj @@ -20,6 +20,7 @@ Org.Eclipse.TractusX.Portal.Backend.PortalBackend.Migrations + Org.Eclipse.TractusX.Portal.Backend.PortalBackend.Migrations net6.0 enable enable @@ -30,7 +31,6 @@ Exe true - Org.Eclipse.TractusX.Portal.Backend.PortalBackend.Migrations @@ -51,27 +51,19 @@ + - + + Always - - - - - - true - PreserveNewest - PreserveNewest + - - Seeder\Data\ - - - + + PreserveNewest diff --git a/src/portalbackend/PortalBackend.Migrations/Program.cs b/src/portalbackend/PortalBackend.Migrations/Program.cs index c9f497e718..5ab5bf7896 100644 --- a/src/portalbackend/PortalBackend.Migrations/Program.cs +++ b/src/portalbackend/PortalBackend.Migrations/Program.cs @@ -29,8 +29,6 @@ using Org.Eclipse.TractusX.Portal.Backend.Framework.Seeding.DependencyInjection; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities; using Serilog; -using Serilog.Events; -using Serilog.Formatting.Json; using System.Reflection; LoggingExtensions.EnsureInitialized(); diff --git a/src/portalbackend/PortalBackend.Migrations/Seeder/BatchInsertSeeder.cs b/src/portalbackend/PortalBackend.Migrations/Seeder/BatchInsertSeeder.cs index e207477a4a..26054b52ee 100644 --- a/src/portalbackend/PortalBackend.Migrations/Seeder/BatchInsertSeeder.cs +++ b/src/portalbackend/PortalBackend.Migrations/Seeder/BatchInsertSeeder.cs @@ -151,7 +151,8 @@ private async Task SeedTableForBaseEntity(string fileName, CancellationToken private async Task SeedTable(string fileName, Func keySelector, CancellationToken cancellationToken) where T : class { _logger.LogInformation("Start seeding {Filename}", fileName); - var data = await SeederHelper.GetSeedData(_logger, fileName, _settings.DataPaths, cancellationToken, _settings.TestDataEnvironments.ToArray()).ConfigureAwait(false); + var additionalEnvironments = _settings.TestDataEnvironments ?? Enumerable.Empty(); + var data = await SeederHelper.GetSeedData(_logger, fileName, _settings.DataPaths, cancellationToken, additionalEnvironments.ToArray()).ConfigureAwait(false); _logger.LogInformation("Found {ElementCount} data", data.Count); if (data.Any()) { diff --git a/src/portalbackend/PortalBackend.Migrations/Seeder/BatchUpdateSeeder.cs b/src/portalbackend/PortalBackend.Migrations/Seeder/BatchUpdateSeeder.cs index 1634557fdc..aa3c1fb7ec 100644 --- a/src/portalbackend/PortalBackend.Migrations/Seeder/BatchUpdateSeeder.cs +++ b/src/portalbackend/PortalBackend.Migrations/Seeder/BatchUpdateSeeder.cs @@ -117,6 +117,16 @@ await SeedTable("verified_cr dbEntry.ValidFrom = entry.ValidFrom; }, cancellationToken).ConfigureAwait(false); + await SeedTable("company_service_accounts", + x => x.Id, + x => x.dataEntity.Description != x.dbEntity.Description || x.dataEntity.Name != x.dbEntity.Name || x.dataEntity.ClientId != x.dbEntity.ClientId, + (dbEntry, entry) => + { + dbEntry.Description = entry.Description; + dbEntry.Name = entry.Name; + dbEntry.ClientId = entry.ClientId; + }, cancellationToken).ConfigureAwait(false); + await _context.SaveChangesAsync(cancellationToken).ConfigureAwait(false); _logger.LogInformation("Finished BaseEntityBatch Seeder"); } @@ -124,7 +134,8 @@ await SeedTable("verified_cr private async Task SeedTable(string fileName, Func keySelector, Func<(T dataEntity, T dbEntity), bool> whereClause, Action updateEntries, CancellationToken cancellationToken) where T : class { _logger.LogInformation("Start seeding {Filename}", fileName); - var data = await SeederHelper.GetSeedData(_logger, fileName, _settings.DataPaths, cancellationToken, _settings.TestDataEnvironments.ToArray()).ConfigureAwait(false); + var additionalEnvironments = _settings.TestDataEnvironments ?? Enumerable.Empty(); + var data = await SeederHelper.GetSeedData(_logger, fileName, _settings.DataPaths, cancellationToken, additionalEnvironments.ToArray()).ConfigureAwait(false); _logger.LogInformation("Found {ElementCount} data", data.Count); if (data.Any()) { diff --git a/src/portalbackend/PortalBackend.Migrations/Seeder/Data/company_service_accounts.json b/src/portalbackend/PortalBackend.Migrations/Seeder/Data/company_service_accounts.json index 195516c4ee..f38380dd27 100644 --- a/src/portalbackend/PortalBackend.Migrations/Seeder/Data/company_service_accounts.json +++ b/src/portalbackend/PortalBackend.Migrations/Seeder/Data/company_service_accounts.json @@ -4,7 +4,7 @@ "name": "Service Account 01", "description": "Technical User for SD Hub Call to Custodian for SD signature", "company_service_account_type_id": 2, - "client_id": "7e85a0b8-0001-ab67-10d1-000000001006", + "client_id": "dab9dd17-0d31-46c7-b313-aca61225dcd1", "client_client_id": "sa-cl5-custodian-1" }, { @@ -12,7 +12,7 @@ "name": "Service Account 02", "description": "Technical User for Portal to call Custodian Wallet", "company_service_account_type_id": 2, - "client_id": "7e85a0b8-0001-ab67-10d1-000000001007", + "client_id": "50fa6455-a775-4683-b407-57a33a9b9f3b", "client_client_id": "sa-cl5-custodian-2" }, { @@ -20,7 +20,7 @@ "name": "sa-cl3-cx-1", "description": "Semantic Hub", "company_service_account_type_id": 2, - "client_id": "7e85a0b8-0001-ab67-10d1-000000001023", + "client_id": "7beaee76-d447-4531-9433-fd9ce19d1460", "client_client_id": "sa-cl3-cx-1" }, { @@ -28,23 +28,15 @@ "name": "sa-cl7-cx-5", "description": "User for Portal to access BPDM for Company Address publishing into the BPDM", "company_service_account_type_id": 2, - "client_id": "7e85a0b8-0001-ab67-10d1-000000001024", + "client_id": "183aae87-c9cf-4d70-934b-629aa6974c54", "client_client_id": "sa-cl7-cx-5" }, - { - "id": "7e85a0b8-0001-ab67-10d1-0ef508201025", - "name": "sa-cl6-cx-01", - "description": "Technical user for portal to call the daps registration as part of the connector registration service", - "company_service_account_type_id": 2, - "client_id": "7e85a0b8-0001-ab67-10d1-000000001025", - "client_client_id": "sa-cl6-cx-01" - }, { "id": "7e85a0b8-0001-ab67-10d1-0ef508201026", "name": "sa-cl2-01", "description": "Technical User GX-Clearinghouse to enable th CH to POST company application VC creation result", "company_service_account_type_id": 2, - "client_id": "7e85a0b8-0001-ab67-10d1-000000001026", + "client_id": "6bf6f4e5-562c-4382-945f-e5fef59423e2", "client_client_id": "sa-cl2-01" }, { @@ -52,7 +44,7 @@ "name": "sa-cl2-02", "description": "Technical User GX-Clearinghouse to enable th CH to POST Self-Description (SD) for LP and SO", "company_service_account_type_id": 2, - "client_id": "7e85a0b8-0001-ab67-10d1-000000001027", + "client_id": "2d19b59b-4970-4cc0-a561-a9dac9d49045", "client_client_id": "sa-cl2-02" }, { @@ -60,15 +52,47 @@ "name": "sa-cl8-cx-1", "description": "Technical User created for the portal to call the self-description factory for SD creation LP and SO", "company_service_account_type_id": 2, - "client_id": "7e85a0b8-0001-ab67-10d1-000000001028", + "client_id": "c2bdc736-ca35-43c4-8e18-27e7425df9f0", "client_client_id": "sa-cl8-cx-1" }, { "id": "d21d2e8a-fe35-483c-b2b8-4100ed7f0953", "name": "system-internal", - "description": "Technical User for Portal ProccessWorker", + "description": "Technical User for Portal ProcessWorker", "company_service_account_type_id": 2, "client_id": "c5709899-0415-4385-be80-76d2bfd31724", "client_client_id": "system-internal" + }, + { + "id": "7e85a0b8-0001-ab67-10d1-0ef508201029", + "name": "sa-cl1-reg-2", + "description": "Technical User for Portal-Backend to call Keycloak", + "company_service_account_type_id": 2, + "client_id": "cdf11dff-530a-4fd4-97b9-84e4d60ac21e", + "client_client_id": "sa-cl1-reg-2" + }, + { + "id": "7e85a0b8-0001-ab67-10d1-0ef508201030", + "name": "sa-cl2-03", + "description": "Technical User AutoSetup trigger", + "company_service_account_type_id": 2, + "client_id": "cad1382b-0dd4-4ac7-8183-1c08386c84e8", + "client_client_id": "sa-cl2-03" + }, + { + "id": "7e85a0b8-0001-ab67-10d1-0ef508201031", + "name": "sa-cl21-01", + "description": "Technical User Discovery Finder", + "company_service_account_type_id": 2, + "client_id": "b09392dd-8b0f-4a32-bb0b-d00a4091b890", + "client_client_id": "sa-cl21-01" + }, + { + "id": "7e85a0b8-0001-ab67-10d1-0ef508201032", + "name": "sa-cl22-01", + "description": "Technical User BPN Discovery", + "company_service_account_type_id": 2, + "client_id": "f1806543-d0ca-41cb-b029-883cdfb11a8e", + "client_client_id": "sa-cl22-01" } ] diff --git a/src/portalbackend/PortalBackend.Migrations/Seeder/Data/identities.json b/src/portalbackend/PortalBackend.Migrations/Seeder/Data/identities.json index cf8fdcdac8..488c9e65d8 100644 --- a/src/portalbackend/PortalBackend.Migrations/Seeder/Data/identities.json +++ b/src/portalbackend/PortalBackend.Migrations/Seeder/Data/identities.json @@ -41,14 +41,6 @@ "user_entity_id": "f014ed5d-9e05-4f29-a5c0-227c7e7b479e", "identity_type_id": 2 }, - { - "id": "7e85a0b8-0001-ab67-10d1-0ef508201025", - "company_id": "2dc4249f-b5ca-4d42-bef1-7a7a950a4f87", - "date_created": "2022-06-01 18:01:33.439000 +00:00", - "user_status_id": 1, - "user_entity_id": "50dfc73d-e0a1-4992-be95-75def1dadbfa", - "identity_type_id": 2 - }, { "id": "7e85a0b8-0001-ab67-10d1-0ef508201026", "company_id": "2dc4249f-b5ca-4d42-bef1-7a7a950a4f87", @@ -80,5 +72,37 @@ "user_status_id": 1, "user_entity_id": "090c9121-7380-4bb0-bb10-fffd344f930a", "identity_type_id": 2 + }, + { + "id": "7e85a0b8-0001-ab67-10d1-0ef508201029", + "company_id": "2dc4249f-b5ca-4d42-bef1-7a7a950a4f87", + "date_created": "2023-01-01 18:01:33.439000 +00:00", + "user_status_id": 1, + "user_entity_id": "e69c1397-eee8-434a-b83b-dc7944bb9bdd", + "identity_type_id": 2 + }, + { + "id": "7e85a0b8-0001-ab67-10d1-0ef508201030", + "company_id": "2dc4249f-b5ca-4d42-bef1-7a7a950a4f87", + "date_created": "2023-01-01 18:01:33.439000 +00:00", + "user_status_id": 1, + "user_entity_id": "a0bbb8fa-cc40-44e3-828d-342e782fd284", + "identity_type_id": 2 + }, + { + "id": "7e85a0b8-0001-ab67-10d1-0ef508201031", + "company_id": "2dc4249f-b5ca-4d42-bef1-7a7a950a4f87", + "date_created": "2023-01-01 18:01:33.439000 +00:00", + "user_status_id": 1, + "user_entity_id": "319d6b7f-bd88-4103-8124-e8ac4c791acf", + "identity_type_id": 2 + }, + { + "id": "7e85a0b8-0001-ab67-10d1-0ef508201032", + "company_id": "2dc4249f-b5ca-4d42-bef1-7a7a950a4f87", + "date_created": "2023-01-01 18:01:33.439000 +00:00", + "user_status_id": 1, + "user_entity_id": "b52bd8e5-98ce-48b4-af43-0b43b45d0358", + "identity_type_id": 2 } ] diff --git a/src/portalbackend/PortalBackend.Migrations/appsettings.json b/src/portalbackend/PortalBackend.Migrations/appsettings.json index 89a679afdf..4d2b05a2e5 100644 --- a/src/portalbackend/PortalBackend.Migrations/appsettings.json +++ b/src/portalbackend/PortalBackend.Migrations/appsettings.json @@ -28,14 +28,16 @@ "PortalDb": "Server=placeholder;Database=placeholder;Port=5432;User Id=placeholder;Password=placeholder;Ssl Mode=Disable;" }, "DeleteIntervalInDays": 80, - "Seeding":{ - "DataPaths":[], + "Seeding": { + "DataPaths": [ + "Seeder/Data" + ], "TestDataEnvironments": [] }, "ProcessIdentity": { - "UserEntityId": "", - "ProcessUserId": "", - "IdentityTypeId": 0, - "ProcessUserCompanyId": "" + "UserEntityId": "090c9121-7380-4bb0-bb10-fffd344f930a", + "ProcessUserId": "d21d2e8a-fe35-483c-b2b8-4100ed7f0953", + "IdentityTypeId": 2, + "ProcessUserCompanyId": "2dc4249f-b5ca-4d42-bef1-7a7a950a4f87" } } diff --git a/src/processes/Processes.Worker/appsettings.json b/src/processes/Processes.Worker/appsettings.json index d7eb26b9e2..179fcbc3a7 100644 --- a/src/processes/Processes.Worker/appsettings.json +++ b/src/processes/Processes.Worker/appsettings.json @@ -14,10 +14,7 @@ { "Name": "Console" } ], "Enrich": [ - "FromLogContext", - "WithMachineName", - "WithProcessId", - "WithThreadId" + "FromLogContext" ], "Properties": { "Application": "Org.Eclipse.TractusX.Portal.Backend.Process.Worker" diff --git a/src/provisioning/Provisioning.Library/Extensions/IdentityProviderManager.cs b/src/provisioning/Provisioning.Library/Extensions/IdentityProviderManager.cs index d45f2836f2..6466a8b789 100644 --- a/src/provisioning/Provisioning.Library/Extensions/IdentityProviderManager.cs +++ b/src/provisioning/Provisioning.Library/Extensions/IdentityProviderManager.cs @@ -187,7 +187,7 @@ private Task CreateCentralIdentityProviderOrganisationMapperAsync(string alias, Name = _Settings.MappedCompanyAttribute + "-mapper", _IdentityProviderMapper = "hardcoded-attribute-idp-mapper", IdentityProviderAlias = alias, - Config = new Dictionary + Config = new Dictionary { ["syncMode"] = "INHERIT", ["attribute"] = _Settings.MappedCompanyAttribute, diff --git a/src/provisioning/Provisioning.Library/Models/IdentityProviderMapper.cs b/src/provisioning/Provisioning.Library/Models/IdentityProviderMapper.cs index 9ccb17d5df..3361089113 100644 --- a/src/provisioning/Provisioning.Library/Models/IdentityProviderMapper.cs +++ b/src/provisioning/Provisioning.Library/Models/IdentityProviderMapper.cs @@ -22,4 +22,4 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Provisioning.Library.Models; -public record IdentityProviderMapperModel(string Id, string Name, IdentityProviderMapperType Type, IDictionary Config); +public record IdentityProviderMapperModel(string Id, string Name, IdentityProviderMapperType Type, IDictionary Config); diff --git a/src/provisioning/Provisioning.Library/ProvisioningManagerStartupServiceExtensions.cs b/src/provisioning/Provisioning.Library/ProvisioningManagerStartupServiceExtensions.cs index fe19012292..2e18c59868 100644 --- a/src/provisioning/Provisioning.Library/ProvisioningManagerStartupServiceExtensions.cs +++ b/src/provisioning/Provisioning.Library/ProvisioningManagerStartupServiceExtensions.cs @@ -45,6 +45,6 @@ public static IServiceCollection AddProvisioningManager(this IServiceCollection } return services - .AddServiceAccountCreation(configuration.GetSection("Provisioning")); + .AddServiceAccountCreation(configuration.GetSection("Provisioning")); } } diff --git a/src/provisioning/Provisioning.Library/Service/ServiceAccountCreation.cs b/src/provisioning/Provisioning.Library/Service/ServiceAccountCreation.cs index b379c93cbd..a75e37ba0a 100644 --- a/src/provisioning/Provisioning.Library/Service/ServiceAccountCreation.cs +++ b/src/provisioning/Provisioning.Library/Service/ServiceAccountCreation.cs @@ -103,7 +103,7 @@ public ServiceAccountCreation( await _provisioningManager.AddProtocolMapperAsync(serviceAccountData.InternalClientId).ConfigureAwait(false); } - var identity = _portalRepositories.GetInstance().CreateIdentity(companyId, UserStatusId.ACTIVE); + var identity = _portalRepositories.GetInstance().CreateIdentity(companyId, UserStatusId.ACTIVE, IdentityTypeId.COMPANY_SERVICE_ACCOUNT); var serviceAccount = serviceAccountsRepository.CreateCompanyServiceAccount( identity.Id, enhancedName, diff --git a/src/provisioning/Provisioning.Library/Service/UserProvisioningService.cs b/src/provisioning/Provisioning.Library/Service/UserProvisioningService.cs index 612c1792a2..c65d533c97 100644 --- a/src/provisioning/Provisioning.Library/Service/UserProvisioningService.cs +++ b/src/provisioning/Provisioning.Library/Service/UserProvisioningService.cs @@ -138,7 +138,7 @@ public UserProvisioningService(IProvisioningManager provisioningManager, IPortal return (identity, companyUserId); } - identity = userRepository.CreateIdentity(companyId, UserStatusId.ACTIVE); + identity = userRepository.CreateIdentity(companyId, UserStatusId.ACTIVE, IdentityTypeId.COMPANY_USER); companyUserId = userRepository.CreateCompanyUser(identity.Id, user.FirstName, user.LastName, user.Email).Id; if (businessPartnerNumber != null) { diff --git a/src/provisioning/Provisioning.Migrations/Provisioning.Migrations.csproj b/src/provisioning/Provisioning.Migrations/Provisioning.Migrations.csproj index 72e81bdbd5..13652f380f 100644 --- a/src/provisioning/Provisioning.Migrations/Provisioning.Migrations.csproj +++ b/src/provisioning/Provisioning.Migrations/Provisioning.Migrations.csproj @@ -56,9 +56,10 @@ - - Always - + + + PreserveNewest + diff --git a/src/provisioning/Provisioning.Migrations/appsettings.json b/src/provisioning/Provisioning.Migrations/appsettings.json index 9758e7972f..21260ca178 100644 --- a/src/provisioning/Provisioning.Migrations/appsettings.json +++ b/src/provisioning/Provisioning.Migrations/appsettings.json @@ -27,6 +27,7 @@ "ProvisioningDb": "Server=placeholder;Database=placeholder;Port=5432;User Id=placeholder;Password=placeholder;Ssl Mode=Disable;" }, "Seeding":{ + "DataPaths": [], "TestDataEnvironments": [] } } diff --git a/src/registration/Registration.Service/appsettings.json b/src/registration/Registration.Service/appsettings.json index ff825cf7a0..de9e14fc41 100644 --- a/src/registration/Registration.Service/appsettings.json +++ b/src/registration/Registration.Service/appsettings.json @@ -15,9 +15,6 @@ ], "Enrich": [ "FromLogContext", - "WithMachineName", - "WithProcessId", - "WithThreadId", "WithCorrelationId" ], "Properties": { diff --git a/tests/administration/Administration.Service.Tests/BusinessLogic/CompanyDataBusinessLogicTests.cs b/tests/administration/Administration.Service.Tests/BusinessLogic/CompanyDataBusinessLogicTests.cs index ef94f2180d..460a7dd6d4 100644 --- a/tests/administration/Administration.Service.Tests/BusinessLogic/CompanyDataBusinessLogicTests.cs +++ b/tests/administration/Administration.Service.Tests/BusinessLogic/CompanyDataBusinessLogicTests.cs @@ -38,7 +38,7 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Tests.Busin public class CompanyDataBusinessLogicTests { - private readonly IdentityData _identity = new(Guid.NewGuid().ToString(), Guid.NewGuid(), IdentityTypeId.COMPANY_USER, Guid.NewGuid()); + private readonly IdentityData _identity = new("4C1A6851-D4E7-4E10-A011-3732CD045E8A", Guid.NewGuid(), IdentityTypeId.COMPANY_USER, Guid.NewGuid()); private readonly Guid _traceabilityExternalTypeDetailId = Guid.NewGuid(); private readonly Guid _validCredentialId = Guid.NewGuid(); private readonly IFixture _fixture; @@ -55,6 +55,7 @@ public class CompanyDataBusinessLogicTests private readonly IDateTimeProvider _dateTimeProvider; private readonly CompanyDataBusinessLogic _sut; + private readonly IIdentityService _identityService; public CompanyDataBusinessLogicTests() { @@ -76,6 +77,7 @@ public CompanyDataBusinessLogicTests() _mailingService = A.Fake(); _custodianService = A.Fake(); _dateTimeProvider = A.Fake(); + _identityService = A.Fake(); A.CallTo(() => _portalRepositories.GetInstance()).Returns(_companyRepository); A.CallTo(() => _portalRepositories.GetInstance()).Returns(_consentRepository); @@ -85,8 +87,10 @@ public CompanyDataBusinessLogicTests() A.CallTo(() => _portalRepositories.GetInstance()).Returns(_languageRepository); A.CallTo(() => _portalRepositories.GetInstance()).Returns(_notificationRepository); + A.CallTo(() => _identityService.IdentityData).Returns(_identity); + var options = Options.Create(new CompanyDataSettings { MaxPageSize = 20, UseCaseParticipationMediaTypes = new[] { MediaTypeId.PDF }, SsiCertificateMediaTypes = new[] { MediaTypeId.PDF } }); - _sut = new CompanyDataBusinessLogic(_portalRepositories, _mailingService, _custodianService, _dateTimeProvider, options); + _sut = new CompanyDataBusinessLogic(_portalRepositories, _mailingService, _custodianService, _dateTimeProvider, _identityService, options); } #region GetOwnCompanyDetails @@ -100,7 +104,7 @@ public async Task GetOwnCompanyDetailsAsync_ExpectedResults() .ReturnsLazily(() => companyAddressDetailData); // Act - var result = await _sut.GetCompanyDetailsAsync(_identity.CompanyId); + var result = await _sut.GetCompanyDetailsAsync(); // Assert result.Should().NotBeNull(); @@ -115,7 +119,7 @@ public async Task GetOwnCompanyDetailsAsync_ThrowsConflictException() .ReturnsLazily(() => (CompanyAddressDetailData?)null); // Act - async Task Act() => await _sut.GetCompanyDetailsAsync(_identity.CompanyId); + async Task Act() => await _sut.GetCompanyDetailsAsync(); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -130,7 +134,9 @@ public async Task GetOwnCompanyDetailsAsync_ThrowsConflictException() public async Task GetCompanyRoleAndConsentAgreementDetails_CallsExpected() { // Arrange - var companyRoleConsentDatas = new[] { + const string languageShortName = "en"; + var companyId = Guid.NewGuid(); + var companyRoleConsentData = new[] { _fixture.Create(), new CompanyRoleConsentData( _fixture.Create(), @@ -143,9 +149,8 @@ public async Task GetCompanyRoleAndConsentAgreementDetails_CallsExpected() }), _fixture.Create(), }; - var companyId = Guid.NewGuid(); - var languageShortName = "en"; + A.CallTo(() => _identityService.IdentityData).Returns(_identity with { CompanyId = companyId }); A.CallTo(() => _companyRepository.GetCompanyStatusDataAsync(A._)) .Returns((true, true)); @@ -153,16 +158,16 @@ public async Task GetCompanyRoleAndConsentAgreementDetails_CallsExpected() .Returns(true); A.CallTo(() => _companyRepository.GetCompanyRoleAndConsentAgreementDataAsync(A._, A._)) - .Returns(companyRoleConsentDatas.ToAsyncEnumerable()); + .Returns(companyRoleConsentData.ToAsyncEnumerable()); // Act - var result = await _sut.GetCompanyRoleAndConsentAgreementDetailsAsync(companyId, languageShortName).ToListAsync().ConfigureAwait(false); + var result = await _sut.GetCompanyRoleAndConsentAgreementDetailsAsync(languageShortName).ToListAsync().ConfigureAwait(false); // Assert result.Should().NotBeNull() - .And.HaveSameCount(companyRoleConsentDatas); + .And.HaveSameCount(companyRoleConsentData); - result.Zip(companyRoleConsentDatas).Should() + result.Zip(companyRoleConsentData).Should() .AllSatisfy(x => x.Should().Match<(CompanyRoleConsentViewData Result, CompanyRoleConsentData Mock)>( z => z.Result.CompanyRoleId == z.Mock.CompanyRoleId && @@ -179,8 +184,9 @@ public async Task GetCompanyRoleAndConsentAgreementDetails_CallsExpected() public async Task GetCompanyRoleAndConsentAgreementDetails_ThrowsNotFoundException() { // Arrange - var languageShortName = "en"; + const string languageShortName = "en"; var companyId = Guid.NewGuid(); + A.CallTo(() => _identityService.IdentityData).Returns(_identity with { CompanyId = companyId }); A.CallTo(() => _companyRepository.GetCompanyStatusDataAsync(A._)) .Returns((false, false)); @@ -189,7 +195,7 @@ public async Task GetCompanyRoleAndConsentAgreementDetails_ThrowsNotFoundExcepti .Returns(true); // Act - async Task Act() => await _sut.GetCompanyRoleAndConsentAgreementDetailsAsync(companyId, languageShortName).ToListAsync().ConfigureAwait(false); + async Task Act() => await _sut.GetCompanyRoleAndConsentAgreementDetailsAsync(languageShortName).ToListAsync().ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -202,7 +208,8 @@ public async Task GetCompanyRoleAndConsentAgreementDetails_ThrowsConflictExcepti { // Arrange var companyId = Guid.NewGuid(); - var languageShortName = "en"; + const string languageShortName = "en"; + A.CallTo(() => _identityService.IdentityData).Returns(_identity with { CompanyId = companyId }); A.CallTo(() => _companyRepository.GetCompanyStatusDataAsync(A._)) .Returns((false, true)); @@ -211,7 +218,7 @@ public async Task GetCompanyRoleAndConsentAgreementDetails_ThrowsConflictExcepti .Returns(true); // Act - async Task Act() => await _sut.GetCompanyRoleAndConsentAgreementDetailsAsync(companyId, languageShortName).ToListAsync().ConfigureAwait(false); + async Task Act() => await _sut.GetCompanyRoleAndConsentAgreementDetailsAsync(languageShortName).ToListAsync().ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -223,13 +230,14 @@ public async Task GetCompanyRoleAndConsentAgreementDetails_ThrowsConflictExcepti public async Task GetCompanyRoleAndConsentAgreementDetails_Throws() { // Arrange - var languageShortName = "eng"; + const string languageShortName = "eng"; + A.CallTo(() => _identityService.IdentityData).Returns(_identity with { CompanyId = Guid.NewGuid() }); A.CallTo(() => _languageRepository.IsValidLanguageCode(languageShortName)) .Returns(false); // Act - async Task Act() => await _sut.GetCompanyRoleAndConsentAgreementDetailsAsync(Guid.NewGuid(), languageShortName).ToListAsync().ConfigureAwait(false); + async Task Act() => await _sut.GetCompanyRoleAndConsentAgreementDetailsAsync(languageShortName).ToListAsync().ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -310,7 +318,7 @@ public async Task CreateCompanyRoleAndConsentAgreementDetailsAsync_ReturnsExpect }); // Act - await _sut.CreateCompanyRoleAndConsentAgreementDetailsAsync((_identity.UserId, _identity.CompanyId), companyRoleConsentDetails).ConfigureAwait(false); + await _sut.CreateCompanyRoleAndConsentAgreementDetailsAsync(companyRoleConsentDetails).ConfigureAwait(false); // Assert A.CallTo(() => _companyRepository.GetCompanyRolesDataAsync(companyId, A>._)).MustHaveHappenedOnceExactly(); @@ -359,7 +367,7 @@ public async Task CreateCompanyRoleAndConsentAgreementDetailsAsync_CompanyStatus .Returns((true, false, null, null)); // Act - async Task Act() => await _sut.CreateCompanyRoleAndConsentAgreementDetailsAsync((_identity.UserId, _identity.CompanyId), companyRoleConsentDetails).ConfigureAwait(false); + async Task Act() => await _sut.CreateCompanyRoleAndConsentAgreementDetailsAsync(companyRoleConsentDetails).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -412,7 +420,7 @@ public async Task CreateCompanyRoleAndConsentAgreementDetailsAsync_MissingAgreem .Returns(agreementData); // Act - async Task Act() => await _sut.CreateCompanyRoleAndConsentAgreementDetailsAsync((_identity.UserId, _identity.CompanyId), companyRoleConsentDetails).ConfigureAwait(false); + async Task Act() => await _sut.CreateCompanyRoleAndConsentAgreementDetailsAsync(companyRoleConsentDetails).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -470,7 +478,7 @@ public async Task CreateCompanyRoleAndConsentAgreementDetailsAsync_ExtraAgreemen .Returns(agreementData); // Act - async Task Act() => await _sut.CreateCompanyRoleAndConsentAgreementDetailsAsync((_identity.UserId, _identity.CompanyId), companyRoleConsentDetails).ConfigureAwait(false); + async Task Act() => await _sut.CreateCompanyRoleAndConsentAgreementDetailsAsync(companyRoleConsentDetails).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -497,7 +505,7 @@ public async Task CreateCompanyRoleAndConsentAgreementDetailsAsync_AllUnassigned .Returns((true, true, Enumerable.Empty(), consentStatusDetails)); // Act - async Task Act() => await _sut.CreateCompanyRoleAndConsentAgreementDetailsAsync((_identity.UserId, _identity.CompanyId), companyRoleConsentDetails).ConfigureAwait(false); + async Task Act() => await _sut.CreateCompanyRoleAndConsentAgreementDetailsAsync(companyRoleConsentDetails).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -514,7 +522,7 @@ public async Task CreateCompanyRoleAndConsentAgreementDetailsAsync_ThrowsConflic .Returns(((bool, bool, IEnumerable?, IEnumerable?))default); // Act - async Task Act() => await _sut.CreateCompanyRoleAndConsentAgreementDetailsAsync((_identity.UserId, _identity.CompanyId), companyRoleConsentDetails).ConfigureAwait(false); + async Task Act() => await _sut.CreateCompanyRoleAndConsentAgreementDetailsAsync(companyRoleConsentDetails).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -531,7 +539,7 @@ public async Task CreateCompanyRoleAndConsentAgreementDetailsAsync_ThrowsUnexpec .Returns((true, true, null!, null!)); // Act - async Task Act() => await _sut.CreateCompanyRoleAndConsentAgreementDetailsAsync((_identity.UserId, _identity.CompanyId), companyRoleConsentDetails).ConfigureAwait(false); + async Task Act() => await _sut.CreateCompanyRoleAndConsentAgreementDetailsAsync(companyRoleConsentDetails).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -548,7 +556,7 @@ public async Task CreateCompanyRoleAndConsentAgreementDetailsAsync_AgreementAssi .Returns((true, true, Enumerable.Empty(), null!)); // Act - async Task Act() => await _sut.CreateCompanyRoleAndConsentAgreementDetailsAsync((_identity.UserId, _identity.CompanyId), companyRoleConsentDetails).ConfigureAwait(false); + async Task Act() => await _sut.CreateCompanyRoleAndConsentAgreementDetailsAsync(companyRoleConsentDetails).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -566,11 +574,12 @@ public async Task GetCompanyAssigendUseCaseDetailsAsync_ResturnsExpected() // Arrange var companyId = Guid.NewGuid(); var companyAssignedUseCaseData = _fixture.CreateMany(2).ToAsyncEnumerable(); + A.CallTo(() => _identityService.IdentityData).Returns(_identity with { CompanyId = companyId }); A.CallTo(() => _companyRepository.GetCompanyAssigendUseCaseDetailsAsync(A._)) .Returns(companyAssignedUseCaseData); // Act - var result = await _sut.GetCompanyAssigendUseCaseDetailsAsync(companyId).ToListAsync().ConfigureAwait(false); + var result = await _sut.GetCompanyAssigendUseCaseDetailsAsync().ToListAsync().ConfigureAwait(false); // Assert result.Should().NotBeNull(); @@ -585,11 +594,12 @@ public async Task CreateCompanyAssignedUseCaseDetailsAsync_NoContent_ReturnsExpe var useCaseId = Guid.NewGuid(); var companyId = Guid.NewGuid(); + A.CallTo(() => _identityService.IdentityData).Returns(_identity with { CompanyId = companyId }); A.CallTo(() => _companyRepository.GetCompanyStatusAndUseCaseIdAsync(A._, A._)) .Returns((false, true, true)); // Act - var result = await _sut.CreateCompanyAssignedUseCaseDetailsAsync(companyId, useCaseId).ConfigureAwait(false); + var result = await _sut.CreateCompanyAssignedUseCaseDetailsAsync(useCaseId).ConfigureAwait(false); // Assert A.CallTo(() => _companyRepository.GetCompanyStatusAndUseCaseIdAsync(companyId, useCaseId)).MustHaveHappenedOnceExactly(); @@ -605,11 +615,12 @@ public async Task CreateCompanyAssignedUseCaseDetailsAsync_AlreadyReported_Retur var useCaseId = Guid.NewGuid(); var companyId = Guid.NewGuid(); + A.CallTo(() => _identityService.IdentityData).Returns(_identity with { CompanyId = companyId }); A.CallTo(() => _companyRepository.GetCompanyStatusAndUseCaseIdAsync(A._, A._)) .Returns((true, true, true)); // Act - var result = await _sut.CreateCompanyAssignedUseCaseDetailsAsync(companyId, useCaseId).ConfigureAwait(false); + var result = await _sut.CreateCompanyAssignedUseCaseDetailsAsync(useCaseId).ConfigureAwait(false); // Assert result.Should().BeFalse(); @@ -626,11 +637,12 @@ public async Task CreateCompanyAssignedUseCaseDetailsAsync_ThrowsConflictExcepti var useCaseId = Guid.NewGuid(); var companyId = Guid.NewGuid(); + A.CallTo(() => _identityService.IdentityData).Returns(_identity with { CompanyId = companyId }); A.CallTo(() => _companyRepository.GetCompanyStatusAndUseCaseIdAsync(A._, A._)) .Returns((false, false, true)); // Act - async Task Act() => await _sut.CreateCompanyAssignedUseCaseDetailsAsync(companyId, useCaseId).ConfigureAwait(false); + async Task Act() => await _sut.CreateCompanyAssignedUseCaseDetailsAsync(useCaseId).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -645,11 +657,12 @@ public async Task RemoveCompanyAssignedUseCaseDetailsAsync_ReturnsExpected() var useCaseId = Guid.NewGuid(); var companyId = Guid.NewGuid(); + A.CallTo(() => _identityService.IdentityData).Returns(_identity with { CompanyId = companyId }); A.CallTo(() => _companyRepository.GetCompanyStatusAndUseCaseIdAsync(A._, A._)) .Returns((true, true, true)); // Act - await _sut.RemoveCompanyAssignedUseCaseDetailsAsync(companyId, useCaseId).ConfigureAwait(false); + await _sut.RemoveCompanyAssignedUseCaseDetailsAsync(useCaseId).ConfigureAwait(false); // Assert A.CallTo(() => _companyRepository.GetCompanyStatusAndUseCaseIdAsync(companyId, useCaseId)).MustHaveHappenedOnceExactly(); @@ -664,11 +677,12 @@ public async Task RemoveCompanyAssignedUseCaseDetailsAsync_companyStatus_ThrowsC var useCaseId = Guid.NewGuid(); var companyId = Guid.NewGuid(); + A.CallTo(() => _identityService.IdentityData).Returns(_identity with { CompanyId = companyId }); A.CallTo(() => _companyRepository.GetCompanyStatusAndUseCaseIdAsync(A._, A._)) .Returns((true, false, true)); // Act - async Task Act() => await _sut.RemoveCompanyAssignedUseCaseDetailsAsync(companyId, useCaseId).ConfigureAwait(false); + async Task Act() => await _sut.RemoveCompanyAssignedUseCaseDetailsAsync(useCaseId).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -683,11 +697,12 @@ public async Task RemoveCompanyAssignedUseCaseDetailsAsync_useCaseId_ThrowsConfl var useCaseId = Guid.NewGuid(); var companyId = Guid.NewGuid(); + A.CallTo(() => _identityService.IdentityData).Returns(_identity with { CompanyId = companyId }); A.CallTo(() => _companyRepository.GetCompanyStatusAndUseCaseIdAsync(A._, A._)) .Returns((false, true, true)); // Act - async Task Act() => await _sut.RemoveCompanyAssignedUseCaseDetailsAsync(companyId, useCaseId).ConfigureAwait(false); + async Task Act() => await _sut.RemoveCompanyAssignedUseCaseDetailsAsync(useCaseId).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -710,7 +725,7 @@ public async Task GetUseCaseParticipationAsync_WithValidRequest_ReturnsExpected( .Returns(_fixture.Build().With(x => x.VerifiedCredentials, verifiedCredentials).CreateMany(5).ToAsyncEnumerable()); // Act - var result = await _sut.GetUseCaseParticipationAsync(_identity.CompanyId, "en").ConfigureAwait(false); + var result = await _sut.GetUseCaseParticipationAsync("en").ConfigureAwait(false); // Assert result.Should().HaveCount(5); @@ -727,7 +742,7 @@ public async Task GetUseCaseParticipationAsync_WithMultipleSsiDetailData_Returns .Returns(_fixture.Build().With(x => x.VerifiedCredentials, verifiedCredentials).CreateMany(5).ToAsyncEnumerable()); // Act - var Act = () => _sut.GetUseCaseParticipationAsync(_identity.CompanyId, "en"); + var Act = () => _sut.GetUseCaseParticipationAsync("en"); // Assert var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); @@ -746,27 +761,12 @@ public async Task GetSsiCertificates_WithValidRequest_ReturnsExpected() .Returns(_fixture.Build().With(x => x.SsiDetailData, _fixture.CreateMany(1)).CreateMany(5).ToAsyncEnumerable()); // Act - var result = await _sut.GetSsiCertificatesAsync(_identity.CompanyId).ConfigureAwait(false); + var result = await _sut.GetSsiCertificatesAsync().ConfigureAwait(false); // Assert result.Should().HaveCount(5); } - [Fact] - public async Task GetSsiCertificates_WithMultipleSsiDetailData_ReturnsExpected() - { - // Arrange - A.CallTo(() => _companySsiDetailsRepository.GetSsiCertificates(_identity.CompanyId)) - .Returns(_fixture.Build().With(x => x.SsiDetailData, _fixture.CreateMany(2)).CreateMany(5).ToAsyncEnumerable()); - - // Act - async Task Act() => await _sut.GetSsiCertificatesAsync(_identity.CompanyId).ConfigureAwait(false); - - // Assert - var ex = await Assert.ThrowsAsync(Act); - ex.Message.Should().Be("There should only be one pending or active ssi detail be assigned"); - } - #endregion #region CreateUseCaseParticipation @@ -779,7 +779,7 @@ public async Task CreateUseCaseParticipation_WithInvalidDocumentContentType_Thro var data = new UseCaseParticipationCreationData(_traceabilityExternalTypeDetailId, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, file); // Act - async Task Act() => await _sut.CreateUseCaseParticipation((_identity.UserId, _identity.CompanyId), data, CancellationToken.None).ConfigureAwait(false); + async Task Act() => await _sut.CreateUseCaseParticipation(data, CancellationToken.None).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -796,7 +796,7 @@ public async Task CreateUseCaseParticipation_WithNotExistingDetailId_ThrowsContr var data = new UseCaseParticipationCreationData(verifiedCredentialExternalTypeDetailId, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, file); // Act - async Task Act() => await _sut.CreateUseCaseParticipation((_identity.UserId, _identity.CompanyId), data, CancellationToken.None).ConfigureAwait(false); + async Task Act() => await _sut.CreateUseCaseParticipation(data, CancellationToken.None).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -815,7 +815,7 @@ public async Task CreateUseCaseParticipation_WithRequestAlreadyExisting_ThrowsCo .Returns(true); // Act - async Task Act() => await _sut.CreateUseCaseParticipation((_identity.UserId, _identity.CompanyId), data, CancellationToken.None).ConfigureAwait(false); + async Task Act() => await _sut.CreateUseCaseParticipation(data, CancellationToken.None).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -852,7 +852,7 @@ public async Task CreateUseCaseParticipation_WithValidCall_CreatesExpected() .Returns(new Document(documentId, null!, null!, null!, default, default, default, default)); // Act - await _sut.CreateUseCaseParticipation((_identity.UserId, _identity.CompanyId), data, CancellationToken.None).ConfigureAwait(false); + await _sut.CreateUseCaseParticipation(data, CancellationToken.None).ConfigureAwait(false); // Assert A.CallTo(() => _documentRepository.CreateDocument(A._, A._, A._, MediaTypeId.PDF, DocumentTypeId.PRESENTATION, A>._)) @@ -885,7 +885,7 @@ public async Task CreateSsiCertificate_WithInvalidDocumentContentType_ThrowsUnsu var data = new SsiCertificateCreationData(VerifiedCredentialTypeId.DISMANTLER_CERTIFICATE, file); // Act - async Task Act() => await _sut.CreateSsiCertificate((_identity.UserId, _identity.CompanyId), data, CancellationToken.None).ConfigureAwait(false); + async Task Act() => await _sut.CreateSsiCertificate(data, CancellationToken.None).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -901,7 +901,7 @@ public async Task CreateSsiCertificate_WithWrongVerifiedCredentialType_ThrowsCon var data = new SsiCertificateCreationData(VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, file); // Act - async Task Act() => await _sut.CreateSsiCertificate((_identity.UserId, _identity.CompanyId), data, CancellationToken.None).ConfigureAwait(false); + async Task Act() => await _sut.CreateSsiCertificate(data, CancellationToken.None).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -920,7 +920,7 @@ public async Task CheckSsiDetailsExistsForCompany_WithRequestAlreadyExisting_Thr .Returns(true); // Act - async Task Act() => await _sut.CreateSsiCertificate((_identity.UserId, _identity.CompanyId), data, CancellationToken.None).ConfigureAwait(false); + async Task Act() => await _sut.CreateSsiCertificate(data, CancellationToken.None).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act); @@ -957,7 +957,7 @@ public async Task CreateSsiCertificate_WithValidCall_CreatesExpected() .Returns(new Document(documentId, null!, null!, null!, default, default, default, default)); // Act - await _sut.CreateSsiCertificate((_identity.UserId, _identity.CompanyId), data, CancellationToken.None).ConfigureAwait(false); + await _sut.CreateSsiCertificate(data, CancellationToken.None).ConfigureAwait(false); // Assert A.CallTo(() => _documentRepository.CreateDocument(A._, A._, A._, MediaTypeId.PDF, DocumentTypeId.PRESENTATION, A>._)) @@ -1023,7 +1023,7 @@ public async Task ApproveCredential_WithoutExistingSsiDetail_ThrowsNotFoundExcep var notExistingId = Guid.NewGuid(); A.CallTo(() => _companySsiDetailsRepository.GetSsiApprovalData(notExistingId)) .Returns(new ValueTuple()); - async Task Act() => await _sut.ApproveCredential(_identity.CompanyId, notExistingId, CancellationToken.None).ConfigureAwait(false); + async Task Act() => await _sut.ApproveCredential(notExistingId, CancellationToken.None).ConfigureAwait(false); // Act var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); @@ -1047,7 +1047,7 @@ public async Task ApproveCredential_WithStatusNotPending_ThrowsConflictException .Create(); A.CallTo(() => _companySsiDetailsRepository.GetSsiApprovalData(alreadyActiveId)) .Returns(new ValueTuple(true, approvalData)); - async Task Act() => await _sut.ApproveCredential(_identity.CompanyId, alreadyActiveId, CancellationToken.None).ConfigureAwait(false); + async Task Act() => await _sut.ApproveCredential(alreadyActiveId, CancellationToken.None).ConfigureAwait(false); // Act var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); @@ -1071,7 +1071,7 @@ public async Task ApproveCredential_WithBpnNotSetActiveSsiDetail_ThrowsConflictE .Create(); A.CallTo(() => _companySsiDetailsRepository.GetSsiApprovalData(alreadyActiveId)) .Returns(new ValueTuple(true, approvalData)); - async Task Act() => await _sut.ApproveCredential(_identity.CompanyId, alreadyActiveId, CancellationToken.None).ConfigureAwait(false); + async Task Act() => await _sut.ApproveCredential(alreadyActiveId, CancellationToken.None).ConfigureAwait(false); // Act var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); @@ -1097,7 +1097,7 @@ public async Task ApproveCredential_WithoutExternalDetailSet_ThrowsConflictExcep .Create(); A.CallTo(() => _companySsiDetailsRepository.GetSsiApprovalData(alreadyActiveId)) .Returns(new ValueTuple(true, approvalData)); - async Task Act() => await _sut.ApproveCredential(_identity.CompanyId, alreadyActiveId, CancellationToken.None).ConfigureAwait(false); + async Task Act() => await _sut.ApproveCredential(alreadyActiveId, CancellationToken.None).ConfigureAwait(false); // Act var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); @@ -1165,7 +1165,7 @@ public async Task ApproveCredential_WithValidRequest_ReturnsExpected(VerifiedCre }); // Act - await _sut.ApproveCredential(_identity.UserId, _validCredentialId, CancellationToken.None).ConfigureAwait(false); + await _sut.ApproveCredential(_validCredentialId, CancellationToken.None).ConfigureAwait(false); // Assert A.CallTo(() => _mailingService.SendMails(recipientMail, A>._, A>.That.Matches(x => x.Count() == 1 && x.Single() == "CredentialApproval"))) @@ -1230,7 +1230,7 @@ public async Task ApproveCredential_WithInvalidCredentialType_ThrowsException() .Returns(new ValueTuple(true, data)); // Act - async Task Act() => await _sut.ApproveCredential(_identity.UserId, _validCredentialId, CancellationToken.None).ConfigureAwait(false); + async Task Act() => await _sut.ApproveCredential(_validCredentialId, CancellationToken.None).ConfigureAwait(false); // Assert var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); @@ -1292,7 +1292,7 @@ public async Task ApproveCredential_WithoutUserMail_ReturnsExpected(VerifiedCred }); // Act - await _sut.ApproveCredential(_identity.UserId, _validCredentialId, CancellationToken.None).ConfigureAwait(false); + await _sut.ApproveCredential(_validCredentialId, CancellationToken.None).ConfigureAwait(false); // Assert A.CallTo(() => _mailingService.SendMails(A._, A>._, A>.That.Matches(x => x.Count() == 1 && x.Single() == "CredentialRejected"))) @@ -1333,7 +1333,7 @@ public async Task RejectCredential_WithoutExistingSsiDetail_ThrowsNotFoundExcept var notExistingId = Guid.NewGuid(); A.CallTo(() => _companySsiDetailsRepository.GetSsiRejectionData(notExistingId)) .Returns(new ValueTuple()); - async Task Act() => await _sut.RejectCredential(_identity.CompanyId, notExistingId).ConfigureAwait(false); + async Task Act() => await _sut.RejectCredential(notExistingId).ConfigureAwait(false); // Act var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); @@ -1354,7 +1354,7 @@ public async Task RejectCredential_WithNotPendingSsiDetail_ThrowsNotFoundExcepti var alreadyInactiveId = Guid.NewGuid(); A.CallTo(() => _companySsiDetailsRepository.GetSsiRejectionData(alreadyInactiveId)) .Returns(new ValueTuple(true, status, VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK, Guid.NewGuid(), null, null, null)); - async Task Act() => await _sut.RejectCredential(_identity.CompanyId, alreadyInactiveId).ConfigureAwait(false); + async Task Act() => await _sut.RejectCredential(alreadyInactiveId).ConfigureAwait(false); // Act var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); @@ -1394,7 +1394,7 @@ public async Task RejectCredential_WithValidRequest_ReturnsExpected() }); // Act - await _sut.RejectCredential(_identity.UserId, _validCredentialId).ConfigureAwait(false); + await _sut.RejectCredential(_validCredentialId).ConfigureAwait(false); // Assert A.CallTo(() => _mailingService.SendMails(recipientMail, A>._, A>.That.Matches(x => x.Count() == 1 && x.Single() == "CredentialRejected"))) @@ -1437,7 +1437,7 @@ public async Task RejectCredential_WithoutUserMail_ReturnsExpected() }); // Act - await _sut.RejectCredential(_identity.UserId, _validCredentialId).ConfigureAwait(false); + await _sut.RejectCredential(_validCredentialId).ConfigureAwait(false); // Assert A.CallTo(() => _mailingService.SendMails(recipientMail, A>._, A>.That.Matches(x => x.Count() == 1 && x.Single() == "CredentialRejected"))) diff --git a/tests/administration/Administration.Service.Tests/BusinessLogic/ConnectorsBusinessLogicTests.cs b/tests/administration/Administration.Service.Tests/BusinessLogic/ConnectorsBusinessLogicTests.cs index 49c2aec538..c23a841b92 100644 --- a/tests/administration/Administration.Service.Tests/BusinessLogic/ConnectorsBusinessLogicTests.cs +++ b/tests/administration/Administration.Service.Tests/BusinessLogic/ConnectorsBusinessLogicTests.cs @@ -474,13 +474,16 @@ public async Task ProcessClearinghouseSelfDescription_WithExistingSelfDescriptio public async Task DeleteConnectorAsync_WithDocumentId_ExpectedCalls() { // Arrange - const DocumentStatusId documentStatusId = DocumentStatusId.LOCKED; + const DocumentStatusId DocumentStatusId = DocumentStatusId.LOCKED; var connectorId = Guid.NewGuid(); var connector = new Connector(connectorId, null!, null!, null!); var selfDescriptionDocumentId = Guid.NewGuid(); - var assignedOfferSubscriptions = _fixture.CreateMany(2); + var connectorOfferSubscriptions = new[] { + new ConnectorOfferSubscription(_fixture.Create(), OfferSubscriptionStatusId.PENDING), + new ConnectorOfferSubscription(_fixture.Create(), OfferSubscriptionStatusId.PENDING), + }; A.CallTo(() => _connectorsRepository.GetConnectorDeleteDataAsync(A._, _identity.CompanyId)) - .Returns((true, true, selfDescriptionDocumentId, documentStatusId, ConnectorStatusId.ACTIVE, assignedOfferSubscriptions)); + .Returns(new DeleteConnectorData(true, selfDescriptionDocumentId, DocumentStatusId, ConnectorStatusId.ACTIVE, connectorOfferSubscriptions)); A.CallTo(() => _documentRepository.AttachAndModifyDocument(A._, A>._, A>._)) .Invokes((Guid docId, Action? initialize, Action modify) @@ -514,9 +517,12 @@ public async Task DeleteConnectorAsync_WithPendingAndWithoutDocumentId_ExpectedC { // Arrange var connectorId = Guid.NewGuid(); - var assignedOfferSubscriptions = _fixture.CreateMany(2); + var connectorOfferSubscriptions = new[] { + new ConnectorOfferSubscription(_fixture.Create(), OfferSubscriptionStatusId.PENDING), + new ConnectorOfferSubscription(_fixture.Create(), OfferSubscriptionStatusId.PENDING), + }; A.CallTo(() => _connectorsRepository.GetConnectorDeleteDataAsync(A._, _identity.CompanyId)) - .Returns((true, true, null, null, ConnectorStatusId.PENDING, assignedOfferSubscriptions)); + .Returns(new DeleteConnectorData(true, null, null, ConnectorStatusId.PENDING, connectorOfferSubscriptions)); // Act await _logic.DeleteConnectorAsync(connectorId).ConfigureAwait(false); @@ -532,12 +538,15 @@ public async Task DeleteConnectorAsync_WithPendingAndWithoutDocumentId_ExpectedC public async Task DeleteConnectorAsync_WithPendingAndDocumentId_ExpectedCalls() { // Arrange - const DocumentStatusId documentStatusId = DocumentStatusId.LOCKED; + const DocumentStatusId DocumentStatusId = DocumentStatusId.LOCKED; var connectorId = Guid.NewGuid(); var selfDescriptionDocumentId = Guid.NewGuid(); - var assignedOfferSubscriptions = _fixture.CreateMany(2); + var connectorOfferSubscriptions = new[] { + new ConnectorOfferSubscription(_fixture.Create(), OfferSubscriptionStatusId.PENDING), + new ConnectorOfferSubscription(_fixture.Create(), OfferSubscriptionStatusId.PENDING), + }; A.CallTo(() => _connectorsRepository.GetConnectorDeleteDataAsync(A._, _identity.CompanyId)) - .Returns((true, true, selfDescriptionDocumentId, documentStatusId, ConnectorStatusId.PENDING, assignedOfferSubscriptions)); + .Returns(new DeleteConnectorData(true, selfDescriptionDocumentId, DocumentStatusId, ConnectorStatusId.PENDING, connectorOfferSubscriptions)); // Act await _logic.DeleteConnectorAsync(connectorId).ConfigureAwait(false); @@ -554,11 +563,11 @@ public async Task DeleteConnectorAsync_WithPendingAndDocumentId_ExpectedCalls() public async Task DeleteConnectorAsync_WithoutAssignedOfferSubscriptions_ExpectedCalls() { // Arrange - const DocumentStatusId documentStatusId = DocumentStatusId.LOCKED; + const DocumentStatusId DocumentStatusId = DocumentStatusId.LOCKED; var connectorId = Guid.NewGuid(); var selfDescriptionDocumentId = Guid.NewGuid(); A.CallTo(() => _connectorsRepository.GetConnectorDeleteDataAsync(A._, _identity.CompanyId)) - .Returns((true, true, selfDescriptionDocumentId, documentStatusId, ConnectorStatusId.PENDING, Enumerable.Empty())); + .Returns(new DeleteConnectorData(true, selfDescriptionDocumentId, DocumentStatusId, ConnectorStatusId.PENDING, Enumerable.Empty())); // Act await _logic.DeleteConnectorAsync(connectorId).ConfigureAwait(false); @@ -577,7 +586,7 @@ public async Task DeleteConnectorAsync_WithOutDocumentId_ExpectedCalls() // Arrange var connectorId = Guid.NewGuid(); A.CallTo(() => _connectorsRepository.GetConnectorDeleteDataAsync(connectorId, _identity.CompanyId)) - .Returns((true, true, null, null, ConnectorStatusId.ACTIVE, Enumerable.Empty())); + .Returns(new DeleteConnectorData(true, null, null, ConnectorStatusId.ACTIVE, Enumerable.Empty())); // Act async Task Act() => await _logic.DeleteConnectorAsync(connectorId).ConfigureAwait(false); @@ -593,7 +602,7 @@ public async Task DeleteConnectorAsync_WithInactiveConnector_ThrowsConflictExcep // Arrange var connectorId = Guid.NewGuid(); A.CallTo(() => _connectorsRepository.GetConnectorDeleteDataAsync(connectorId, _identity.CompanyId)) - .Returns((true, true, null, null, ConnectorStatusId.ACTIVE, Enumerable.Empty())); + .Returns(new DeleteConnectorData(true, null, null, ConnectorStatusId.ACTIVE, Enumerable.Empty())); // Act async Task Act() => await _logic.DeleteConnectorAsync(connectorId).ConfigureAwait(false); @@ -609,7 +618,7 @@ public async Task DeleteConnectorAsync_ThrowsNotFoundException() // Arrange var connectorId = Guid.NewGuid(); A.CallTo(() => _connectorsRepository.GetConnectorDeleteDataAsync(connectorId, _identity.CompanyId)) - .Returns((false, false, null, null, ConnectorStatusId.ACTIVE, Enumerable.Empty())); + .Returns((DeleteConnectorData)default!); // Act async Task Act() => await _logic.DeleteConnectorAsync(connectorId).ConfigureAwait(false); @@ -625,7 +634,7 @@ public async Task DeleteConnectorAsync_ThrowsForbiddenException() // Arrange var connectorId = Guid.NewGuid(); A.CallTo(() => _connectorsRepository.GetConnectorDeleteDataAsync(connectorId, _identity.CompanyId)) - .Returns((true, false, null, null, default, Enumerable.Empty())); + .Returns(new DeleteConnectorData(false, null, null, default, Enumerable.Empty())); // Act async Task Act() => await _logic.DeleteConnectorAsync(connectorId).ConfigureAwait(false); @@ -635,6 +644,56 @@ public async Task DeleteConnectorAsync_ThrowsForbiddenException() ex.Message.Should().Be($"company {_identity.CompanyId} is neither provider nor host-company of connector {connectorId}"); } + [Fact] + public async Task DeleteConnectorAsync_WithPendingAndWithoutDocumentId_ThrowsForbiddenException() + { + // Arrange + var connectorId = Guid.NewGuid(); + var offerSubscriptionId1 = _fixture.Create(); + var offerSubscriptionId2 = _fixture.Create(); + var offerSubscriptionId3 = _fixture.Create(); + var connectorOfferSubscriptions = new[] { + new ConnectorOfferSubscription(offerSubscriptionId1, OfferSubscriptionStatusId.ACTIVE), + new ConnectorOfferSubscription(offerSubscriptionId2, OfferSubscriptionStatusId.ACTIVE), + new ConnectorOfferSubscription(offerSubscriptionId3, OfferSubscriptionStatusId.PENDING), + }; + A.CallTo(() => _connectorsRepository.GetConnectorDeleteDataAsync(A._, _identity.CompanyId)) + .Returns(new DeleteConnectorData(true, null, null, ConnectorStatusId.PENDING, connectorOfferSubscriptions)); + + // Act + async Task Act() => await _logic.DeleteConnectorAsync(connectorId).ConfigureAwait(false); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Be($"Deletion Failed. Connector {connectorId} connected to an active offer subscription [{offerSubscriptionId1},{offerSubscriptionId2}]"); + } + + [Fact] + public async Task DeleteConnectorAsync_WithPendingAndDocumentId_ThrowsForbiddenException() + { + // Arrange + const DocumentStatusId DocumentStatusId = DocumentStatusId.LOCKED; + var connectorId = Guid.NewGuid(); + var selfDescriptionDocumentId = Guid.NewGuid(); + var offerSubscriptionId1 = _fixture.Create(); + var offerSubscriptionId2 = _fixture.Create(); + var offerSubscriptionId3 = _fixture.Create(); + var connectorOfferSubscriptions = new[] { + new ConnectorOfferSubscription(offerSubscriptionId1, OfferSubscriptionStatusId.ACTIVE), + new ConnectorOfferSubscription(offerSubscriptionId2, OfferSubscriptionStatusId.ACTIVE), + new ConnectorOfferSubscription(offerSubscriptionId3, OfferSubscriptionStatusId.PENDING), + }; + A.CallTo(() => _connectorsRepository.GetConnectorDeleteDataAsync(A._, _identity.CompanyId)) + .Returns(new DeleteConnectorData(true, selfDescriptionDocumentId, DocumentStatusId, ConnectorStatusId.PENDING, connectorOfferSubscriptions)); + + // Act + async Task Act() => await _logic.DeleteConnectorAsync(connectorId).ConfigureAwait(false); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Be($"Deletion Failed. Connector {connectorId} connected to an active offer subscription [{offerSubscriptionId1},{offerSubscriptionId2}]"); + } + #endregion #region GetManagedConnectorForIamUserAsync diff --git a/tests/administration/Administration.Service.Tests/Controllers/CompanyDataControllerTests.cs b/tests/administration/Administration.Service.Tests/Controllers/CompanyDataControllerTests.cs index 7981e9214f..38f5b16dfc 100644 --- a/tests/administration/Administration.Service.Tests/Controllers/CompanyDataControllerTests.cs +++ b/tests/administration/Administration.Service.Tests/Controllers/CompanyDataControllerTests.cs @@ -34,8 +34,6 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Tests.Contr public class CompanyDataControllerTests { - private const string IamUserId = "4C1A6851-D4E7-4E10-A011-3732CD045E8A"; - private readonly IdentityData _identity = new(IamUserId, Guid.NewGuid(), IdentityTypeId.COMPANY_USER, Guid.NewGuid()); private readonly ICompanyDataBusinessLogic _logic; private readonly CompanyDataController _controller; private readonly Fixture _fixture; @@ -45,7 +43,6 @@ public CompanyDataControllerTests() _fixture = new Fixture(); _logic = A.Fake(); this._controller = new CompanyDataController(_logic); - _controller.AddControllerContextWithClaim(IamUserId, _identity); } [Fact] @@ -53,7 +50,7 @@ public async Task GetOwnCompanyDetailsAsync_ReturnsExpected() { // Arrange var companyAddressDetailData = _fixture.Create(); - A.CallTo(() => _logic.GetCompanyDetailsAsync(A._)) + A.CallTo(() => _logic.GetCompanyDetailsAsync()) .Returns(companyAddressDetailData); // Act @@ -61,7 +58,7 @@ public async Task GetOwnCompanyDetailsAsync_ReturnsExpected() // Assert result.Should().BeOfType(); - A.CallTo(() => _logic.GetCompanyDetailsAsync(_identity.CompanyId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _logic.GetCompanyDetailsAsync()).MustHaveHappenedOnceExactly(); } [Fact] @@ -69,14 +66,14 @@ public async Task GetCompanyAssigendUseCaseDetailsAsync_ReturnsExpectedResult() { // Arrange var companyRoleConsentDatas = _fixture.CreateMany(2).ToAsyncEnumerable(); - A.CallTo(() => _logic.GetCompanyAssigendUseCaseDetailsAsync(A._)) + A.CallTo(() => _logic.GetCompanyAssigendUseCaseDetailsAsync()) .Returns(companyRoleConsentDatas); // Act await this._controller.GetCompanyAssigendUseCaseDetailsAsync().ToListAsync(); // Assert - A.CallTo(() => _logic.GetCompanyAssigendUseCaseDetailsAsync(_identity.CompanyId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _logic.GetCompanyAssigendUseCaseDetailsAsync()).MustHaveHappenedOnceExactly(); } [Fact] @@ -84,14 +81,14 @@ public async Task CreateCompanyAssignedUseCaseDetailsAsync_NoContent_ReturnsExpe { // Arrange var useCaseData = _fixture.Create(); - A.CallTo(() => _logic.CreateCompanyAssignedUseCaseDetailsAsync(A._, A._)) + A.CallTo(() => _logic.CreateCompanyAssignedUseCaseDetailsAsync(A._)) .Returns(true); // Act var result = await this._controller.CreateCompanyAssignedUseCaseDetailsAsync(useCaseData).ConfigureAwait(false); // Assert - A.CallTo(() => _logic.CreateCompanyAssignedUseCaseDetailsAsync(_identity.CompanyId, useCaseData.useCaseId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _logic.CreateCompanyAssignedUseCaseDetailsAsync(useCaseData.useCaseId)).MustHaveHappenedOnceExactly(); result.Should().BeOfType(); result.StatusCode.Should().Be((int)HttpStatusCode.Created); } @@ -101,14 +98,14 @@ public async Task CreateCompanyAssignedUseCaseDetailsAsync_AlreadyReported_Retur { // Arrange var useCaseData = _fixture.Create(); - A.CallTo(() => _logic.CreateCompanyAssignedUseCaseDetailsAsync(A._, A._)) + A.CallTo(() => _logic.CreateCompanyAssignedUseCaseDetailsAsync(A._)) .Returns(false); // Act var result = await this._controller.CreateCompanyAssignedUseCaseDetailsAsync(useCaseData).ConfigureAwait(false); // Assert - A.CallTo(() => _logic.CreateCompanyAssignedUseCaseDetailsAsync(_identity.CompanyId, useCaseData.useCaseId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _logic.CreateCompanyAssignedUseCaseDetailsAsync(useCaseData.useCaseId)).MustHaveHappenedOnceExactly(); result.Should().BeOfType(); result.StatusCode.Should().Be((int)HttpStatusCode.NoContent); } @@ -123,7 +120,7 @@ public async Task RemoveCompanyAssignedUseCaseDetailsAsync_ReturnsExpectedResult var result = await this._controller.RemoveCompanyAssignedUseCaseDetailsAsync(useCaseData).ConfigureAwait(false); // Assert - A.CallTo(() => _logic.RemoveCompanyAssignedUseCaseDetailsAsync(_identity.CompanyId, useCaseData.useCaseId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _logic.RemoveCompanyAssignedUseCaseDetailsAsync(useCaseData.useCaseId)).MustHaveHappenedOnceExactly(); result.Should().BeOfType(); } @@ -133,14 +130,14 @@ public async Task GetCompanyRoleAndConsentAgreementDetailsAsync_ReturnsExpectedR // Arrange var languageShortName = "en"; var companyRoleConsentDatas = _fixture.CreateMany(2).ToAsyncEnumerable(); - A.CallTo(() => _logic.GetCompanyRoleAndConsentAgreementDetailsAsync(A._, A._)) + A.CallTo(() => _logic.GetCompanyRoleAndConsentAgreementDetailsAsync(A._)) .Returns(companyRoleConsentDatas); // Act await this._controller.GetCompanyRoleAndConsentAgreementDetailsAsync(languageShortName).ToListAsync().ConfigureAwait(false); // Assert - A.CallTo(() => _logic.GetCompanyRoleAndConsentAgreementDetailsAsync(_identity.CompanyId, languageShortName)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _logic.GetCompanyRoleAndConsentAgreementDetailsAsync(languageShortName)).MustHaveHappenedOnceExactly(); } [Fact] @@ -153,7 +150,7 @@ public async Task CreateCompanyRoleAndConsentAgreementDetailsAsync() var result = await this._controller.CreateCompanyRoleAndConsentAgreementDetailsAsync(companyRoleConsentDetails).ConfigureAwait(false); // Assert - A.CallTo(() => _logic.CreateCompanyRoleAndConsentAgreementDetailsAsync(new(_identity.UserId, _identity.CompanyId), companyRoleConsentDetails)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _logic.CreateCompanyRoleAndConsentAgreementDetailsAsync(companyRoleConsentDetails)).MustHaveHappenedOnceExactly(); result.Should().BeOfType(); } @@ -161,14 +158,14 @@ public async Task CreateCompanyRoleAndConsentAgreementDetailsAsync() public async Task GetUseCaseParticipation_WithValidRequest_ReturnsExpected() { // Arrange - A.CallTo(() => _logic.GetUseCaseParticipationAsync(_identity.CompanyId, null)) + A.CallTo(() => _logic.GetUseCaseParticipationAsync(null)) .Returns(_fixture.CreateMany(5)); // Act var result = await _controller.GetUseCaseParticipation(null).ConfigureAwait(false); // Assert - A.CallTo(() => _logic.GetUseCaseParticipationAsync(_identity.CompanyId, null)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _logic.GetUseCaseParticipationAsync(null)).MustHaveHappenedOnceExactly(); result.Should().HaveCount(5); } @@ -176,14 +173,14 @@ public async Task GetUseCaseParticipation_WithValidRequest_ReturnsExpected() public async Task GetUseCaseParticipation_WithLanguageExplicitlySet_ReturnsExpected() { // Arrange - A.CallTo(() => _logic.GetUseCaseParticipationAsync(_identity.CompanyId, "de")) + A.CallTo(() => _logic.GetUseCaseParticipationAsync("de")) .Returns(_fixture.CreateMany(5)); // Act var result = await _controller.GetUseCaseParticipation("de").ConfigureAwait(false); // Assert - A.CallTo(() => _logic.GetUseCaseParticipationAsync(_identity.CompanyId, "de")).MustHaveHappenedOnceExactly(); + A.CallTo(() => _logic.GetUseCaseParticipationAsync("de")).MustHaveHappenedOnceExactly(); result.Should().HaveCount(5); } @@ -191,14 +188,14 @@ public async Task GetUseCaseParticipation_WithLanguageExplicitlySet_ReturnsExpec public async Task GetSsiCertificationData_WithValidData_ReturnsExpected() { // Arrange - A.CallTo(() => _logic.GetSsiCertificatesAsync(_identity.CompanyId)) + A.CallTo(() => _logic.GetSsiCertificatesAsync()) .Returns(_fixture.CreateMany(5)); // Act var result = await _controller.GetSsiCertificationData().ConfigureAwait(false); // Assert - A.CallTo(() => _logic.GetSsiCertificatesAsync(_identity.CompanyId)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _logic.GetSsiCertificatesAsync()).MustHaveHappenedOnceExactly(); result.Should().HaveCount(5); } @@ -213,7 +210,7 @@ public async Task CreateUseCaseParticipation_WithValidRequest_ReturnsExpected() await _controller.CreateUseCaseParticipation(data, CancellationToken.None).ConfigureAwait(false); // Assert - A.CallTo(() => _logic.CreateUseCaseParticipation(A>.That.Matches(x => x.Item1 == _identity.UserId && x.Item2 == _identity.CompanyId), data, A._)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _logic.CreateUseCaseParticipation(data, A._)).MustHaveHappenedOnceExactly(); } [Fact] @@ -227,7 +224,7 @@ public async Task CreateSsiCertificate_WithValidRequest_ReturnsExpected() await _controller.CreateSsiCertificate(data, CancellationToken.None).ConfigureAwait(false); // Assert - A.CallTo(() => _logic.CreateSsiCertificate(A>.That.Matches(x => x.Item1 == _identity.UserId && x.Item2 == _identity.CompanyId), data, A._)).MustHaveHappenedOnceExactly(); + A.CallTo(() => _logic.CreateSsiCertificate(data, A._)).MustHaveHappenedOnceExactly(); } [Fact] @@ -240,7 +237,7 @@ public async Task ApproveCredential_WithValidData_CallsExpected() await _controller.ApproveCredential(credentialId, CancellationToken.None).ConfigureAwait(false); // Assert - A.CallTo(() => _logic.ApproveCredential(_identity.UserId, credentialId, A._)) + A.CallTo(() => _logic.ApproveCredential(credentialId, A._)) .MustHaveHappenedOnceExactly(); } @@ -254,7 +251,7 @@ public async Task RejectCredentialWithValidData_CallsExpected() await _controller.RejectCredential(credentialId).ConfigureAwait(false); // Assert - A.CallTo(() => _logic.RejectCredential(_identity.UserId, credentialId)) + A.CallTo(() => _logic.RejectCredential(credentialId)) .MustHaveHappenedOnceExactly(); } diff --git a/tests/administration/Administration.Service.Tests/Controllers/UserControllerTest.cs b/tests/administration/Administration.Service.Tests/Controllers/UserControllerTest.cs index c35e7bda60..257e8abd9f 100644 --- a/tests/administration/Administration.Service.Tests/Controllers/UserControllerTest.cs +++ b/tests/administration/Administration.Service.Tests/Controllers/UserControllerTest.cs @@ -44,9 +44,8 @@ public UserControllerTest() _fixture = new Fixture(); _logic = A.Fake(); _rolesLogic = A.Fake(); - var logger = A.Fake>(); var uploadBusinessLogic = A.Fake(); - this._controller = new UserController(logger, _logic, uploadBusinessLogic, _rolesLogic); + this._controller = new UserController(_logic, uploadBusinessLogic, _rolesLogic); _controller.AddControllerContextWithClaim(IamUserId, _identity); } diff --git a/tests/administration/Administration.Service.Tests/IntegrationTests/ConnectorsControllerIntegrationTests.cs b/tests/administration/Administration.Service.Tests/IntegrationTests/ConnectorsControllerIntegrationTests.cs index a25017143e..f319837b10 100644 --- a/tests/administration/Administration.Service.Tests/IntegrationTests/ConnectorsControllerIntegrationTests.cs +++ b/tests/administration/Administration.Service.Tests/IntegrationTests/ConnectorsControllerIntegrationTests.cs @@ -20,6 +20,7 @@ using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Controllers; using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Tests.EnpointSetup; +using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Tests.IntegrationTests.Seeding; using Org.Eclipse.TractusX.Portal.Backend.Framework.Models; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Models; using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.Extensions; @@ -28,11 +29,11 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Tests.IntegrationTests; -public class ConnectorsControllerIntegrationTests : IClassFixture> +public class ConnectorsControllerIntegrationTests : IClassFixture> { - private readonly IntegrationTestFactory _factory; + private readonly IntegrationTestFactory _factory; - public ConnectorsControllerIntegrationTests(IntegrationTestFactory factory) + public ConnectorsControllerIntegrationTests(IntegrationTestFactory factory) { _factory = factory; } diff --git a/tests/administration/Administration.Service.Tests/IntegrationTests/PublicUrlActiveParticipantTests.cs b/tests/administration/Administration.Service.Tests/IntegrationTests/PublicUrlActiveParticipantTests.cs new file mode 100644 index 0000000000..eb042d5b20 --- /dev/null +++ b/tests/administration/Administration.Service.Tests/IntegrationTests/PublicUrlActiveParticipantTests.cs @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Controllers; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.IntegrationTests; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; +using System.Diagnostics.CodeAnalysis; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] +namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Tests.IntegrationTests; + +public class PublicUrlActiveParticipantTests : BasePublicUrlTests +{ + public PublicUrlActiveParticipantTests(IntegrationTestFactory factory) + : base(factory) + { } + + [Fact] + [SuppressMessage("SonarLint", "S2699", Justification = "Ignored because the assert is done in OpenInformationController_ReturnsCorrectAmount")] + public async Task OpenInformationController_WithActiveParticipant_ReturnsCorrectAmount() + { + await OpenInformationController_ReturnsCorrectAmount(2, + x => x.HttpMethods == "POST" && x.Url == "api/administration/connectors/discovery", + x => x.HttpMethods == "GET" && x.Url == "api/administration/partnernetwork/membercompanies") + .ConfigureAwait(false); + } +} diff --git a/tests/administration/Administration.Service.Tests/IntegrationTests/PublicUrlAppProviderTests.cs b/tests/administration/Administration.Service.Tests/IntegrationTests/PublicUrlAppProviderTests.cs new file mode 100644 index 0000000000..2c0302d5f1 --- /dev/null +++ b/tests/administration/Administration.Service.Tests/IntegrationTests/PublicUrlAppProviderTests.cs @@ -0,0 +1,49 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Controllers; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.IntegrationTests; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; +using System.Diagnostics.CodeAnalysis; + +namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Tests.IntegrationTests; + +public class PublicUrlAppProviderTests : BasePublicUrlTests +{ + public PublicUrlAppProviderTests(IntegrationTestFactory factory) + : base(factory) + { } + + [Fact] + [SuppressMessage("SonarLint", "S2699", Justification = "Ignored because the assert is done in OpenInformationController_ReturnsCorrectAmount")] + public async Task OpenInformationController_WithAppProvider_ReturnsCorrectAmount() + { + await OpenInformationController_ReturnsCorrectAmount(8, + x => x.HttpMethods == "GET" && x.Url == "api/administration/connectors/managed", + x => x.HttpMethods == "POST" && x.Url == "api/administration/connectors/managed", + x => x.HttpMethods == "POST" && x.Url == "api/administration/connectors/discovery", + x => x.HttpMethods == "GET" && x.Url == "api/administration/documents/selfdescription/{documentid}", + x => x.HttpMethods == "GET" && x.Url == "api/administration/partnernetwork/membercompanies", + x => x.HttpMethods == "GET" && x.Url == "api/administration/subscriptionconfiguration/owncompany", + x => x.HttpMethods == "GET" && x.Url == "api/administration/subscriptionconfiguration/process/offer-subscription/{offersubscriptionid}", + x => x.HttpMethods == "POST" && x.Url == "api/administration/subscriptionconfiguration/process/offer-subscription/{offersubscriptionid}/retrigger-provider-callback") + .ConfigureAwait(false); + } +} diff --git a/tests/administration/Administration.Service.Tests/IntegrationTests/PublicUrlServiceProviderTests.cs b/tests/administration/Administration.Service.Tests/IntegrationTests/PublicUrlServiceProviderTests.cs new file mode 100644 index 0000000000..955d0aba1f --- /dev/null +++ b/tests/administration/Administration.Service.Tests/IntegrationTests/PublicUrlServiceProviderTests.cs @@ -0,0 +1,49 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Controllers; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.IntegrationTests; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; +using System.Diagnostics.CodeAnalysis; + +namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Tests.IntegrationTests; + +public class PublicUrlServiceProviderTests : BasePublicUrlTests +{ + public PublicUrlServiceProviderTests(IntegrationTestFactory factory) + : base(factory) + { } + + [Fact] + [SuppressMessage("SonarLint", "S2699", Justification = "Ignored because the assert is done in OpenInformationController_ReturnsCorrectAmount")] + public async Task OpenInformationController_WithServiceProvider_ReturnsCorrectAmount() + { + await OpenInformationController_ReturnsCorrectAmount(8, + x => x.HttpMethods == "GET" && x.Url == "api/administration/connectors/managed", + x => x.HttpMethods == "POST" && x.Url == "api/administration/connectors/managed", + x => x.HttpMethods == "POST" && x.Url == "api/administration/connectors/discovery", + x => x.HttpMethods == "GET" && x.Url == "api/administration/documents/selfdescription/{documentid}", + x => x.HttpMethods == "GET" && x.Url == "api/administration/partnernetwork/membercompanies", + x => x.HttpMethods == "GET" && x.Url == "api/administration/subscriptionconfiguration/owncompany", + x => x.HttpMethods == "GET" && x.Url == "api/administration/subscriptionconfiguration/process/offer-subscription/{offersubscriptionid}", + x => x.HttpMethods == "POST" && x.Url == "api/administration/subscriptionconfiguration/process/offer-subscription/{offersubscriptionid}/retrigger-provider-callback") + .ConfigureAwait(false); + } +} diff --git a/tests/administration/Administration.Service.Tests/IntegrationTests/Seeding/BaseSeeding.cs b/tests/administration/Administration.Service.Tests/IntegrationTests/Seeding/BaseSeeding.cs new file mode 100644 index 0000000000..3dbe574d42 --- /dev/null +++ b/tests/administration/Administration.Service.Tests/IntegrationTests/Seeding/BaseSeeding.cs @@ -0,0 +1,52 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; + +namespace Org.Eclipse.TractusX.Portal.Backend.Administration.Service.Tests.IntegrationTests.Seeding; + +public class BaseSeeding : IBaseSeeding +{ + public Action SeedData() => dbContext => + { + BaseSeed.SeedBaseData().Invoke(dbContext); + + dbContext.Connectors.AddRange(new List + { + new(new Guid("5aea3711-cc54-47b4-b7eb-ba9f3bf1cb15"), "Tes One", "DE", "https://api.tes-one.com") + { + ProviderId = new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), + HostId = new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), + TypeId = ConnectorTypeId.COMPANY_CONNECTOR, + StatusId =ConnectorStatusId.ACTIVE, + }, + new(new Guid("f7310cff-a51d-4af8-9bc3-1525e9d1601b"), "Con on Air", "PT", "https://api.con-on-air.com") + { + ProviderId = new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), + HostId = new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), + TypeId = ConnectorTypeId.CONNECTOR_AS_A_SERVICE, + StatusId = ConnectorStatusId.PENDING, + }, + }); + }; +} diff --git a/tests/externalsystems/Bpdm.Library/BPNAccessTest.cs b/tests/externalsystems/Bpdm.Library/BPNAccessTest.cs index 071fc1387c..8bf2795cf2 100644 --- a/tests/externalsystems/Bpdm.Library/BPNAccessTest.cs +++ b/tests/externalsystems/Bpdm.Library/BPNAccessTest.cs @@ -25,12 +25,7 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Bpdm.Library.Tests; public class BPNAccessTest { - private readonly IFixture _fixture; - - public BPNAccessTest() - { - _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); - } + private readonly IFixture _fixture = new Fixture().Customize(new AutoFakeItEasyCustomization { ConfigureMembers = true }); private void ConfigureHttpClientFactoryFixture(HttpResponseMessage httpResponseMessage, Action? setMessage = null) { @@ -60,141 +55,63 @@ public async Task FetchLegalEntityByBpn_Success_ReturnsExpected() HttpRequestMessage? request = null; const string json = @"{ - ""legalName"": ""string"", - ""bpnl"": ""string"", - ""identifiers"": [ - { - ""value"": ""string"", - ""type"": { - ""technicalKey"": ""string"", - ""name"": ""string"" - }, - ""issuingBody"": ""string"" - } - ], - ""legalShortName"": ""string"", - ""legalForm"": { - ""technicalKey"": ""string"", - ""name"": ""string"", - ""abbreviation"": ""string"" - }, - ""states"": [ - { - ""officialDenotation"": ""string"", - ""validFrom"": ""2023-07-24T11:23:20.887Z"", - ""validTo"": ""2023-07-24T11:23:20.887Z"", - ""type"": { - ""technicalKey"": ""ACTIVE"", - ""name"": ""string"" - } - } - ], - ""classifications"": [ - { - ""value"": ""string"", - ""code"": ""string"", - ""type"": { - ""technicalKey"": ""NACE"", - ""name"": ""string"" - } - } - ], - ""relations"": [ - { - ""type"": { - ""technicalKey"": ""CX_LEGAL_SUCCESSOR_OF"", - ""name"": ""string"" - }, - ""startBpn"": ""string"", - ""endBpn"": ""string"", - ""validFrom"": ""2023-07-24T11:23:20.887Z"", - ""validTo"": ""2023-07-24T11:23:20.887Z"" - } - ], - ""currentness"": ""2023-07-24T11:23:20.887Z"", - ""createdAt"": ""2023-07-24T11:23:20.887Z"", - ""updatedAt"": ""2023-07-24T11:23:20.887Z"", - ""legalAddress"": { - ""bpna"": ""string"", - ""name"": ""string"", - ""states"": [ - { - ""description"": ""string"", - ""validFrom"": ""2023-07-24T11:23:20.887Z"", - ""validTo"": ""2023-07-24T11:23:20.887Z"", - ""type"": { - ""technicalKey"": ""ACTIVE"", - ""name"": ""string"" - } - } - ], + ""legalName"": ""CX-Test-Access"", + ""bpnl"": ""BPNL00000007OR16"", ""identifiers"": [ - { - ""value"": ""string"", - ""type"": { - ""technicalKey"": ""string"", - ""name"": ""string"" + { + ""value"": ""HRB 12345"", + ""type"": { + ""technicalKey"": ""EU_VAT_ID_DE"", + ""name"": ""Value added tax identification number"" + }, + ""issuingBody"": null } - } ], - ""physicalPostalAddress"": { - ""geographicCoordinates"": { - ""longitude"": 0, - ""latitude"": 0, - ""altitude"": 0 - }, - ""country"": { - ""technicalKey"": ""UNDEFINED"", - ""name"": ""string"" - }, - ""postalCode"": ""string"", - ""city"": ""string"", - ""street"": { - ""name"": ""string"", - ""houseNumber"": ""string"", - ""milestone"": ""string"", - ""direction"": ""string"" - }, - ""administrativeAreaLevel1"": { - ""name"": ""string"", - ""regionCode"": ""string"" - }, - ""administrativeAreaLevel2"": ""string"", - ""administrativeAreaLevel3"": ""string"", - ""district"": ""string"", - ""companyPostalCode"": ""string"", - ""industrialZone"": ""string"", - ""building"": ""string"", - ""floor"": ""string"", - ""door"": ""string"" - }, - ""alternativePostalAddress"": { - ""geographicCoordinates"": { - ""longitude"": 0, - ""latitude"": 0, - ""altitude"": 0 - }, - ""country"": { - ""technicalKey"": ""UNDEFINED"", - ""name"": ""string"" - }, - ""postalCode"": ""string"", - ""city"": ""string"", - ""administrativeAreaLevel1"": { - ""name"": ""string"", - ""regionCode"": ""string"" - }, - ""deliveryServiceNumber"": ""string"", - ""type"": ""PO_BOX"", - ""deliveryServiceQualifier"": ""string"" - }, - ""bpnLegalEntity"": ""string"", - ""bpnSite"": ""string"", - ""createdAt"": ""2023-07-24T11:23:20.887Z"", - ""updatedAt"": ""2023-07-24T11:23:20.887Z"", - ""isLegalAddress"": true, - ""isMainAddress"": true - } + ""legalShortName"": ""CX-Test-Access"", + ""legalForm"": null, + ""states"": [], + ""classifications"": [], + ""relations"": [], + ""currentness"": ""2023-07-26T15:27:12.739650Z"", + ""createdAt"": ""2023-07-26T15:27:13.756713Z"", + ""updatedAt"": ""2023-07-26T15:27:13.756719Z"", + ""legalAddress"": { + ""bpna"": ""BPNA000000000LKR"", + ""name"": null, + ""states"": [], + ""identifiers"": [], + ""physicalPostalAddress"": { + ""geographicCoordinates"": null, + ""country"": { + ""technicalKey"": ""DE"", + ""name"": ""Germany"" + }, + ""postalCode"": ""1"", + ""city"": ""Dresden"", + ""street"": { + ""name"": ""Onisamili Road 236"", + ""houseNumber"": """", + ""milestone"": null, + ""direction"": null + }, + ""administrativeAreaLevel1"": null, + ""administrativeAreaLevel2"": null, + ""administrativeAreaLevel3"": null, + ""district"": null, + ""companyPostalCode"": null, + ""industrialZone"": null, + ""building"": null, + ""floor"": null, + ""door"": null + }, + ""alternativePostalAddress"": null, + ""bpnLegalEntity"": ""BPNL00000007OR16"", + ""isLegalAddress"": true, + ""bpnSite"": null, + ""isMainAddress"": false, + ""createdAt"": ""2023-07-26T15:27:13.756112Z"", + ""updatedAt"": ""2023-07-26T15:27:14.267585Z"" + } }"; ConfigureHttpClientFactoryFixture(new HttpResponseMessage @@ -217,7 +134,8 @@ public async Task FetchLegalEntityByBpn_Success_ReturnsExpected() request.RequestUri.Query.Should().Be("?idType=BPN"); result.Should().NotBeNull(); - result.Currentness.Should().BeExactly(DateTimeOffset.Parse("2023-07-24T11:23:20.887Z")); + result.LegalEntityAddress.Should().NotBeNull(); + result.LegalEntityAddress!.Bpna.Should().Be("BPNA000000000LKR"); } [Fact] diff --git a/tests/externalsystems/Bpdm.Library/BpdmBusinessLogicTests.cs b/tests/externalsystems/Bpdm.Library/BpdmBusinessLogicTests.cs index bf81b1916b..5b81c02415 100644 --- a/tests/externalsystems/Bpdm.Library/BpdmBusinessLogicTests.cs +++ b/tests/externalsystems/Bpdm.Library/BpdmBusinessLogicTests.cs @@ -36,6 +36,8 @@ public class BpdmBusinessLogicTests #region Initialization private static readonly Guid IdWithBpn = new("c244f79a-7faf-4c59-bb85-fbfdf72ce46f"); + private static readonly Guid IdWithSharingPending = new("920AF606-9581-4EAD-A7FD-78480F42D3A1"); + private static readonly Guid IdWithSharingError = new("9460ED6B-2DD3-4446-9B9D-9AE3640717F4"); private static readonly Guid IdWithStateCreated = new("bda6d1b5-042e-493a-894c-11f3a89c12b1"); private static readonly Guid IdWithoutZipCode = new("beaa6de5-d411-4da8-850e-06047d3170be"); private static readonly Guid ValidCompanyId = new("abf990f8-0c27-43dc-bbd0-b1bce964d8f4"); @@ -82,7 +84,7 @@ public async Task PushLegalEntity_WithoutBpdmData_ThrowsUnexpectedConditionExcep .ToImmutableDictionary(); var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithBpn, default, checklist, Enumerable.Empty()); A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Matches(x => x == IdWithBpn))) - .ReturnsLazily(() => (ValidCompanyId, null!)); + .Returns((ValidCompanyId, null!)); // Act async Task Act() => await _logic.PushLegalEntity(context, CancellationToken.None).ConfigureAwait(false); @@ -128,7 +130,7 @@ public async Task PushLegalEntity_WithBpn_ThrowsConflictException() .ToImmutableDictionary(); var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithoutZipCode, default, checklist, Enumerable.Empty()); A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Matches(x => x == IdWithoutZipCode))) - .ReturnsLazily(() => (ValidCompanyId, new BpdmData(ValidCompanyName, null!, "Test", null!, null!, "Test", "test", null!, null!, new List<(BpdmIdentifierId UniqueIdentifierId, string Value)>()))); + .Returns((ValidCompanyId, new BpdmData(ValidCompanyName, null!, "Test", null!, null!, "Test", "test", null!, null!, new List<(BpdmIdentifierId UniqueIdentifierId, string Value)>()))); // Act async Task Act() => await _logic.PushLegalEntity(context, CancellationToken.None).ConfigureAwait(false); @@ -151,7 +153,7 @@ public async Task PushLegalEntity_WithoutAlpha2Code_ThrowsConflictException() .ToImmutableDictionary(); var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithoutZipCode, default, checklist, Enumerable.Empty()); A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Matches(x => x == IdWithoutZipCode))) - .ReturnsLazily(() => (ValidCompanyId, new BpdmData(ValidCompanyName, null!, null!, null!, null!, "Test", "test", null!, null!, new List<(BpdmIdentifierId UniqueIdentifierId, string Value)>()))); + .Returns((ValidCompanyId, new BpdmData(ValidCompanyName, null!, null!, null!, null!, "Test", "test", null!, null!, new List<(BpdmIdentifierId UniqueIdentifierId, string Value)>()))); // Act async Task Act() => await _logic.PushLegalEntity(context, CancellationToken.None).ConfigureAwait(false); @@ -174,7 +176,7 @@ public async Task PushLegalEntity_WithoutCity_ThrowsConflictException() .ToImmutableDictionary(); var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithoutZipCode, default, checklist, Enumerable.Empty()); A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Matches(x => x == IdWithoutZipCode))) - .ReturnsLazily(() => (ValidCompanyId, new BpdmData(ValidCompanyName, null!, null!, "DE", null!, null, "test", null!, null!, new List<(BpdmIdentifierId UniqueIdentifierId, string Value)>()))); + .Returns((ValidCompanyId, new BpdmData(ValidCompanyName, null!, null!, "DE", null!, null, "test", null!, null!, new List<(BpdmIdentifierId UniqueIdentifierId, string Value)>()))); // Act async Task Act() => await _logic.PushLegalEntity(context, CancellationToken.None).ConfigureAwait(false); @@ -197,7 +199,7 @@ public async Task PushLegalEntity_WithoutStreetName_ThrowsConflictException() .ToImmutableDictionary(); var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithoutZipCode, default, checklist, Enumerable.Empty()); A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Matches(x => x == IdWithoutZipCode))) - .ReturnsLazily(() => (ValidCompanyId, new BpdmData(ValidCompanyName, null!, null!, "DE", null!, "TEST", null, null!, null!, new List<(BpdmIdentifierId UniqueIdentifierId, string Value)>()))); + .Returns((ValidCompanyId, new BpdmData(ValidCompanyName, null!, null!, "DE", null!, "TEST", null, null!, null!, new List<(BpdmIdentifierId UniqueIdentifierId, string Value)>()))); // Act async Task Act() => await _logic.PushLegalEntity(context, CancellationToken.None).ConfigureAwait(false); @@ -305,6 +307,65 @@ public async Task HandlePullLegalEntity_WithLegalEntityWithoutBpn_ThrowsConflict result.Modified.Should().BeFalse(); } + [Fact] + public async Task HandlePullLegalEntity_WithSharingStateError_ThrowsServiceException() + { + // Arrange + var company = new Company(Guid.NewGuid(), "Test Company", CompanyStatusId.ACTIVE, DateTimeOffset.UtcNow) + { + BusinessPartnerNumber = "1" + }; + var checklistEntry = _fixture.Build() + .With(x => x.ApplicationChecklistEntryStatusId, + ApplicationChecklistEntryStatusId.TO_DO).Create(); + var checklist = new Dictionary + { + {ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE}, + {ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.TO_DO}, + } + .ToImmutableDictionary(); + var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithSharingError, default, checklist, Enumerable.Empty()); + SetupForHandlePullLegalEntity(company); + + // Act + async Task Act() => await _logic.HandlePullLegalEntity(context, CancellationToken.None).ConfigureAwait(false); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Be($"ErrorCode: Code 43, ErrorMessage: This is a test sharing state error"); + } + + [Fact] + public async Task HandlePullLegalEntity_WithSharingTypePending_ReturnsExpected() + { + // Arrange + var company = new Company(Guid.NewGuid(), "Test Company", CompanyStatusId.ACTIVE, DateTimeOffset.UtcNow) + { + BusinessPartnerNumber = "1" + }; + var checklistEntry = _fixture.Build() + .With(x => x.ApplicationChecklistEntryStatusId, + ApplicationChecklistEntryStatusId.TO_DO).Create(); + var checklist = new Dictionary + { + {ApplicationChecklistEntryTypeId.REGISTRATION_VERIFICATION, ApplicationChecklistEntryStatusId.DONE}, + {ApplicationChecklistEntryTypeId.BUSINESS_PARTNER_NUMBER, ApplicationChecklistEntryStatusId.TO_DO}, + } + .ToImmutableDictionary(); + var context = new IApplicationChecklistService.WorkerChecklistProcessStepData(IdWithSharingPending, default, checklist, Enumerable.Empty()); + SetupForHandlePullLegalEntity(company); + + // Act + var result = await _logic.HandlePullLegalEntity(context, CancellationToken.None).ConfigureAwait(false); + + // Assert + result.ModifyChecklistEntry?.Invoke(checklistEntry); + checklistEntry.ApplicationChecklistEntryStatusId.Should().Be(ApplicationChecklistEntryStatusId.TO_DO); + result.ScheduleStepTypeIds.Should().BeNull(); + result.SkipStepTypeIds.Should().BeNull(); + result.Modified.Should().BeFalse(); + } + [Fact] public async Task HandlePullLegalEntity_WithValidData_ReturnsExpected() { @@ -383,18 +444,18 @@ private void SetupFakesForTrigger() .Create(); A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Matches(x => x == IdWithBpn))) - .ReturnsLazily(() => (ValidCompanyId, validData)); + .Returns((ValidCompanyId, validData)); A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Matches(x => x == IdWithStateCreated))) - .ReturnsLazily(() => (ValidCompanyId, new BpdmData(ValidCompanyName, null!, null!, "DE", null!, "Test", "test", null!, null!, new List<(BpdmIdentifierId UniqueIdentifierId, string Value)>()))); + .Returns((ValidCompanyId, new BpdmData(ValidCompanyName, null!, null!, "DE", null!, "Test", "test", null!, null!, new List<(BpdmIdentifierId UniqueIdentifierId, string Value)>()))); A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Matches(x => x == IdWithoutZipCode))) - .ReturnsLazily(() => (ValidCompanyId, new BpdmData(ValidCompanyName, null!, null!, null!, null!, "Test", "test", null!, null!, new List<(BpdmIdentifierId UniqueIdentifierId, string Value)>()))); + .Returns((ValidCompanyId, new BpdmData(ValidCompanyName, null!, null!, null!, null!, "Test", "test", null!, null!, new List<(BpdmIdentifierId UniqueIdentifierId, string Value)>()))); A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Not.Matches(x => x == IdWithStateCreated || x == IdWithBpn || x == IdWithoutZipCode))) - .ReturnsLazily(() => new ValueTuple()); + .Returns(new ValueTuple()); A.CallTo(() => _bpdmService.PutInputLegalEntity( A.That.Matches(x => x.CompanyName == ValidCompanyName && x.ZipCode == "50668"), A._)) - .ReturnsLazily(() => true); + .Returns(true); } private void SetupForHandlePullLegalEntity(Company? company = null) @@ -408,21 +469,31 @@ private void SetupForHandlePullLegalEntity(Company? company = null) .With(x => x.BusinessPartnerNumber, (string?)null) .Create(); - A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Matches(x => x == IdWithBpn))) - .ReturnsLazily(() => (ValidCompanyId, validData)); + A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Matches(x => x == IdWithBpn || x == IdWithSharingError || x == IdWithSharingPending))) + .Returns((ValidCompanyId, validData)); A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Matches(x => x == IdWithStateCreated))) - .ReturnsLazily(() => (ValidCompanyId, new BpdmData(ValidCompanyName, null!, null!, "DE", null!, "Test", "test", null!, null!, new List<(BpdmIdentifierId UniqueIdentifierId, string Value)>()))); + .Returns((ValidCompanyId, new BpdmData(ValidCompanyName, null!, null!, "DE", null!, "Test", "test", null!, null!, new List<(BpdmIdentifierId UniqueIdentifierId, string Value)>()))); A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Matches(x => x == IdWithoutZipCode))) - .ReturnsLazily(() => (ValidCompanyId, new BpdmData(ValidCompanyName, null!, null!, null!, null!, "Test", "test", null!, null!, new List<(BpdmIdentifierId UniqueIdentifierId, string Value)>()))); - A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Not.Matches(x => x == IdWithStateCreated || x == IdWithBpn || x == IdWithoutZipCode))) - .ReturnsLazily(() => new ValueTuple()); + .Returns((ValidCompanyId, new BpdmData(ValidCompanyName, null!, null!, null!, null!, "Test", "test", null!, null!, new List<(BpdmIdentifierId UniqueIdentifierId, string Value)>()))); + A.CallTo(() => _applicationRepository.GetBpdmDataForApplicationAsync(A.That.Not.Matches(x => x == IdWithStateCreated || x == IdWithBpn || x == IdWithoutZipCode || x == IdWithSharingError || x == IdWithSharingPending))) + .Returns(new ValueTuple()); A.CallTo(() => _bpdmService.FetchInputLegalEntity(A.That.Matches(x => x == IdWithStateCreated.ToString()), A._)) .ThrowsAsync(new ServiceException("not found", System.Net.HttpStatusCode.NotFound)); A.CallTo(() => _bpdmService.FetchInputLegalEntity(A.That.Matches(x => x == IdWithoutZipCode.ToString()), A._)) - .ReturnsLazily(() => _fixture.Build().With(x => x.Bpn, (string?)null).Create()); + .Returns(_fixture.Build().With(x => x.Bpn, (string?)null).Create()); A.CallTo(() => _bpdmService.FetchInputLegalEntity(A.That.Matches(x => x == IdWithBpn.ToString()), A._)) - .ReturnsLazily(() => _fixture.Build().With(x => x.Bpn, "CAXSDUMMYCATENAZZ").Create()); + .Returns(_fixture.Build().With(x => x.Bpn, "CAXSDUMMYCATENAZZ").Create()); + A.CallTo(() => _bpdmService.GetSharingState(A.That.Matches(x => x == IdWithBpn || x == IdWithStateCreated || x == IdWithoutZipCode), A._)) + .Returns(_fixture.Build().With(x => x.SharingStateType, BpdmSharingStateType.Success).Create()); + A.CallTo(() => _bpdmService.GetSharingState(IdWithSharingPending, A._)) + .Returns(_fixture.Build().With(x => x.SharingStateType, BpdmSharingStateType.Pending).Create()); + A.CallTo(() => _bpdmService.GetSharingState(IdWithSharingError, A._)) + .Returns(_fixture.Build() + .With(x => x.SharingStateType, BpdmSharingStateType.Error) + .With(x => x.SharingErrorMessage, "This is a test sharing state error") + .With(x => x.SharingErrorCode, "Code 43") + .Create()); if (company != null) { diff --git a/tests/externalsystems/Bpdm.Library/BpdmServiceTests.cs b/tests/externalsystems/Bpdm.Library/BpdmServiceTests.cs index 93d311c320..f69070a100 100644 --- a/tests/externalsystems/Bpdm.Library/BpdmServiceTests.cs +++ b/tests/externalsystems/Bpdm.Library/BpdmServiceTests.cs @@ -237,4 +237,146 @@ public async Task FetchInputLegalEntity_WithInvalidData_ThrowsServiceException() } #endregion + + #region GetSharingState + + [Fact] + public async Task GetSharingState_WithValidResult_ReturnsExpected() + { + // Arrange + var applicationId = new Guid("aa2eeac5-a0e9-46b0-80f0-d48dde49aa23"); + const string json = @"{ + ""totalElements"": 1, + ""totalPages"": 1, + ""page"": 0, + ""contentSize"": 1, + ""content"": [ + { + ""businessPartnerType"": ""LEGAL_ENTITY"", + ""externalId"": ""aa2eeac5-a0e9-46b0-80f0-d48dde49aa23"", + ""sharingStateType"": ""Error"", + ""sharingErrorCode"": ""SharingProcessError"", + ""sharingErrorMessage"": ""Address Identifier Type 'Cheese Region' does not exist (LegalAddressRegionNotFound)"", + ""bpn"": null, + ""sharingProcessStarted"": ""2023-08-04T14:35:30.478594"" + } + ] + }"; + + var httpMessageHandlerMock = new HttpMessageHandlerMock( + HttpStatusCode.OK, + new StringContent(json)); + var httpClient = new HttpClient(httpMessageHandlerMock) + { + BaseAddress = new Uri("https://base.address.com") + }; + A.CallTo(() => _tokenService.GetAuthorizedClient(_options.Value, A._)).Returns(httpClient); + var sut = new BpdmService(_tokenService, _options); + + // Act + var result = await sut.GetSharingState(applicationId, CancellationToken.None).ConfigureAwait(false); + + // Assert + result.Should().NotBeNull(); + result.ExternalId.Should().Be(applicationId); + result.Bpn.Should().BeNull(); + result.SharingErrorCode.Should().Be("SharingProcessError"); + result.SharingStateType.Should().Be(BpdmSharingStateType.Error); + result.BusinessPartnerType.Should().Be(BpdmSharingStateBusinessPartnerType.LEGAL_ENTITY); + result.SharingErrorMessage.Should().Be("Address Identifier Type 'Cheese Region' does not exist (LegalAddressRegionNotFound)"); + } + + [Fact] + public async Task GetSharingState_WithEmtpyObjectResult_ThrowsServiceException() + { + // Arrange + var applicationId = Guid.NewGuid(); + var httpMessageHandlerMock = new HttpMessageHandlerMock( + HttpStatusCode.OK, + new StringContent("{}")); + + var httpClient = new HttpClient(httpMessageHandlerMock) + { + BaseAddress = new Uri("https://base.address.com"), + }; + A.CallTo(() => _tokenService.GetAuthorizedClient(_options.Value, A._)).Returns(httpClient); + var sut = new BpdmService(_tokenService, _options); + + // Act + async Task Act() => await sut.GetSharingState(applicationId, CancellationToken.None).ConfigureAwait(false); + + // Assert + var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); + ex.Message.Should().Be("Access to sharing state did not return a valid legal entity response"); + ex.IsRecoverable.Should().BeTrue(); + } + + [Fact] + public async Task GetSharingState_WithEmtpyResult_ThrowsServiceException() + { + // Arrange + var applicationId = Guid.NewGuid(); + var httpMessageHandlerMock = new HttpMessageHandlerMock( + HttpStatusCode.OK, + new StringContent("")); + + var httpClient = new HttpClient(httpMessageHandlerMock) + { + BaseAddress = new Uri("https://base.address.com"), + }; + A.CallTo(() => _tokenService.GetAuthorizedClient(_options.Value, A._)).Returns(httpClient); + var sut = new BpdmService(_tokenService, _options); + + // Act + async Task Act() => await sut.GetSharingState(applicationId, CancellationToken.None).ConfigureAwait(false); + + // Assert + var ex = await Assert.ThrowsAsync(Act).ConfigureAwait(false); + ex.Message.Should().StartWith("Access to sharing state did not return a valid json response"); + ex.IsRecoverable.Should().BeFalse(); + } + + [Fact] + public async Task GetSharingState_WithNotFoundResult_ReturnsNull() + { + // Arrange + var applicationId = Guid.NewGuid(); + var httpMessageHandlerMock = new HttpMessageHandlerMock(HttpStatusCode.NotFound); + var httpClient = new HttpClient(httpMessageHandlerMock) + { + BaseAddress = new Uri("https://base.address.com") + }; + A.CallTo(() => _tokenService.GetAuthorizedClient(_options.Value, A._)).Returns(httpClient); + var sut = new BpdmService(_tokenService, _options); + + // Act + var Act = () => sut.GetSharingState(applicationId, CancellationToken.None); + + // Assert + var result = await Assert.ThrowsAsync(Act).ConfigureAwait(false); + result.StatusCode.Should().Be(HttpStatusCode.NotFound); + } + + [Fact] + public async Task GetSharingState_WithInvalidData_ThrowsServiceException() + { + // Arrange + var applicationId = Guid.NewGuid(); + var httpMessageHandlerMock = new HttpMessageHandlerMock(HttpStatusCode.BadRequest); + var httpClient = new HttpClient(httpMessageHandlerMock) + { + BaseAddress = new Uri("https://base.address.com") + }; + A.CallTo(() => _tokenService.GetAuthorizedClient(_options.Value, A._)).Returns(httpClient); + var sut = new BpdmService(_tokenService, _options); + + // Act + async Task Act() => await sut.GetSharingState(applicationId, CancellationToken.None).ConfigureAwait(false); + + // Assert + var ex = await Assert.ThrowsAsync(Act); + ex.Message.Should().Be("call to external system bpdm-sharing-state failed with statuscode 400"); + } + + #endregion } diff --git a/tests/framework/Framework.Linq.Tests/NullOrSequenceEqualExtensionsTests.cs b/tests/framework/Framework.Linq.Tests/NullOrSequenceEqualExtensionsTests.cs new file mode 100644 index 0000000000..96c8e27d7a --- /dev/null +++ b/tests/framework/Framework.Linq.Tests/NullOrSequenceEqualExtensionsTests.cs @@ -0,0 +1,110 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 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.Diagnostics.CodeAnalysis; + +namespace Org.Eclipse.TractusX.Portal.Backend.Framework.Linq.Tests; + +public class NullOrSequenceEqualExtensionsTests +{ + [Theory] + [InlineData(new[] { "a", "b", "c" }, new[] { "c", "b", "a" }, true)] + [InlineData(null, new[] { "c", "b", "a" }, false)] + [InlineData(new[] { "a", "b", "c" }, null, false)] + [InlineData(null, null, true)] + [InlineData(new[] { "a", "b", "c" }, new[] { "a", "b", "c", "x" }, false)] + [InlineData(new[] { "a", "b", "c", "x" }, new[] { "a", "b", "c" }, false)] + public void NullOrContentEqual_ReturnsExpected(IEnumerable? first, IEnumerable? second, bool expected) + { + // Act + var result = first.NullOrContentEqual(second); + + // Assert + result.Should().Be(expected); + } + + private class TestComparer : IEqualityComparer + { + public bool Equals(string? x, string? y) => x == y; + public int GetHashCode([DisallowNull] string obj) => throw new NotImplementedException(); + } + + [Theory] + [InlineData(new[] { "a", "b", "c" }, new[] { "a", "b", "c" }, true)] + [InlineData(new[] { "a", "b", "c" }, new[] { "c", "b", "a" }, true)] + [InlineData(null, new[] { "c", "b", "a" }, false)] + [InlineData(new[] { "a", "b", "c" }, null, false)] + [InlineData(null, null, true)] + [InlineData(new[] { "a", "b", "c" }, new[] { "a", "b", "c", "x" }, false)] + [InlineData(new[] { "a", "b", "c", "x" }, new[] { "a", "b", "c" }, false)] + public void NullOrContentEqual_WithComparer_ReturnsExpected(IEnumerable? first, IEnumerable? second, bool expected) + { + // Act + var result = first.NullOrContentEqual(second, new TestComparer()); + + // Assert + result.Should().Be(expected); + } + + [Theory] + [InlineData(new[] { "a", "b", "c" }, new[] { "av", "bv", "cv" }, new[] { "a", "b", "c" }, new[] { "av", "bv", "cv" }, true)] + [InlineData(new[] { "a", "b", "c" }, new[] { "av", "bv", "cv" }, new[] { "c", "b", "a" }, new[] { "cv", "bv", "av" }, true)] + [InlineData(null, null, new[] { "c", "b", "a" }, new[] { "cv", "bv", "av" }, false)] + [InlineData(new[] { "a", "b", "c" }, new[] { "av", "bv", "cv" }, null, null, false)] + [InlineData(null, null, null, null, true)] + [InlineData(new[] { "a", "b", "c", "x" }, new[] { "av", "bv", "cv", "xv" }, new[] { "a", "b", "c" }, new[] { "av", "bv", "cv" }, false)] + [InlineData(new[] { "a", "b", "c" }, new[] { "av", "bv", "cv" }, new[] { "a", "b", "c", "x" }, new[] { "av", "bv", "cv", "xv" }, false)] + [InlineData(new[] { "a", "b", "x" }, new[] { "av", "bv", "cv" }, new[] { "a", "b", "c" }, new[] { "av", "bv", "cv" }, false)] + [InlineData(new[] { "a", "b", "c" }, new[] { "av", "bv", "xv" }, new[] { "a", "b", "c" }, new[] { "av", "bv", "cv" }, false)] + public void NullOrContentEqual_WithKeyValuePairs_ReturnsExpected(IEnumerable? first, IEnumerable firstValues, IEnumerable? second, IEnumerable secondValues, bool expected) + { + // Arrange + var firstItems = first?.Zip(firstValues, (x, y) => new KeyValuePair(x, y)); + var secondItems = second?.Zip(secondValues, (x, y) => new KeyValuePair(x, y)); + + // Act + var result = firstItems.NullOrContentEqual(secondItems); + + // Assert + result.Should().Be(expected); + } + + [Theory] + [InlineData(new[] { "a", "b", "c" }, new[] { "a1", "a2", "a3" }, new[] { "b1", "b2", "b3" }, new[] { "c1", "c2", "c3" }, new[] { "a", "b", "c" }, new[] { "a1", "a2", "a3" }, new[] { "b1", "b2", "b3" }, new[] { "c1", "c2", "c3" }, true)] + [InlineData(new[] { "c", "b", "a" }, new[] { "c1", "c2", "c3" }, new[] { "b1", "b2", "b3" }, new[] { "a1", "a2", "a3" }, new[] { "a", "b", "c" }, new[] { "a1", "a2", "a3" }, new[] { "b1", "b2", "b3" }, new[] { "c1", "c2", "c3" }, true)] + [InlineData(new[] { "a", "b", "c" }, new[] { "a3", "a2", "a1" }, new[] { "b1", "b2", "b3" }, new[] { "c1", "c2", "c3" }, new[] { "a", "b", "c" }, new[] { "a1", "a2", "a3" }, new[] { "b1", "b2", "b3" }, new[] { "c1", "c2", "c3" }, true)] + [InlineData(new[] { "a", "b", "x" }, new[] { "a1", "a2", "a3" }, new[] { "b1", "b2", "b3" }, new[] { "c1", "c2", "c3" }, new[] { "a", "b", "c" }, new[] { "a1", "a2", "a3" }, new[] { "b1", "b2", "b3" }, new[] { "c1", "c2", "c3" }, false)] + [InlineData(new[] { "a", "b", "c" }, new[] { "a1", "a2", "x3" }, new[] { "b1", "b2", "b3" }, new[] { "c1", "c2", "c3" }, new[] { "a", "b", "c" }, new[] { "a1", "a2", "a3" }, new[] { "b1", "b2", "b3" }, new[] { "c1", "c2", "c3" }, false)] + [InlineData(null, new string[] { }, new string[] { }, new string[] { }, new[] { "a", "b", "c" }, new[] { "a1", "a2", "a3" }, new[] { "b1", "b2", "b3" }, new[] { "c1", "c2", "c3" }, false)] + [InlineData(new[] { "a", "b", "c" }, new[] { "a1", "a2", "a3" }, new[] { "b1", "b2", "b3" }, new[] { "c1", "c2", "c3" }, null, new string[] { }, new string[] { }, new string[] { }, false)] + [InlineData(null, new string[] { }, new string[] { }, new string[] { }, null, new string[] { }, new string[] { }, new string[] { }, true)] + public void NullOrContentEqual_WithKeyValuePairEnumerables_ReturnsExpected(IEnumerable? first, IEnumerable firstFirstValues, IEnumerable firstSecondValues, IEnumerable firstThirdValues, IEnumerable? second, IEnumerable secondFirstValues, IEnumerable secondSecondValues, IEnumerable secondThirdValues, bool expected) + { + // Arrange + var firstItems = first?.Zip(new[] { firstFirstValues, firstSecondValues, firstThirdValues }, (x, y) => new KeyValuePair>(x, y)); + var secondItems = second?.Zip(new[] { secondFirstValues, secondSecondValues, secondThirdValues }, (x, y) => new KeyValuePair>(x, y)); + + // Act + var result = firstItems.NullOrContentEqual(secondItems); + + // Assert + result.Should().Be(expected); + } +} diff --git a/tests/framework/Framework.Models.Tests/HasNextEnumeratorExtensionsTests.cs b/tests/framework/Framework.Models.Tests/HasNextEnumeratorExtensionsTests.cs new file mode 100644 index 0000000000..4ab837fde4 --- /dev/null +++ b/tests/framework/Framework.Models.Tests/HasNextEnumeratorExtensionsTests.cs @@ -0,0 +1,92 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.Extensions; +using System.Collections.Immutable; + +namespace Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Tests; + +public class HasNextEnumeratorExtensionsTests +{ + private readonly IFixture _fixture; + + public HasNextEnumeratorExtensionsTests() + { + _fixture = new Fixture(); + } + + [Fact] + public void HasNextEnumerator_ReturnsExpected() + { + // Arrange + var data = _fixture.CreateMany(5).ToImmutableArray(); + var extra1 = _fixture.Create(); + var extra2 = _fixture.Create(); + var sut = data.AsFakeIEnumerable(out var enumerator).GetHasNextEnumerator(); + + IEnumerable<(string, bool)> Act(IHasNextEnumerator hasNextEnumerator) + { + while (hasNextEnumerator.HasNext) + { + yield return (hasNextEnumerator.Current, hasNextEnumerator.HasNext); + hasNextEnumerator.Advance(); + } + yield return (extra1, hasNextEnumerator.HasNext); + hasNextEnumerator.Advance(); + yield return (extra2, hasNextEnumerator.HasNext); + hasNextEnumerator.Dispose(); + } + + // Act + var result = Act(sut).ToList(); + + // Assert + var expected = data.Select(x => (x, true)).Append((extra1, false)).Append((extra2, false)); + result.Should().HaveSameCount(expected).And.ContainInOrder(expected); + A.CallTo(() => enumerator.MoveNext()).MustHaveHappened(7, Times.Exactly); + A.CallTo(() => enumerator.Current).MustHaveHappened(5, Times.Exactly); + A.CallTo(() => enumerator.Dispose()).MustHaveHappenedOnceExactly(); + } + + [Fact] + public void HasNextEnumerator_ThrowsExpected() + { + // Arrange + var data = _fixture.CreateMany(5).ToImmutableArray(); + var sut = data.AsFakeIEnumerable(out var enumerator).GetHasNextEnumerator(); + + static IEnumerable Act(IHasNextEnumerator hasNextEnumerator) + { + while (hasNextEnumerator.HasNext) + { + yield return hasNextEnumerator.Current; + hasNextEnumerator.Advance(); + } + yield return hasNextEnumerator.Current; + } + + // Act + var result = Assert.Throws(() => Act(sut).ToList()); + + // Assert + A.CallTo(() => enumerator.MoveNext()).MustHaveHappened(6, Times.Exactly); + A.CallTo(() => enumerator.Current).MustHaveHappened(6, Times.Exactly); + } +} diff --git a/tests/framework/Framework.PublicInfos.Tests/Framework.PublicInfos.Tests.csproj b/tests/framework/Framework.PublicInfos.Tests/Framework.PublicInfos.Tests.csproj new file mode 100644 index 0000000000..28f13d8965 --- /dev/null +++ b/tests/framework/Framework.PublicInfos.Tests/Framework.PublicInfos.Tests.csproj @@ -0,0 +1,49 @@ + + + + + Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos.Tests + Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos.Tests + net6.0 + enable + enable + false + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + diff --git a/tests/framework/Framework.PublicInfos.Tests/PublicInformationBusinessLogicTests.cs b/tests/framework/Framework.PublicInfos.Tests/PublicInformationBusinessLogicTests.cs new file mode 100644 index 0000000000..e33e22d816 --- /dev/null +++ b/tests/framework/Framework.PublicInfos.Tests/PublicInformationBusinessLogicTests.cs @@ -0,0 +1,159 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Routing; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.DBAccess.Repositories; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Identities; +using System.Reflection; + +namespace Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos.Tests; + +public class PublicInformationBusinessLogicTests +{ + private readonly Guid _participantCompany = Guid.NewGuid(); + private readonly Guid _appProviderCompany = Guid.NewGuid(); + private readonly IIdentityService _identityService; + private readonly IPublicInformationBusinessLogic _sut; + + public PublicInformationBusinessLogicTests() + { + _identityService = A.Fake(); + var companyRepository = A.Fake(); + var portalRepositories = A.Fake(); + + var actionDescriptorCollectionProvider = A.Fake(); + SetupActionDescriptorCollectionProvider(actionDescriptorCollectionProvider); + SetupCompanyRepository(companyRepository); + A.CallTo(() => portalRepositories.GetInstance()).Returns(companyRepository); + + _sut = new PublicInformationBusinessLogic(actionDescriptorCollectionProvider, portalRepositories, _identityService); + } + + [Fact] + public async Task GetPublicUrls_ForParticipant_ReturnsExpected() + { + // Arrange + A.CallTo(() => _identityService.IdentityData).Returns(new IdentityData("4C1A6851-D4E7-4E10-A011-3732CD045E8A", Guid.NewGuid(), IdentityTypeId.COMPANY_USER, _participantCompany)); + + // Act + var result = await _sut.GetPublicUrls().ConfigureAwait(false); + + // Assert + result.Should().HaveCount(1).And.Satisfy(x => x.HttpMethods == "GET" && x.Url == "all"); + } + + [Fact] + public async Task GetPublicUrls_ForAppProvider_ReturnsExpected() + { + // Arrange + A.CallTo(() => _identityService.IdentityData).Returns(new IdentityData("4C1A6851-D4E7-4E10-A011-3732CD045E8A", Guid.NewGuid(), IdentityTypeId.COMPANY_USER, _appProviderCompany)); + + // Act + var result = await _sut.GetPublicUrls().ConfigureAwait(false); + + // Assert + result.Should().HaveCount(3).And.Satisfy( + x => x.HttpMethods == "GET" && x.Url == "all", + x => x.HttpMethods == "GET" && x.Url == "participant", + x => x.HttpMethods == "POST" && x.Url == "participant" + ); + } + + #region Setup + + private void SetupCompanyRepository(ICompanyRepository companyRepository) + { + A.CallTo(() => companyRepository.GetOwnCompanyRolesAsync(_appProviderCompany)).Returns(new[] { CompanyRoleId.ACTIVE_PARTICIPANT, CompanyRoleId.APP_PROVIDER }.ToAsyncEnumerable()); + A.CallTo(() => companyRepository.GetOwnCompanyRolesAsync(_participantCompany)).Returns(new[] { CompanyRoleId.ACTIVE_PARTICIPANT }.ToAsyncEnumerable()); + } + + private static void SetupActionDescriptorCollectionProvider(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider) + { + var methods = new[] + { + nameof(TestController.OnlyAppProvider), + nameof(TestController.All), + nameof(TestController.OnlyAppProviderPost) + }; + + var actionDescriptors = typeof(TestController).GetMethods().Where(x => methods.Contains(x.Name)).Select(x => new ControllerActionDescriptor + { + MethodInfo = x, + ActionConstraints = new List + { + new HttpMethodActionConstraint(x.GetCustomAttribute()!.HttpMethods) + }, + AttributeRouteInfo = new AttributeRouteInfo + { + Template = x.GetCustomAttribute()!.Template + } + }).ToList(); + + A.CallTo(() => actionDescriptorCollectionProvider.ActionDescriptors).Returns(new ActionDescriptorCollection(actionDescriptors, 1)); + } + + #endregion +} + +[ApiController] +internal class TestController : ControllerBase +{ + [PublicUrl(CompanyRoleId.ACTIVE_PARTICIPANT, CompanyRoleId.APP_PROVIDER, CompanyRoleId.SERVICE_PROVIDER)] + [HttpGet] + [Route("all")] +#pragma warning disable CA1822 + public void All() +#pragma warning restore CA1822 + { + } + + [PublicUrl(CompanyRoleId.APP_PROVIDER)] + [HttpGet] + [Route("participant")] +#pragma warning disable CA1822 + public void OnlyAppProvider() +#pragma warning restore CA1822 + { + } + + [PublicUrl(CompanyRoleId.APP_PROVIDER)] + [HttpPost] + [Route("participant")] +#pragma warning disable CA1822 + public void OnlyAppProviderPost() +#pragma warning restore CA1822 + { + } + + [HttpGet] + [Route("none")] +#pragma warning disable CA1822 + public void NoRole() +#pragma warning restore CA1822 + { + } +} diff --git a/tests/framework/Framework.PublicInfos.Tests/Usings.cs b/tests/framework/Framework.PublicInfos.Tests/Usings.cs new file mode 100644 index 0000000000..d0c35ff438 --- /dev/null +++ b/tests/framework/Framework.PublicInfos.Tests/Usings.cs @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 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 + ********************************************************************************/ + +global using AutoFixture; +global using AutoFixture.AutoFakeItEasy; +global using FakeItEasy; +global using FluentAssertions; +global using Xunit; diff --git a/tests/keycloak/Keycloak.Seeding.Tests/Keycloak.Seeding.Tests.csproj b/tests/keycloak/Keycloak.Seeding.Tests/Keycloak.Seeding.Tests.csproj new file mode 100644 index 0000000000..4e6046340e --- /dev/null +++ b/tests/keycloak/Keycloak.Seeding.Tests/Keycloak.Seeding.Tests.csproj @@ -0,0 +1,53 @@ + + + + + Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Tests + Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Tests + net6.0 + enable + enable + false + + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + PreserveNewest + + + diff --git a/tests/keycloak/Keycloak.Seeding.Tests/KeycloakRealmModelTests.cs b/tests/keycloak/Keycloak.Seeding.Tests/KeycloakRealmModelTests.cs new file mode 100644 index 0000000000..cd580509fe --- /dev/null +++ b/tests/keycloak/Keycloak.Seeding.Tests/KeycloakRealmModelTests.cs @@ -0,0 +1,405 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Framework.Linq; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.BusinessLogic; +using Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Models; +namespace Org.Eclipse.TractusX.Portal.Backend.Keycloak.Seeding.Tests; + +public class KeycloakRealmModelTests +{ + [Fact] + public async Task SeedDataHandlerImportsExpected() + { + // Arrange + var sut = new SeedDataHandler(); + + // Act + await sut.Import("TestSeeds/test-realm.json", CancellationToken.None).ConfigureAwait(false); + + var keycloakRealm = sut.KeycloakRealm; + var clients = sut.Clients; + var clientRoles = sut.ClientRoles; + var realmRoles = sut.RealmRoles; + var identityProviders = sut.IdentityProviders; + var identityProviderMappers = sut.IdentityProviderMappers; + var users = sut.Users; + var authenticationFlows = sut.TopLevelCustomAuthenticationFlows; + var clientScopes = sut.ClientScopes; + + // Assert + keycloakRealm.Should().NotBeNull() + .And.Match(x => + x.Id == "TestRealmId" && + x.Realm == "TestRealm" && + x.DisplayName == "TestRealm Display Name" && + x.DisplayNameHtml == "TestRealm HTML Display Name" && + x.NotBefore == 0 && + x.DefaultSignatureAlgorithm == "RS256" && + x.RevokeRefreshToken == false && + x.RefreshTokenMaxReuse == 0 && + x.AccessTokenLifespan == 300 && + x.AccessTokenLifespanForImplicitFlow == 900 && + x.SsoSessionIdleTimeout == 1800 && + x.SsoSessionMaxLifespan == 36000 && + x.SsoSessionIdleTimeoutRememberMe == 0 && + x.SsoSessionMaxLifespanRememberMe == 0 && + x.OfflineSessionIdleTimeout == 2592000 && + x.OfflineSessionMaxLifespanEnabled == false && + x.OfflineSessionMaxLifespan == 5184000 && + x.ClientSessionIdleTimeout == 0 && + x.ClientSessionMaxLifespan == 0 && + x.ClientOfflineSessionIdleTimeout == 0 && + x.ClientOfflineSessionMaxLifespan == 0 && + x.AccessCodeLifespan == 60 && + x.AccessCodeLifespanUserAction == 300 && + x.AccessCodeLifespanLogin == 1800 && + x.ActionTokenGeneratedByAdminLifespan == 43200 && + x.ActionTokenGeneratedByUserLifespan == 300 && + x.Oauth2DeviceCodeLifespan == 600 && + x.Oauth2DevicePollingInterval == 5 && + x.Enabled == true && + x.SslRequired == "external" && + x.RegistrationAllowed == false && + x.RegistrationEmailAsUsername == false && + x.RememberMe == false && + x.VerifyEmail == false && + x.LoginWithEmailAllowed == true && + x.DuplicateEmailsAllowed == false && + x.ResetPasswordAllowed == false && + x.EditUsernameAllowed == false && + x.BruteForceProtected == false && + x.PermanentLockout == false && + x.MaxFailureWaitSeconds == 900 && + x.MinimumQuickLoginWaitSeconds == 60 && + x.WaitIncrementSeconds == 60 && + x.QuickLoginCheckMilliSeconds == 1000 && + x.MaxDeltaTimeSeconds == 43200 && + x.FailureFactor == 30 && + x.Roles != null && + x.Roles.Client != null && + x.Roles.Client == clientRoles && + x.Roles.Realm != null && + x.Roles.Realm == realmRoles && + x.Groups != null && + // roles and groups are being asserted separately + x.DefaultRole != null && + x.DefaultRole.Id == "fd20bacb-f39f-499c-8fc3-c3d14e0770d9" && + x.DefaultRole.Name == "default-roles-testrealm" && + x.DefaultRole.Description == "${role_default-roles}" && + x.DefaultRole.Composite == true && + x.DefaultRole.ClientRole == false && + x.DefaultRole.ContainerId == "TestRealm" && + x.RequiredCredentials != null && + x.RequiredCredentials.SequenceEqual(new[] { "password" }) && + x.OtpPolicyType == "totp" && + x.OtpPolicyAlgorithm == "HmacSHA1" && + x.OtpPolicyInitialCounter == 0 && + x.OtpPolicyDigits == 6 && + x.OtpPolicyLookAheadWindow == 1 && + x.OtpPolicyPeriod == 30 && + x.OtpSupportedApplications != null && + x.OtpSupportedApplications.SequenceEqual(new[] { "FreeOTP", "Google Authenticator" }) && + x.PasswordPolicy == "notUsername(undefined) and notEmail(undefined)" && + x.WebAuthnPolicyRpEntityName == "keycloak" && + x.WebAuthnPolicySignatureAlgorithms != null && + x.WebAuthnPolicySignatureAlgorithms.SequenceEqual(new[] { "ES256" }) && + x.WebAuthnPolicyRpId == "" && + x.WebAuthnPolicyAttestationConveyancePreference == "not specified" && + x.WebAuthnPolicyAuthenticatorAttachment == "not specified" && + x.WebAuthnPolicyRequireResidentKey == "not specified" && + x.WebAuthnPolicyUserVerificationRequirement == "not specified" && + x.WebAuthnPolicyCreateTimeout == 0 && + x.WebAuthnPolicyAvoidSameAuthenticatorRegister == false && + x.WebAuthnPolicyAcceptableAaguids != null && + x.WebAuthnPolicyAcceptableAaguids.SequenceEqual(Enumerable.Empty()) && + x.WebAuthnPolicyPasswordlessRpEntityName == "keycloak" && + x.WebAuthnPolicyPasswordlessSignatureAlgorithms != null && + x.WebAuthnPolicyPasswordlessSignatureAlgorithms.SequenceEqual(new[] { "ES256" }) && + x.WebAuthnPolicyPasswordlessRpId == "" && + x.WebAuthnPolicyPasswordlessAttestationConveyancePreference == "not specified" && + x.WebAuthnPolicyPasswordlessAuthenticatorAttachment == "not specified" && + x.WebAuthnPolicyPasswordlessRequireResidentKey == "not specified" && + x.WebAuthnPolicyPasswordlessUserVerificationRequirement == "not specified" && + x.WebAuthnPolicyPasswordlessCreateTimeout == 0 && + x.WebAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister == false && + x.WebAuthnPolicyPasswordlessAcceptableAaguids != null && + x.WebAuthnPolicyPasswordlessAcceptableAaguids.SequenceEqual(Enumerable.Empty()) && + x.Users != null && + x.ScopeMappings != null && + x.ClientScopeMappings != null && + x.Clients != null && + x.Clients == clients && + x.ClientScopes != null && + x.ClientScopes == clientScopes && + // users, scopeMappings, clientScopeMappings, clients, clientScopes are being asserted separately + x.DefaultDefaultClientScopes != null && + x.DefaultDefaultClientScopes.SequenceEqual(new[] { "role_list", "profile", "email", "roles", "web-origins" }) && + x.DefaultOptionalClientScopes != null && + x.DefaultOptionalClientScopes.SequenceEqual(new[] { "offline_access", "address", "phone", "microprofile-jwt" }) && + x.BrowserSecurityHeaders != null && + x.BrowserSecurityHeaders.ContentSecurityPolicyReportOnly == "" && + x.BrowserSecurityHeaders.XContentTypeOptions == "nosniff" && + x.BrowserSecurityHeaders.XRobotsTag == "none" && + x.BrowserSecurityHeaders.XFrameOptions == "SAMEORIGIN" && + x.BrowserSecurityHeaders.ContentSecurityPolicy == "frame-src 'self'; frame-ancestors 'self'; object-src 'none';" && + x.BrowserSecurityHeaders.XXSSProtection == "1; mode=block" && + x.BrowserSecurityHeaders.StrictTransportSecurity == "max-age=31536000; includeSubDomains" && + x.SmtpServer != null && + x.SmtpServer.ReplyToDisplayName == "Test Reply To Display Name" && + x.SmtpServer.Starttls == "true" && + x.SmtpServer.Auth == "true" && + x.SmtpServer.Ssl == "true" && + x.SmtpServer.EnvelopeFrom == "envelope-from@test.host" && + x.SmtpServer.Password == "**********" && + x.SmtpServer.Port == "25" && + x.SmtpServer.Host == "test.host" && + x.SmtpServer.ReplyTo == "reply-to@test.host" && + x.SmtpServer.From == "from@test.host" && + x.SmtpServer.FromDisplayName == "Test From Display Name" && + x.SmtpServer.User == "testSmtpLogin" && + x.LoginTheme == "base" && + x.AccountTheme == "base" && + x.AdminTheme == "base" && + x.EmailTheme == "base" && + x.EventsEnabled == false && + x.EventsListeners != null && + x.EventsListeners.SequenceEqual(new[] { "jboss-logging" }) && + x.EnabledEventTypes != null && + x.EnabledEventTypes.SequenceEqual(Enumerable.Empty()) && + x.AdminEventsEnabled == false && + x.AdminEventsDetailsEnabled == false && + x.IdentityProviders != null && + x.IdentityProviders == identityProviders && + x.IdentityProviderMappers != null && + x.IdentityProviderMappers == identityProviderMappers && + // identityProviders, identityProviderMappers, components are being asserted separately + x.InternationalizationEnabled == true && + x.SupportedLocales != null && + x.SupportedLocales.SequenceEqual(new[] { "de", "no", "ru", "sv", "pt-BR", "lt", "en", "it", "fr", "hu", "zh-CN", "es", "cs", "ja", "sk", "pl", "da", "ca", "nl", "tr" }) && + x.DefaultLocale == "en" && + // authenticationFlows, authenticatorConfigs, requiredActions are being asserted separately + x.BrowserFlow == "browser" && + x.RegistrationFlow == "registration" && + x.DirectGrantFlow == "direct grant" && + x.ResetCredentialsFlow == "reset credentials" && + x.ClientAuthenticationFlow == "clients" && + x.DockerAuthenticationFlow == "docker auth" && + x.Attributes != null && + x.Attributes.SequenceEqual(new Dictionary { + { "cibaBackchannelTokenDeliveryMode", "poll" }, + { "cibaAuthRequestedUserHint", "login_hint" }, + { "oauth2DevicePollingInterval", "5" }, + { "clientOfflineSessionMaxLifespan", "0" }, + { "clientSessionIdleTimeout", "0" }, + { "userProfileEnabled", "false" }, + { "clientOfflineSessionIdleTimeout", "0" }, + { "cibaInterval", "5" }, + { "cibaExpiresIn", "120" }, + { "oauth2DeviceCodeLifespan", "600" }, + { "parRequestUriLifespan", "60" }, + { "clientSessionMaxLifespan", "0" }, + { "frontendUrl", "http://frontend.url" } + }) && + x.KeycloakVersion == "16.1.1" && + x.UserManagedAccessAllowed == false + ); + + keycloakRealm.Groups.Should().ContainSingle() + .Which.Should().Match(x => + x.Id == "145bc75c-7755-4cd2-a746-45097fb2883a" && + x.Name == "Test Group 1" && + x.Path == "/Test Group 1" && + x.Attributes.NullOrContentEqual( + new Dictionary> + { + { "Test Group 1 Attribute", new [] { "Test Group 1 Attribute Value" } } + }, + null) && + x.RealmRoles != null && + x.RealmRoles.SequenceEqual( + new[] + { + "offline_access" + }) && + x.ClientRoles.NullOrContentEqual( + new Dictionary> + { + { "realm-management", new [] { "create-client" } } + }, + null) + ); + + realmRoles.Should().HaveCount(4).And.Satisfy( + x => + x.Id == "fd20bacb-f39f-499c-8fc3-c3d14e0770d9" && + x.Name == "default-roles-testrealm" && + x.Description == "${role_default-roles}" && + x.Composite == true && + x.Composites != null && + x.Composites.Realm != null && + x.Composites.Realm.SequenceEqual(new[] { "offline_access", "uma_authorization" }) && + x.Composites.Client != null && + x.Composites.Client.NullOrContentEqual( + new Dictionary> + { + { "account", new [] { "view-profile", "manage-account" } } + }, + null + ) && + x.ClientRole == false && + x.ContainerId == "TestRealm" && + x.Attributes != null && + !x.Attributes.Any(), + x => + x.Id == "e967f3b2-535a-4805-ac04-2b5004684b2f" && + x.Name == "offline_access" && + x.Description == "${role_offline-access}" && + x.Composite == false && + x.Composites == null && + x.ClientRole == false && + x.ContainerId == "TestRealm" && + x.Attributes != null && + !x.Attributes.Any(), + x => + x.Id == "a1960dd3-b079-4e7c-91e0-42a51bbb5e30", + x => + x.Id == "e9b8d11f-8e45-4910-8dbb-aa206764f1bc"); + + clientRoles.Should().HaveCount(8) + .And.ContainKeys(new[] { "realm-management", "security-admin-console", "admin-cli", "account-console", "broker", "TestClientId", "account", "TestServiceAccount1" }); + + clientRoles.Should().ContainKey("TestClientId") + .WhoseValue.Should().HaveCount(2) + .And.Satisfy( + x => + x.Id == "889fd981-c56f-4b46-bc43-f62e1004185e" && + x.Name == "Test Composite Role" && + x.Description == "Test Composite Role Description" && + x.Composite == true && + x.Composites != null && + x.Composites.Client.NullOrContentEqual( + new Dictionary> + { + { "TestClientId", new [] { "test_role_1" } } + }, + null) && + x.ClientRole == true && + x.ContainerId == "654052fa-59c4-484e-90f7-0c389c0e9d37" && + x.Attributes.NullOrContentEqual( + new Dictionary> + { + { "Test Composite Role Attribute", new [] { "Test Composite Role Attribute Value" } } + }, + null + ), + x => + x.Id == "a178ae7c-be38-44b6-8b9b-c90ccf9c7d51" && + x.Name == "test_role_1" && + x.Description == "Test Role 1 Description" && + x.Composite == false && + x.Composites == null && + x.ClientRole == true && + x.ContainerId == "654052fa-59c4-484e-90f7-0c389c0e9d37" && + x.Attributes.NullOrContentEqual( + new Dictionary> + { + { "test_role_1_attribute", new [] { "test_role_1_attribute_value" } } + }, + null + )); + + users.Should().HaveCount(2).And.Satisfy( + x => + x.Id == "345577d1-6232-4fac-ad44-6ef8e3924993" && + x.CreatedTimestamp == 1690020450507 && + x.Username == "service-account-testserviceaccount1" && + x.Enabled == true && + x.Totp == false && + x.EmailVerified == false && + x.ServiceAccountClientId == "TestServiceAccount1" && + x.DisableableCredentialTypes != null && + !x.DisableableCredentialTypes.Any() && + x.RequiredActions != null && + !x.RequiredActions.Any() && + x.RealmRoles != null && + x.RealmRoles.SequenceEqual(new[] { "default-roles-testrealm" }) && + x.NotBefore == 0 && + x.Groups != null && + !x.Groups.Any(), + x => + x.Id == "502dabcf-01c7-47d9-a88e-0be4279097b5" && + x.CreatedTimestamp == 1652788086549 && + x.Username == "testuser1" && + x.Enabled == true && + x.Totp == false && + x.EmailVerified == false && + x.FirstName == "Test" && + x.LastName == "User" && + x.Email == "test.user@mail.org" && + x.Attributes.NullOrContentEqual(new Dictionary> { { "foo", new[] { "DEADBEEF", "deadbeef" } } }, null) && + x.Credentials != null && + !x.Credentials.Any() && + x.DisableableCredentialTypes != null && + !x.DisableableCredentialTypes.Any() && + x.FederatedIdentities != null && + x.FederatedIdentities.Select(i => new ValueTuple(i.IdentityProvider, i.UserId, i.UserName)).NullOrContentEqual(new[] { new ValueTuple("Test Identity Provider", "testIdentityProviderUserId1", "testIdentityProviderUserName1") }, null) && + x.RealmRoles != null && + x.RealmRoles.SequenceEqual(new[] { "default-roles-testrealm", "test_realm_role_1" }) && + x.ClientRoles.NullOrContentEqual(new Dictionary> { { "TestClientId", new[] { "test_role_1" } } }, null) && + x.NotBefore == 0 && + x.Groups != null && + !x.Groups.Any()); + + keycloakRealm.RequiredActions.Should().HaveCount(7).And.Satisfy( + x => + x.Alias == "CONFIGURE_TOTP" && + x.Name == "Configure OTP" && + x.ProviderId == "CONFIGURE_TOTP" && + x.Enabled == true && + x.DefaultAction == false && + x.Priority == 10 && + x.Config != null, + x => + x.Alias == "terms_and_conditions", + x => + x.Alias == "UPDATE_PASSWORD", + x => + x.Alias == "UPDATE_PROFILE", + x => + x.Alias == "VERIFY_EMAIL", + x => + x.Alias == "delete_account", + x => + x.Alias == "update_user_locale"); + + keycloakRealm.ClientProfiles.Should().NotBeNull() + .And.Match( + x => + x.Profiles != null && + !x.Profiles.Any()); + + keycloakRealm.ClientPolicies.Should().NotBeNull() + .And.Match( + x => + x.Policies != null && + !x.Policies.Any()); + } +} diff --git a/tests/keycloak/Keycloak.Seeding.Tests/TestSeeds/test-realm.json b/tests/keycloak/Keycloak.Seeding.Tests/TestSeeds/test-realm.json new file mode 100644 index 0000000000..60c752d4ac --- /dev/null +++ b/tests/keycloak/Keycloak.Seeding.Tests/TestSeeds/test-realm.json @@ -0,0 +1,2628 @@ +{ + "id": "TestRealmId", + "realm": "TestRealm", + "displayName": "TestRealm Display Name", + "displayNameHtml": "TestRealm HTML Display Name", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "ssoSessionIdleTimeoutRememberMe": 0, + "ssoSessionMaxLifespanRememberMe": 0, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": false, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": false, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "fd20bacb-f39f-499c-8fc3-c3d14e0770d9", + "name": "default-roles-testrealm", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": [ + "offline_access", + "uma_authorization" + ], + "client": { + "account": [ + "view-profile", + "manage-account" + ] + } + }, + "clientRole": false, + "containerId": "TestRealm", + "attributes": {} + }, + { + "id": "e967f3b2-535a-4805-ac04-2b5004684b2f", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "TestRealm", + "attributes": {} + }, + { + "id": "a1960dd3-b079-4e7c-91e0-42a51bbb5e30", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "TestRealm", + "attributes": {} + }, + { + "id": "e9b8d11f-8e45-4910-8dbb-aa206764f1bc", + "name": "test_realm_role_1", + "description": "Test Realm Role 1 Description", + "composite": false, + "clientRole": false, + "containerId": "TestRealm", + "attributes": {} + } + ], + "client": { + "realm-management": [ + { + "id": "1f492b89-7169-4c05-a34c-70f83b54b497", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + }, + { + "id": "2022d50c-e683-4944-8c99-5d871ceadd2a", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + }, + { + "id": "f3815060-bed1-443d-8049-374bc1b824b7", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-users", + "query-groups" + ] + } + }, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + }, + { + "id": "3ce7ae6b-656e-497e-abc4-7cfcd720120c", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + }, + { + "id": "d98c4320-cba6-4303-9d79-0314c004a8b7", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + }, + { + "id": "8412e19e-1f78-4d54-b7dc-edd968c08bf5", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + }, + { + "id": "07c4eaaa-aebb-4dab-b323-194e576419eb", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + }, + { + "id": "6a2a1807-c040-4820-aefc-d118a50aa5f4", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + }, + { + "id": "03fceee7-5976-4740-8e0e-599e1f3effb6", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + }, + { + "id": "3a154067-bc58-4f56-b527-eb31c6f82630", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + }, + { + "id": "a75f5dad-0ed7-4b07-bc56-f2ccfc4fbf84", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + }, + { + "id": "e25f95d5-5e6f-4eaf-a2c0-c9506a1923be", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + }, + { + "id": "d7d26863-7ac1-4754-8fa1-8acff1d4db55", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + }, + { + "id": "a593a3af-2376-4054-91e5-1afad644f7eb", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + }, + { + "id": "d9ecc12a-f3b7-4d30-9fb0-86908c5de3ec", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "view-users", + "view-identity-providers", + "query-clients", + "view-realm", + "manage-authorization", + "view-events", + "query-realms", + "view-authorization", + "impersonation", + "create-client", + "manage-realm", + "manage-clients", + "manage-events", + "view-clients", + "manage-identity-providers", + "manage-users", + "query-users", + "query-groups" + ] + } + }, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + }, + { + "id": "7186f358-cedb-41b0-9622-f4de80d79c46", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + }, + { + "id": "ed9b9e4d-10f7-44c5-93f9-9b9190e2fca7", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + }, + { + "id": "4a8202cc-1fae-4b91-906c-4a0436d2ccae", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + }, + { + "id": "de5e4e76-47b8-41a8-849e-50867c6cbda2", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "87739941-332d-4641-a44c-08a3d88c7d33", + "attributes": {} + } + ], + "security-admin-console": [], + "admin-cli": [], + "account-console": [], + "broker": [ + { + "id": "3eace03c-6971-4d95-8071-5320754a2c39", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "de0cff26-0054-4391-a853-046124a4146e", + "attributes": {} + } + ], + "TestClientId": [ + { + "id": "889fd981-c56f-4b46-bc43-f62e1004185e", + "name": "Test Composite Role", + "description": "Test Composite Role Description", + "composite": true, + "composites": { + "client": { + "TestClientId": [ + "test_role_1" + ] + } + }, + "clientRole": true, + "containerId": "654052fa-59c4-484e-90f7-0c389c0e9d37", + "attributes": { + "Test Composite Role Attribute": [ + "Test Composite Role Attribute Value" + ] + } + }, + { + "id": "a178ae7c-be38-44b6-8b9b-c90ccf9c7d51", + "name": "test_role_1", + "description": "Test Role 1 Description", + "composite": false, + "clientRole": true, + "containerId": "654052fa-59c4-484e-90f7-0c389c0e9d37", + "attributes": { + "test_role_1_attribute": [ + "test_role_1_attribute_value" + ] + } + } + ], + "account": [ + { + "id": "4b93b855-2ee5-44ac-a6d5-ce93abe2cf7a", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "e98c40ad-b04f-47b5-b720-6202b1cbddd1", + "attributes": {} + }, + { + "id": "117746d5-e384-42c0-9eec-4d0b3d768cff", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "e98c40ad-b04f-47b5-b720-6202b1cbddd1", + "attributes": {} + }, + { + "id": "345999a2-4b40-48f2-84e8-9092ba34bc55", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "e98c40ad-b04f-47b5-b720-6202b1cbddd1", + "attributes": {} + }, + { + "id": "1314e7f0-5e1d-41c4-ab4f-0a1738b58068", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "e98c40ad-b04f-47b5-b720-6202b1cbddd1", + "attributes": {} + }, + { + "id": "3ff5a9fe-2c42-43a3-8554-df1092ab7870", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "e98c40ad-b04f-47b5-b720-6202b1cbddd1", + "attributes": {} + }, + { + "id": "1d93d79a-547a-4154-81a2-8e787887ad44", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "e98c40ad-b04f-47b5-b720-6202b1cbddd1", + "attributes": {} + }, + { + "id": "cce5c412-8448-4f49-9bb2-fb03ca1a80aa", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": [ + "manage-account-links" + ] + } + }, + "clientRole": true, + "containerId": "e98c40ad-b04f-47b5-b720-6202b1cbddd1", + "attributes": {} + } + ], + "TestServiceAccount1": [] + } + }, + "groups": [ + { + "id": "145bc75c-7755-4cd2-a746-45097fb2883a", + "name": "Test Group 1", + "path": "/Test Group 1", + "attributes": { + "Test Group 1 Attribute": [ + "Test Group 1 Attribute Value" + ] + }, + "realmRoles": [ + "offline_access" + ], + "clientRoles": { + "realm-management": [ + "create-client" + ] + }, + "subGroups": [] + } + ], + "defaultRole": { + "id": "fd20bacb-f39f-499c-8fc3-c3d14e0770d9", + "name": "default-roles-testrealm", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "TestRealm" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpSupportedApplications": [ + "FreeOTP", + "Google Authenticator" + ], + "passwordPolicy": "notUsername(undefined) and notEmail(undefined)", + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "users": [ + { + "id": "345577d1-6232-4fac-ad44-6ef8e3924993", + "createdTimestamp": 1690020450507, + "username": "service-account-testserviceaccount1", + "enabled": true, + "totp": false, + "emailVerified": false, + "serviceAccountClientId": "TestServiceAccount1", + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": [ + "default-roles-testrealm" + ], + "notBefore": 0, + "groups": [] + }, + { + "id" : "502dabcf-01c7-47d9-a88e-0be4279097b5", + "createdTimestamp" : 1652788086549, + "username" : "testuser1", + "enabled" : true, + "totp" : false, + "emailVerified" : false, + "firstName" : "Test", + "lastName" : "User", + "email" : "test.user@mail.org", + "attributes" : { + "foo" : [ "DEADBEEF", "deadbeef" ] + }, + "credentials" : [ ], + "disableableCredentialTypes" : [ ], + "requiredActions" : [ ], + "federatedIdentities" : [ { + "identityProvider" : "Test Identity Provider", + "userId" : "testIdentityProviderUserId1", + "userName" : "testIdentityProviderUserName1" + } ], + "realmRoles" : [ + "default-roles-testrealm", + "test_realm_role_1" + ], + "clientRoles" : { + "TestClientId": [ + "test_role_1" + ] + }, + "notBefore" : 0, + "groups" : [ ] + } + ], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account" + ] + } + ] + }, + "clients": [ + { + "id": "e98c40ad-b04f-47b5-b720-6202b1cbddd1", + "clientId": "account", + "name": "${client_account}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/TestRealm/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/TestRealm/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "e5b17125-e629-4159-9fa6-4e2f62560428", + "clientId": "account-console", + "name": "${client_account-console}", + "rootUrl": "${authBaseUrl}", + "baseUrl": "/realms/TestRealm/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/TestRealm/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "281f6d75-d44e-4434-92b3-734fdab7d825", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "896d03e6-338a-4666-af3a-579021d54e5c", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "de0cff26-0054-4391-a853-046124a4146e", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "87739941-332d-4641-a44c-08a3d88c7d33", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "a66fddb5-e65d-4f9c-9fc9-667d2ea0592d", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/TestRealm/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/TestRealm/console/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "355884f5-5b54-4821-85fe-ece076229650", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "654052fa-59c4-484e-90f7-0c389c0e9d37", + "clientId": "TestClientId", + "name": "TestClient Name", + "description": "TestClient Description", + "rootUrl": "https://testclient.url", + "adminUrl": "https://testclient.url", + "baseUrl": "https://base.url", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "https://testclient.url/*" + ], + "webOrigins": [ + "https://testclient.url" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.multivalued.roles": "false", + "saml.force.post.binding": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature.keyinfo.ext": "false", + "use.refresh.tokens": "true", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "backchannel.logout.url": "https://logout.url", + "client_credentials.use_refresh_token": "false", + "policyUri": "https://policy.url", + "saml.client.signature": "false", + "require.pushed.authorization.requests": "false", + "saml.assertion.signature": "false", + "id.token.as.detached.signature": "false", + "saml.encrypt": "false", + "login_theme": "base", + "logoUri": "https://logo.url", + "saml.server.signature": "false", + "exclude.session.state.from.auth.response": "false", + "saml.artifact.binding": "false", + "tosUri": "https://terms-of-service.url", + "saml_force_name_id_format": "false", + "tls.client.certificate.bound.access.tokens": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "cc0388f4-50c1-410e-b754-c590e877315f", + "clientId": "TestServiceAccount1", + "rootUrl": "https://testserviceaccount.url", + "adminUrl": "https://testserviceaccount.url", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "**********", + "redirectUris": [ + "https://testserviceaccount.url/*" + ], + "webOrigins": [ + "https://testserviceaccount.url" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "saml.assertion.signature": "false", + "id.token.as.detached.signature": "false", + "saml.multivalued.roles": "false", + "saml.force.post.binding": "false", + "saml.encrypt": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "saml.server.signature": "false", + "saml.server.signature.keyinfo.ext": "false", + "use.refresh.tokens": "true", + "exclude.session.state.from.auth.response": "false", + "oidc.ciba.grant.enabled": "false", + "saml.artifact.binding": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "false", + "saml_force_name_id_format": "false", + "saml.client.signature": "false", + "tls.client.certificate.bound.access.tokens": "false", + "require.pushed.authorization.requests": "false", + "saml.authnstatement": "false", + "display.on.consent.screen": "false", + "saml.onetimeuse.condition": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "5c38f7a1-c4da-4c5f-b8d7-e2fdd2248250", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "id": "17d7644f-f284-4a41-8301-b3726d9826d5", + "name": "TestServiceAccountMapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-hardcoded-role-mapper", + "consentRequired": false, + "config": { + "role": "TestClientId.Test Composite Role" + } + }, + { + "id": "a54c1b2b-8dba-4351-aa9e-89cbb0203f3a", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientId", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientId", + "jsonType.label": "String" + } + }, + { + "id": "da4d5925-4fe1-4933-8556-266dcab94193", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "31df3aac-4bad-49e9-b088-e9a00e9bff72", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "1767746c-ae41-42de-9bf8-99ff57c7a490", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${addressScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "02a55f03-dc9d-4fcb-abf9-89d871cf5ae4", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + }, + { + "id": "48a7ef22-05b9-41e6-b495-84e9fbb0696e", + "name": "Test_Client_Scope", + "description": "Test Client Scope Description", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "gui.order": "10", + "consent.screen.text": "Test Client Scope Consent Screen Text" + }, + "protocolMappers": [ + { + "id": "84614455-7442-434f-96bf-8481ae7f215b", + "name": "Test Client Scope Mapper", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "TestUserAttribute", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "TestUserAttributeClaim", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "8a5593eb-ae2d-433a-a00a-2e7aa7364f8c", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "true", + "consent.screen.text": "${rolesScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "14dd3314-a0c4-431b-9db3-096562d0441b", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "0187056f-db87-4041-86c6-b809fe96dbe2", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "1bc6d7a5-3daf-475e-ae2e-7681a920d1b9", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "478009d6-eeca-472d-97f6-955244edd1bb", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${emailScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "6d977e59-f029-44bc-b30c-b4b6cffb5690", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "819d30b3-cc6d-4a35-89c7-9be53a6acfed", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "b65c960e-f771-40be-a949-9258dc49a219", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "d00d372b-1b8a-425b-99ea-349d60f3e68d", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "false", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "f6e1e67f-7319-4d3d-9b72-751dea0e9b8b", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${phoneScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "7ff49850-dfa8-4a62-99b4-96b8cb50a56f", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean" + } + }, + { + "id": "67594b7e-1e7d-4ee1-b75a-2ad8dfafce8b", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "dd6b6dab-b697-441d-aeab-7010ec74c12a", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "ac4c0c8b-95e2-40ba-ad60-3209a0c319ba", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String" + } + }, + { + "id": "a0ce86ec-5e1e-41e5-9923-c2ef215a4415", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "multivalued": "true", + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String" + } + } + ] + }, + { + "id": "765f1a12-0a37-4719-9b50-28670e614ba0", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false", + "consent.screen.text": "" + }, + "protocolMappers": [ + { + "id": "1af3908c-d76b-4349-96a3-2f50045690af", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "ff8becf8-4d9e-4f2c-a1b9-81bc021e9c5e", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "true", + "consent.screen.text": "${profileScopeConsentText}" + }, + "protocolMappers": [ + { + "id": "ea66aa91-0424-4d6a-a810-6cc91c03fd33", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String" + } + }, + { + "id": "34d2d469-8fde-4440-9061-3acde41da4fb", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String" + } + }, + { + "id": "6724d118-ada3-4fb3-9eb5-b0700f6a084a", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String" + } + }, + { + "id": "a09f0982-e1f2-492f-8ca5-56a1f4b08bf2", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String" + } + }, + { + "id": "b44a93cd-adb9-4e20-a408-007e0210bddb", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String" + } + }, + { + "id": "3ad1f31a-5bd2-4131-818f-b2955fc7323d", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "String" + } + }, + { + "id": "a1875498-a1b9-4626-a041-0e949534b97f", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "64332625-9dce-4358-8345-d2672bdc9f32", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String" + } + }, + { + "id": "b593051e-c35f-4e9a-b8de-f0716c20f047", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String" + } + }, + { + "id": "d3fed22a-62cb-45a5-b1fa-d830d20a0bd2", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String" + } + }, + { + "id": "68818fbd-cf35-4a2b-bc68-bf390c90fec6", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String" + } + }, + { + "id": "5fcfea65-9a92-4dba-a044-0e31c6ba510d", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String" + } + }, + { + "id": "4f7f63f7-1fc2-40c4-b3f8-5fc86ea507b1", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String" + } + }, + { + "id": "2db4c9a9-0a17-40d3-b231-b4fc66c3a1ab", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "userinfo.token.claim": "true", + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": { + "replyToDisplayName": "Test Reply To Display Name", + "starttls": "true", + "auth": "true", + "ssl": "true", + "envelopeFrom": "envelope-from@test.host", + "password": "**********", + "port": "25", + "host": "test.host", + "replyTo": "reply-to@test.host", + "from": "from@test.host", + "fromDisplayName": "Test From Display Name", + "user": "testSmtpLogin" + }, + "loginTheme": "base", + "accountTheme": "base", + "adminTheme": "base", + "emailTheme": "base", + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "identityProviders": [ + { + "alias": "Test Identity Provider", + "displayName": "Test Identity Provider Display Name", + "internalId": "bc51ae11-d6e1-4ce0-907e-94f7fd9dc2bd", + "providerId": "oidc", + "enabled": true, + "updateProfileFirstLoginMode": "on", + "trustEmail": false, + "storeToken": false, + "addReadTokenRoleOnCreate": false, + "authenticateByDefault": false, + "linkOnly": false, + "firstBrokerLoginFlowAlias": "first broker login", + "config": { + "tokenUrl": "https://token.url", + "clientId": "TestIdentityProviderClientId", + "authorizationUrl": "https://authorization.url", + "clientAuthMethod": "client_secret_basic", + "syncMode": "IMPORT", + "clientAssertionSigningAlg": "RS256", + "clientSecret": "**********", + "issuer": "TestIdentityProviderIssuer", + "useJwksUrl": "true" + } + } + ], + "identityProviderMappers": [ + { + "id": "14f6555c-4822-4403-8a66-cc9a74298f54", + "name": "Test Identity Provider Mapper", + "identityProviderAlias": "Test Identity Provider", + "identityProviderMapper": "hardcoded-user-session-attribute-idp-mapper", + "config": { + "syncMode": "INHERIT", + "attribute.value": "Test Identity Provider User Session Attribute Value", + "attribute": "Test Identity Provider User Session Attribute" + } + } + ], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "e67d6a3c-37b4-430f-82c9-332438b0b3ff", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "977c3808-e043-4506-9a66-2508a311da61", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "fcaf187f-5225-4883-9771-7eb649ce87f1", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "0a03e8c1-9826-49b7-a609-55d089ff2306", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "01716316-219e-4021-9def-d5b98b50f2e7", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-full-name-mapper", + "saml-role-list-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-usermodel-property-mapper", + "saml-user-attribute-mapper", + "saml-user-property-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-address-mapper" + ] + } + }, + { + "id": "f0d0fe6d-02a1-463e-8158-2d9c57f841ac", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "c6440cd5-52a4-43f1-b029-74a0dc739cac", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-full-name-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-usermodel-property-mapper", + "saml-user-property-mapper", + "saml-user-attribute-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-role-list-mapper", + "oidc-address-mapper" + ] + } + }, + { + "id": "b9a5ee58-0022-4a08-8ed4-800def11cb61", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + } + ], + "org.keycloak.userprofile.UserProfileProvider": [ + { + "id": "d64f8d91-1ee5-4a96-b941-8d582c3a24fb", + "providerId": "declarative-user-profile", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "07bbc736-7a62-4e63-8ec0-599ed5a379c6", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "6e2bf391-0bc7-414c-87ff-6dd8c509f79a", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS256" + ] + } + }, + { + "id": "15ba9746-5ee5-4579-9f42-f187ab838316", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "1bd5473b-2890-465c-a50f-7d301db43595", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + } + ] + }, + "internationalizationEnabled": true, + "supportedLocales": [ + "de", + "no", + "ru", + "sv", + "pt-BR", + "lt", + "en", + "it", + "fr", + "hu", + "zh-CN", + "es", + "cs", + "ja", + "sk", + "pl", + "da", + "ca", + "nl", + "tr" + ], + "defaultLocale": "en", + "authenticationFlows": [ + { + "id": "81bd03c7-999b-4898-ac03-b88c5f0e4007", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "859a3cc6-56ec-49c7-a46f-a3cb2f8f6041", + "alias": "Authentication Options", + "description": "Authentication options.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "basic-auth", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "basic-auth-otp", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "c9b723e4-324b-4407-9020-188ba4753400", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "cecda6a1-f332-467c-be42-0723f139d5c1", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "4c3d1cda-9dc0-46c6-9095-4c2cb04fd9d8", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "8206e3c9-84a3-4bd0-93a5-5d248e4d92c2", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "Account verification options", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "96a70ee2-cab5-4cbc-892d-bd027465ad10", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "e2b95b79-ef96-4882-83f5-9b7509193265", + "alias": "Test Copy of browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": false, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorConfig": "Test Create Authenticator Config", + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 25, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "Test Copy of browser forms", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "2fed62ae-b8f7-455f-8307-608ac43a4a39", + "alias": "Test Copy of browser Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": false, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "42b20d07-84de-465d-844f-7966b6e855a4", + "alias": "Test Copy of browser forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": false, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "flowAlias": "Test Copy of browser Browser - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "b616beea-be82-42d5-ac8b-a42d486bbf2c", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "c229101b-4c57-4cf1-8bd1-0a95d7ca5b4a", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "17563373-64dc-46cc-b96d-e70de317ad73", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "forms", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "de560818-add0-412f-a9b9-227189ab949f", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "701265b9-fd4e-4011-9472-d07424ab147c", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "5819589c-87a8-4194-bc32-db29b98a6d3d", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "1a2825b2-05fc-41fd-9194-9eab8a91b13d", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "User creation or linking", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "d68251c7-185b-47fc-b969-65f27a428523", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "1dcdf402-d09b-4641-a947-2b520c227ac1", + "alias": "http challenge", + "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "no-cookie-redirect", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "flowAlias": "Authentication Options", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "12d7c15c-e52f-4b27-857b-346f61094ff5", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "flowAlias": "registration form", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "887737ac-8e52-4b64-9549-fd74ae51ef6f", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-profile-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "eebb540e-e696-4521-9dd4-6d8bbf808b9f", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "99d3a907-4450-4784-9a87-5e40d912b40a", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "36027236-3771-4f82-8254-920f06ddf040", + "alias": "Test Create Authenticator Config", + "config": { + "defaultProvider": "Test Identity Provider 1" + } + }, + { + "id": "3aeb9413-f2d0-4a00-818a-c49d61abce0c", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "3de25624-da46-4da8-a3e1-79add2c6243f", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "terms_and_conditions", + "name": "Terms and Conditions", + "providerId": "terms_and_conditions", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DevicePollingInterval": "5", + "clientOfflineSessionMaxLifespan": "0", + "clientSessionIdleTimeout": "0", + "userProfileEnabled": "false", + "clientOfflineSessionIdleTimeout": "0", + "cibaInterval": "5", + "cibaExpiresIn": "120", + "oauth2DeviceCodeLifespan": "600", + "parRequestUriLifespan": "60", + "clientSessionMaxLifespan": "0", + "frontendUrl": "http://frontend.url" + }, + "keycloakVersion": "16.1.1", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} \ No newline at end of file diff --git a/tests/keycloak/Keycloak.Seeding.Tests/Usings.cs b/tests/keycloak/Keycloak.Seeding.Tests/Usings.cs new file mode 100644 index 0000000000..d0c35ff438 --- /dev/null +++ b/tests/keycloak/Keycloak.Seeding.Tests/Usings.cs @@ -0,0 +1,25 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 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 + ********************************************************************************/ + +global using AutoFixture; +global using AutoFixture.AutoFakeItEasy; +global using FakeItEasy; +global using FluentAssertions; +global using Xunit; diff --git a/tests/maintenance/Maintenance.App.Tests/Setup/TestDbFixture.cs b/tests/maintenance/Maintenance.App.Tests/Setup/TestDbFixture.cs index b76c873770..47f3129075 100644 --- a/tests/maintenance/Maintenance.App.Tests/Setup/TestDbFixture.cs +++ b/tests/maintenance/Maintenance.App.Tests/Setup/TestDbFixture.cs @@ -25,7 +25,6 @@ using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.Migrations.Seeder; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities; using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared; -using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; using Xunit.Extensions.AssemblyFixture; [assembly: TestFramework(AssemblyFixtureFramework.TypeName, AssemblyFixtureFramework.AssemblyName)] @@ -33,22 +32,17 @@ namespace Org.Eclipse.TractusX.Portal.Backend.PortalBackend.Maintenance.App.Test public class TestDbFixture : IAsyncLifetime { - public readonly PostgreSqlTestcontainer _container; - - public TestDbFixture() - { - _container = new TestcontainersBuilder() - .WithDatabase(new PostgreSqlTestcontainerConfiguration - { - Database = "test_db", - Username = "postgres", - Password = "postgres", - }) - .WithImage("postgres") - .WithCleanUp(true) - .WithName(Guid.NewGuid().ToString()) - .Build(); - } + public readonly PostgreSqlTestcontainer _container = new TestcontainersBuilder() + .WithDatabase(new PostgreSqlTestcontainerConfiguration + { + Database = "test_db", + Username = "postgres", + Password = "postgres", + }) + .WithImage("postgres") + .WithCleanUp(true) + .WithName(Guid.NewGuid().ToString()) + .Build(); /// /// Foreach test a new portalDbContext will be created and filled with the custom seeding data. @@ -96,7 +90,6 @@ await _container.StartAsync() ); var context = new PortalDbContext(optionsBuilder.Options, new FakeIdentityService()); await context.Database.MigrateAsync(); - BaseSeed.SeedBasedata().Invoke(context); await context.SaveChangesAsync(); } diff --git a/tests/marketplace/Apps.Service.Tests/Apps.Service.Tests.csproj b/tests/marketplace/Apps.Service.Tests/Apps.Service.Tests.csproj index 8331dd5763..7aa6e8ca04 100644 --- a/tests/marketplace/Apps.Service.Tests/Apps.Service.Tests.csproj +++ b/tests/marketplace/Apps.Service.Tests/Apps.Service.Tests.csproj @@ -22,6 +22,7 @@ Org.Eclipse.TractusX.Portal.Backend.Apps.Service.Tests + Org.Eclipse.TractusX.Portal.Backend.Apps.Service.Tests net6.0 enable enable @@ -52,4 +53,9 @@ + + + Always + + diff --git a/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppReleaseBusinessLogicTest.cs b/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppReleaseBusinessLogicTest.cs index ed26b16f94..e6fd5b98f9 100644 --- a/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppReleaseBusinessLogicTest.cs +++ b/tests/marketplace/Apps.Service.Tests/BusinessLogic/AppReleaseBusinessLogicTest.cs @@ -896,7 +896,7 @@ public async Task SetInstanceType_WithWrongOfferState_ThrowsConflictException() // Assert var ex = await Assert.ThrowsAsync(Act); - ex.Message.Should().Be($"App {appId} is not in Status {OfferStatusId.CREATED} or {OfferStatusId.IN_REVIEW}"); + ex.Message.Should().Be($"App {appId} is not in Status {OfferStatusId.CREATED}"); } [Fact] diff --git a/tests/marketplace/Apps.Service.Tests/Controllers/AppReleaseProcessControllerTest.cs b/tests/marketplace/Apps.Service.Tests/Controllers/AppReleaseProcessControllerTest.cs index 9fb94fa7ea..74e4693b56 100644 --- a/tests/marketplace/Apps.Service.Tests/Controllers/AppReleaseProcessControllerTest.cs +++ b/tests/marketplace/Apps.Service.Tests/Controllers/AppReleaseProcessControllerTest.cs @@ -51,28 +51,6 @@ public AppReleaseProcessControllerTest() _controller.AddControllerContextWithClaim(IamUserId, _identity); } - [Fact] - public async Task UpdateApp_ReturnsNoContent() - { - // Arrange - var appId = new Guid("5cf74ef8-e0b7-4984-a872-474828beb5d2"); - var data = new AppEditableDetail( - new LocalizedDescription[] - { - new("en", "This is a long description", "description") - }, - "https://test.provider.com", - null, - null); - - // Act - var result = await this._controller.UpdateApp(appId, data).ConfigureAwait(false); - - // Assert - Assert.IsType(result); - A.CallTo(() => _logic.UpdateAppAsync(appId, data, _identity.CompanyId)).MustHaveHappenedOnceExactly(); - } - [Fact] public async Task UpdateAppDocument_ReturnsExpectedResult() { diff --git a/tests/marketplace/Apps.Service.Tests/IntegrationTests/PublicUrlActiveParticipantTests.cs b/tests/marketplace/Apps.Service.Tests/IntegrationTests/PublicUrlActiveParticipantTests.cs new file mode 100644 index 0000000000..105f8bac6d --- /dev/null +++ b/tests/marketplace/Apps.Service.Tests/IntegrationTests/PublicUrlActiveParticipantTests.cs @@ -0,0 +1,42 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Apps.Service.Controllers; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.IntegrationTests; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; +using System.Diagnostics.CodeAnalysis; +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] +namespace Org.Eclipse.TractusX.Portal.Backend.Apps.Service.Tests.IntegrationTests; + +public class PublicUrlActiveParticipantTests : BasePublicUrlTests +{ + public PublicUrlActiveParticipantTests(IntegrationTestFactory factory) + : base(factory) + { } + + [Fact] + [SuppressMessage("SonarLint", "S2699", Justification = "Ignored because the assert is done in OpenInformationController_ReturnsCorrectAmount")] + public async Task OpenInformationController_WithActiveParticipant_ReturnsCorrectAmount() + { + await OpenInformationController_ReturnsCorrectAmount(0).ConfigureAwait(false); + } +} diff --git a/tests/marketplace/Apps.Service.Tests/IntegrationTests/PublicUrlAppProviderTests.cs b/tests/marketplace/Apps.Service.Tests/IntegrationTests/PublicUrlAppProviderTests.cs new file mode 100644 index 0000000000..4239e70150 --- /dev/null +++ b/tests/marketplace/Apps.Service.Tests/IntegrationTests/PublicUrlAppProviderTests.cs @@ -0,0 +1,44 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Apps.Service.Controllers; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.IntegrationTests; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; +using System.Diagnostics.CodeAnalysis; +using Xunit; + +namespace Org.Eclipse.TractusX.Portal.Backend.Apps.Service.Tests.IntegrationTests; + +public class PublicUrlAppProviderTests : BasePublicUrlTests +{ + public PublicUrlAppProviderTests(IntegrationTestFactory factory) + : base(factory) + { } + + [Fact] + [SuppressMessage("SonarLint", "S2699", Justification = "Ignored because the assert is done in OpenInformationController_ReturnsCorrectAmount")] + public async Task OpenInformationController_WithAppProvider_ReturnsCorrectAmount() + { + await OpenInformationController_ReturnsCorrectAmount(2, + x => x.HttpMethods == "POST" && x.Url == "api/apps/start-autosetup", + x => x.HttpMethods == "GET" && x.Url == "api/apps/{appid}/subscription/{subscriptionid}/provider") + .ConfigureAwait(false); + } +} diff --git a/tests/marketplace/Apps.Service.Tests/IntegrationTests/PublicUrlServiceProviderTests.cs b/tests/marketplace/Apps.Service.Tests/IntegrationTests/PublicUrlServiceProviderTests.cs new file mode 100644 index 0000000000..bddb29a73a --- /dev/null +++ b/tests/marketplace/Apps.Service.Tests/IntegrationTests/PublicUrlServiceProviderTests.cs @@ -0,0 +1,42 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Apps.Service.Controllers; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.IntegrationTests; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; +using System.Diagnostics.CodeAnalysis; +using Xunit; + +namespace Org.Eclipse.TractusX.Portal.Backend.Apps.Service.Tests.IntegrationTests; + +public class PublicUrlServiceProviderTests : BasePublicUrlTests +{ + public PublicUrlServiceProviderTests(IntegrationTestFactory factory) + : base(factory) + { } + + [Fact] + [SuppressMessage("SonarLint", "S2699", Justification = "Ignored because the assert is done in OpenInformationController_ReturnsCorrectAmount")] + public async Task OpenInformationController_WithServiceProvider_ReturnsCorrectAmount() + { + await OpenInformationController_ReturnsCorrectAmount(0) + .ConfigureAwait(false); + } +} diff --git a/tests/marketplace/Apps.Service.Tests/appsettings.IntegrationTests.json b/tests/marketplace/Apps.Service.Tests/appsettings.IntegrationTests.json new file mode 100644 index 0000000000..e65e979862 --- /dev/null +++ b/tests/marketplace/Apps.Service.Tests/appsettings.IntegrationTests.json @@ -0,0 +1,191 @@ +{ + "HealthChecks": [], + "Cors": { + "AllowedOrigins": [] + }, + "Keycloak": { + "central": { + "ConnectionString": "", + "ClientId": "", + "ClientSecret": "", + "AuthRealm": "" + }, + "shared": { + "ConnectionString": "", + "ClientId": "", + "ClientSecret": "", + "AuthRealm": "" + } + }, + "JwtBearerOptions": { + "RequireHttpsMetadata": true, + "MetadataAddress": "", + "SaveToken": true, + "TokenValidationParameters": { + "ValidateIssuer": true, + "ValidIssuer": "", + "ValidateIssuerSigningKey": true, + "ValidAudience": "", + "ValidateAudience": true, + "ValidateLifetime": true, + "ClockSkew": 600000 + } + }, + "ConnectionStrings": { + "PortalDb": "justaplaceholder", + "ProvisioningDB": "justaplaceholder" + }, + "AppMarketPlace": { + "CatenaAdminRoles": [ + { + "ClientId": "Cl2-CX-Portal", + "UserRoleNames": [ + "CX Admin" + ] + } + ], + "SubmitAppNotificationTypeIds": [ + "APP_RELEASE_REQUEST" + ], + "BasePortalAddress": "http://localhost:3000", + "AppOverviewAddress": "http://localhost:3000/appoverview", + "SalesManagerRoles": [ + { + "ClientId": "Cl2-CX-Portal", + "UserRoleNames": [ + "Sales Manager" + ] + } + ], + "ServiceManagerRoles": [ + { + "ClientId": "Cl2-CX-Portal", + "UserRoleNames": [ + "App Manager" + ] + } + ], + "OfferStatusIds": [ + "IN_REVIEW", + "ACTIVE" + ], + "ActiveAppCompanyAdminRoles": [ + { + "ClientId": "Cl2-CX-Portal", + "UserRoleNames": [ + "IT Admin", + "Company Admin" + ] + } + ], + "ActiveAppNotificationTypeIds": [ + "APP_ROLE_ADDED" + ], + "ApproveAppNotificationTypeIds": [ + "APP_RELEASE_APPROVAL" + ], + "ApproveAppUserRoles": [ + { + "ClientId": "Cl2-CX-Portal", + "UserRoleNames": [ + "Sales Manager", + "Service Manager" + ] + } + ], + "ApplicationsMaxPageSize": 20, + "AppImageDocumentTypeIds": [ + "APP_LEADIMAGE", + "APP_IMAGE", + "APP_CONTRACT", + "ADDITIONAL_DETAILS", + "APP_TECHNICAL_INFORMATION", + "CONFORMITY_APPROVAL_BUSINESS_APPS" + ], + "ITAdminRoles": [ + { + "ClientId": "Cl2-CX-Portal", + "UserRoleNames": [ + "IT Admin" + ] + } + ], + "UserManagementAddress": "http://localhost:3000/usermanagement", + "DeleteDocumentTypeIds": [ + "APP_CONTRACT", + "ADDITIONAL_DETAILS", + "APP_TECHNICAL_INFORMATION", + "APP_LEADIMAGE", + "APP_IMAGE", + "CONFORMITY_APPROVAL_BUSINESS_APPS" + ], + "SubmitAppDocumentTypeIds": [ + "APP_LEADIMAGE", + "APP_IMAGE", + "CONFORMITY_APPROVAL_BUSINESS_APPS" + ], + "UploadAppDocumentTypeIds": [ + { + "DocumentTypeId": "APP_TECHNICAL_INFORMATION", + "MediaTypes": [ + "PDF" + ] + }, + { + "DocumentTypeId": "APP_LEADIMAGE", + "MediaTypes": [ + "PDF" + ] + }, + { + "DocumentTypeId": "APP_IMAGE", + "MediaTypes": [ + "PDF" + ] + }, + { + "DocumentTypeId": "APP_CONTRACT", + "MediaTypes": [ + "JPEG", + "PNG", + "SVG" + ] + }, + { + "DocumentTypeId": "ADDITIONAL_DETAILS", + "MediaTypes": [ + "JPEG", + "PNG", + "SVG" + ] + }, + { + "DocumentTypeId": "CONFORMITY_APPROVAL_BUSINESS_APPS", + "MediaTypes": [ + "PDF" + ] + } + ], + "TechnicalUserProfileClient": "technical_roles_management", + "CompanyAdminRoles": [ + { + "ClientId": "Cl2-CX-Portal", + "UserRoleNames": [ + "Company Admin" + ] + } + ] + }, + "Provisioning": { + "CentralRealm": "CX-Central", + "CentralRealmId": "CX-Central" + }, + "MailingService": { + "Mail": { + "SmtpHost": "test", + "SmtpPort": 587, + "SmtpUser": "test", + "SmtpPassword": "test" + } + } +} diff --git a/tests/marketplace/Offers.Library.Tests/Service/OfferSetupServiceTests.cs b/tests/marketplace/Offers.Library.Tests/Service/OfferSetupServiceTests.cs index 79459bda3d..fa27a5eeb6 100644 --- a/tests/marketplace/Offers.Library.Tests/Service/OfferSetupServiceTests.cs +++ b/tests/marketplace/Offers.Library.Tests/Service/OfferSetupServiceTests.cs @@ -18,6 +18,7 @@ * SPDX-License-Identifier: Apache-2.0 ********************************************************************************/ +using Microsoft.Extensions.Logging; using Org.Eclipse.TractusX.Portal.Backend.Framework.ErrorHandling; using Org.Eclipse.TractusX.Portal.Backend.Framework.Models.Configuration; using Org.Eclipse.TractusX.Portal.Backend.Mailing.SendMail; @@ -105,7 +106,7 @@ public OfferSetupServiceTests() A.CallTo(() => _portalRepositories.GetInstance()).Returns(_offerRepository); A.CallTo(() => _portalRepositories.GetInstance()).Returns(_userRolesRepository); - _sut = new OfferSetupService(_portalRepositories, _provisioningManager, _serviceAccountCreation, _notificationService, _offerSubscriptionProcessService, _mailingService, _technicalUserProfileService); + _sut = new OfferSetupService(_portalRepositories, _provisioningManager, _serviceAccountCreation, _notificationService, _offerSubscriptionProcessService, _mailingService, _technicalUserProfileService, A.Fake>()); } #region AutoSetupServiceAsync diff --git a/tests/marketplace/Services.Service.Tests/IntegrationTests/PublicUrlActiveParticipantTests.cs b/tests/marketplace/Services.Service.Tests/IntegrationTests/PublicUrlActiveParticipantTests.cs new file mode 100644 index 0000000000..6512b19226 --- /dev/null +++ b/tests/marketplace/Services.Service.Tests/IntegrationTests/PublicUrlActiveParticipantTests.cs @@ -0,0 +1,43 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Services.Service.Controllers; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.IntegrationTests; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; +using System.Diagnostics.CodeAnalysis; +using Xunit; + +[assembly: CollectionBehavior(DisableTestParallelization = true)] +namespace Org.Eclipse.TractusX.Portal.Backend.Services.Service.Tests.IntegrationTests; + +public class PublicUrlActiveParticipantTests : BasePublicUrlTests +{ + public PublicUrlActiveParticipantTests(IntegrationTestFactory factory) + : base(factory) + { } + + [Fact] + [SuppressMessage("SonarLint", "S2699", Justification = "Ignored because the assert is done in OpenInformationController_ReturnsCorrectAmount")] + public async Task OpenInformationController_WithActiveParticipant_ReturnsCorrectAmount() + { + await OpenInformationController_ReturnsCorrectAmount(0) + .ConfigureAwait(false); + } +} diff --git a/tests/marketplace/Services.Service.Tests/IntegrationTests/PublicUrlAppProviderTests.cs b/tests/marketplace/Services.Service.Tests/IntegrationTests/PublicUrlAppProviderTests.cs new file mode 100644 index 0000000000..e9965431b4 --- /dev/null +++ b/tests/marketplace/Services.Service.Tests/IntegrationTests/PublicUrlAppProviderTests.cs @@ -0,0 +1,42 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Services.Service.Controllers; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.IntegrationTests; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; +using System.Diagnostics.CodeAnalysis; +using Xunit; + +namespace Org.Eclipse.TractusX.Portal.Backend.Services.Service.Tests.IntegrationTests; + +public class PublicUrlAppProviderTests : BasePublicUrlTests +{ + public PublicUrlAppProviderTests(IntegrationTestFactory factory) + : base(factory) + { } + + [Fact] + [SuppressMessage("SonarLint", "S2699", Justification = "Ignored because the assert is done in OpenInformationController_ReturnsCorrectAmount")] + public async Task OpenInformationController_WithAppProvider_ReturnsCorrectAmount() + { + await OpenInformationController_ReturnsCorrectAmount(0) + .ConfigureAwait(false); + } +} diff --git a/tests/marketplace/Services.Service.Tests/IntegrationTests/PublicUrlServiceProviderTests.cs b/tests/marketplace/Services.Service.Tests/IntegrationTests/PublicUrlServiceProviderTests.cs new file mode 100644 index 0000000000..e37f90f796 --- /dev/null +++ b/tests/marketplace/Services.Service.Tests/IntegrationTests/PublicUrlServiceProviderTests.cs @@ -0,0 +1,43 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Services.Service.Controllers; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.IntegrationTests; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; +using System.Diagnostics.CodeAnalysis; +using Xunit; + +namespace Org.Eclipse.TractusX.Portal.Backend.Services.Service.Tests.IntegrationTests; + +public class PublicUrlServiceProviderTests : BasePublicUrlTests +{ + public PublicUrlServiceProviderTests(IntegrationTestFactory factory) + : base(factory) + { } + + [Fact] + [SuppressMessage("SonarLint", "S2699", Justification = "Ignored because the assert is done in OpenInformationController_ReturnsCorrectAmount")] + public async Task OpenInformationController_WithServiceProvider_ReturnsCorrectAmount() + { + await OpenInformationController_ReturnsCorrectAmount(1, + x => x.HttpMethods == "GET" && x.Url == "api/services/{serviceid}/subscription/{subscriptionid}/provider") + .ConfigureAwait(false); + } +} diff --git a/tests/marketplace/Services.Service.Tests/Services.Service.Tests.csproj b/tests/marketplace/Services.Service.Tests/Services.Service.Tests.csproj index 5f59583de6..a2aa9660ba 100644 --- a/tests/marketplace/Services.Service.Tests/Services.Service.Tests.csproj +++ b/tests/marketplace/Services.Service.Tests/Services.Service.Tests.csproj @@ -33,7 +33,6 @@ - @@ -52,4 +51,9 @@ + + + Always + + diff --git a/tests/marketplace/Services.Service.Tests/appsettings.IntegrationTests.json b/tests/marketplace/Services.Service.Tests/appsettings.IntegrationTests.json new file mode 100644 index 0000000000..ccba9abb4a --- /dev/null +++ b/tests/marketplace/Services.Service.Tests/appsettings.IntegrationTests.json @@ -0,0 +1,141 @@ +{ + "HealthChecks": [], + "Cors": { + "AllowedOrigins": [] + }, + "Keycloak": { + "central": { + "ConnectionString": "", + "ClientId": "", + "ClientSecret": "", + "AuthRealm": "" + }, + "shared": { + "ConnectionString": "", + "ClientId": "", + "ClientSecret": "", + "AuthRealm": "" + } + }, + "JwtBearerOptions": { + "RequireHttpsMetadata": true, + "MetadataAddress": "", + "SaveToken": true, + "TokenValidationParameters": { + "ValidateIssuer": true, + "ValidIssuer": "", + "ValidateIssuerSigningKey": true, + "ValidAudience": "", + "ValidateAudience": true, + "ValidateLifetime": true, + "ClockSkew": 600000 + } + }, + "ConnectionStrings": { + "PortalDb": "justaplaceholder", + "ProvisioningDB": "justaplaceholder" + }, + "Provisioning": { + "CentralRealm": "CX-Central", + "CentralRealmId": "CX-Central" + }, + "Services":{ + "BasePortalAddress": "http://localhost:3000", + "UserManagementAddress": "http://localhost:3000/usermanagement", + "ServiceMarketplaceAddress": "http://localhost:3000/servicemarketplace", + "CatenaAdminRoles": { + "Cl2-CX-Portal": [ + "CX Admin" + ] + }, + "CompanyAdminRoles": { + "Cl2-CX-Portal": [ + "IT Admin" + ] + }, + "ServiceAccountRoles": { + "technical_roles_management": [ + "Digital Twin Management" + ] + }, + "ServiceManagerRoles": { + "Cl2-CX-Portal": [ + "Service Manager" + ] + }, + "SalesManagerRoles": { + "Cl2-CX-Portal": [ + "Sales Manager" + ] + }, + "DocumentTypeIds": [ + "ADDITIONAL_DETAILS" + ], + "ContentTypeSettings": [ + "application/pdf" + ], + "SubmitServiceNotificationTypeIds": [ + "SERVICE_RELEASE_REQUEST" + ], + "ApproveServiceNotificationTypeIds": [ + "SERVICE_RELEASE_APPROVAL" + ], + "ApproveServiceUserRoles": { + "Cl2-CX-Portal": [ + "Sales Manager", + "Service Manager" + ] + }, + "ITAdminRoles": { + "Cl2-CX-Portal": [ + "IT Admin" + ] + }, + "TechnicalUserProfileClient": "technical_roles_management", + "ServiceImageDocumentTypeIds": [ + "SERVICE_LEADIMAGE" + ], + "DeleteDocumentTypeIds": [ + "APP_CONTRACT", + "DATA_CONTRACT", + "ADDITIONAL_DETAILS", + "APP_LEADIMAGE", + "APP_IMAGE" + ], + "UploadServiceDocumentTypeIds": { + "APP_CONTRACT": [ + "application/pdf" + ], + "ADDITIONAL_DETAILS": [ + "application/pdf" + ], + "APP_TECHNICAL_INFORMATION": [ + "application/pdf" + ], + "APP_LEADIMAGE": [ + "image/jpeg", + "image/png", + "image/svg+xml" + ], + "APP_IMAGE": [ + "image/jpeg", + "image/png", + "image/svg+xml" + ], + "CONFORMITY_APPROVAL_BUSINESS_APPS": [ + "application/pdf" + ] + }, + "OfferStatusIds": [ + "IN_REVIEW" + ] + }, + "MailingService": { + "Mail": { + "SmtpHost": "test", + "SmtpPort": 587, + "SmtpUser": "test", + "SmtpPassword": "test" + } + } +} diff --git a/tests/notifications/Notifications.Service.Tests/IntegrationTests/NotificationControllerIntegrationTests.cs b/tests/notifications/Notifications.Service.Tests/IntegrationTests/NotificationControllerIntegrationTests.cs index 3effdf637a..841c7dca08 100644 --- a/tests/notifications/Notifications.Service.Tests/IntegrationTests/NotificationControllerIntegrationTests.cs +++ b/tests/notifications/Notifications.Service.Tests/IntegrationTests/NotificationControllerIntegrationTests.cs @@ -28,11 +28,11 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Notifications.Service.Tests.IntegrationTests; -public class NotificationControllerIntegrationTests : IClassFixture> +public class NotificationControllerIntegrationTests : IClassFixture> { - private readonly IntegrationTestFactory _factory; + private readonly IntegrationTestFactory _factory; - public NotificationControllerIntegrationTests(IntegrationTestFactory factory) + public NotificationControllerIntegrationTests(IntegrationTestFactory factory) { _factory = factory; } diff --git a/tests/notifications/Notifications.Service.Tests/IntegrationTests/Seeding.cs b/tests/notifications/Notifications.Service.Tests/IntegrationTests/Seeding.cs new file mode 100644 index 0000000000..960d3ac1b3 --- /dev/null +++ b/tests/notifications/Notifications.Service.Tests/IntegrationTests/Seeding.cs @@ -0,0 +1,68 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; + +namespace Org.Eclipse.TractusX.Portal.Backend.Notifications.Service.Tests; + +public class Seeding : IBaseSeeding +{ + public Action SeedData() => dbContext => + { + BaseSeed.SeedBaseData().Invoke(dbContext); + + dbContext.NotificationTypeAssignedTopics.AddRange(new List() + { + new(NotificationTypeId.INFO, NotificationTopicId.INFO), + new(NotificationTypeId.TECHNICAL_USER_CREATION, NotificationTopicId.INFO), + new(NotificationTypeId.CONNECTOR_REGISTERED, NotificationTopicId.INFO), + new(NotificationTypeId.WELCOME_SERVICE_PROVIDER, NotificationTopicId.INFO), + new(NotificationTypeId.WELCOME_CONNECTOR_REGISTRATION, NotificationTopicId.INFO), + new(NotificationTypeId.WELCOME, NotificationTopicId.INFO), + new(NotificationTypeId.WELCOME_USE_CASES, NotificationTopicId.INFO), + new(NotificationTypeId.WELCOME_APP_MARKETPLACE, NotificationTopicId.INFO), + new(NotificationTypeId.ACTION, NotificationTopicId.ACTION), + new(NotificationTypeId.APP_SUBSCRIPTION_REQUEST, NotificationTopicId.ACTION), + new(NotificationTypeId.SERVICE_REQUEST, NotificationTopicId.ACTION), + new(NotificationTypeId.APP_SUBSCRIPTION_ACTIVATION, NotificationTopicId.OFFER), + new(NotificationTypeId.APP_RELEASE_REQUEST, NotificationTopicId.OFFER), + new(NotificationTypeId.SERVICE_ACTIVATION, NotificationTopicId.OFFER), + new(NotificationTypeId.APP_ROLE_ADDED, NotificationTopicId.OFFER), + new(NotificationTypeId.APP_RELEASE_APPROVAL, NotificationTopicId.OFFER), + new(NotificationTypeId.SERVICE_RELEASE_REQUEST, NotificationTopicId.OFFER), + new(NotificationTypeId.SERVICE_RELEASE_APPROVAL, NotificationTopicId.OFFER), + new(NotificationTypeId.APP_RELEASE_REJECTION, NotificationTopicId.OFFER), + new(NotificationTypeId.SERVICE_RELEASE_REJECTION, NotificationTopicId.OFFER) + }); + + dbContext.Notifications.AddRange(new List + { + new (new Guid("94F22922-04F6-4A4E-B976-1BF2FF3DE973"), new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001"), DateTimeOffset.UtcNow, NotificationTypeId.ACTION, false), + new (new Guid("5FCBA636-E0F6-4C86-B5CC-7711A55669B6"), new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001"), DateTimeOffset.UtcNow, NotificationTypeId.ACTION, true), + new (new Guid("8bdaada7-4885-4aa7-87ce-1a325492a485"), new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001"), DateTimeOffset.UtcNow, NotificationTypeId.ACTION, true), + new (new Guid("9D03FE54-3581-4399-84DD-D606E9A2B3D5"), new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001"), DateTimeOffset.UtcNow, NotificationTypeId.ACTION, false), + new (new Guid("34782A2E-7B54-4E78-85BA-419AF534837F"), new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001"), DateTimeOffset.UtcNow, NotificationTypeId.INFO, true), + new (new Guid("19AFFED7-13F0-4868-9A23-E77C23D8C889"), new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001"), DateTimeOffset.UtcNow, NotificationTypeId.INFO, false), + }); + }; +} diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/CompanySsiDetailsRepositoryTests.cs b/tests/portalbackend/PortalBackend.DBAccess.Tests/CompanySsiDetailsRepositoryTests.cs index b23f859f57..ead006f20b 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/CompanySsiDetailsRepositoryTests.cs +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/CompanySsiDetailsRepositoryTests.cs @@ -83,13 +83,15 @@ public async Task GetAllCredentialDetails_WithValidData_ReturnsExpected() // Assert result.Should().NotBeNull(); - result.Count.Should().Be(5); - result.Should().HaveCount(5); - result.Where(x => x.CompanyId == _validCompanyId).Should().HaveCount(4) + result.Count.Should().Be(7); + result.Should().HaveCount(7); + result.Where(x => x.CompanyId == _validCompanyId).Should().HaveCount(6) .And.Satisfy( x => x.VerifiedCredentialTypeId == VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK && x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.PENDING, x => x.VerifiedCredentialTypeId == VerifiedCredentialTypeId.PCF_FRAMEWORK && x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.PENDING, x => x.VerifiedCredentialTypeId == VerifiedCredentialTypeId.DISMANTLER_CERTIFICATE && x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.PENDING, + x => x.VerifiedCredentialTypeId == VerifiedCredentialTypeId.DISMANTLER_CERTIFICATE && x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.INACTIVE, + x => x.VerifiedCredentialTypeId == VerifiedCredentialTypeId.DISMANTLER_CERTIFICATE && x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.INACTIVE, x => x.VerifiedCredentialTypeId == VerifiedCredentialTypeId.BEHAVIOR_TWIN_FRAMEWORK && x.CompanySsiDetailStatusId == CompanySsiDetailStatusId.INACTIVE); result.Where(x => x.CompanyId == new Guid("3390c2d7-75c1-4169-aa27-6ce00e1f3cdd")).Should().ContainSingle() .And.Satisfy(x => x.VerifiedCredentialTypeId == VerifiedCredentialTypeId.TRACEABILITY_FRAMEWORK); @@ -161,9 +163,9 @@ public async Task GetSsiCertificates_WithValidData_ReturnsExpected() result.Should().ContainSingle() .Which.Should().Match(x => x.CredentialType == VerifiedCredentialTypeId.DISMANTLER_CERTIFICATE && - x.SsiDetailData != null && - x.SsiDetailData.Count() == 1 && - x.SsiDetailData.Single().ParticipationStatus == CompanySsiDetailStatusId.PENDING); + x.SsiDetailData.Count() == 3 && + x.SsiDetailData.Count(x => x.ParticipationStatus == CompanySsiDetailStatusId.PENDING) == 1 && + x.SsiDetailData.Count(x => x.ParticipationStatus == CompanySsiDetailStatusId.INACTIVE) == 2); } #endregion diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/ConnectorRepositoryTests.cs b/tests/portalbackend/PortalBackend.DBAccess.Tests/ConnectorRepositoryTests.cs index 5860d72910..ccc73f4a6e 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/ConnectorRepositoryTests.cs +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/ConnectorRepositoryTests.cs @@ -278,8 +278,7 @@ public async Task GetSelfDescriptionDocumentDataAsync_WithoutDocumentId_ReturnsE // Assert result.Should().NotBeNull(); - result.IsValidConnectorId.Should().BeTrue(); - result.IsProvidingOrHostCompany.Should().BeTrue(); + result!.IsProvidingOrHostCompany.Should().BeTrue(); result.SelfDescriptionDocumentId.Should().BeNull(); } @@ -294,8 +293,7 @@ public async Task GetSelfDescriptionDocumentDataAsync_WithDocumentId_ReturnsExpe // Assert result.Should().NotBeNull(); - result.IsValidConnectorId.Should().BeTrue(); - result.IsProvidingOrHostCompany.Should().BeTrue(); + result!.IsProvidingOrHostCompany.Should().BeTrue(); result.SelfDescriptionDocumentId.Should().Be(new Guid("e020787d-1e04-4c0b-9c06-bd1cd44724b3")); result.DocumentStatusId.Should().Be(DocumentStatusId.LOCKED); } @@ -311,8 +309,7 @@ public async Task GetSelfDescriptionDocumentDataAsync_WithoutExistingCompanyId_R // Assert result.Should().NotBeNull(); - result.IsValidConnectorId.Should().BeTrue(); - result.IsProvidingOrHostCompany.Should().BeFalse(); + result!.IsProvidingOrHostCompany.Should().BeFalse(); } [Fact] @@ -324,10 +321,25 @@ public async Task GetSelfDescriptionDocumentDataAsync_WithoutExistingConnectorId // Act var result = await sut.GetConnectorDeleteDataAsync(new Guid(), new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87")).ConfigureAwait(false); + // Assert + result.Should().BeNull(); + } + + [Fact] + public async Task GetSelfDescriptionDocumentDataAsync_WithConnectorOfferSubscription_ReturnsExpected() + { + // Arrange + var (sut, _) = await CreateSut().ConfigureAwait(false); + + // Act + var result = await sut.GetConnectorDeleteDataAsync(new Guid("4618c650-709c-4580-956a-85b76eecd4b8"), new Guid("41fd2ab8-71cd-4546-9bef-a388d91b2542")).ConfigureAwait(false); + // Assert result.Should().NotBeNull(); - result.IsValidConnectorId.Should().BeFalse(); - result.SelfDescriptionDocumentId.Should().BeNull(); + result!.ConnectorStatus.Should().Be(ConnectorStatusId.PENDING); + result!.ConnectorOfferSubscriptions.Should().Satisfy( + x => x.AssignedOfferSubscriptionIds == new Guid("014afd09-e51a-4ecf-83ab-a5380d9af832") + && x.OfferSubscriptionStatus == OfferSubscriptionStatusId.PENDING); } #endregion diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/OfferRepositoryTests.cs b/tests/portalbackend/PortalBackend.DBAccess.Tests/OfferRepositoryTests.cs index e1377cd881..22beffd274 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/OfferRepositoryTests.cs +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/OfferRepositoryTests.cs @@ -136,14 +136,15 @@ public async Task GetAllActiveApps_ReturnsExpectedResult() var offers = await sut.GetAllActiveAppsAsync(null!, Constants.DefaultLanguage).ToListAsync().ConfigureAwait(false); // Assert - offers.Should().HaveCount(7).And.Satisfy( + offers.Should().HaveCount(8).And.Satisfy( x => x.Name == "Test App", x => x.Name == "Test App 3", x => x.Name == "Trace-X", x => x.Name == "Project Implementation: Earth Commerce", x => x.Name == "Top App", x => x.Name == "Test App 1", - x => x.Name == "Test App 2" + x => x.Name == "Test App 2", + x => x.Name == "Test App Tech User" ); } diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/OfferSubscriptionViewTests.cs b/tests/portalbackend/PortalBackend.DBAccess.Tests/OfferSubscriptionViewTests.cs index 6655d378cc..d5eecb2922 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/OfferSubscriptionViewTests.cs +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/OfferSubscriptionViewTests.cs @@ -50,7 +50,7 @@ public async Task OfferSubscriptionView_GetAll_ReturnsExpected() // Act var result = await sut.OfferSubscriptionView.ToListAsync().ConfigureAwait(false); - result.Should().HaveCount(12); + result.Should().HaveCount(13); } [Fact] diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/companies.test.json b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/companies.test.json index 7f9a71aa97..d942627dec 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/companies.test.json +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/companies.test.json @@ -58,5 +58,15 @@ "company_status_id": 2, "address_id": "86da3e1c-a634-1234-ad44-988074612999", "self_description_document_id": null + }, + { + "id": "41fd2ab8-71cd-4546-9bef-a388d91b2543", + "date_created": "2022-03-24 18:01:33.438000 +00:00", + "business_partner_number": "BPNL00000003LLHN", + "name": "Security Companies", + "shortname": "Security Companirs", + "company_status_id": 1, + "address_id": "86da3e1c-a634-41a6-ad44-9880746123e4", + "self_description_document_id": null } ] diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/company_service_accounts.test.json b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/company_service_accounts.test.json index d02c9f3041..4c290a785d 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/company_service_accounts.test.json +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/company_service_accounts.test.json @@ -31,5 +31,15 @@ "company_service_account_type_id": 1, "offer_subscription_id": "0b2ca541-206d-48ad-bc02-fb61fbcb5552", "company_id": "41fd2ab8-71cd-4546-9bef-a388d91b2542" + }, + { + "id": "93eecd4e-ca47-4dd2-85bf-775ea72eb009", + "name": "test-user-service-accounts", + "description": "test-user-service-account-descs", + "company_service_account_type_id": 1, + "offer_subscription_id": "0b2ca541-206d-48ad-bc02-fb61fbcb5562", + "company_id": "41fd2ab8-71cd-4546-9bef-a388d91b2543", + "client_client_id": "sa-x-2" } + ] diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/company_ssi_details.test.json b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/company_ssi_details.test.json index 1afa6c8b1f..c8318cb17f 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/company_ssi_details.test.json +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/company_ssi_details.test.json @@ -52,5 +52,25 @@ "creator_user_id": "ac1cf001-7fbc-1f2f-817f-bce058020006", "date_created": "2023-06-01 00:00:00.000000 +00:00", "verified_credential_external_type_use_case_detail_id": "1268a76a-ca19-4dd8-b932-01f24071d562" + }, + { + "id": "9f5b9934-4014-4099-91e9-7b1aee696b08", + "company_id": "2dc4249f-b5ca-4d42-bef1-7a7a950a4f87", + "verified_credential_type_id": 4, + "company_ssi_detail_status_id": 3, + "document_id": "9685f744-9d90-4102-a949-fcd0bb86f954", + "expiry_date": null, + "creator_user_id": "ac1cf001-7fbc-1f2f-817f-bce058020006", + "date_created": "2023-06-01 00:00:00.000000 +00:00" + }, + { + "id": "9f5b9934-4014-4099-91e9-7b1aee696b09", + "company_id": "2dc4249f-b5ca-4d42-bef1-7a7a950a4f87", + "verified_credential_type_id": 4, + "company_ssi_detail_status_id": 3, + "document_id": "88793f9f-c5a4-4621-847b-3d47cd839283", + "expiry_date": null, + "creator_user_id": "ac1cf001-7fbc-1f2f-817f-bce058020006", + "date_created": "2023-06-01 00:00:00.000000 +00:00" } ] diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/identities.test.json b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/identities.test.json index d46d5dc602..58c962d5e9 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/identities.test.json +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/identities.test.json @@ -126,5 +126,13 @@ "user_status_id": 1, "user_entity_id": null, "identity_type_id": 2 + }, + { + "id": "93eecd4e-ca47-4dd2-85bf-775ea72eb009", + "date_created": "2022-06-01 18:01:33.439000 +00:00", + "company_id": "41fd2ab8-71cd-4546-9bef-a388d91b2543", + "user_status_id": 1, + "user_entity_id": null, + "identity_type_id": 2 } ] diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/offer_subscriptions.test.json b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/offer_subscriptions.test.json index e023ee2b37..f2b633e8f3 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/offer_subscriptions.test.json +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/offer_subscriptions.test.json @@ -109,5 +109,15 @@ "last_editor_id": null, "display_name": null, "description": null + }, + { + "company_id": "41fd2ab8-71cd-4546-9bef-a388d91b2543", + "offer_id": "19e05734-9b1f-1234-b961-8ffa1d7b6979", + "offer_subscription_status_id": 1, + "requester_id": "ac1cf001-7fbc-1f2f-817f-bce0575a0011", + "id": "0b2ca541-206d-48ad-bc02-fb61fbcb5562", + "last_editor_id": null, + "display_name": null, + "description": null } ] diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/offers.test.json b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/offers.test.json index fb1e222550..1f35b21261 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/offers.test.json +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/Seeder/Data/offers.test.json @@ -253,5 +253,22 @@ "offer_type_id": 1, "last_editor_id": null, "license_type_id":1 + }, + { + "id": "19e05734-9b1f-1234-b961-8ffa1d7b6979", + "name": "Test App Tech User", + "date_created": "2022-10-01 00:00:00.000000 +00:00", + "date_released": "2022-10-01 00:00:00.000005 +00:00", + "marketing_url": null, + "contact_email": null, + "contact_number": null, + "provider": "Test Company Tech", + "provider_company_id": "41fd2ab8-71cd-4546-9bef-a388d91b2543", + "offer_status_id": 3, + "date_last_changed": "2022-10-01 00:00:00.000000 +00:00", + "sales_manager_id": "ac1cf001-7fbc-1f2f-817f-bce058020005", + "offer_type_id": 1, + "last_editor_id": null, + "license_type_id":1 } ] diff --git a/tests/portalbackend/PortalBackend.DBAccess.Tests/ServiceAccountRespotitoryTests.cs b/tests/portalbackend/PortalBackend.DBAccess.Tests/ServiceAccountRespotitoryTests.cs index cbd820686e..0d24b2c726 100644 --- a/tests/portalbackend/PortalBackend.DBAccess.Tests/ServiceAccountRespotitoryTests.cs +++ b/tests/portalbackend/PortalBackend.DBAccess.Tests/ServiceAccountRespotitoryTests.cs @@ -127,7 +127,7 @@ public async Task GetOwnCompanyServiceAccountWithIamServiceAccountRolesAsync_Ret // Assert result.Should().NotBe(default); - result!.ClientId.Should().Be("7e85a0b8-0001-ab67-10d1-000000001006"); + result!.ClientId.Should().Be("dab9dd17-0d31-46c7-b313-aca61225dcd1"); } [Fact] @@ -207,14 +207,15 @@ public async Task GetOwnCompanyServiceAccountDetailedDataUntrackedAsync_WithInva #region GetOwnCompanyServiceAccountDetailedDataUntrackedAsync [Theory] - [InlineData(10, 0, 10, 10)] - [InlineData(10, 1, 9, 9)] + [InlineData(3, 0, 10, 3)] + [InlineData(3, 1, 9, 2)] public async Task GetOwnCompanyServiceAccountsUntracked_ReturnsExpectedResult(int count, int page, int size, int expected) { // Arrange + var newvalidCompanyId = new Guid("41fd2ab8-71cd-4546-9bef-a388d91b2542"); var (sut, _) = await CreateSut().ConfigureAwait(false); // Act - var result = await sut.GetOwnCompanyServiceAccountsUntracked(_validCompanyId, null, null)(page, size).ConfigureAwait(false); + var result = await sut.GetOwnCompanyServiceAccountsUntracked(newvalidCompanyId, null, null)(page, size).ConfigureAwait(false); // Assert result.Should().NotBeNull(); @@ -222,8 +223,8 @@ public async Task GetOwnCompanyServiceAccountsUntracked_ReturnsExpectedResult(in result.Data.Should().HaveCount(expected); if (expected > 0) { - result.Data.First().CompanyServiceAccountTypeId.Should().Be(CompanyServiceAccountTypeId.OWN); - result.Data.First().IsOwner.Should().BeTrue(); + result.Data.First().CompanyServiceAccountTypeId.Should().Be(CompanyServiceAccountTypeId.MANAGED); + result.Data.First().IsOwner.Should().BeFalse(); } } @@ -249,7 +250,7 @@ public async Task GetOwnCompanyServiceAccountsUntracked_WithClientIdAndProvider_ var (sut, _) = await CreateSut().ConfigureAwait(false); // Act - var result = await sut.GetOwnCompanyServiceAccountsUntracked(new("41fd2ab8-71cd-4546-9bef-a388d91b2542"), "sa-x-1", false)(0, 10).ConfigureAwait(false); + var result = await sut.GetOwnCompanyServiceAccountsUntracked(new("41fd2ab8-71cd-4546-9bef-a388d91b2543"), "sa-x-2", false)(0, 10).ConfigureAwait(false); // Assert result!.Count.Should().Be(1); @@ -282,8 +283,8 @@ public async Task GetOwnCompanyServiceAccountsUntracked_WithSearch_ReturnsExpect var result = await sut.GetOwnCompanyServiceAccountsUntracked(_validCompanyId, "sa-cl", null)(0, 10).ConfigureAwait(false); // Assert - result!.Count.Should().Be(8); - result.Data.Should().HaveCount(8); + result!.Count.Should().Be(11); + result.Data.Should().HaveCount(10); } #endregion diff --git a/tests/provisioning/Provisioning.Library.Tests/Extensions/ServiceAccountCreationTests.cs b/tests/provisioning/Provisioning.Library.Tests/Extensions/ServiceAccountCreationTests.cs index b8d2f1a8e0..b34fef4b80 100644 --- a/tests/provisioning/Provisioning.Library.Tests/Extensions/ServiceAccountCreationTests.cs +++ b/tests/provisioning/Provisioning.Library.Tests/Extensions/ServiceAccountCreationTests.cs @@ -166,10 +166,10 @@ private void Setup(ICollection? serviceAccounts = null, I A.CallTo(() => _provisioningManager.SetupCentralServiceAccountClientAsync(A._, A._)) .Returns(new ServiceAccountData("internal-sa1", _iamUserId, new ClientAuthData(IamClientAuthMethod.SECRET))); - A.CallTo(() => _userRepository.CreateIdentity(_companyId, A._)) - .Invokes((Guid companyId, UserStatusId userStatusId) => + A.CallTo(() => _userRepository.CreateIdentity(_companyId, A._, IdentityTypeId.COMPANY_SERVICE_ACCOUNT)) + .Invokes((Guid companyId, UserStatusId userStatusId, IdentityTypeId identityTypeId) => { - var identity = new Identity(Guid.NewGuid(), DateTimeOffset.UtcNow, companyId, userStatusId, IdentityTypeId.COMPANY_SERVICE_ACCOUNT); + var identity = new Identity(Guid.NewGuid(), DateTimeOffset.UtcNow, companyId, userStatusId, identityTypeId); identities?.Add(identity); }) .Returns(new Identity(_identityId, default, default, default, default)); diff --git a/tests/provisioning/Provisioning.Library.Tests/UserProvisioningServiceCreateUsersTests.cs b/tests/provisioning/Provisioning.Library.Tests/UserProvisioningServiceCreateUsersTests.cs index 1373f68f2b..d1ced283ab 100644 --- a/tests/provisioning/Provisioning.Library.Tests/UserProvisioningServiceCreateUsersTests.cs +++ b/tests/provisioning/Provisioning.Library.Tests/UserProvisioningServiceCreateUsersTests.cs @@ -257,8 +257,8 @@ public async Task TestCreateUsersNotExistingCompanyUserWithoutKeycloakUserSucces A.CallTo(() => _userRepository.GetMatchingCompanyIamUsersByNameEmail(A.That.IsEqualTo(userInfo.FirstName), A._, A._, A._, A>._)) .Returns(new[] { (UserEntityId: (string?)null, CompanyUserId: Guid.Empty) }.ToAsyncEnumerable()); - A.CallTo(() => _userRepository.CreateIdentity(A._, A._)) - .ReturnsLazily((Guid companyId, UserStatusId userStatusId) => new Identity(Guid.NewGuid(), DateTimeOffset.UtcNow, companyId, userStatusId, IdentityTypeId.COMPANY_USER) + A.CallTo(() => _userRepository.CreateIdentity(A._, A._, IdentityTypeId.COMPANY_USER)) + .ReturnsLazily((Guid companyId, UserStatusId userStatusId, IdentityTypeId identityId) => new Identity(Guid.NewGuid(), DateTimeOffset.UtcNow, companyId, userStatusId, identityId) { UserEntityId = centralUserId }); @@ -277,7 +277,7 @@ public async Task TestCreateUsersNotExistingCompanyUserWithoutKeycloakUserSucces A.CallTo(() => _provisioningManager.GetProviderUserLinkDataForCentralUserIdAsync(A._)).MustNotHaveHappened(); A.CallTo(() => _provisioningManager.CreateCentralUserAsync(A._, A)>>._)).MustHaveHappened(userCreationInfoIdp.Count, Times.Exactly); A.CallTo(() => _provisioningManager.CreateCentralUserAsync(A.That.Matches(u => u.FirstName == userInfo.FirstName), A)>>._)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _userRepository.CreateIdentity(_companyNameIdpAliasData.CompanyId, UserStatusId.ACTIVE)).MustHaveHappened(userCreationInfoIdp.Count, Times.Exactly); + A.CallTo(() => _userRepository.CreateIdentity(_companyNameIdpAliasData.CompanyId, UserStatusId.ACTIVE, IdentityTypeId.COMPANY_USER)).MustHaveHappened(userCreationInfoIdp.Count, Times.Exactly); A.CallTo(() => _userRepository.CreateCompanyUser(A._, A._, A._, A._)).MustHaveHappened(userCreationInfoIdp.Count, Times.Exactly); A.CallTo(() => _userRepository.CreateCompanyUser(A._, userInfo.FirstName, userInfo.LastName, userInfo.Email)).MustHaveHappenedOnceExactly(); A.CallTo(() => _businessPartnerRepository.CreateCompanyUserAssignedBusinessPartner(A._, _companyNameIdpAliasData.BusinessPartnerNumber!)).MustHaveHappened(userCreationInfoIdp.Count, Times.Exactly); @@ -313,7 +313,7 @@ public async Task TestCreateUsersExistingCompanyUserWithoutKeycloakUserSuccess() A.CallTo(() => _provisioningManager.GetProviderUserLinkDataForCentralUserIdAsync(A._)).MustNotHaveHappened(); A.CallTo(() => _provisioningManager.CreateCentralUserAsync(A._, A)>>._)).MustHaveHappened(userCreationInfoIdp.Count, Times.Exactly); A.CallTo(() => _provisioningManager.CreateCentralUserAsync(A.That.Matches(u => u.FirstName == userInfo.FirstName), A)>>._)).MustHaveHappenedOnceExactly(); - A.CallTo(() => _userRepository.CreateIdentity(A._, UserStatusId.ACTIVE)).MustHaveHappened(userCreationInfoIdp.Count - 1, Times.Exactly); + A.CallTo(() => _userRepository.CreateIdentity(A._, UserStatusId.ACTIVE, IdentityTypeId.COMPANY_USER)).MustHaveHappened(userCreationInfoIdp.Count - 1, Times.Exactly); A.CallTo(() => _userRepository.CreateCompanyUser(A._, A._, A._, A._)).MustHaveHappened(userCreationInfoIdp.Count - 1, Times.Exactly); A.CallTo(() => _userRepository.CreateCompanyUser(A._, userInfo.FirstName, A._, A._)).MustNotHaveHappened(); A.CallTo(() => _businessPartnerRepository.CreateCompanyUserAssignedBusinessPartner(A._, _companyNameIdpAliasData.BusinessPartnerNumber!)).MustHaveHappened(userCreationInfoIdp.Count - 1, Times.Exactly); @@ -407,14 +407,14 @@ private void SetupRepositories() A.CallTo(() => _portalRepositories.GetInstance()).Returns(_businessPartnerRepository); A.CallTo(() => _portalRepositories.GetInstance()).Returns(_userRolesRepository); - A.CallTo(() => _userRepository.CreateIdentity(A._, A._)) - .ReturnsLazily((Guid companyId, UserStatusId userStatusId) => + A.CallTo(() => _userRepository.CreateIdentity(A._, A._, IdentityTypeId.COMPANY_USER)) + .ReturnsLazily((Guid companyId, UserStatusId userStatusId, IdentityTypeId identityTypeId) => new Identity( Guid.NewGuid(), DateTimeOffset.UtcNow, companyId, userStatusId, - IdentityTypeId.COMPANY_USER)); + identityTypeId)); A.CallTo(() => _userRepository.CreateCompanyUser(A._, A._, A._, A._)) .ReturnsLazily((Guid _, string firstName, string _, string _) => diff --git a/tests/registration/Registration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs b/tests/registration/Registration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs index f642d2b773..7f1687f461 100644 --- a/tests/registration/Registration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs +++ b/tests/registration/Registration.Service.Tests/BusinessLogic/RegistrationBusinessLogicTest.cs @@ -187,7 +187,7 @@ public async Task GetCompanyBpdmDetailDataByBusinessPartnerNumber_WithValidBpn_R var bpdmAddress = _fixture.Build() .With(x => x.BpnLegalEntity, name) - .With(x => x.Bpnl, businessPartnerNumber) + .With(x => x.Bpna, businessPartnerNumber) .With(x => x.PhysicalPostalAddress, _fixture.Build() .With(x => x.Country, _fixture.Build().With(x => x.TechnicalKey, country).Create()) .With(x => x.AdministrativeAreaLevel1, _fixture.Build().With(x => x.RegionCode, region).Create()) diff --git a/tests/shared/Tests.Shared/Extensions/HttpExtensions.cs b/tests/shared/Tests.Shared/Extensions/HttpExtensions.cs index 655dade910..d79fe9d44a 100644 --- a/tests/shared/Tests.Shared/Extensions/HttpExtensions.cs +++ b/tests/shared/Tests.Shared/Extensions/HttpExtensions.cs @@ -28,9 +28,11 @@ public static class HttpExtensions { public static async Task GetResultFromContent(this HttpResponseMessage response) { - await using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); - var options = new JsonSerializerOptions(); - options.Converters.Add(new JsonStringEnumConverter(allowIntegerValues: false)); + using var responseStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false); + var options = new JsonSerializerOptions + { + Converters = { new JsonStringEnumConverter(allowIntegerValues: false) } + }; return await JsonSerializer.DeserializeAsync(responseStream, options).ConfigureAwait(false) ?? throw new InvalidOperationException(); } diff --git a/tests/shared/Tests.Shared/IntegrationTests/BasePublicUrlTests.cs b/tests/shared/Tests.Shared/IntegrationTests/BasePublicUrlTests.cs new file mode 100644 index 0000000000..9ac40537b2 --- /dev/null +++ b/tests/shared/Tests.Shared/IntegrationTests/BasePublicUrlTests.cs @@ -0,0 +1,60 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 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 FluentAssertions; +using Org.Eclipse.TractusX.Portal.Backend.Framework.PublicInfos; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.Extensions; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; +using System.Linq.Expressions; +using System.Net; +using Xunit; + +namespace Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.IntegrationTests; + +public class BasePublicUrlTests : IClassFixture> + where TController : class + where TSeeding : class, IBaseSeeding +{ + protected readonly IntegrationTestFactory Factory; + + public BasePublicUrlTests(IntegrationTestFactory factory) + { + Factory = factory; + } + + protected async Task OpenInformationController_ReturnsCorrectAmount(int resultCount, params Expression>[] satisfyPredicates) + { + // Arrange + var client = Factory.CreateClient(); + var endpoint = new InformationEndpoints(client); + + // Act + var response = await endpoint.Get().ConfigureAwait(false); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + var result = await response.GetResultFromContent>(); + result.Should().HaveCount(resultCount); + if (satisfyPredicates.Any()) + { + result.Should().Satisfy(satisfyPredicates); + } + } +} diff --git a/tests/shared/Tests.Shared/IntegrationTests/EndpointSetup/InformationEndpoints.cs b/tests/shared/Tests.Shared/IntegrationTests/EndpointSetup/InformationEndpoints.cs new file mode 100644 index 0000000000..c4f51bd60d --- /dev/null +++ b/tests/shared/Tests.Shared/IntegrationTests/EndpointSetup/InformationEndpoints.cs @@ -0,0 +1,41 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.IntegrationTests.EndpointSetup; + +namespace Org.Eclipse.TractusX.Portal.Backend.Tests.Shared; + +public class InformationEndpoints +{ + private readonly HttpClient _client; + + public static string Path => Paths.Base; + + public InformationEndpoints(HttpClient client) + { + this._client = client; + } + + public async Task Get() + { + var request = new HttpRequestMessage(HttpMethod.Get, $"{Path}info"); + return await this._client.SendAsync(request); + } +} diff --git a/tests/shared/Tests.Shared/IntegrationTests/EndpointSetup/Paths.cs b/tests/shared/Tests.Shared/IntegrationTests/EndpointSetup/Paths.cs index 061ebbe39d..4786514b8d 100644 --- a/tests/shared/Tests.Shared/IntegrationTests/EndpointSetup/Paths.cs +++ b/tests/shared/Tests.Shared/IntegrationTests/EndpointSetup/Paths.cs @@ -22,6 +22,7 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.IntegrationTests.Endp public static class Paths { - public static readonly string Notification = "/api/notification"; - public static readonly string Connectors = "/api/administration/connectors"; + public static readonly string Base = "/api/"; + public static readonly string Notification = $"{Base}notification"; + public static readonly string Connectors = $"{Base}administration/connectors"; } diff --git a/tests/shared/Tests.Shared/IntegrationTests/IntegrationTestFactory.cs b/tests/shared/Tests.Shared/IntegrationTests/IntegrationTestFactory.cs index 32ef5e047b..a317d832a3 100644 --- a/tests/shared/Tests.Shared/IntegrationTests/IntegrationTestFactory.cs +++ b/tests/shared/Tests.Shared/IntegrationTests/IntegrationTestFactory.cs @@ -34,29 +34,27 @@ using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.Migrations.Seeder; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Identities; +using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; using Xunit; +[assembly: CollectionBehavior(DisableTestParallelization = true)] namespace Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.IntegrationTests; -public class IntegrationTestFactory : WebApplicationFactory, IAsyncLifetime +public class IntegrationTestFactory : WebApplicationFactory, IAsyncLifetime where TTestClass : class + where TSeedingData : class, IBaseSeeding { - private readonly TestcontainerDatabase _container; - - public IntegrationTestFactory() - { - _container = new TestcontainersBuilder() - .WithDatabase(new PostgreSqlTestcontainerConfiguration - { - Database = "test_db", - Username = "postgres", - Password = "postgres", - }) - .WithImage("postgres") - .WithCleanUp(true) - .WithName(Guid.NewGuid().ToString()) - .Build(); - } + protected readonly TestcontainerDatabase _container = new TestcontainersBuilder() + .WithDatabase(new PostgreSqlTestcontainerConfiguration + { + Database = "test_db", + Username = "postgres", + Password = "postgres", + }) + .WithImage("postgres") + .WithCleanUp(true) + .WithName(Guid.NewGuid().ToString()) + .Build(); protected override void ConfigureWebHost(IWebHostBuilder builder) { @@ -81,7 +79,7 @@ protected override void ConfigureWebHost(IWebHostBuilder builder) x => x.MigrationsAssembly(typeof(BatchInsertSeeder).Assembly.GetName().Name) .MigrationsHistoryTable("__efmigrations_history_portal")); }); - services.EnsureDbCreated(); + services.EnsureDbCreatedWithSeeding(); services.AddSingleton(); }); } diff --git a/tests/shared/Tests.Shared/IntegrationTests/ServiceCollectionExtensions.cs b/tests/shared/Tests.Shared/IntegrationTests/ServiceCollectionExtensions.cs index ce417d3798..57c16e1786 100644 --- a/tests/shared/Tests.Shared/IntegrationTests/ServiceCollectionExtensions.cs +++ b/tests/shared/Tests.Shared/IntegrationTests/ServiceCollectionExtensions.cs @@ -21,6 +21,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities; +using Org.Eclipse.TractusX.Portal.Backend.Provisioning.ProvisioningEntities; using Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; namespace Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.IntegrationTests; @@ -34,7 +35,7 @@ public static void RemoveProdDbContext(this IServiceCollection services) wher services.Remove(descriptor); } - public static void EnsureDbCreated(this IServiceCollection services) + public static void EnsureDbCreatedWithSeeding(this IServiceCollection services) where TSeedingData : IBaseSeeding { var serviceProvider = services.BuildServiceProvider(); @@ -42,7 +43,8 @@ public static void EnsureDbCreated(this IServiceCollection services) var scopedServices = scope.ServiceProvider; var context = scopedServices.GetRequiredService(); context.Database.Migrate(); - BaseSeed.SeedBasedata().Invoke(context); + var result = ((IBaseSeeding)Activator.CreateInstance(typeof(TSeedingData)))?.SeedData(); + result?.Invoke(context); context.SaveChanges(); } diff --git a/tests/shared/Tests.Shared/TestSeeds/ActiveParticipantSeeding.cs b/tests/shared/Tests.Shared/TestSeeds/ActiveParticipantSeeding.cs new file mode 100644 index 0000000000..77033cf22d --- /dev/null +++ b/tests/shared/Tests.Shared/TestSeeds/ActiveParticipantSeeding.cs @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; + +namespace Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; + +public class ActiveParticipantSeeding : IBaseSeeding +{ + public Action SeedData() => dbContext => + { + BaseSeed.SeedBaseData().Invoke(dbContext); + + dbContext.CompanyAssignedRoles.AddRange(new[] + { + new CompanyAssignedRole(new("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), CompanyRoleId.ACTIVE_PARTICIPANT) + }); + }; +} diff --git a/tests/shared/Tests.Shared/TestSeeds/AppProviderSeeding.cs b/tests/shared/Tests.Shared/TestSeeds/AppProviderSeeding.cs new file mode 100644 index 0000000000..08e8e1a141 --- /dev/null +++ b/tests/shared/Tests.Shared/TestSeeds/AppProviderSeeding.cs @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; + +namespace Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; + +public class AppProviderSeeding : IBaseSeeding +{ + public Action SeedData() => dbContext => + { + BaseSeed.SeedBaseData().Invoke(dbContext); + + dbContext.CompanyAssignedRoles.AddRange(new[] + { + new CompanyAssignedRole(new("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), CompanyRoleId.APP_PROVIDER) + }); + }; +} diff --git a/tests/shared/Tests.Shared/TestSeeds/BaseSeed.cs b/tests/shared/Tests.Shared/TestSeeds/BaseSeed.cs index 0fc937adcb..ff89e8acf1 100644 --- a/tests/shared/Tests.Shared/TestSeeds/BaseSeed.cs +++ b/tests/shared/Tests.Shared/TestSeeds/BaseSeed.cs @@ -24,226 +24,52 @@ namespace Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; -public static class BaseSeed +public class BaseSeed { - public static Action SeedBasedata() => dbContext => + public static Action SeedBaseData() => dbContext => { - dbContext.Languages.AddRange(new List - { - new ("de"), - new ("en") - }); + dbContext.Languages.AddRange(new List { new("de"), new("en") }); dbContext.Countries.AddRange(new List { - new("DE", "Deutschland", "Germany") - { - Alpha3Code = "DEU" - }, - new("PT", "Portugal", "Portugal") - { - Alpha3Code = "PRT" - } - }); - - dbContext.UseCases.AddRange(new List - { - new(new Guid("06b243a4-ba51-4bf3-bc40-5d79a2231b90"), "Modular Production", "MP") + new("DE", "Deutschland", "Germany") {Alpha3Code = "DEU"}, + new("PT", "Portugal", "Portugal") {Alpha3Code = "PRT"} }); dbContext.Addresses.AddRange(new List
{ new(new Guid("b4db3945-19a7-4a50-97d6-e66e8dfd04fb"), "Munich", "Street", "DE", DateTimeOffset.UtcNow) { - Zipcode = "00001", - Streetnumber = "1", - Region = "BY", - Streetadditional = "foo" - }, - new(new Guid("12302f9b-418c-4b8c-aea8-3eedf67e6a02"), "Munich", "Street", "DE", DateTimeOffset.UtcNow) - { - Zipcode = "00001", - Streetnumber = "2" - }, - new(new Guid("1fdf48eb-53f1-4d44-9685-c8f78189b156"), "Munich", "Street", "DE", DateTimeOffset.UtcNow) - { - Zipcode = "00001", - Streetnumber = "2" - }, + Zipcode = "00001", Streetnumber = "1", Region = "BY", Streetadditional = "foo" + } }); dbContext.Companies.AddRange(new List { - new(new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), "Catena-X", CompanyStatusId.ACTIVE, DateTimeOffset.UtcNow) + new(new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), "Catena-X", CompanyStatusId.ACTIVE, + DateTimeOffset.UtcNow) { AddressId = new Guid("b4db3945-19a7-4a50-97d6-e66e8dfd04fb"), Shortname = "Cat-X", BusinessPartnerNumber = "CAXSDUMMYCATENAZZ", - }, - new(new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f99"), "Test Company", CompanyStatusId.ACTIVE, DateTimeOffset.UtcNow) - { - AddressId = new Guid("12302f9b-418c-4b8c-aea8-3eedf67e6a02"), - Shortname = "Test", - }, - new(new Guid("27538eac-27a3-4f74-9306-e5149b93ade5"), "Submitted Company With Bpn", CompanyStatusId.ACTIVE, DateTimeOffset.UtcNow) - { - AddressId = new Guid("1fdf48eb-53f1-4d44-9685-c8f78189b156"), - Shortname = "Test", - BusinessPartnerNumber = "CAXSTESTYCATENAZZ", } }); dbContext.Identities.AddRange(new List { - new(new Guid("ac1cf001-7fbc-1f2f-817f-bce058019990"), DateTimeOffset.UtcNow, new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), UserStatusId.ACTIVE, IdentityTypeId.COMPANY_USER) - { - UserEntityId = "623770c5-cf38-4b9f-9a35-f8b9ae972e2e" - }, - new(new Guid("ac1cf001-7fbc-1f2f-817f-bce058019991"), DateTimeOffset.UtcNow, new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), UserStatusId.ACTIVE, IdentityTypeId.COMPANY_USER) - { - UserEntityId = "3d8142f1-860b-48aa-8c2b-1ccb18699f66" - }, - new(new Guid("ac1cf001-7fbc-1f2f-817f-bce058019992"), DateTimeOffset.UtcNow, new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), UserStatusId.ACTIVE, IdentityTypeId.COMPANY_USER) - { - UserEntityId = "47ea7f1f-f10d-4cb2-acaf-b77323ef25b4" - }, - new(new Guid("ac1cf001-7fbc-1f2f-817f-bce058019993"), DateTimeOffset.UtcNow, new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), UserStatusId.ACTIVE, IdentityTypeId.COMPANY_USER), - new(new Guid("ac1cf001-7fbc-1f2f-817f-bce058020000"), DateTimeOffset.UtcNow, new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), UserStatusId.ACTIVE, IdentityTypeId.COMPANY_USER) - { - UserEntityId = "623770c5-cf38-4b9f-9a35-f8b9ae972e2d" - }, - new(new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001"), DateTimeOffset.UtcNow, new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), UserStatusId.ACTIVE, IdentityTypeId.COMPANY_USER) + new(new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001"), DateTimeOffset.UtcNow, + new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), UserStatusId.ACTIVE, IdentityTypeId.COMPANY_USER) { UserEntityId = "3d8142f1-860b-48aa-8c2b-1ccb18699f65" - }, - new(new Guid("40ed8c0d-b506-4c15-b2a9-85fee4b0c280"), DateTimeOffset.UtcNow, new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), UserStatusId.INACTIVE, IdentityTypeId.COMPANY_USER), - new(new Guid("22b7bfef-19f5-4d8a-9fb4-af1a5e978f21"), DateTimeOffset.UtcNow, new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), UserStatusId.DELETED, IdentityTypeId.COMPANY_USER), - new(new Guid("adf37b09-53f3-48ea-b8fb-8cbb7fd79324"), DateTimeOffset.UtcNow, new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f99"), UserStatusId.ACTIVE, IdentityTypeId.COMPANY_USER) - { - UserEntityId = "4b8f156e-5dfc-4a58-9384-1efb195c1c34" - }, - - new (new Guid("7259744a-2ab0-49bf-9fe3-fcb88f6ad332"), DateTimeOffset.UtcNow, new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), UserStatusId.ACTIVE, IdentityTypeId.COMPANY_SERVICE_ACCOUNT) + } }); dbContext.CompanyUsers.AddRange(new List { - new(new Guid("ac1cf001-7fbc-1f2f-817f-bce058019990")) - { - Email = "tester.user1@test.de", - Firstname = "Test User 1", - Lastname = "cx-user-2", - }, - new(new Guid("ac1cf001-7fbc-1f2f-817f-bce058019991")) - { - Email = "tester.user2@test.de", - Firstname = "Test User 2", - Lastname = "cx-admin-2", - }, - new(new Guid("ac1cf001-7fbc-1f2f-817f-bce058019992")) - { - Email = "tester.user3@test.de", - Firstname = "Test User 3", - Lastname = "company-admin-2", - }, - new(new Guid("ac1cf001-7fbc-1f2f-817f-bce058019993")) - { - Email = "tester.user4@test.de", - Firstname = "Test User 4", - Lastname = "it-admin-2", - }, - new(new Guid("ac1cf001-7fbc-1f2f-817f-bce058020000")) - { - Email = "tester.user5@test.de", - Firstname = "Test User 5", - Lastname = "CX User", - }, new(new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001")) { - Email = "tester.user6@test.de", - Firstname = "Test User 6", - Lastname = "CX Admin", - }, - new(new Guid("40ed8c0d-b506-4c15-b2a9-85fee4b0c280")) - { - Email = "tester.user7@test.de", - Firstname = "Test User 7", - Lastname = "Inactive", - }, - new(new Guid("22b7bfef-19f5-4d8a-9fb4-af1a5e978f21")) - { - Email = "tester.user8@test.de", - Firstname = "Test User 8", - Lastname = "Deleted", - }, - new(new Guid("adf37b09-53f3-48ea-b8fb-8cbb7fd79324")) - { - Email = "tester.user@test.de", - Firstname = "Test User", - Lastname = "Test Company", - }, - }); - - dbContext.IamClients.AddRange(new List - { - new (new Guid("0c9051d0-d032-11ec-9d64-0242ac120002"), "Cl2-CX-Portal"), - new (new Guid("f032a034-d035-11ec-9d64-0242ac120002"), "Cl1-CX-Registration"), - new (new Guid("cf207afb-d213-4c33-becc-0cabeef174a7"), "https://catenax-int-dismantler-s66pftcc.authentication.eu10.hana.ondemand.com"), - }); - - dbContext.NotificationTypeAssignedTopics.AddRange(new List() - { - new(NotificationTypeId.INFO, NotificationTopicId.INFO), - new(NotificationTypeId.TECHNICAL_USER_CREATION, NotificationTopicId.INFO), - new(NotificationTypeId.CONNECTOR_REGISTERED, NotificationTopicId.INFO), - new(NotificationTypeId.WELCOME_SERVICE_PROVIDER, NotificationTopicId.INFO), - new(NotificationTypeId.WELCOME_CONNECTOR_REGISTRATION, NotificationTopicId.INFO), - new(NotificationTypeId.WELCOME, NotificationTopicId.INFO), - new(NotificationTypeId.WELCOME_USE_CASES, NotificationTopicId.INFO), - new(NotificationTypeId.WELCOME_APP_MARKETPLACE, NotificationTopicId.INFO), - new(NotificationTypeId.ACTION, NotificationTopicId.ACTION), - new(NotificationTypeId.APP_SUBSCRIPTION_REQUEST, NotificationTopicId.ACTION), - new(NotificationTypeId.SERVICE_REQUEST, NotificationTopicId.ACTION), - new(NotificationTypeId.APP_SUBSCRIPTION_ACTIVATION, NotificationTopicId.OFFER), - new(NotificationTypeId.APP_RELEASE_REQUEST, NotificationTopicId.OFFER), - new(NotificationTypeId.SERVICE_ACTIVATION, NotificationTopicId.OFFER), - new(NotificationTypeId.APP_ROLE_ADDED, NotificationTopicId.OFFER), - new(NotificationTypeId.APP_RELEASE_APPROVAL, NotificationTopicId.OFFER), - new(NotificationTypeId.SERVICE_RELEASE_REQUEST, NotificationTopicId.OFFER), - new(NotificationTypeId.SERVICE_RELEASE_APPROVAL, NotificationTopicId.OFFER), - new(NotificationTypeId.APP_RELEASE_REJECTION, NotificationTopicId.OFFER), - new(NotificationTypeId.SERVICE_RELEASE_REJECTION, NotificationTopicId.OFFER) - }); - - dbContext.Notifications.AddRange(new List - { - new (new Guid("94F22922-04F6-4A4E-B976-1BF2FF3DE973"), new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001"), DateTimeOffset.UtcNow, NotificationTypeId.ACTION, false), - new (new Guid("5FCBA636-E0F6-4C86-B5CC-7711A55669B6"), new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001"), DateTimeOffset.UtcNow, NotificationTypeId.ACTION, true), - new (new Guid("8bdaada7-4885-4aa7-87ce-1a325492a485"), new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001"), DateTimeOffset.UtcNow, NotificationTypeId.ACTION, true), - new (new Guid("9D03FE54-3581-4399-84DD-D606E9A2B3D5"), new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001"), DateTimeOffset.UtcNow, NotificationTypeId.ACTION, false), - new (new Guid("34782A2E-7B54-4E78-85BA-419AF534837F"), new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001"), DateTimeOffset.UtcNow, NotificationTypeId.INFO, true), - new (new Guid("19AFFED7-13F0-4868-9A23-E77C23D8C889"), new Guid("ac1cf001-7fbc-1f2f-817f-bce058020001"), DateTimeOffset.UtcNow, NotificationTypeId.INFO, false), - }); - - dbContext.Connectors.AddRange(new List - { - new(new Guid("5aea3711-cc54-47b4-b7eb-ba9f3bf1cb15"), "Tes One", "DE", "https://api.tes-one.com") - { - ProviderId = new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), - HostId = new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), - TypeId = ConnectorTypeId.COMPANY_CONNECTOR, - StatusId =ConnectorStatusId.ACTIVE, - }, - new(new Guid("f7310cff-a51d-4af8-9bc3-1525e9d1601b"), "Con on Air", "PT", "https://api.con-on-air.com") - { - ProviderId = new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), - HostId = new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), - TypeId = ConnectorTypeId.CONNECTOR_AS_A_SERVICE, - StatusId = ConnectorStatusId.PENDING, - }, + Email = "tester.user6@test.de", Firstname = "Test User 6", Lastname = "CX Admin", + } }); - - dbContext.CountryAssignedIdentifiers.AddRange(new("DE", UniqueIdentifierId.COMMERCIAL_REG_NUMBER), new("DE", UniqueIdentifierId.VAT_ID) { BpdmIdentifierId = BpdmIdentifierId.EU_VAT_ID_DE }); - dbContext.CompanyIdentifiers.AddRange(new(new Guid("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), UniqueIdentifierId.COMMERCIAL_REG_NUMBER, "REG08154711"), new(new Guid("27538eac-27a3-4f74-9306-e5149b93ade5"), UniqueIdentifierId.VAT_ID, "DE123456789")); }; } diff --git a/tests/shared/Tests.Shared/TestSeeds/IBaseSeeding.cs b/tests/shared/Tests.Shared/TestSeeds/IBaseSeeding.cs new file mode 100644 index 0000000000..768574f048 --- /dev/null +++ b/tests/shared/Tests.Shared/TestSeeds/IBaseSeeding.cs @@ -0,0 +1,28 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities; + +namespace Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; + +public interface IBaseSeeding +{ + Action SeedData(); +} diff --git a/tests/shared/Tests.Shared/TestSeeds/ServiceProviderSeeding.cs b/tests/shared/Tests.Shared/TestSeeds/ServiceProviderSeeding.cs new file mode 100644 index 0000000000..c87c498d76 --- /dev/null +++ b/tests/shared/Tests.Shared/TestSeeds/ServiceProviderSeeding.cs @@ -0,0 +1,38 @@ +/******************************************************************************** + * Copyright (c) 2021, 2023 BMW Group AG + * Copyright (c) 2021, 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Apache License, Version 2.0 which is available at + * https://www.apache.org/licenses/LICENSE-2.0. + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * SPDX-License-Identifier: Apache-2.0 + ********************************************************************************/ + +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Entities; +using Org.Eclipse.TractusX.Portal.Backend.PortalBackend.PortalEntities.Enums; + +namespace Org.Eclipse.TractusX.Portal.Backend.Tests.Shared.TestSeeds; + +public class ServiceProviderSeeding : IBaseSeeding +{ + public Action SeedData() => dbContext => + { + BaseSeed.SeedBaseData().Invoke(dbContext); + + dbContext.CompanyAssignedRoles.AddRange(new[] + { + new CompanyAssignedRole(new("2dc4249f-b5ca-4d42-bef1-7a7a950a4f87"), CompanyRoleId.SERVICE_PROVIDER) + }); + }; +} diff --git a/tests/shared/Tests.Shared/Tests.Shared.csproj b/tests/shared/Tests.Shared/Tests.Shared.csproj index 98a436ba08..d1525ae029 100644 --- a/tests/shared/Tests.Shared/Tests.Shared.csproj +++ b/tests/shared/Tests.Shared/Tests.Shared.csproj @@ -31,6 +31,7 @@ + @@ -49,11 +50,13 @@ + + - + @@ -64,8 +67,4 @@ - - - -