diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d6afb67..1f10825 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,14 +16,11 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: - java-version: 17 + java-version: 21 distribution: 'temurin' - server-id: github # value of repository/id field of the pom.xml - server-username: GITHUB_USER_REF - server-password: GITHUB_TOKEN_REF - name: Cache Maven packages uses: actions/cache@v4 with: @@ -33,8 +30,6 @@ jobs: - name: Build and analyze env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - GITHUB_USER_REF: ${{ secrets.GH_PACKAGE_REPO_USERNAME }} - GITHUB_TOKEN_REF: ${{ secrets.GH_PACKAGE_REPO_PASSWORD }} run: mvn -B -U verify build: if: github.event_name != 'pull_request' @@ -44,10 +39,10 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: - java-version: 17 + java-version: 21 distribution: 'temurin' server-id: github # value of repository/id field of the pom.xml server-username: GITHUB_USER_REF @@ -67,8 +62,4 @@ jobs: - name: Build and analyze env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any - SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }} - GITHUB_USER_REF: ${{ secrets.GH_PACKAGE_REPO_USERNAME }} - GITHUB_TOKEN_REF: ${{ secrets.GH_PACKAGE_REPO_PASSWORD }} - run: mvn -B -U verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar + run: mvn -B verify diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 2eeb8d6..e9fb544 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -72,10 +72,10 @@ jobs: # echo "Run, Build Application using script" # ./location_of_script_within_repo/buildscript.sh - - name: Set up JDK 17 + - name: Set up JDK 21 uses: actions/setup-java@v4 with: - java-version: 17 + java-version: 21 distribution: 'temurin' server-id: github # value of repository/id field of the pom.xml server-username: GITHUB_USER_REF diff --git a/.github/workflows/publish_docker_3key.yaml b/.github/workflows/publish_docker_3key.yaml new file mode 100644 index 0000000..c39179a --- /dev/null +++ b/.github/workflows/publish_docker_3key.yaml @@ -0,0 +1,75 @@ +name: Publish 3Key Docker image + +on: + push: + branches: [develop] + tags: + - '*' + workflow_dispatch: + +jobs: + push_to_registry: + name: Push Docker images + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Install Cosign + uses: sigstore/cosign-installer@v3.5.0 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to 3Key Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_HUB_3KEY_USERNAME }} + password: ${{ secrets.DOCKER_HUB_3KEY_PASSWORD }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: | + 3keycompany/czertainly-ejbca-ng-connector + tags: | + type=ref,event=tag + type=raw,value=develop-latest + type=sha,prefix=develop-,format=long + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + id: build-and-push + with: + context: . + platforms: linux/amd64,linux/arm64 + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + - name: Sign images with a key + run: | + images="" + for tag in ${TAGS}; do + images+="${tag}@${DIGEST} " + done + cosign sign --yes --key env://COSIGN_PRIVATE_KEY ${images} + env: + TAGS: ${{ steps.meta.outputs.tags }} + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + DIGEST: ${{ steps.build-and-push.outputs.digest }} + + - name: Push README to 3Key Docker Hub + uses: christian-korneck/update-container-description-action@v1 + env: + DOCKER_USER: ${{ secrets.DOCKER_HUB_3KEY_USERNAME }} + DOCKER_PASS: ${{ secrets.DOCKER_HUB_3KEY_PASSWORD }} + with: + destination_container_repo: 3keycompany/czertainly-ejbca-ng-connector + provider: dockerhub diff --git a/.github/workflows/publish_docker_czertainly.yaml b/.github/workflows/publish_docker_czertainly.yaml new file mode 100644 index 0000000..fabd9b8 --- /dev/null +++ b/.github/workflows/publish_docker_czertainly.yaml @@ -0,0 +1,75 @@ +name: Publish CZERTAINLY Docker image + +on: + push: + branches: [develop] + tags: + - '*' + workflow_dispatch: + +jobs: + push_to_registry: + name: Push Docker images + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Install Cosign + uses: sigstore/cosign-installer@v3.5.0 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to CZERTAINLY Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_HUB_CZERTAINLY_USERNAME }} + password: ${{ secrets.DOCKER_HUB_CZERTAINLY_PASSWORD }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: | + czertainly/czertainly-ejbca-ng-connector + tags: | + type=ref,event=tag + type=raw,value=develop-latest + type=sha,prefix=develop-,format=long + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + id: build-and-push + with: + context: . + platforms: linux/amd64,linux/arm64 + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + - name: Sign images with a key + run: | + images="" + for tag in ${TAGS}; do + images+="${tag}@${DIGEST} " + done + cosign sign --yes --key env://COSIGN_PRIVATE_KEY ${images} + env: + TAGS: ${{ steps.meta.outputs.tags }} + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + DIGEST: ${{ steps.build-and-push.outputs.digest }} + + - name: Push README to CZERTAINLY Docker Hub + uses: christian-korneck/update-container-description-action@v1 + env: + DOCKER_USER: ${{ secrets.DOCKER_HUB_CZERTAINLY_USERNAME }} + DOCKER_PASS: ${{ secrets.DOCKER_HUB_CZERTAINLY_PASSWORD }} + with: + destination_container_repo: czertainly/czertainly-ejbca-ng-connector + provider: dockerhub diff --git a/.github/workflows/publish_harbor_3key.yaml b/.github/workflows/publish_harbor_3key.yaml new file mode 100644 index 0000000..51060df --- /dev/null +++ b/.github/workflows/publish_harbor_3key.yaml @@ -0,0 +1,76 @@ +name: Publish 3Key Harbor image + +on: + push: + branches: [develop] + tags: + - '*' + workflow_dispatch: + +jobs: + push_to_registry: + name: Push Docker images + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Install Cosign + uses: sigstore/cosign-installer@v3.5.0 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to 3Key Harbor + uses: docker/login-action@v3 + with: + registry: harbor.3key.company + username: ${{ secrets.HARBOR_3KEY_USERNAME }} + password: ${{ secrets.HARBOR_3KEY_PASSWORD }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: | + harbor.3key.company/czertainly/czertainly-ejbca-ng-connector + tags: | + type=ref,event=tag + type=raw,value=develop-latest + type=sha,prefix=develop-,format=long + + - name: Build and push Docker image + uses: docker/build-push-action@v6 + id: build-and-push + with: + context: . + platforms: linux/amd64,linux/arm64 + file: ./Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + + - name: Sign images with a key + run: | + images="" + for tag in ${TAGS}; do + images+="${tag}@${DIGEST} " + done + cosign sign --yes --key env://COSIGN_PRIVATE_KEY ${images} + env: + TAGS: ${{ steps.meta.outputs.tags }} + COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }} + COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }} + DIGEST: ${{ steps.build-and-push.outputs.digest }} + + - name: Push README to 3Key Harbor + uses: christian-korneck/update-container-description-action@v1 + env: + DOCKER_USER: ${{ secrets.HARBOR_3KEY_USERNAME }} + DOCKER_PASS: ${{ secrets.HARBOR_3KEY_PASSWORD }} + with: + destination_container_repo: harbor.3key.company/czertainly/czertainly-ejbca-ng-connector + provider: harbor2 diff --git a/.github/workflows/test_docker_image.yaml b/.github/workflows/test_docker_image.yaml new file mode 100644 index 0000000..06dde42 --- /dev/null +++ b/.github/workflows/test_docker_image.yaml @@ -0,0 +1,43 @@ +name: Test Docker image + +on: + pull_request: + branches: [ develop ] + workflow_dispatch: + +jobs: + push_to_registry: + name: Build Docker images + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: | + czertainly/czertainly-ejbca-ng-connector + 3keycompany/czertainly-ejbca-ng-connector + harbor.3key.company/czertainly/czertainly-ejbca-ng-connector + tags: | + type=ref,event=tag + type=raw,value=develop-latest + type=sha,prefix=develop-,format=long + + - name: Test build Docker image + uses: docker/build-push-action@v6 + id: build-and-push + with: + context: . + platforms: linux/amd64,linux/arm64 + file: ./Dockerfile + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} \ No newline at end of file diff --git a/.github/workflows/workflow_run_pruner.yml b/.github/workflows/workflow_run_pruner.yml deleted file mode 100644 index 7ecff8b..0000000 --- a/.github/workflows/workflow_run_pruner.yml +++ /dev/null @@ -1,96 +0,0 @@ -# This workflow prunes old workflow runs for an entire repository. - -name: Workflow Run Pruner - -on: - workflow_dispatch: - -jobs: - prune: - runs-on: ubuntu-latest - timeout-minutes: 10 - - steps: - - name: Prune cancelled/skipped runs - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GH_ACTIONS_PERSONAL_ACCESS_TOKEN }} - script: | - const cancelled = await github.actions.listWorkflowRunsForRepo({ - owner: context.repo.owner, - per_page: 100, - repo: context.repo.repo, - status: 'cancelled', - }); - - const skipped = await github.actions.listWorkflowRunsForRepo({ - owner: context.repo.owner, - per_page: 100, - repo: context.repo.repo, - status: 'skipped', - }); - - for (const response of [cancelled, skipped]) { - for (const run of response.data.workflow_runs) { - console.log(`Run id ${run.id} of '${run.name}' is a cancelled/skipped run. Deleting...`); - await github.actions.deleteWorkflowRun({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: run.id - }); - } - } - - - name: Prune runs older than 30 days - uses: actions/github-script@v7 - with: - github-token: ${{ secrets.GH_ACTIONS_PERSONAL_ACCESS_TOKEN }} - script: | - const days_to_expiration = 30; - const ms_in_day = 86400000; - const now = Date.now(); - const pages = 5; - - // we don't want to prune old runs from test.yml - // because we track the duration of runs over time - - const workflows = [ - 'build.yml', - 'publish-package.yml', - 'workflow_run_pruner.yml' - ] - - let runs_to_delete = []; - - for (const workflow of workflows) { - for (let page = 0; page < pages; page += 1) { - let response = await github.actions.listWorkflowRuns({ - owner: context.repo.owner, - page: page, - per_page: 100, - repo: context.repo.repo, - workflow_id: workflow - }); - - if (response.data.workflow_runs.length > 0) { - for (const run of response.data.workflow_runs) { - if (now - Date.parse(run.created_at) > ms_in_day * days_to_expiration) { - runs_to_delete.push([run.id, run.name]); - } - } - } - } - } - - for (const run of runs_to_delete) { - console.log(`Run id ${run[0]} of '${run[1]}' is older than ${days_to_expiration} days. Deleting...`); - try { - await github.actions.deleteWorkflowRun({ - owner: context.repo.owner, - repo: context.repo.repo, - run_id: run[0] - }); - } catch (error) { - // ignore errors - } - } diff --git a/Dockerfile b/Dockerfile index 856d43d..d566adf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,15 +1,13 @@ # Build stage -FROM maven:3.8.7-eclipse-temurin-17 as build +FROM maven:3.9.9-eclipse-temurin-21 as build COPY src /home/app/src COPY pom.xml /home/app COPY settings.xml /root/.m2/settings.xml -ARG SERVER_USERNAME -ARG SERVER_PASSWORD -RUN mvn -f /home/app/pom.xml clean package COPY docker /home/app/docker +RUN mvn -f /home/app/pom.xml clean package # Package stage -FROM eclipse-temurin:17.0.10_7-jre-alpine +FROM eclipse-temurin:21.0.4_7-jre-alpine MAINTAINER CZERTAINLY diff --git a/README.md b/README.md index d3a9556..bd60070 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # CZERTAINLY EJBCA NG Connector -> This repository is part of the open-source project CZERTAINLY. You can find more information about the project at [CZERTAINLY](https://github.com/3KeyCompany/CZERTAINLY) repository, including the contribution guide. +> This repository is part of the open-source project CZERTAINLY. You can find more information about the project at [CZERTAINLY](https://github.com/CZERTAINLY/CZERTAINLY) repository, including the contribution guide. EJBCA NG `Connector` is the implementation of the following `Function Groups` and `Kinds`: @@ -33,7 +33,7 @@ EJBCA NG works under the principle of `RA Profiles`. The `Connector` provides th With the help of `RA Profiles` and the CSR information provided by the `Client` using the REST API, the `Connector` communicates with the `Authority` to get the `Certificate`. -To know more about the `Core`, refer to [CZERTAINLY Core](https://github.com/3KeyCompany/CZERTAINLY-Core) +To know more about the `Core`, refer to [CZERTAINLY Core](https://github.com/CZERTAINLY/CZERTAINLY-Core) ## Certificate Discovery @@ -76,13 +76,13 @@ For discovering `Certificates` from the EJBCA, the following attributes can be u ## Interfaces -EJBCA NG `Connector` implements `v2 Authority Provider` and `Discovery Provider` interfaces. To learn more about the interfaces and end points, refer to the [CZERTAINLY Interfaces](https://github.com/3KeyCompany/CZERTAINLY-Interfaces). +EJBCA NG `Connector` implements `v2 Authority Provider` and `Discovery Provider` interfaces. To learn more about the interfaces and end points, refer to the [CZERTAINLY Interfaces](https://github.com/CZERTAINLY/CZERTAINLY-Interfaces). For more information, please refer to the [CZERTAINLY documentation](https://docs.czertainly.com). ## Docker container -EJBCA NG `Connector` is provided as a Docker container. Use the `docker pull harbor.3key.company/czertainly/czertainly-ejbca-ng-connector:tagname` to pull the required image from the repository. It can be configured using the following environment variables: +EJBCA NG `Connector` is provided as a Docker container. Use the `docker pull czertainly/czertainly-ejbca-ng-connector:tagname` to pull the required image from the repository. It can be configured using the following environment variables: | Variable | Description | Required | Default value | |--------------------------|--------------------------------------------------------|----------------------------------------------------|---------------| diff --git a/pom.xml b/pom.xml index db8f6dc..a7a92a3 100644 --- a/pom.xml +++ b/pom.xml @@ -7,26 +7,21 @@ com.czertainly dependencies - 1.1.0 + 1.2.0 ejbca-ng-connector - 1.9.0 + 1.10.0 CZERTAINLY-EJBCA-NG-Connector - 17 - 3KeyCompany_CZERTAINLY-EJBCA-NG-Connector + 21 + czertainly + https://sonarcloud.io + CZERTAINLY_CZERTAINLY-EJBCA-NG-Connector - - github - https://maven.pkg.github.com/3keycompany/* - - true - - ossrh https://s01.oss.sonatype.org/content/repositories/snapshots @@ -34,11 +29,10 @@ - com.czertainly interfaces - 2.12.0 + 2.13.0 @@ -107,6 +101,11 @@ flyway-core + + org.flywaydb + flyway-database-postgresql + + org.apache.commons commons-lang3 @@ -195,6 +194,13 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + @{argLine} + + org.jacoco jacoco-maven-plugin diff --git a/settings.xml b/settings.xml index c74062f..ccf5a06 100644 --- a/settings.xml +++ b/settings.xml @@ -15,13 +15,6 @@ ossrh-releases https://s01.oss.sonatype.org/content/repositories/releases - - github - https://maven.pkg.github.com/3keycompany/* - - true - - @@ -29,8 +22,6 @@ github - ${env.SERVER_USERNAME} - ${env.SERVER_PASSWORD} diff --git a/src/main/java/com/czertainly/ca/connector/ejbca/api/CertificateControllerImpl.java b/src/main/java/com/czertainly/ca/connector/ejbca/api/CertificateControllerImpl.java index 18638af..30c2ede 100644 --- a/src/main/java/com/czertainly/ca/connector/ejbca/api/CertificateControllerImpl.java +++ b/src/main/java/com/czertainly/ca/connector/ejbca/api/CertificateControllerImpl.java @@ -30,6 +30,11 @@ public class CertificateControllerImpl implements CertificateController { public static final String ATTRIBUTE_EMAIL_LABEL = "Email"; public static final String ATTRIBUTE_SAN_LABEL = "Subject Alternative Name"; public static final String ATTRIBUTE_EXTENSION_LABEL = "Extension Data"; + + public static final String ATTRIBUTE_EMAIL_UUID = "0b378474-ebe9-4a17-9d3d-0577eb16aa34"; + public static final String ATTRIBUTE_SAN_UUID = "2cfd8c1a-e867-42f1-ab6c-67fb1964e163"; + public static final String ATTRIBUTE_EXTENSION_UUID = "72324d22-12cb-47ee-a02e-0b1da2013eee"; + private CertificateEjbcaService certificateEjbcaService; @Autowired @@ -42,7 +47,7 @@ public List listIssueCertificateAttributes(String uuid) { List attrs = new ArrayList<>(); DataAttribute email = new DataAttribute(); - email.setUuid("0b378474-ebe9-4a17-9d3d-0577eb16aa34"); + email.setUuid(ATTRIBUTE_EMAIL_UUID); email.setName(ATTRIBUTE_EMAIL); email.setDescription("End Entity email address"); email.setType(AttributeType.DATA); @@ -58,7 +63,7 @@ public List listIssueCertificateAttributes(String uuid) { attrs.add(email); DataAttribute san = new DataAttribute(); - san.setUuid("2cfd8c1a-e867-42f1-ab6c-67fb1964e163"); + san.setUuid(ATTRIBUTE_SAN_UUID); san.setName(ATTRIBUTE_SAN); san.setDescription("Comma separated Subject Alternative Names"); san.setType(AttributeType.DATA); @@ -74,9 +79,9 @@ public List listIssueCertificateAttributes(String uuid) { attrs.add(san); DataAttribute extension = new DataAttribute(); - extension.setUuid("72324d22-12cb-47ee-a02e-0b1da2013eee"); + extension.setUuid(ATTRIBUTE_EXTENSION_UUID); extension.setName(ATTRIBUTE_EXTENSION); - extension.setDescription("Comma separated Extension Data"); + extension.setDescription("Comma separated Extension Data in the format OID=Value"); extension.setType(AttributeType.DATA); extension.setContentType(AttributeContentType.STRING); DataAttributeProperties extensionProperties = new DataAttributeProperties(); diff --git a/src/main/java/com/czertainly/ca/connector/ejbca/service/impl/AttributeServiceImpl.java b/src/main/java/com/czertainly/ca/connector/ejbca/service/impl/AttributeServiceImpl.java index 5c1a50d..1e4a7d0 100644 --- a/src/main/java/com/czertainly/ca/connector/ejbca/service/impl/AttributeServiceImpl.java +++ b/src/main/java/com/czertainly/ca/connector/ejbca/service/impl/AttributeServiceImpl.java @@ -27,19 +27,29 @@ public class AttributeServiceImpl implements AttributeService { private static final Logger logger = LoggerFactory.getLogger(AttributesController.class); + public static final String DATA_ATTRIBUTE_URL_NAME = "url"; + public static final String DATA_ATTRIBUTE_URL_UUID = "87e968ca-9404-4128-8b58-3ab5db2ba06e"; + public static final String DATA_ATTRIBUTE_URL_LABEL = "EJBCA WS URL"; + public static final String DATA_ATTRIBUTE_URL_DESCRIPTION = "URL of EJBCA web services"; + + public static final String DATA_ATTRIBUTE_CREDENTIAL_NAME = "credential"; + public static final String DATA_ATTRIBUTE_CREDENTIAL_UUID = "9379ca2c-aa51-42c8-8afd-2a2d16c99c57"; + public static final String DATA_ATTRIBUTE_CREDENTIAL_LABEL = "Credential"; + public static final String DATA_ATTRIBUTE_CREDENTIAL_DESCRIPTION = "SoftKeyStore Credential representing EJBCA administrator for the communication"; + @Override public List getAttributes(String kind) { logger.debug("Getting the attributes for {}", kind); List attrs = new ArrayList<>(); DataAttribute url = new DataAttribute(); - url.setUuid("87e968ca-9404-4128-8b58-3ab5db2ba06e"); - url.setName("url"); - url.setDescription("URL of EJBCA web services"); + url.setUuid(DATA_ATTRIBUTE_URL_UUID); + url.setName(DATA_ATTRIBUTE_URL_NAME); + url.setDescription(DATA_ATTRIBUTE_URL_DESCRIPTION); url.setType(AttributeType.DATA); url.setContentType(AttributeContentType.STRING); DataAttributeProperties urlProperties = new DataAttributeProperties(); - urlProperties.setLabel("EJBCA WS URL"); + urlProperties.setLabel(DATA_ATTRIBUTE_URL_LABEL); urlProperties.setRequired(true); urlProperties.setReadOnly(false); urlProperties.setVisible(true); @@ -49,13 +59,13 @@ public List getAttributes(String kind) { attrs.add(url); DataAttribute credential = new DataAttribute(); - credential.setUuid("9379ca2c-aa51-42c8-8afd-2a2d16c99c57"); - credential.setName("credential"); - credential.setDescription("SoftKeyStore Credential representing EJBCA administrator for the communication"); + credential.setUuid(DATA_ATTRIBUTE_CREDENTIAL_UUID); + credential.setName(DATA_ATTRIBUTE_CREDENTIAL_NAME); + credential.setDescription(DATA_ATTRIBUTE_CREDENTIAL_DESCRIPTION); credential.setType(AttributeType.DATA); credential.setContentType(AttributeContentType.CREDENTIAL); DataAttributeProperties credentialProperties = new DataAttributeProperties(); - credentialProperties.setLabel("Credential"); + credentialProperties.setLabel(DATA_ATTRIBUTE_CREDENTIAL_LABEL); credentialProperties.setRequired(true); credentialProperties.setReadOnly(false); credentialProperties.setVisible(true); diff --git a/src/main/java/com/czertainly/ca/connector/ejbca/service/impl/DiscoveryServiceImpl.java b/src/main/java/com/czertainly/ca/connector/ejbca/service/impl/DiscoveryServiceImpl.java index 7e98f98..19b8e8e 100644 --- a/src/main/java/com/czertainly/ca/connector/ejbca/service/impl/DiscoveryServiceImpl.java +++ b/src/main/java/com/czertainly/ca/connector/ejbca/service/impl/DiscoveryServiceImpl.java @@ -35,6 +35,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; @@ -105,7 +106,7 @@ public DiscoveryProviderDto getProviderDtoData(DiscoveryDataRequestDto request, dto.setCertificateData(new ArrayList<>()); dto.setTotalCertificatesDiscovered(0); } else { - Pageable page = PageRequest.of(request.getPageNumber() <= 0 ? 0 : request.getPageNumber() - 1, request.getItemsPerPage()); + Pageable page = PageRequest.of(request.getPageNumber() <= 0 ? 0 : request.getPageNumber() - 1, request.getItemsPerPage(), Sort.by(Sort.Direction.ASC, "id")); dto.setCertificateData(certificateRepository.findAllByDiscoveryId(history.getId(), page).stream().map(Certificate::mapToDto).collect(Collectors.toList())); } return dto; diff --git a/src/main/java/com/czertainly/ca/connector/ejbca/service/impl/EjbcaServiceImpl.java b/src/main/java/com/czertainly/ca/connector/ejbca/service/impl/EjbcaServiceImpl.java index 98f6898..5ebe1a8 100644 --- a/src/main/java/com/czertainly/ca/connector/ejbca/service/impl/EjbcaServiceImpl.java +++ b/src/main/java/com/czertainly/ca/connector/ejbca/service/impl/EjbcaServiceImpl.java @@ -30,7 +30,6 @@ import org.springframework.web.reactive.function.client.WebClient; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -275,7 +274,7 @@ private void prepareEndEntity(UserDataVOWS user, List raPro } String extension = AttributeDefinitionUtils.getSingleItemAttributeContentValue(CertificateControllerImpl.ATTRIBUTE_EXTENSION, issueAttrs, StringAttributeContent.class).getData(); - setUserExtensions(user, extension); + EjbcaUtils.setUserExtensions(user, extension); } private void prepareEndEntityWithMeta(UserDataVOWS user, List raProfileAttrs, List metadata) { @@ -292,7 +291,7 @@ private void prepareEndEntityWithMeta(UserDataVOWS user, List raProfileAttrs) { @@ -324,19 +323,4 @@ private void setUserProfiles(UserDataVOWS user, List raProf user.setKeyRecoverable(keyRecoverable); } - private void setUserExtensions(UserDataVOWS user, String extension) { - if (StringUtils.isNotBlank(extension)) { - List ei = new ArrayList<>(); - String[] extensions = extension.split(",[ ]*"); // remove spaces after the comma - for (String data : extensions) { - String[] extValue = data.split("=", 2); // split the string using = to 2 values - // TODO: validation of the data - ExtendedInformationWS eiWs = new ExtendedInformationWS(); - eiWs.setName(extValue[0]); - eiWs.setValue(extValue[1]); - ei.add(eiWs); - } - user.getExtendedInformation().addAll(ei); - } - } } diff --git a/src/main/java/com/czertainly/ca/connector/ejbca/util/EjbcaUtils.java b/src/main/java/com/czertainly/ca/connector/ejbca/util/EjbcaUtils.java index e5b1621..aa17bd6 100644 --- a/src/main/java/com/czertainly/ca/connector/ejbca/util/EjbcaUtils.java +++ b/src/main/java/com/czertainly/ca/connector/ejbca/util/EjbcaUtils.java @@ -1,13 +1,13 @@ package com.czertainly.ca.connector.ejbca.util; +import com.czertainly.api.exception.ValidationException; import com.czertainly.api.model.core.authority.EndEntityDto; import com.czertainly.api.model.core.authority.EndEntityExtendedInfoDto; import com.czertainly.api.model.core.authority.EndEntityStatus; -import com.czertainly.ca.connector.ejbca.ws.MatchType; -import com.czertainly.ca.connector.ejbca.ws.MatchWith; -import com.czertainly.ca.connector.ejbca.ws.UserDataVOWS; -import com.czertainly.ca.connector.ejbca.ws.UserMatch; +import com.czertainly.ca.connector.ejbca.ws.*; +import org.apache.commons.lang3.StringUtils; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -45,4 +45,31 @@ public static EndEntityDto mapToUserDetailDTO(UserDataVOWS userDataVOWS) { } return userDetailDTO; } + + public static void setUserExtensions(UserDataVOWS user, String extension) { + if (StringUtils.isNotBlank(extension)) { + List ei = new ArrayList<>(); + String[] extensions = extension.split(", *"); // remove spaces after the comma + for (String data : extensions) { + String[] extValue = data.split("=", 2); // split the string using = to 2 values + + // Validation of the data + if (extValue.length != 2) { + throw new ValidationException("Invalid extension format: " + data); + } + + String key = extValue[0].trim(); + String value = extValue[1].trim(); + + OidUtils.validateOidFormat(key); + + ExtendedInformationWS eiWs = new ExtendedInformationWS(); + eiWs.setName(key); + eiWs.setValue(value); + ei.add(eiWs); + } + user.getExtendedInformation().addAll(ei); + } + } + } diff --git a/src/main/java/com/czertainly/ca/connector/ejbca/util/OidUtils.java b/src/main/java/com/czertainly/ca/connector/ejbca/util/OidUtils.java new file mode 100644 index 0000000..83f8925 --- /dev/null +++ b/src/main/java/com/czertainly/ca/connector/ejbca/util/OidUtils.java @@ -0,0 +1,25 @@ +package com.czertainly.ca.connector.ejbca.util; + +import com.czertainly.api.exception.ValidationException; +import org.apache.commons.lang3.StringUtils; + +public class OidUtils { + + // Validation method for OID + public static void validateOidFormat(String oid) { + // OID should be a series of integers separated by dots + if (StringUtils.isBlank(oid)) { + throw new ValidationException("OID cannot be empty"); + } + String[] parts = oid.split("\\."); + if (parts.length == 0) { + throw new ValidationException("OID cannot be empty"); + } + for (String part : parts) { + if (!part.matches("\\d+")) { + throw new ValidationException("OID should be a series of integers separated by dots"); + } + } + } + +} diff --git a/src/test/java/com/czertainly/ca/connector/ejbca/util/EjbcaUtilsTest.java b/src/test/java/com/czertainly/ca/connector/ejbca/util/EjbcaUtilsTest.java new file mode 100644 index 0000000..60b8da0 --- /dev/null +++ b/src/test/java/com/czertainly/ca/connector/ejbca/util/EjbcaUtilsTest.java @@ -0,0 +1,69 @@ +package com.czertainly.ca.connector.ejbca.util; + +import com.czertainly.api.exception.ValidationException; +import com.czertainly.ca.connector.ejbca.ws.UserDataVOWS; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class EjbcaUtilsTest { + + private static final String username = "test"; + private static final String password = "test"; + private static final String subjectDn = "CN=test"; + + private UserDataVOWS userData; + + @BeforeEach + public void setUp() { + userData = new UserDataVOWS(); + userData.setUsername(username); + userData.setPassword(password); + userData.setSubjectDN(subjectDn); + } + + @Test + public void setUserExtensions_WrongData() { + String extensions = "wrong_extensions"; + ValidationException ex = Assertions.assertThrows( + ValidationException.class, + () -> EjbcaUtils.setUserExtensions(userData, extensions) + ); + Assertions.assertEquals("Invalid extension format: " + extensions, ex.getMessage()); + } + + @Test + public void setUserExtensions_Ok() { + String extensions = "1.1.1.1.1=sample extension"; + Assertions.assertDoesNotThrow(() -> EjbcaUtils.setUserExtensions(userData, extensions)); + } + + @Test + public void setUserExtensions_NotOk() { + String extensions = "my extension=sample extension"; + ValidationException ex = Assertions.assertThrows( + ValidationException.class, + () -> EjbcaUtils.setUserExtensions(userData, extensions) + ); + Assertions.assertEquals("OID should be a series of integers separated by dots", ex.getMessage()); + } + + @Test + public void setUserExtensions_Ok_Multiple() { + String extensions = "1.1.1.1.1=sample extension,2.2.2.2=something, 3.3.3=third one"; + Assertions.assertDoesNotThrow(() -> EjbcaUtils.setUserExtensions(userData, extensions)); + } + + @Test + public void setUserExtensions_NotOk_Multiple() { + String extensions = "1.1.1.1.1=sample extension,2.2.2.2=something,=third one"; + ValidationException ex = Assertions.assertThrows( + ValidationException.class, + () -> EjbcaUtils.setUserExtensions(userData, extensions) + ); + Assertions.assertEquals("OID cannot be empty", ex.getMessage()); + } + +}