diff --git a/.ci/openshift-ci/Dockerfile b/.ci/openshift-ci/Dockerfile index 5e641f593ec..0f2a70e5089 100644 --- a/.ci/openshift-ci/Dockerfile +++ b/.ci/openshift-ci/Dockerfile @@ -11,13 +11,17 @@ # Red Hat, Inc. - initial API and implementation # Dockerfile to bootstrap build and test in openshift-ci -FROM registry.ci.openshift.org/openshift/release:golang-1.18 +FROM registry.ci.openshift.org/openshift/release:golang-1.20 # hadolint ignore=DL3002 USER 0 SHELL ["/bin/bash", "-c"] -# Install yq, kubectl, chectl cli used by olm/olm.sh script. +# Temporary workaround since mirror.centos.org is down and can be replaced with vault.centos.org +RUN sed -i s/mirror.centos.org/vault.centos.org/g /etc/yum.repos.d/*.repo && \ + sed -i s/^#.*baseurl=http/baseurl=http/g /etc/yum.repos.d/*.repo && \ + sed -i s/^mirrorlist=http/#mirrorlist=http/g /etc/yum.repos.d/*.repo + # hadolint ignore=DL3041 # Install yq, kubectl, chectl cli. RUN yum install --assumeyes -d1 psmisc python3-pip httpd-tools nodejs && \ diff --git a/.ci/openshift-ci/ca.crt b/.ci/openshift-ci/ca.crt index 66dfa1513c6..cfc8d11155d 100644 --- a/.ci/openshift-ci/ca.crt +++ b/.ci/openshift-ci/ca.crt @@ -1,19 +1,19 @@ -----BEGIN CERTIFICATE----- MIIDDDCCAfSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAmMSQwIgYDVQQDDBtpbmdy -ZXNzLW9wZXJhdG9yQDE3MDI5MTczMzkwHhcNMjMxMjE4MTYzNTM4WhcNMjUxMjE3 -MTYzNTM5WjAmMSQwIgYDVQQDDBtpbmdyZXNzLW9wZXJhdG9yQDE3MDI5MTczMzkw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDDVfcIyqKLYcrmieDOCvAq -RKFR0PXqjl54DyfhkP7TC/rA4WMaJgYamYNNRcy4rOb+i3P1yepeVlXj+4gka41W -fh1QghqDGPpQTF7I8SDhnz3qdQymgSSkrxBWOGgJZRG3Gd50CNeh+ADdNlOxzZhJ -c1HI9/36Zp0M7RHLNwrYzKf/Scrcl3t/Ps4KHKsNtjNygnPSTjGwza5TTTAvGQ+h -3+c+bKJHZp6VFoEpubmBfCG8x+vJwBNI+YauXwti9EqFutnAbOiQ4aOvqQYINngV -OQTOg3xnifnooaR2iZPOtxPLUDMydHP9sDVNGLICpnKcFN6x5JKq1fAuwOAMRMQr +ZXNzLW9wZXJhdG9yQDE3MjQwNzU0NzgwHhcNMjQwODE5MTM1MTE3WhcNMjYwODE5 +MTM1MTE4WjAmMSQwIgYDVQQDDBtpbmdyZXNzLW9wZXJhdG9yQDE3MjQwNzU0Nzgw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCgfFNrBJSxPGNjA0S5+DUa +Usqks86BMzzK7f6Fln90Aiw6eHgznvLlYLsUZUpCrbuOR2DbLy4NF4z1QMKwakY4 +ClyNuxFUB823EzxdWsQx10LYeOglRWivNPln4n9TkCmet2fpSquI6LVnvFAJWWjE +exAZt0JjOLfcLChNTZCdyxBp/v2eVM1uNj9F/EeQp+8IjZ4FJREuyQnFAA55DzYH +qqsmscjLjWZeamZCKPufBWkHmxAfhxqSDiM9rfr9AuDQbgU85q4TKA9xpQmRHTsQ +PNv7IeKAexrhin467FbIfbVcUOMxPe80i/wWW3Z9T/c9UNDkyfg26Y/u94/irk1h AgMBAAGjRTBDMA4GA1UdDwEB/wQEAwICpDASBgNVHRMBAf8ECDAGAQH/AgEAMB0G -A1UdDgQWBBRQfZkM6C20GT1YiTiS694vRPP2MDANBgkqhkiG9w0BAQsFAAOCAQEA -qd9ug2Bo2s3CG0qjNtk19WYYAuwkEMZU9L/FbSfmPkkOTbrZB71ofZbVan8Dx9/W -SfbkN1eJQNxd7QIDlExKEW2ufJIMCDe6haLHV8x+z3fISHg3wMUCPtadLG/exafy -agfj8PQgnJ1sbA/fKl0rqcVYuIihZ0DdMe28Yj92BFcs5+TXzVYzjyeIGonUZrCR -LtW2CxV7Ngue9pkWGsqQSlRwzp3knYfZxeQLneZcGlsSV4r9LOKIAA/MhBGgpwVc -zrh7L93jajHD1AD0sRdUQp4VVhysXVmMMSAjaLkqHsvSyJp7yKJOmrts6a274qbU -J1E6cXqtfF0NwRo2D9BouQ== +A1UdDgQWBBRUi8oPN5+mKa/wzaBztYbxY8QvFDANBgkqhkiG9w0BAQsFAAOCAQEA +I5kiJUCNW/+7Pu7emoNb5H0L774AIccDmiXk4aS7Kbkhno9IuErdmYEbntjqllbt +kFYSlcccW7AZm2EQ5SWOjhdMuDnF1krngv4ffHTN3RxIp0TbSLV8bqjQrwHxDf0s +n8XYTrB3mIdmb0BK7SsaH5rubFCDWLxOMe9CT5SZsN258CoOm9fxOfE2SnxzLxjZ +2rTUozKXLB+xPyezZsQ2hGPbQJzf7KRBITqOJbaTRDDDxfXr/IosYvGaPGpOC/aw +4t2kbdLTJeiQAlZPwup9NXTPze+MEyAo2udooMACcxdiOEQc00bXvzKF0Qlvy6wC +v2ib/BnCyVIZ3On5CwwJSA== -----END CERTIFICATE----- diff --git a/.ci/openshift-ci/common.sh b/.ci/openshift-ci/common.sh index b13b8d948f3..d4fcee43821 100644 --- a/.ci/openshift-ci/common.sh +++ b/.ci/openshift-ci/common.sh @@ -31,7 +31,7 @@ export PUBLIC_REPO_WORKSPACE_NAME=${PUBLIC_REPO_WORKSPACE_NAME:-"public-repo-wks export PRIVATE_REPO_WORKSPACE_NAME=${PRIVATE_REPO_WORKSPACE_NAME:-"private-repo-wksp-testname"} export PUBLIC_PROJECT_NAME=${PUBLIC_PROJECT_NAME:-"public-repo"} export PRIVATE_PROJECT_NAME=${PRIVATE_PROJECT_NAME:-"private-repo"} -export YAML_FILE_NAME=${YAML_FILE_NAME:-"devfile.yaml"} +export TEST_FILE_NAME=${TEST_FILE_NAME:-"Date.txt"} export CUSTOM_CONFIG_MAP_NAME=${CUSTOM_CONFIG_MAP_NAME:-"custom-ca-certificates"} export GIT_SSL_CONFIG_MAP_NAME=${GIT_SSL_CONFIG_MAP_NAME:-"che-self-signed-cert"} @@ -376,10 +376,10 @@ testProjectIsCloned() { OCP_USER_NAMESPACE=$2 WORKSPACE_POD_NAME=$(oc get pods -n ${OCP_USER_NAMESPACE} | grep workspace | awk '{print $1}') - if oc exec -it -n ${OCP_USER_NAMESPACE} ${WORKSPACE_POD_NAME} -- test -f /projects/${PROJECT_NAME}/${YAML_FILE_NAME}; then - echo "======= [INFO] Project file /projects/${PROJECT_NAME}/${YAML_FILE_NAME} exists. =======" + if oc exec -it -n ${OCP_USER_NAMESPACE} ${WORKSPACE_POD_NAME} -- test -f /projects/${PROJECT_NAME}/${TEST_FILE_NAME}; then + echo "======= [INFO] Project file /projects/${PROJECT_NAME}/${TEST_FILE_NAME} exists. =======" else - echo "======= [INFO] Project file /projects/${PROJECT_NAME}/${YAML_FILE_NAME} is absent. =======" + echo "======= [INFO] Project file /projects/${PROJECT_NAME}/${TEST_FILE_NAME} is absent. =======" return 1 fi } @@ -555,12 +555,13 @@ testCloneGitRepoNoProjectExists() { runTestWorkspaceWithGitRepoUrl ${WS_NAME} ${PROJECT_NAME} ${GIT_REPO_URL} ${OCP_USER_NAMESPACE} echo "------- [INFO] Check the private repository is NOT cloned with NO PAT/OAuth setup. -------" testProjectIsCloned ${PROJECT_NAME} ${OCP_USER_NAMESPACE} && \ - { echo "####### [ERROR] Project file /projects/${PROJECT_NAME}/${YAML_FILE_NAME} should NOT be present. ####### + { echo "####### [ERROR] Project file /projects/${PROJECT_NAME}/${TEST_FILE_NAME} should NOT be present. ####### ####### Cause possible: PR code regress or service is changed. Need to investigate it. #######" && exit 1; } - echo "======= [INFO] Project file /projects/${PROJECT_NAME}/${YAML_FILE_NAME} is NOT present. This is EXPECTED. =======" + echo "======= [INFO] Project file /projects/${PROJECT_NAME}/${TEST_FILE_NAME} is NOT present. This is EXPECTED. =======" } -# Test that the repository is cloned when PAT, OAuth or SSH is configured +# Verify that a public repository is cloned without requiring PAT, OAuth, or SSH configuration. +# Verify that a public or private repository is cloned when PAT, OAuth, or SSH configuration is provided. testCloneGitRepoProjectShouldExists() { WS_NAME=$1 PROJECT_NAME=$2 @@ -570,7 +571,7 @@ testCloneGitRepoProjectShouldExists() { runTestWorkspaceWithGitRepoUrl ${WS_NAME} ${PROJECT_NAME} ${GIT_REPO_URL} ${OCP_USER_NAMESPACE} echo "------- [INFO] Check the repository is cloned. -------" testProjectIsCloned ${PROJECT_NAME} ${OCP_USER_NAMESPACE} || \ - { echo "####### [ERROR] Project file /projects/${PROJECT_NAME}/${YAML_FILE_NAME} should be present. ####### + { echo "####### [ERROR] Project file /projects/${PROJECT_NAME}/${TEST_FILE_NAME} should be present. ####### ###### Cause possible: PR code regress or service is changed. Need to investigate it. #######" && exit 1; } } diff --git a/.ci/openshift-ci/test-gitlab-no-pat-oauth-flow.sh b/.ci/openshift-ci/test-gitlab-no-pat-oauth-flow.sh index 34628547a5c..826c29c5803 100644 --- a/.ci/openshift-ci/test-gitlab-no-pat-oauth-flow.sh +++ b/.ci/openshift-ci/test-gitlab-no-pat-oauth-flow.sh @@ -21,6 +21,12 @@ echo "======= [INFO] OpenShift CI infrastructure is ready. Running test. ======= export PUBLIC_REPO_URL=${PUBLIC_REPO_URL:-"https://gitlab.com/chepullreq1/public-repo.git"} export PRIVATE_REPO_URL=${PRIVATE_REPO_URL:-"https://gitlab.com/chepullreq1/private-repo.git"} +export PUBLIC_REPO_WITH_DOT_DEFILE_URL=${PUBLIC_REPO_WITH_DOT_DEFILE_URL:-"https://gitlab.com/chepullreq1/public-repo-dot-devfile.git"} +export PRIVATE_REPO_WITH_DOT_DEFILE_URL=${PRIVATE_REPO_WITH_DOT_DEFILE_URL:-"https://gitlab.com/chepullreq1/private-repo-dot-devfile.git"} + +export NAME_OF_PUBLIC_REPO_WITH_DOT_DEFILE=${NAME_OF_PUBLIC_REPO_WITH_DOT_DEFILE:-"public-repo-dot-devfile"} +export NAME_OF_PRIVATE_REPO_WITH_DOT_DEFILE=${NAME_OF_PRIVATE_REPO_WITH_DOT_DEFILE:-"private-repo-dot-devfile"} + # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh @@ -29,7 +35,20 @@ trap "catchFinish" EXIT SIGINT setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} testFactoryResolverNoPatOAuth ${PUBLIC_REPO_URL} ${PRIVATE_REPO_URL} +testFactoryResolverNoPatOAuth ${PUBLIC_REPO_WITH_DOT_DEFILE_URL} ${PRIVATE_REPO_WITH_DOT_DEFILE_URL} + +echo "------- [INFO] Check clone a public repository without PAT -------" testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} + +echo "------- [INFO] Check clone a public repository with .devfile.yaml and without PAT -------" +testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${NAME_OF_PUBLIC_REPO_WITH_DOT_DEFILE} ${PUBLIC_REPO_WITH_DOT_DEFILE_URL} ${USER_CHE_NAMESPACE} +deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} + +echo "------- [INFO] Check clone a private repository without PAT is not available -------" testCloneGitRepoNoProjectExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_URL} ${USER_CHE_NAMESPACE} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} + +echo "------- [INFO] Check clone a private repository with .devfile.yaml and without PAT is not available -------" +testCloneGitRepoNoProjectExists ${PRIVATE_REPO_WORKSPACE_NAME} ${NAME_OF_PRIVATE_REPO_WITH_DOT_DEFILE} ${PRIVATE_REPO_WITH_DOT_DEFILE_URL} ${USER_CHE_NAMESPACE} +deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} diff --git a/.ci/openshift-ci/test-gitlab-with-pat-setup-flow.sh b/.ci/openshift-ci/test-gitlab-with-pat-setup-flow.sh index 24125b9aefa..d08895f88f0 100644 --- a/.ci/openshift-ci/test-gitlab-with-pat-setup-flow.sh +++ b/.ci/openshift-ci/test-gitlab-with-pat-setup-flow.sh @@ -25,6 +25,12 @@ export GIT_PROVIDER_URL=${GIT_PROVIDER_URL:-"https://gitlab.com"} export PRIVATE_REPO_SSH_URL=${PRIVATE_REPO_SSH_URL:-"git@gitlab.com:chepullreq1/private-repo.git"} export PRIVATE_REPO_RAW_PATH_URL=${PRIVATE_REPO_URL:-"https://gitlab.com/chepullreq1/private-repo/-/raw/main/devfile.yaml"} +export PUBLIC_REPO_WITH_DOT_DEFILE_URL=${PUBLIC_REPO_WITH_DOT_DEFILE_URL:-"https://gitlab.com/chepullreq1/public-repo-dot-devfile.git"} +export PRIVATE_REPO_WITH_DOT_DEFILE_URL=${PRIVATE_REPO_WITH_DOT_DEFILE_URL:-"https://gitlab.com/chepullreq1/private-repo-dot-devfile.git"} + +export NAME_OF_PUBLIC_REPO_WITH_DOT_DEFILE=${NAME_OF_PUBLIC_REPO_WITH_DOT_DEFILE:-"public-repo-dot-devfile"} +export NAME_OF_PRIVATE_REPO_WITH_DOT_DEFILE=${NAME_OF_PRIVATE_REPO_WITH_DOT_DEFILE:-"private-repo-dot-devfile"} + # import common test functions SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" source "${SCRIPT_DIR}"/common.sh @@ -35,17 +41,28 @@ setupTestEnvironment ${OCP_NON_ADMIN_USER_NAME} setupPersonalAccessToken ${GIT_PROVIDER_TYPE} ${GIT_PROVIDER_URL} ${GITLAB_PAT} requestProvisionNamespace testFactoryResolverWithPatOAuth ${PUBLIC_REPO_URL} ${PRIVATE_REPO_URL} +testFactoryResolverWithPatOAuth ${PUBLIC_REPO_WITH_DOT_DEFILE_URL} ${PRIVATE_REPO_WITH_DOT_DEFILE_URL} echo "------- [INFO] Check clone public repository with PAT setup -------" testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${PUBLIC_PROJECT_NAME} ${PUBLIC_REPO_URL} ${USER_CHE_NAMESPACE} testGitCredentialsData ${USER_CHE_NAMESPACE} ${GITLAB_PAT} ${GIT_PROVIDER_URL} deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} +echo "------- [INFO] Check clone public repository that has .devfile.yaml and with PAT setup -------" +testCloneGitRepoProjectShouldExists ${PUBLIC_REPO_WORKSPACE_NAME} ${NAME_OF_PUBLIC_REPO_WITH_DOT_DEFILE} ${PUBLIC_REPO_WITH_DOT_DEFILE_URL} ${USER_CHE_NAMESPACE} +testGitCredentialsData ${USER_CHE_NAMESPACE} ${GITLAB_PAT} ${GIT_PROVIDER_URL} +deleteTestWorkspace ${PUBLIC_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} + echo "------- [INFO] Check clone private repository with PAT setup -------" testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_URL} ${USER_CHE_NAMESPACE} testGitCredentialsData ${USER_CHE_NAMESPACE} ${GITLAB_PAT} ${GIT_PROVIDER_URL} deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} +echo "------- [INFO] Check clone private repository that has .devfile.yaml and with PAT setup -------" +testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${NAME_OF_PRIVATE_REPO_WITH_DOT_DEFILE} ${PRIVATE_REPO_WITH_DOT_DEFILE_URL} ${USER_CHE_NAMESPACE} +testGitCredentialsData ${USER_CHE_NAMESPACE} ${GITLAB_PAT} ${GIT_PROVIDER_URL} +deleteTestWorkspace ${PRIVATE_REPO_WORKSPACE_NAME} ${USER_CHE_NAMESPACE} + echo "------- [INFO] Check clone private repository by raw devfile URL with PAT setup -------" testFactoryResolverResponse ${PRIVATE_REPO_RAW_PATH_URL} 200 testCloneGitRepoProjectShouldExists ${PRIVATE_REPO_WORKSPACE_NAME} ${PRIVATE_PROJECT_NAME} ${PRIVATE_REPO_RAW_PATH_URL} ${USER_CHE_NAMESPACE} diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5fa797a26d0..e564fb35b30 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -38,6 +38,8 @@ Pull Request Policy: https://github.com/eclipse/che/wiki/Development-Workflow#pu - [ ] [Relevant contributing documentation updated](https://github.com/eclipse/che/blob/master/CONTRIBUTING.md#relevant-contributing-documentation-updated) - [ ] [CI/CD changes implemented, documented and communicated](https://github.com/eclipse/che/blob/master/CONTRIBUTING.md#cicd-changes-implemented-documented-and-communicated) +### Release Notes + ### Reviewers Reviewers, please comment how you tested the PR when approving it. diff --git a/.github/workflows/build-pr-check.yml b/.github/workflows/build-pr-check.yml index ea75d678bf5..8fa0f46300c 100644 --- a/.github/workflows/build-pr-check.yml +++ b/.github/workflows/build-pr-check.yml @@ -29,6 +29,8 @@ jobs: distribution: 'temurin' java-version: '11' cache: 'maven' + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - name: Login to docker.io if: github.event_name == 'pull_request' uses: redhat-actions/podman-login@v1 @@ -45,9 +47,6 @@ jobs: registry: quay.io - name: Build with Maven run: mvn -B clean install -U -Pintegration - - name: Build images + - name: Build and push images if: github.event_name == 'pull_request' - run: ./build/build.sh --tag:${{ env.PR_IMAGE_TAG }} - - name: Push images - if: github.event_name == 'pull_request' - run: podman push quay.io/eclipse/che-server:${{ env.PR_IMAGE_TAG }} + run: ./build/build.sh --tag:${{ env.PR_IMAGE_TAG }} --build-platforms:linux/amd64,linux/ppc64le,linux/arm64 --builder:podman --push-image diff --git a/.github/workflows/next-build.yml b/.github/workflows/next-build.yml index b95aa186447..b5084d0a27c 100644 --- a/.github/workflows/next-build.yml +++ b/.github/workflows/next-build.yml @@ -28,6 +28,8 @@ jobs: distribution: 'temurin' java-version: '11' cache: 'maven' + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - name: Login to docker.io uses: redhat-actions/podman-login@v1 with: @@ -42,15 +44,11 @@ jobs: registry: quay.io - name: Build with Maven run: mvn -B clean install -U -Pintegration - - name: Build images + - name: Build and push images id: build run: | echo "short_sha1=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - ./build/build.sh --tag:next --sha-tag - - name: Push docker images - run: | - podman push quay.io/eclipse/che-server:next - podman push quay.io/eclipse/che-server:${{ steps.build.outputs.short_sha1 }} + ./build/build.sh --tag:next --sha-tag --build-platforms:linux/amd64,linux/ppc64le,linux/arm64 --builder:podman --push-image - name: Create failure MM message if: ${{ failure() }} run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4508e269f1e..0033df4bc1b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,14 +16,6 @@ on: description: 'The version that is going to be released. Should be in format 7.y.z' required: true default: '' - releaseParent: - description: 'If true, will also release Che Parent.' - required: false - default: 'false' - versionParent: - description: 'Specify Che Parent version here' - required: false - default: '7.15.0' forceRecreateTags: description: 'If true, tags will be overriden and regenerated. Use with caution.' required: false @@ -49,19 +41,14 @@ jobs: with: path: che-server fetch-depth: 0 - - uses: actions/checkout@v3 - if: ${{ github.event.inputs.releaseParent == 'true' }} - with: - repository: eclipse/che-parent - token: ${{ secrets.CHE_BOT_GITHUB_TOKEN }} - path: che-parent - fetch-depth: 0 - name: Check existing tag run: | if [[ "$FORCE_RECREATE_TAGS" == "false" ]] && [[ $(cd che && git ls-remote --exit-code origin refs/tags/${{ github.event.inputs.version }}) ]]; then echo "cannot create release, when tag already exists" exit 1 fi + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 - name: Login to docker.io uses: docker/login-action@v2 with: @@ -111,8 +98,6 @@ jobs: export CHE_GITHUB_SSH_KEY=${{ secrets.CHE_GITHUB_SSH_KEY}} export CHE_NPM_AUTH_TOKEN=${{ secrets.CHE_NPM_AUTH_TOKEN}} - export RELEASE_CHE_PARENT="${{ github.event.inputs.releaseParent }}" - export VERSION_CHE_PARENT="${{ github.event.inputs.versionParent }}" export DEPLOY_TO_NEXUS="${{ github.event.inputs.deployOnNexus }}" export AUTORELEASE_ON_NEXUS="${{ github.event.inputs.autoReleaseOnNexus }}" export REBUILD_FROM_EXISTING_TAGS="${{ github.event.inputs.rebuildFromExistingTags }}" diff --git a/RELEASE.md b/RELEASE.md index 9c6e527aa12..899dcb4bd85 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,12 +1,9 @@ # Automated release workflow Release is performed with GitHub Actions workflow [release.yml](https://github.com/eclipse/che/actions/workflows/release.yml). -It is also used to release [Che Parent](https://github.com/eclipse/che-parent), if appropriate input parameter `releaseParent` is set to true. The release will perform the build of Maven Artifacts, build and push of all nesessary docker images, and bumping up development version. [make-release.sh](https://github.com/eclipse/che/blob/master/make-release.sh) is the script that can be used for standalone release outside of GitHub Actions. However, ensure that all environment variables are set in place before invoking `./make-release.sh`, similarly to how it is outlined in [release.yml](https://github.com/eclipse/che/actions/workflows/release.yml): -RELEASE_CHE_PARENT - if `true`, will perform the release of Che Parent as well. -VERSION_CHE_PARENT - if RELEASE_CHE_PARENT is `true`, here the version of Che Parent must be provided. REBUILD_FROM_EXISTING_TAGS - if `true`, release will not create new tag, but instead checkout to existing one. Use this to rerun failed attempts, without having to recreate the tag. BUILD_AND_PUSH_IMAGES - if `true`, will build all asociated images in [dockerfiles](https://github.com/eclipse/che/tree/master/dockerfiles) directory. Set `false`, if this step needs to be skipped. BUMP_NEXT_VERSION - if `true`, will increase the development versions in main and bugfix branches. Set false, if this step needs to be skipped diff --git a/assembly/assembly-che-tomcat/pom.xml b/assembly/assembly-che-tomcat/pom.xml index a869dd42c62..96f41f5d1a6 100644 --- a/assembly/assembly-che-tomcat/pom.xml +++ b/assembly/assembly-che-tomcat/pom.xml @@ -17,7 +17,7 @@ che-assembly-parent org.eclipse.che - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT assembly-che-tomcat jar diff --git a/assembly/assembly-main/pom.xml b/assembly/assembly-main/pom.xml index ee2e93eab23..10b3bab5e84 100644 --- a/assembly/assembly-main/pom.xml +++ b/assembly/assembly-main/pom.xml @@ -17,7 +17,7 @@ che-assembly-parent org.eclipse.che - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT assembly-main pom diff --git a/assembly/assembly-root-war/pom.xml b/assembly/assembly-root-war/pom.xml index cf713972a16..4e924a1dde2 100644 --- a/assembly/assembly-root-war/pom.xml +++ b/assembly/assembly-root-war/pom.xml @@ -17,7 +17,7 @@ che-assembly-parent org.eclipse.che - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT assembly-root-war war diff --git a/assembly/assembly-swagger-war/pom.xml b/assembly/assembly-swagger-war/pom.xml index ff6f87ff33a..0ebcd25addc 100644 --- a/assembly/assembly-swagger-war/pom.xml +++ b/assembly/assembly-swagger-war/pom.xml @@ -17,7 +17,7 @@ che-assembly-parent org.eclipse.che - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT assembly-swagger-war war diff --git a/assembly/assembly-wsmaster-war/pom.xml b/assembly/assembly-wsmaster-war/pom.xml index d88c5d986cd..852b9d9adc6 100644 --- a/assembly/assembly-wsmaster-war/pom.xml +++ b/assembly/assembly-wsmaster-war/pom.xml @@ -17,7 +17,7 @@ che-assembly-parent org.eclipse.che - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT assembly-wsmaster-war war diff --git a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java index 2122eb65135..ab5ceae1c26 100644 --- a/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java +++ b/assembly/assembly-wsmaster-war/src/main/java/org/eclipse/che/api/deploy/WsMasterModule.java @@ -29,8 +29,6 @@ import org.eclipse.che.api.core.rest.MessageBodyAdapterInterceptor; import org.eclipse.che.api.deploy.jsonrpc.CheJsonRpcWebSocketConfigurationModule; import org.eclipse.che.api.factory.server.FactoryAcceptValidator; -import org.eclipse.che.api.factory.server.FactoryCreateValidator; -import org.eclipse.che.api.factory.server.FactoryEditValidator; import org.eclipse.che.api.factory.server.FactoryParametersResolver; import org.eclipse.che.api.factory.server.RawDevfileUrlFactoryParameterResolver; import org.eclipse.che.api.factory.server.ScmFileResolver; @@ -48,7 +46,9 @@ import org.eclipse.che.api.factory.server.github.GithubScmFileResolver; import org.eclipse.che.api.factory.server.github.GithubScmFileResolverSecond; import org.eclipse.che.api.factory.server.gitlab.GitlabFactoryParametersResolver; +import org.eclipse.che.api.factory.server.gitlab.GitlabFactoryParametersResolverSecond; import org.eclipse.che.api.factory.server.gitlab.GitlabScmFileResolver; +import org.eclipse.che.api.factory.server.gitlab.GitlabScmFileResolverSecond; import org.eclipse.che.api.system.server.ServiceTermination; import org.eclipse.che.api.system.server.SystemModule; import org.eclipse.che.api.user.server.NotImplementedTokenValidator; @@ -133,10 +133,6 @@ protected void configure() { // factory bind(FactoryAcceptValidator.class) .to(org.eclipse.che.api.factory.server.impl.FactoryAcceptValidatorImpl.class); - bind(FactoryCreateValidator.class) - .to(org.eclipse.che.api.factory.server.impl.FactoryCreateValidatorImpl.class); - bind(FactoryEditValidator.class) - .to(org.eclipse.che.api.factory.server.impl.FactoryEditValidatorImpl.class); bind(org.eclipse.che.api.factory.server.FactoryService.class); bind(ScmService.class); install(new org.eclipse.che.api.factory.server.jpa.FactoryJpaModule()); @@ -152,6 +148,9 @@ protected void configure() { .addBinding() .to(BitbucketServerAuthorizingFactoryParametersResolver.class); factoryParametersResolverMultibinder.addBinding().to(GitlabFactoryParametersResolver.class); + factoryParametersResolverMultibinder + .addBinding() + .to(GitlabFactoryParametersResolverSecond.class); factoryParametersResolverMultibinder.addBinding().to(BitbucketFactoryParametersResolver.class); factoryParametersResolverMultibinder .addBinding() @@ -167,6 +166,7 @@ protected void configure() { scmFileResolverResolverMultibinder.addBinding().to(GithubScmFileResolverSecond.class); scmFileResolverResolverMultibinder.addBinding().to(BitbucketScmFileResolver.class); scmFileResolverResolverMultibinder.addBinding().to(GitlabScmFileResolver.class); + scmFileResolverResolverMultibinder.addBinding().to(GitlabScmFileResolverSecond.class); scmFileResolverResolverMultibinder.addBinding().to(BitbucketServerScmFileResolver.class); scmFileResolverResolverMultibinder.addBinding().to(AzureDevOpsScmFileResolver.class); scmFileResolverResolverMultibinder.addBinding().to(GitSshScmFileResolver.class); diff --git a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties index d6b2b544c49..8a27612ca33 100644 --- a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties +++ b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties @@ -647,19 +647,27 @@ che.workspace.devfile.async.storage.plugin=eclipse/che-async-pv-plugin/latest che.integration.bitbucket.server_endpoints=NULL # GitLab endpoints used for factory integrations. -# A comma separated list of GitLab server URLs or `NULL` if no integration is expected. -che.integration.gitlab.server_endpoints=NULL # The address of the GitLab server with configured OAuth 2 integration. che.integration.gitlab.oauth_endpoint=NULL +# The address of the GitLab server with configured OAuth 2 integration. (The second GitLab instance). +che.integration.gitlab.oauth_endpoint_2=NULL + # Configuration of GitLab OAuth2 client. Used to obtain personal access tokens. # Location of the file with GitLab client ID. che.oauth2.gitlab.clientid_filepath=NULL +# Configuration of GitLab OAuth2 client. Used to obtain personal access tokens. +# Location of the file with GitLab client ID. (The second GitLab instance). +che.oauth2.gitlab.clientid_filepath_2=NULL + # Location of the file with GitLab client secret. che.oauth2.gitlab.clientsecret_filepath=NULL +# Location of the file with GitLab client secret. (The second GitLab instance). +che.oauth2.gitlab.clientsecret_filepath_2=NULL + ### Advanced authorization # Comma separated list of users allowed to access Che. che.infra.kubernetes.advanced_authorization.allow_users=NULL diff --git a/assembly/pom.xml b/assembly/pom.xml index 81ef8019434..b19d33c6ded 100644 --- a/assembly/pom.xml +++ b/assembly/pom.xml @@ -17,7 +17,7 @@ che-server org.eclipse.che - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT ../pom.xml che-assembly-parent diff --git a/build/build.sh b/build/build.sh index 261b61f7113..fd197313736 100755 --- a/build/build.sh +++ b/build/build.sh @@ -45,6 +45,8 @@ init() { ORGANIZATION="quay.io/eclipse" PREFIX="che" TAG="next" + LATEST_TAG=false + PUSH_IMAGE=false SKIP_TESTS=false NAME="che" ARGS="" @@ -52,6 +54,7 @@ init() { DOCKERFILE="" BUILD_COMMAND="build" BUILD_ARGS="" + BUILD_PLATFORMS="" while [ $# -gt 0 ]; do case $1 in @@ -79,9 +82,15 @@ init() { --skip-tests) SKIP_TESTS=true shift ;; + --push-image) + PUSH_IMAGE=true + shift ;; --sha-tag) SHA_TAG=$(git rev-parse --short HEAD) shift ;; + --latest-tag) + LATEST_TAG=true + shift ;; --dockerfile:*) DOCKERFILE="${1#*:}" shift ;; @@ -89,6 +98,12 @@ init() { BUILD_ARGS_CSV="${1#*:}" prepare_build_args $BUILD_ARGS_CSV shift ;; + --build-platforms:*) + BUILD_PLATFORMS="${1#*:}" + shift ;; + --builder:*) + BUILDER="${1#*:}" + shift ;; --*) printf "${RED}Unknown parameter: $1${NC}\n"; exit 2 ;; *) @@ -97,6 +112,7 @@ init() { done IMAGE_NAME="$ORGANIZATION/$PREFIX-$NAME:$TAG" + IMAGE_MANIFEST="$NAME-$TAG" } build() { @@ -171,41 +187,107 @@ build_image() { -e "s;\${BUILD_PREFIX};${PREFIX};" \ -e "s;\${BUILD_TAG};${TAG};" \ > ${DIR}/.Dockerfile - cd "${DIR}" && "${BUILDER}" "${BUILD_COMMAND}" -f ${DIR}/.Dockerfile -t ${IMAGE_NAME} ${BUILD_ARGS} . - DOCKER_BUILD_STATUS=$? - rm ${DIR}/.Dockerfile - if [ $DOCKER_BUILD_STATUS -eq 0 ]; then - printf "Build of ${BLUE}${IMAGE_NAME} ${GREEN}[OK]${NC}\n" + cd "${DIR}" + + if [[ -n $BUILD_PLATFORMS ]]; then + if [[ $BUILDER == "podman" ]]; then + printf "${BOLD}Creating manifest ${IMAGE_MANIFEST}${NC}\n" + "${BUILDER}" manifest create ${IMAGE_MANIFEST} + DOCKER_STATUS=$? + if [ ! $DOCKER_STATUS -eq 0 ]; then + printf "${RED}Failure when creating manifest ${IMAGE_MANIFEST}${NC}\n" + exit 1 + fi + + printf "${BOLD}Building image ${IMAGE_NAME}${NC}\n" + "${BUILDER}" build --platform ${BUILD_PLATFORMS} -f ${DIR}/.Dockerfile --manifest ${IMAGE_MANIFEST} . + DOCKER_STATUS=$? + if [ ! $DOCKER_STATUS -eq 0 ]; then + printf "${RED}Failure when building docker image ${IMAGE_NAME}${NC}\n" + exit 1 + fi + else + printf "${RED}Multi-platform image building is only supported for podman builder${NC}\n" + exit 1 + fi + else + "${BUILDER}" "${BUILD_COMMAND}" -f ${DIR}/.Dockerfile -t ${IMAGE_NAME} ${BUILD_ARGS} . + DOCKER_STATUS=$? + if [ ! $DOCKER_STATUS -eq 0 ]; then + printf "${RED}Failure when building docker image ${IMAGE_NAME}${NC}\n" + exit 1 + fi + fi + + printf "Build of ${BLUE}${IMAGE_NAME} ${GREEN}[OK]${NC}\n" + + if [[ $PUSH_IMAGE == "true" ]]; then + push_image ${IMAGE_NAME} ${IMAGE_NAME} + if [ ! -z "${SHA_TAG}" ]; then SHA_IMAGE_NAME=${ORGANIZATION}/${PREFIX}-${NAME}:${SHA_TAG} - "${BUILDER}" tag ${IMAGE_NAME} ${SHA_IMAGE_NAME} + printf "Re-tagging with SHA based tag ${BLUE}${SHA_IMAGE_NAME} ${GREEN}[OK]${NC}\n" + push_image ${IMAGE_NAME} ${SHA_IMAGE_NAME} + fi + + if [[ ${LATEST_TAG} == "true" ]]; then + LATEST_IMAGE_NAME=${ORGANIZATION}/${PREFIX}-${NAME}:latest + printf "Re-tagging with latest tag ${BLUE}${LATEST_IMAGE_NAME} ${GREEN}[OK]${NC}\n" + push_image ${IMAGE_NAME} ${LATEST_IMAGE_NAME} + fi + fi + + if [ ! -z "${IMAGE_ALIASES}" ]; then + for TMP_IMAGE_NAME in ${IMAGE_ALIASES} + do + "${BUILDER}" tag ${IMAGE_NAME} ${TMP_IMAGE_NAME}:${TAG} DOCKER_TAG_STATUS=$? if [ $DOCKER_TAG_STATUS -eq 0 ]; then - printf "Re-tagging with SHA based tag ${BLUE}${SHA_IMAGE_NAME} ${GREEN}[OK]${NC}\n" + printf " /alias ${BLUE}${TMP_IMAGE_NAME}:${TAG}${NC} ${GREEN}[OK]${NC}\n" else - printf "${RED}Failure when tagging docker image ${SHA_IMAGE_NAME}${NC}\n" + printf "${RED}Failure when building docker image ${IMAGE_NAME}${NC}\n" exit 1 fi + + done + fi + + if [[ -n $BUILD_PLATFORMS ]] && [[ $BUILDER == "podman" ]]; then + # Remove manifest list from local storage + ${BUILDER} manifest rm ${IMAGE_MANIFEST} + fi + + printf "${GREEN}Script run successfully: ${BLUE}${IMAGE_NAME}${NC}\n" +} + +push_image() { + local image=$1 + local tagged_image=$2 + + printf "Pushing manifest ${BLUE}${image} ${NC}\n" + if [[ -n $BUILD_PLATFORMS ]] && [[ $BUILDER == "podman" ]]; then + ${BUILDER} manifest push ${IMAGE_MANIFEST} docker://${tagged_image} + DOCKER_STATUS=$? + if [ ! $DOCKER_STATUS -eq 0 ]; then + printf "${RED}Failure when pushing image ${image}${NC}\n" + exit 1 fi - if [ ! -z "${IMAGE_ALIASES}" ]; then - for TMP_IMAGE_NAME in ${IMAGE_ALIASES} - do - "${BUILDER}" tag ${IMAGE_NAME} ${TMP_IMAGE_NAME}:${TAG} - DOCKER_TAG_STATUS=$? - if [ $DOCKER_TAG_STATUS -eq 0 ]; then - printf " /alias ${BLUE}${TMP_IMAGE_NAME}:${TAG}${NC} ${GREEN}[OK]${NC}\n" - else - printf "${RED}Failure when building docker image ${IMAGE_NAME}${NC}\n" - exit 1 - fi - - done - fi - printf "${GREEN}Script run successfully: ${BLUE}${IMAGE_NAME}${NC}\n" else - printf "${RED}Failure when building docker image ${IMAGE_NAME}${NC}\n" - exit 1 + ${BUILDER} tag ${image} ${tagged_image} + DOCKER_STATUS=$? + if [ ! $DOCKER_STATUS -eq 0 ]; then + printf "${RED}Failure when tagging image ${tagged_image}${NC}\n" + exit 1 + fi + + ${BUILDER} push ${image} + DOCKER_STATUS=$? + if [ ! $DOCKER_STATUS -eq 0 ]; then + printf "${RED}Failure when pushing image ${image}${NC}\n" + exit 1 + fi fi + printf "Push of ${BLUE}${tagged_image} ${GREEN}[OK]${NC}\n" } get_full_path() { diff --git a/build/dockerfiles/Dockerfile b/build/dockerfiles/Dockerfile index 67001b6e017..2fb9f42c985 100644 --- a/build/dockerfiles/Dockerfile +++ b/build/dockerfiles/Dockerfile @@ -10,7 +10,7 @@ # # https://access.redhat.com/containers/?tab=tags#/registry.access.redhat.com/ubi8-minimal -FROM registry.access.redhat.com/ubi8-minimal:8.9-1161 +FROM registry.access.redhat.com/ubi8-minimal:8.10-1086 USER root ENV CHE_HOME=/home/user/eclipse-che ENV JAVA_HOME=/usr/lib/jvm/jre diff --git a/build/dockerfiles/brew.Dockerfile b/build/dockerfiles/brew.Dockerfile index 593d7e1e555..a93278f0296 100644 --- a/build/dockerfiles/brew.Dockerfile +++ b/build/dockerfiles/brew.Dockerfile @@ -10,7 +10,7 @@ # # https://access.redhat.com/containers/?tab=tags#/registry.access.redhat.com/ubi8-minimal -FROM ubi8-minimal:8.9-1161 +FROM ubi8-minimal:8.10-1086 USER root ENV CHE_HOME=/home/user/devspaces ENV JAVA_HOME=/usr/lib/jvm/jre diff --git a/core/che-core-api-core/pom.xml b/core/che-core-api-core/pom.xml index 500617b8e91..7e6ac15a918 100644 --- a/core/che-core-api-core/pom.xml +++ b/core/che-core-api-core/pom.xml @@ -17,7 +17,7 @@ che-core-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-core jar diff --git a/core/che-core-api-dto-maven-plugin/pom.xml b/core/che-core-api-dto-maven-plugin/pom.xml index 37d14d28fca..5fea9ccd085 100644 --- a/core/che-core-api-dto-maven-plugin/pom.xml +++ b/core/che-core-api-dto-maven-plugin/pom.xml @@ -17,7 +17,7 @@ che-core-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-dto-maven-plugin maven-plugin diff --git a/core/che-core-api-dto/pom.xml b/core/che-core-api-dto/pom.xml index 558305e9345..49d3733e719 100644 --- a/core/che-core-api-dto/pom.xml +++ b/core/che-core-api-dto/pom.xml @@ -17,7 +17,7 @@ che-core-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-dto jar diff --git a/core/che-core-api-model/pom.xml b/core/che-core-api-model/pom.xml index 44dd4b204a9..379a9e3899b 100644 --- a/core/che-core-api-model/pom.xml +++ b/core/che-core-api-model/pom.xml @@ -17,7 +17,7 @@ che-core-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-model jar diff --git a/core/che-core-logback/pom.xml b/core/che-core-logback/pom.xml index bcb3972e4b9..871991b1c77 100644 --- a/core/che-core-logback/pom.xml +++ b/core/che-core-logback/pom.xml @@ -17,7 +17,7 @@ che-core-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-logback jar diff --git a/core/che-core-metrics-core/pom.xml b/core/che-core-metrics-core/pom.xml index 98a5ad9bf20..06c07f8b80f 100644 --- a/core/che-core-metrics-core/pom.xml +++ b/core/che-core-metrics-core/pom.xml @@ -17,7 +17,7 @@ che-core-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-metrics-core Che Core :: Commons :: Metrics :: Core diff --git a/core/che-core-tracing-core/pom.xml b/core/che-core-tracing-core/pom.xml index 15a1d298f31..7266785bca0 100644 --- a/core/che-core-tracing-core/pom.xml +++ b/core/che-core-tracing-core/pom.xml @@ -17,7 +17,7 @@ che-core-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-tracing-core Che Core :: Commons :: Tracing :: Core diff --git a/core/che-core-tracing-metrics/pom.xml b/core/che-core-tracing-metrics/pom.xml index 7754f9d2396..3d96286cbb0 100644 --- a/core/che-core-tracing-metrics/pom.xml +++ b/core/che-core-tracing-metrics/pom.xml @@ -17,7 +17,7 @@ che-core-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-tracing-metrics Che Core :: Commons :: Tracing :: Metrics diff --git a/core/che-core-tracing-web/pom.xml b/core/che-core-tracing-web/pom.xml index 43a245794d6..7e78f38f139 100644 --- a/core/che-core-tracing-web/pom.xml +++ b/core/che-core-tracing-web/pom.xml @@ -17,7 +17,7 @@ che-core-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-tracing-web Che Core :: Commons :: Tracing :: Web diff --git a/core/che-core-typescript-dto-maven-plugin/pom.xml b/core/che-core-typescript-dto-maven-plugin/pom.xml index 76b81482573..ea5391ce3dc 100644 --- a/core/che-core-typescript-dto-maven-plugin/pom.xml +++ b/core/che-core-typescript-dto-maven-plugin/pom.xml @@ -17,7 +17,7 @@ che-core-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-typescript-dto-maven-plugin maven-plugin diff --git a/core/commons/che-core-commons-annotations/pom.xml b/core/commons/che-core-commons-annotations/pom.xml index fec0bb1ea0d..d1185910efc 100644 --- a/core/commons/che-core-commons-annotations/pom.xml +++ b/core/commons/che-core-commons-annotations/pom.xml @@ -17,7 +17,7 @@ che-core-commons-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-commons-annotations jar diff --git a/core/commons/che-core-commons-inject/pom.xml b/core/commons/che-core-commons-inject/pom.xml index a467210e4f7..e7bc7a7186f 100644 --- a/core/commons/che-core-commons-inject/pom.xml +++ b/core/commons/che-core-commons-inject/pom.xml @@ -17,7 +17,7 @@ che-core-commons-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-commons-inject jar diff --git a/core/commons/che-core-commons-j2ee/pom.xml b/core/commons/che-core-commons-j2ee/pom.xml index e0b96d4f89b..e24e0fadb58 100644 --- a/core/commons/che-core-commons-j2ee/pom.xml +++ b/core/commons/che-core-commons-j2ee/pom.xml @@ -17,7 +17,7 @@ che-core-commons-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-commons-j2ee jar diff --git a/core/commons/che-core-commons-json/pom.xml b/core/commons/che-core-commons-json/pom.xml index d495406e31a..84df6f684b8 100644 --- a/core/commons/che-core-commons-json/pom.xml +++ b/core/commons/che-core-commons-json/pom.xml @@ -17,7 +17,7 @@ che-core-commons-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-commons-json jar diff --git a/core/commons/che-core-commons-lang/pom.xml b/core/commons/che-core-commons-lang/pom.xml index 8a7d08ab27f..61956c666c7 100644 --- a/core/commons/che-core-commons-lang/pom.xml +++ b/core/commons/che-core-commons-lang/pom.xml @@ -17,7 +17,7 @@ che-core-commons-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-commons-lang jar diff --git a/core/commons/che-core-commons-observability/pom.xml b/core/commons/che-core-commons-observability/pom.xml index 5619d99babf..2291d19648d 100644 --- a/core/commons/che-core-commons-observability/pom.xml +++ b/core/commons/che-core-commons-observability/pom.xml @@ -17,7 +17,7 @@ che-core-commons-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-commons-observability Che Core :: Commons :: Tracing and Monitoring wrapper diff --git a/core/commons/che-core-commons-schedule/pom.xml b/core/commons/che-core-commons-schedule/pom.xml index 7ff4d6ed718..c3c0295c681 100644 --- a/core/commons/che-core-commons-schedule/pom.xml +++ b/core/commons/che-core-commons-schedule/pom.xml @@ -17,7 +17,7 @@ che-core-commons-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-commons-schedule jar diff --git a/core/commons/che-core-commons-test/pom.xml b/core/commons/che-core-commons-test/pom.xml index 29dc987af26..b67ee3c3b07 100644 --- a/core/commons/che-core-commons-test/pom.xml +++ b/core/commons/che-core-commons-test/pom.xml @@ -17,7 +17,7 @@ che-core-commons-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-commons-test jar diff --git a/core/commons/che-core-commons-tracing/pom.xml b/core/commons/che-core-commons-tracing/pom.xml index d984b3fb4fb..d56c0774ffd 100644 --- a/core/commons/che-core-commons-tracing/pom.xml +++ b/core/commons/che-core-commons-tracing/pom.xml @@ -17,7 +17,7 @@ che-core-commons-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-commons-tracing Che Core :: Commons :: Tracing diff --git a/core/commons/pom.xml b/core/commons/pom.xml index e16b9d5241e..f763c0a4eb6 100644 --- a/core/commons/pom.xml +++ b/core/commons/pom.xml @@ -17,7 +17,7 @@ che-core-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT ../pom.xml che-core-commons-parent diff --git a/core/pom.xml b/core/pom.xml index 5b27f60542f..905d140bf07 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -17,7 +17,7 @@ che-server org.eclipse.che - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT ../pom.xml org.eclipse.che.core diff --git a/infrastructures/infrastructure-distributed/pom.xml b/infrastructures/infrastructure-distributed/pom.xml index e2f0b436ddc..672a842f8ed 100644 --- a/infrastructures/infrastructure-distributed/pom.xml +++ b/infrastructures/infrastructure-distributed/pom.xml @@ -17,7 +17,7 @@ che-infrastructures-parent org.eclipse.che.infrastructure - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT infrastructure-distributed jar diff --git a/infrastructures/infrastructure-factory/pom.xml b/infrastructures/infrastructure-factory/pom.xml index d04b4db2fed..ce2d186a3b6 100644 --- a/infrastructures/infrastructure-factory/pom.xml +++ b/infrastructures/infrastructure-factory/pom.xml @@ -17,7 +17,7 @@ che-infrastructures-parent org.eclipse.che.infrastructure - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT infrastructure-factory jar diff --git a/infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesAuthorisationRequestManager.java b/infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesAuthorisationRequestManager.java index 8af694936e6..085382c7654 100644 --- a/infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesAuthorisationRequestManager.java +++ b/infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesAuthorisationRequestManager.java @@ -21,6 +21,8 @@ import com.google.gson.Gson; import io.fabric8.kubernetes.api.model.ConfigMap; import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.dsl.base.PatchContext; +import io.fabric8.kubernetes.client.dsl.base.PatchType; import jakarta.ws.rs.core.UriInfo; import java.net.URL; import java.util.HashSet; @@ -118,7 +120,7 @@ private void patchConfigMap(ConfigMap configMap) { .configMaps() .inNamespace(getFirstNamespace()) .withName(PREFERENCES_CONFIGMAP_NAME) - .patch(configMap); + .patch(PatchContext.of(PatchType.STRATEGIC_MERGE), configMap); } catch (UnsatisfiedScmPreconditionException | ScmConfigurationPersistenceException | InfrastructureException e) { diff --git a/infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManager.java b/infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManager.java index 869808717a7..12e1c8d0997 100644 --- a/infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManager.java +++ b/infrastructures/infrastructure-factory/src/main/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManager.java @@ -23,10 +23,14 @@ import io.fabric8.kubernetes.api.model.SecretBuilder; import io.fabric8.kubernetes.client.KubernetesClientException; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Base64; +import java.util.Collections; +import java.util.Comparator; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Singleton; import org.eclipse.che.api.factory.server.scm.GitCredentialManager; @@ -148,8 +152,8 @@ public PersonalAccessToken fetchAndSave(Subject cheUser, String scmServerUrl) @Override public Optional get(Subject cheUser, String scmServerUrl) - throws ScmConfigurationPersistenceException { - return doGetPersonalAccessToken(cheUser, null, scmServerUrl); + throws ScmConfigurationPersistenceException, ScmCommunicationException { + return doGetPersonalAccessTokens(cheUser, null, scmServerUrl).stream().findFirst(); } @Override @@ -170,24 +174,22 @@ public PersonalAccessToken get(String scmServerUrl) @Override public Optional get( Subject cheUser, String oAuthProviderName, @Nullable String scmServerUrl) - throws ScmConfigurationPersistenceException { - return doGetPersonalAccessToken(cheUser, oAuthProviderName, scmServerUrl); + throws ScmConfigurationPersistenceException, ScmCommunicationException { + return doGetPersonalAccessTokens(cheUser, oAuthProviderName, scmServerUrl).stream().findFirst(); } - private Optional doGetPersonalAccessToken( + private List doGetPersonalAccessTokens( Subject cheUser, @Nullable String oAuthProviderName, @Nullable String scmServerUrl) - throws ScmConfigurationPersistenceException { + throws ScmConfigurationPersistenceException, ScmCommunicationException { + List result = new ArrayList<>(); try { LOG.debug( "Fetching personal access token for user {} and OAuth provider {}", cheUser.getUserId(), oAuthProviderName); for (KubernetesNamespaceMeta namespaceMeta : namespaceFactory.list()) { - List secrets = - namespaceFactory - .access(null, namespaceMeta.getName()) - .secrets() - .get(KUBERNETES_PERSONAL_ACCESS_TOKEN_LABEL_SELECTOR); + List secrets = doGetPersonalAccessTokenSecrets(namespaceMeta); + for (Secret secret : secrets) { LOG.debug("Checking secret {}", secret.getMetadata().getName()); if (deleteSecretIfMisconfigured(secret)) { @@ -219,7 +221,8 @@ private Optional doGetPersonalAccessToken( personalAccessTokenParams.getScmTokenName(), personalAccessTokenParams.getScmTokenId(), personalAccessTokenParams.getToken()); - return Optional.of(personalAccessToken); + result.add(personalAccessToken); + continue; } // Removing token that is no longer valid. If several tokens exist the next one could @@ -239,7 +242,30 @@ private Optional doGetPersonalAccessToken( LOG.debug("Failed to get personal access token", e); throw new ScmConfigurationPersistenceException(e.getMessage(), e); } - return Optional.empty(); + return result; + } + + private List doGetPersonalAccessTokenSecrets(KubernetesNamespaceMeta namespaceMeta) + throws ScmConfigurationPersistenceException { + try { + List secrets = + namespaceFactory + .access(null, namespaceMeta.getName()) + .secrets() + .get(KUBERNETES_PERSONAL_ACCESS_TOKEN_LABEL_SELECTOR); + + // sort secrets to get the newest one first + // Assign to new list to avoid UnsupportedOperationException (ImmutableList) + secrets = + secrets.stream() + .sorted(Comparator.comparing(secret -> secret.getMetadata().getCreationTimestamp())) + .collect(Collectors.toList()); + Collections.reverse(secrets); + return secrets; + } catch (InfrastructureException e) { + LOG.debug("Failed to get personal access token secret", e); + throw new ScmConfigurationPersistenceException(e.getMessage(), e); + } } /** @@ -330,6 +356,39 @@ public PersonalAccessToken getAndStore(String scmServerUrl) return personalAccessToken; } + @Override + public void forceRefreshPersonalAccessToken(String scmServerUrl) + throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException, + UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException { + Subject subject = EnvironmentContext.getCurrent().getSubject(); + PersonalAccessToken personalAccessToken = + scmPersonalAccessTokenFetcher.refreshPersonalAccessToken(subject, scmServerUrl); + gitCredentialManager.createOrReplace(personalAccessToken); + store(personalAccessToken); + removePreviousTokenSecretsIfPresent(scmServerUrl); + } + + private void removePreviousTokenSecretsIfPresent(String scmServerUrl) + throws ScmConfigurationPersistenceException, UnsatisfiedScmPreconditionException { + try { + for (KubernetesNamespaceMeta namespaceMeta : namespaceFactory.list()) { + List secrets = doGetPersonalAccessTokenSecrets(namespaceMeta); + for (int i = 1; i < secrets.size(); i++) { + Secret secret = secrets.get(i); + if (secret.getMetadata().getAnnotations().get(ANNOTATION_SCM_URL).equals(scmServerUrl)) { + cheServerKubernetesClientFactory + .create() + .secrets() + .inNamespace(getFirstNamespace()) + .delete(secret); + } + } + } + } catch (InfrastructureException e) { + throw new ScmConfigurationPersistenceException(e.getMessage(), e); + } + } + @Override public void storeGitCredentials(String scmServerUrl) throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException, diff --git a/infrastructures/infrastructure-factory/src/test/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManagerTest.java b/infrastructures/infrastructure-factory/src/test/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManagerTest.java index 7556e46569a..703accfdf95 100644 --- a/infrastructures/infrastructure-factory/src/test/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManagerTest.java +++ b/infrastructures/infrastructure-factory/src/test/java/org/eclipse/che/api/factory/server/scm/kubernetes/KubernetesPersonalAccessTokenManagerTest.java @@ -102,6 +102,7 @@ public void shouldTrimBlankCharsInToken() throws Exception { ObjectMeta meta1 = new ObjectMetaBuilder() + .withCreationTimestamp("2021-07-01T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, @@ -185,6 +186,7 @@ public void testGetTokenFromNamespace() throws Exception { ObjectMeta meta1 = new ObjectMetaBuilder() + .withCreationTimestamp("2021-07-01T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, @@ -196,6 +198,7 @@ public void testGetTokenFromNamespace() throws Exception { .build(); ObjectMeta meta2 = new ObjectMetaBuilder() + .withCreationTimestamp("2021-07-02T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, @@ -207,6 +210,7 @@ public void testGetTokenFromNamespace() throws Exception { .build(); ObjectMeta meta3 = new ObjectMetaBuilder() + .withCreationTimestamp("2021-07-03T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, @@ -343,6 +347,7 @@ public void testGetTokenFromNamespaceWithTrailingSlashMismatch() throws Exceptio ObjectMeta meta1 = new ObjectMetaBuilder() + .withCreationTimestamp("2021-07-01T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, @@ -354,6 +359,7 @@ public void testGetTokenFromNamespaceWithTrailingSlashMismatch() throws Exceptio .build(); ObjectMeta meta2 = new ObjectMetaBuilder() + .withCreationTimestamp("2021-08-01T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, @@ -458,7 +464,7 @@ public void shouldDeleteInvalidTokensOnGet() throws Exception { } @Test(dependsOnMethods = "shouldDeleteInvalidTokensOnGet") - public void shouldReturnFirstValidToken() throws Exception { + public void shouldReturnFirstValidTokenAndDeleteTheInvalidOne() throws Exception { // given KubernetesNamespaceMeta meta = new KubernetesNamespaceMetaImpl("test"); when(namespaceFactory.list()).thenReturn(singletonList(meta)); @@ -477,6 +483,7 @@ public void shouldReturnFirstValidToken() throws Exception { }); when(cheServerKubernetesClientFactory.create()).thenReturn(kubeClient); when(kubeClient.secrets()).thenReturn(secretsMixedOperation); + when(secretsMixedOperation.inNamespace(eq(meta.getName()))).thenReturn(nonNamespaceOperation); Map data1 = Map.of("token", Base64.getEncoder().encodeToString("token1".getBytes(UTF_8))); @@ -484,6 +491,7 @@ public void shouldReturnFirstValidToken() throws Exception { Map.of("token", Base64.getEncoder().encodeToString("token2".getBytes(UTF_8))); ObjectMeta meta1 = new ObjectMetaBuilder() + .withCreationTimestamp("2021-07-01T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, @@ -497,6 +505,7 @@ public void shouldReturnFirstValidToken() throws Exception { .build(); ObjectMeta meta2 = new ObjectMetaBuilder() + .withCreationTimestamp("2021-07-02T12:00:00Z") .withAnnotations( Map.of( ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, @@ -518,5 +527,70 @@ public void shouldReturnFirstValidToken() throws Exception { // then assertTrue(token.isPresent()); assertEquals(token.get().getScmTokenId(), "id2"); + verify(nonNamespaceOperation, times(1)).delete(eq(secret1)); + } + + @Test + public void shouldReturnFirstValidTokenAndDeleteTheOlderOne() throws Exception { + // given + KubernetesNamespaceMeta meta = new KubernetesNamespaceMetaImpl("test"); + when(namespaceFactory.list()).thenReturn(singletonList(meta)); + KubernetesNamespace kubernetesnamespace = Mockito.mock(KubernetesNamespace.class); + KubernetesSecrets secrets = Mockito.mock(KubernetesSecrets.class); + when(kubernetesnamespace.secrets()).thenReturn(secrets); + when(cheServerKubernetesClientFactory.create()).thenReturn(kubeClient); + when(kubeClient.secrets()).thenReturn(secretsMixedOperation); + Map data1 = + Map.of("token", Base64.getEncoder().encodeToString("token1".getBytes(UTF_8))); + Map data2 = + Map.of("token", Base64.getEncoder().encodeToString("token2".getBytes(UTF_8))); + ObjectMeta meta1 = + new ObjectMetaBuilder() + .withCreationTimestamp("2021-07-01T12:00:00Z") + .withAnnotations( + Map.of( + ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, + "github", + ANNOTATION_CHE_USERID, + "user1", + ANNOTATION_SCM_URL, + "http://host1", + ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_ID, + "id1")) + .build(); + ObjectMeta meta2 = + new ObjectMetaBuilder() + .withCreationTimestamp("2021-07-02T12:00:00Z") + .withAnnotations( + Map.of( + ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_NAME, + "github", + ANNOTATION_CHE_USERID, + "user1", + ANNOTATION_SCM_URL, + "http://host1", + ANNOTATION_SCM_PERSONAL_ACCESS_TOKEN_ID, + "id2")) + .build(); + Secret secret1 = new SecretBuilder().withMetadata(meta1).withData(data1).build(); + Secret secret2 = new SecretBuilder().withMetadata(meta2).withData(data2).build(); + when(secrets.get(any(LabelSelector.class))).thenReturn(Arrays.asList(secret1, secret2)); + when(namespaceFactory.access(eq(null), eq(meta.getName()))).thenReturn(kubernetesnamespace); + when(cheServerKubernetesClientFactory.create()).thenReturn(kubeClient); + when(kubeClient.secrets()).thenReturn(secretsMixedOperation); + when(secretsMixedOperation.inNamespace(eq(meta.getName()))).thenReturn(nonNamespaceOperation); + when(kubernetesnamespace.secrets()).thenReturn(secrets); + PersonalAccessToken token = + new PersonalAccessToken( + "http://host1", "provider", "cheUser", "username", "token-name", "tid-24", "token123"); + when(scmPersonalAccessTokenFetcher.refreshPersonalAccessToken( + any(org.eclipse.che.commons.subject.Subject.class), eq("http://host1"))) + .thenReturn(token); + + // when + personalAccessTokenManager.forceRefreshPersonalAccessToken("http://host1"); + + // then + verify(nonNamespaceOperation, times(1)).delete(eq(secret1)); } } diff --git a/infrastructures/infrastructure-metrics/pom.xml b/infrastructures/infrastructure-metrics/pom.xml index 6bf326f7ed8..eb8bc6019a9 100644 --- a/infrastructures/infrastructure-metrics/pom.xml +++ b/infrastructures/infrastructure-metrics/pom.xml @@ -17,7 +17,7 @@ che-infrastructures-parent org.eclipse.che.infrastructure - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT ../pom.xml infrastructure-metrics diff --git a/infrastructures/infrastructure-permission/pom.xml b/infrastructures/infrastructure-permission/pom.xml index 6924f98858c..bda595bbed1 100644 --- a/infrastructures/infrastructure-permission/pom.xml +++ b/infrastructures/infrastructure-permission/pom.xml @@ -17,7 +17,7 @@ che-infrastructures-parent org.eclipse.che.infrastructure - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT infrastructure-permission Infrastructure :: Kubernetes Permissions diff --git a/infrastructures/kubernetes/pom.xml b/infrastructures/kubernetes/pom.xml index 8887072f061..4d3f81c4893 100644 --- a/infrastructures/kubernetes/pom.xml +++ b/infrastructures/kubernetes/pom.xml @@ -17,7 +17,7 @@ che-infrastructures-parent org.eclipse.che.infrastructure - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT infrastructure-kubernetes Infrastructure :: Kubernetes diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/CredentialsSecretConfigurator.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/CredentialsSecretConfigurator.java index cf52218b624..a33edb2a921 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/CredentialsSecretConfigurator.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/CredentialsSecretConfigurator.java @@ -25,6 +25,8 @@ import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This {@link NamespaceConfigurator} ensures that Secret {@link @@ -45,6 +47,8 @@ public class CredentialsSecretConfigurator implements NamespaceConfigurator { private static final String MERGED_GIT_CREDENTIALS_SECRET_NAME = "devworkspace-merged-git-credentials"; + private static final Logger LOG = LoggerFactory.getLogger(CredentialsSecretConfigurator.class); + @Inject public CredentialsSecretConfigurator( CheServerKubernetesClientFactory cheServerKubernetesClientFactory, @@ -79,7 +83,7 @@ public void configure(NamespaceResolutionContext namespaceResolutionContext, Str | ScmConfigurationPersistenceException | UnsatisfiedScmPreconditionException | ScmUnauthorizedException e) { - throw new RuntimeException(e); + LOG.error(e.getMessage(), e); } }); } diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/OAuthTokenSecretsConfigurator.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/OAuthTokenSecretsConfigurator.java index 0ab31f71223..5739b26c38e 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/OAuthTokenSecretsConfigurator.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/namespace/configurator/OAuthTokenSecretsConfigurator.java @@ -17,12 +17,15 @@ import javax.inject.Singleton; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.api.workspace.server.spi.NamespaceResolutionContext; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.workspace.infrastructure.kubernetes.CheServerKubernetesClientFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Ensures that OAuth token that are represented by Kubernetes Secrets are valid. @@ -44,6 +47,8 @@ public class OAuthTokenSecretsConfigurator implements NamespaceConfigurator { "app.kubernetes.io/part-of", "che.eclipse.org", "app.kubernetes.io/component", "scm-personal-access-token"); + private static final Logger LOG = LoggerFactory.getLogger(OAuthTokenSecretsConfigurator.class); + @Inject public OAuthTokenSecretsConfigurator( CheServerKubernetesClientFactory cheServerKubernetesClientFactory, @@ -74,8 +79,8 @@ public void configure(NamespaceResolutionContext namespaceResolutionContext, Str Subject cheSubject = EnvironmentContext.getCurrent().getSubject(); personalAccessTokenManager.get( cheSubject, s.getMetadata().getAnnotations().get(ANNOTATION_SCM_URL)); - } catch (ScmConfigurationPersistenceException e) { - throw new RuntimeException(e); + } catch (ScmConfigurationPersistenceException | ScmCommunicationException e) { + LOG.error(e.getMessage(), e); } }); } diff --git a/infrastructures/openshift/pom.xml b/infrastructures/openshift/pom.xml index 734d51f6601..02bbed67c7d 100644 --- a/infrastructures/openshift/pom.xml +++ b/infrastructures/openshift/pom.xml @@ -17,7 +17,7 @@ che-infrastructures-parent org.eclipse.che.infrastructure - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT infrastructure-openshift Infrastructure :: OpenShift diff --git a/infrastructures/pom.xml b/infrastructures/pom.xml index 73d12cee6fd..4427e836894 100644 --- a/infrastructures/pom.xml +++ b/infrastructures/pom.xml @@ -17,7 +17,7 @@ che-server org.eclipse.che - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT ../pom.xml org.eclipse.che.infrastructure diff --git a/make-release.sh b/make-release.sh index 572dbfeffe0..2eef842f42e 100755 --- a/make-release.sh +++ b/make-release.sh @@ -7,6 +7,7 @@ REGISTRY="quay.io" ORGANIZATION="eclipse" IMAGE="quay.io/eclipse/che-server" +BUILD_PLATFORMS="linux/amd64,linux/ppc64le,linux/arm64" sed_in_place() { SHORT_UNAME=$(uname -s) @@ -49,14 +50,9 @@ evaluateCheVariables() { BASEBRANCH="${BRANCH}" fi echo "Basebranch: ${BASEBRANCH}" - echo "Release che-parent: ${RELEASE_CHE_PARENT}" - echo "Version che-parent: ${VERSION_CHE_PARENT}" } checkoutProjects() { - if [[ ${RELEASE_CHE_PARENT} = "true" ]]; then - checkoutProject git@github.com:eclipse/che-parent - fi checkoutProject git@github.com:eclipse-che/che-server } @@ -86,11 +82,6 @@ checkoutProject() { } checkoutTags() { - if [[ ${RELEASE_CHE_PARENT} = "true" ]]; then - cd che-parent - git checkout ${CHE_VERSION} - cd .. - fi cd che-server git checkout ${CHE_VERSION} cd .. @@ -144,9 +135,6 @@ commitChangeOrCreatePR() { } createTags() { - if [[ $RELEASE_CHE_PARENT = "true" ]]; then - tagAndCommit che-parent - fi tagAndCommit che-server } @@ -170,23 +158,9 @@ tagAndCommit() { } prepareRelease() { - if [[ $RELEASE_CHE_PARENT = "true" ]]; then - pushd che-parent >/dev/null - # Install previous version, in case it is not available in central repo - # which is needed for dependent projects - mvn clean install - mvn versions:set -DgenerateBackupPoms=false -DnewVersion=${VERSION_CHE_PARENT} - mvn clean install - popd >/dev/null - echo "[INFO] Che Parent version has been updated to ${VERSION_CHE_PARENT}" - fi - pushd che-server >/dev/null - if [[ $RELEASE_CHE_PARENT = "true" ]]; then - mvn versions:update-parent -DgenerateBackupPoms=false -DallowSnapshots=false -DparentVersion=[${VERSION_CHE_PARENT}] - fi mvn versions:set -DgenerateBackupPoms=false -DallowSnapshots=false -DnewVersion=${CHE_VERSION} - echo "[INFO] Che Server version has been updated to ${CHE_VERSION} (parentVersion = ${VERSION_CHE_PARENT})" + echo "[INFO] Che Server version has been updated to ${CHE_VERSION} " # Replace dependencies in che-server parent sed -i -e "s#.*<\/che.version>#${CHE_VERSION}<\/che.version>#" pom.xml @@ -195,8 +169,7 @@ prepareRelease() { # TODO pull parent pom version from VERSION file, instead of being hardcoded pushd typescript-dto >/dev/null sed -i -e "s#.*<\/che.version>#${CHE_VERSION}<\/che.version>#" dto-pom.xml - sed -i -e "/org.eclipse.che.parent<\/groupId>/ { n; s#.*<\/version>#${VERSION_CHE_PARENT}<\/version>#}" dto-pom.xml - echo "[INFO] Dependencies updated in che typescript DTO (parent = ${VERSION_CHE_PARENT}, che server = ${CHE_VERSION})" + echo "[INFO] Dependencies updated in che typescript DTO (che server = ${CHE_VERSION})" popd >/dev/null # run mvn license format, in case some files that have old license headers have been updated @@ -207,29 +180,6 @@ prepareRelease() { releaseCheServer() { set -x tmpmvnlog=/tmp/mvn.log.txt - if [[ $RELEASE_CHE_PARENT = "true" ]]; then - pushd che-parent >/dev/null - rm -f $tmpmvnlog || true - set +e - mvn clean install -ntp -U -Pcodenvy-release -Dgpg.passphrase=$CHE_OSS_SONATYPE_PASSPHRASE | tee $tmpmvnlog - EXIT_CODE=$? - set -e - # try maven build again if we receive a server error - if grep -q -E "502 - Bad Gateway" $tmpmvnlog; then - rm -f $tmpmvnlog || true - mvn clean install -ntp -U -Pcodenvy-release -Dgpg.passphrase=$CHE_OSS_SONATYPE_PASSPHRASE | tee $tmpmvnlog - EXIT_CODE=$? - fi - # check log for errors if build successful; if failed, no need to check (already failed) - if [ $EXIT_CODE -eq 0 ]; then - checkLogForErrors $tmpmvnlog - echo 'Build of che-parent: Success!' - else - echo '[ERROR] 2. Build of che-parent: Failed!' - exit $EXIT_CODE - fi - popd >/dev/null - fi pushd che-server >/dev/null rm -f $tmpmvnlog || true @@ -262,20 +212,19 @@ releaseTypescriptDto() { popd >/dev/null } -buildImages() { - export BUILDER="docker" +buildAndPushImages() { echo "Going to build docker images" set -e set -o pipefail TAG=$1 # stop / rm all containers - if [[ $(docker ps -aq) != "" ]];then - docker rm -f "$(docker ps -aq)" + if [[ $(podman ps -aq) != "" ]];then + podman rm -f "$(podman ps -aq)" fi - # BUILD IMAGES - bash "$(pwd)/che-server/build/build.sh" --tag:${TAG} + # BUILD AND PUSH IMAGES + bash "$(pwd)/che-server/build/build.sh" --tag:${TAG} --latest-tag --build-platforms:${BUILD_PLATFORMS} --builder:podman --push-image if [[ $? -ne 0 ]]; then echo "ERROR:" echo "build of che-server image $TAG is failed!" @@ -283,24 +232,6 @@ buildImages() { fi } -tagLatestImages() { - echo y | docker tag "${IMAGE}:$1" "${IMAGE}:latest" - if [[ $? -ne 0 ]]; then - die_with "docker tag of '${IMAGE}' image is failed!" - fi -} - -pushImagesOnQuay() { - #PUSH THE IMAGE - echo y | docker push "${IMAGE}:$1" - if [[ $2 == "pushLatest" ]]; then - echo y | docker push "${IMAGE}:latest" - fi - if [[ $? -ne 0 ]]; then - die_with "docker push of '${IMAGE}' image is failed!" - fi -} - bumpVersions() { # infer project version + commit change into ${BASEBRANCH} branch echo "${BASEBRANCH} ${BRANCH}" @@ -320,27 +251,8 @@ bumpVersion() { set -x echo "[info]bumping to version $1 in branch $2" - if [[ $RELEASE_CHE_PARENT = "true" ]]; then - pushd che-parent >/dev/null - git checkout $2 - #install previous version, in case it is not available in central repo - #which is needed for dependent projects - - mvn clean install - mvn versions:set -DgenerateBackupPoms=false -DnewVersion=${CHE_VERSION} - mvn clean install - # run mvn license format, in case some files that have old license headers have been updated - mvn license:format - commitChangeOrCreatePR ${CHE_VERSION} $2 "pr-${2}-to-${1}" - popd >/dev/null - fi - pushd che-server >/dev/null git checkout $2 - if [[ $RELEASE_CHE_PARENT = "true" ]]; then - mvn versions:update-parent -DgenerateBackupPoms=false -DallowSnapshots=true -DparentVersion=[${VERSION_CHE_PARENT}] - fi - # compute current version of root pom current_root_pom_version=$(grep "" pom.xml | sed -r -e "s#.+([^<>]+).*#\1#") @@ -348,7 +260,6 @@ bumpVersion() { sed -i -e "s#.*<\/che.version>#$1<\/che.version>#" pom.xml pushd typescript-dto >/dev/null sed -i -e "s#.*<\/che.version>#${1}<\/che.version>#" dto-pom.xml - sed -i -e "/org.eclipse.che.parent<\/groupId>/ { n; s#.*<\/version>#${VERSION_CHE_PARENT}<\/version>#}" dto-pom.xml popd >/dev/null # update integration tests to new root pom version @@ -404,7 +315,5 @@ releaseCheServer releaseTypescriptDto if [[ "${BUILD_AND_PUSH_IMAGES}" = "true" ]]; then - buildImages ${CHE_VERSION} - tagLatestImages ${CHE_VERSION} - pushImagesOnQuay ${CHE_VERSION} pushLatest + buildAndPushImages ${CHE_VERSION} fi diff --git a/pom.xml b/pom.xml index d1d9278efee..4382de2aec9 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,7 @@ 4.0.0 org.eclipse.che che-server - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT pom Che Server Eclipse Che Server @@ -58,7 +58,7 @@ 2.10.1 2.2.224 0.1.55 - 3.14.9 + 4.12.0 3.6.0 1.5 2.14.0 @@ -97,7 +97,7 @@ Red Hat, Inc. - initial API and implementation ${project.version} - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT 1.0-beta2 Red Hat, Inc. @@ -283,6 +283,16 @@ com.squareup.okhttp3 okhttp ${com.squareup.okhttp3.version} + + + kotlin-stdlib-jdk8 + org.jetbrains.kotlin + + + kotlin-stdlib-common + org.jetbrains.kotlin + + com.squareup.okio @@ -695,6 +705,11 @@ che-core-api-auth-gitlab ${che.version} + + org.eclipse.che.core + che-core-api-auth-gitlab-common + ${che.version} + org.eclipse.che.core che-core-api-auth-openshift @@ -782,6 +797,11 @@ che-core-api-factory-gitlab ${che.version} + + org.eclipse.che.core + che-core-api-factory-gitlab-common + ${che.version} + org.eclipse.che.core che-core-api-factory-shared diff --git a/token.yaml b/token.yaml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/typescript-dto/dto-pom.xml b/typescript-dto/dto-pom.xml index fc6a41316eb..3605748720d 100644 --- a/typescript-dto/dto-pom.xml +++ b/typescript-dto/dto-pom.xml @@ -23,7 +23,7 @@ pom Che TypeScript DTO - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT diff --git a/wsmaster/che-core-api-account/pom.xml b/wsmaster/che-core-api-account/pom.xml index dc25533cb38..2045bcc3e61 100644 --- a/wsmaster/che-core-api-account/pom.xml +++ b/wsmaster/che-core-api-account/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-account Che Core :: API :: Account diff --git a/wsmaster/che-core-api-auth-azure-devops/pom.xml b/wsmaster/che-core-api-auth-azure-devops/pom.xml index 81a4f42b12e..034e7377ea7 100644 --- a/wsmaster/che-core-api-auth-azure-devops/pom.xml +++ b/wsmaster/che-core-api-auth-azure-devops/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-auth-azure-devops jar @@ -27,6 +27,10 @@ com.fasterxml.jackson.core jackson-annotations + + com.fasterxml.jackson.core + jackson-databind + com.google.guava guava @@ -55,6 +59,10 @@ org.eclipse.che.core che-core-api-auth-shared + + org.eclipse.che.core + che-core-api-dto + org.eclipse.che.core che-core-commons-annotations diff --git a/wsmaster/che-core-api-auth-azure-devops/src/main/java/org/eclipse/che/security/oauth/AzureDevOpsOAuthAuthenticator.java b/wsmaster/che-core-api-auth-azure-devops/src/main/java/org/eclipse/che/security/oauth/AzureDevOpsOAuthAuthenticator.java index 7917d41de1d..cde28785aba 100644 --- a/wsmaster/che-core-api-auth-azure-devops/src/main/java/org/eclipse/che/security/oauth/AzureDevOpsOAuthAuthenticator.java +++ b/wsmaster/che-core-api-auth-azure-devops/src/main/java/org/eclipse/che/security/oauth/AzureDevOpsOAuthAuthenticator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -11,13 +11,22 @@ */ package org.eclipse.che.security.oauth; +import static java.lang.String.format; +import static java.net.URLEncoder.encode; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.eclipse.che.commons.json.JsonHelper.fromJson; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; +import static org.eclipse.che.dto.server.DtoFactory.newDto; +import com.fasterxml.jackson.databind.ObjectMapper; import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl; import com.google.api.client.auth.oauth2.AuthorizationCodeTokenRequest; +import com.google.api.client.auth.oauth2.Credential; import com.google.api.client.util.store.MemoryDataStoreFactory; +import com.google.common.io.CharStreams; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.net.URI; import java.net.URL; import java.net.http.HttpClient; @@ -26,7 +35,6 @@ import java.util.List; import javax.inject.Singleton; import org.eclipse.che.api.auth.shared.dto.OAuthToken; -import org.eclipse.che.commons.json.JsonHelper; import org.eclipse.che.commons.json.JsonParseException; /** @@ -39,10 +47,14 @@ public class AzureDevOpsOAuthAuthenticator extends OAuthAuthenticator { private final String azureDevOpsScmApiEndpoint; private final String cheApiEndpoint; private final String azureDevOpsUserProfileDataApiUrl; + private final String tokenUri; + private final String[] redirectUris; private final String API_VERSION = "7.0"; private final String PROVIDER_NAME = "azure-devops"; private final String clientSecret; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + public AzureDevOpsOAuthAuthenticator( String cheApiEndpoint, String clientId, @@ -57,9 +69,11 @@ public AzureDevOpsOAuthAuthenticator( this.clientSecret = clientSecret; this.azureDevOpsScmApiEndpoint = trimEnd(azureDevOpsScmApiEndpoint, '/'); this.azureDevOpsUserProfileDataApiUrl = - String.format( + format( "%s/_apis/profile/profiles/me?api-version=%s", trimEnd(azureDevOpsApiEndpoint, '/'), API_VERSION); + this.tokenUri = tokenUri; + this.redirectUris = redirectUris; configure( clientId, clientSecret, redirectUris, authUri, tokenUri, new MemoryDataStoreFactory()); } @@ -74,7 +88,7 @@ public AzureDevOpsOAuthAuthenticator( public String getAuthenticateUrl(URL requestUrl, List scopes) { AuthorizationCodeRequestUrl url = flow.newAuthorizationUrl().setScopes(scopes); url.set("response_type", "Assertion"); - url.set("redirect_uri", String.format("%s/oauth/callback", cheApiEndpoint)); + url.set("redirect_uri", format("%s/oauth/callback", cheApiEndpoint)); url.setState(prepareState(requestUrl)); return url.build(); } @@ -85,8 +99,8 @@ public final String getOAuthProvider() { } @Override - public OAuthToken getToken(String userId) throws IOException { - final OAuthToken token = super.getToken(userId); + public OAuthToken getOrRefreshToken(String userId) throws IOException { + final OAuthToken token = super.getOrRefreshToken(userId); try { // check if user's token is valid by requesting user profile data if (token == null @@ -116,12 +130,64 @@ private AzureDevOpsUserProfile getUserProfile(String accessToken) try { HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofInputStream()); - return JsonHelper.fromJson(response.body(), AzureDevOpsUserProfile.class, null); + return fromJson(response.body(), AzureDevOpsUserProfile.class, null); } catch (IOException | InterruptedException | JsonParseException e) { throw new OAuthAuthenticationException(e.getMessage(), e); } } + private HttpRequest.BodyPublisher getParamsUrlEncoded(String refreshToken) { + String urlEncoded = + format( + "client_assertion_type=%1s&" + + "client_assertion=%2s&" + + "grant_type=refresh_token&" + + "assertion=%3s&" + + "redirect_uri=%4s", + encode("urn:ietf:params:oauth:client-assertion-type:jwt-bearer", UTF_8), + encode(clientSecret, UTF_8), + refreshToken, + redirectUris[0]); + return HttpRequest.BodyPublishers.ofString(urlEncoded); + } + + /** + * Refresh personal access token. + * + * @param userId user identifier + * @return a refreshed token object or the previous token if the refresh failed + * @throws IOException when error occurs during token loading + */ + public OAuthToken refreshToken(String userId) throws IOException { + if (!isConfigured()) { + throw new IOException(AUTHENTICATOR_IS_NOT_CONFIGURED); + } + + Credential credential = flow.loadCredential(userId); + if (credential == null) { + return null; + } + HttpClient client = HttpClient.newHttpClient(); + HttpRequest request = + HttpRequest.newBuilder(URI.create(tokenUri)) + .POST(getParamsUrlEncoded(credential.getRefreshToken())) + .headers("Content-Type", "application/x-www-form-urlencoded") + .build(); + try { + HttpResponse response = + client.send(request, HttpResponse.BodyHandlers.ofInputStream()); + AzureDevOpsRefreshToken token = + OBJECT_MAPPER.readValue( + CharStreams.toString(new InputStreamReader(response.body(), UTF_8)), + AzureDevOpsRefreshToken.class); + String accessToken = token.getAccessToken(); + credential.setAccessToken(accessToken); + return newDto(OAuthToken.class).withToken(accessToken); + } catch (IOException | InterruptedException exception) { + return newDto(OAuthToken.class).withToken(credential.getAccessToken()); + } + } + /** * Returns the token request. Overrides the default implementation to set the {@code grant_type}, * {@code assertion}, {@code client_assertion} and {@code client_assertion_type} accordingly to diff --git a/wsmaster/che-core-api-auth-azure-devops/src/main/java/org/eclipse/che/security/oauth/AzureDevOpsRefreshToken.java b/wsmaster/che-core-api-auth-azure-devops/src/main/java/org/eclipse/che/security/oauth/AzureDevOpsRefreshToken.java new file mode 100644 index 00000000000..50f7b8939ed --- /dev/null +++ b/wsmaster/che-core-api-auth-azure-devops/src/main/java/org/eclipse/che/security/oauth/AzureDevOpsRefreshToken.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2012-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.security.oauth; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Objects; + +@JsonIgnoreProperties(ignoreUnknown = true) +public class AzureDevOpsRefreshToken { + /** Access token issued by the authorization server. */ + private String accessToken; + + /** Token type. */ + private String tokenType; + + /** Refresh token which can be used to obtain new access tokens. */ + private String refreshToken; + + /** + * Lifetime in seconds of the access token (for example 3600 for an hour) or {@code null} for + * none. + */ + private String expiresInSeconds; + + /** Scope of the access token. */ + private String scope; + + public String getAccessToken() { + return accessToken; + } + + public String getTokenType() { + return tokenType; + } + + public String getRefreshToken() { + return refreshToken; + } + + public String getScope() { + return scope; + } + + public String getExpiresInSeconds() { + return expiresInSeconds; + } + + @JsonProperty("access_token") + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + @JsonProperty("token_type") + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } + + @JsonProperty("refresh_token") + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + @JsonProperty("expires_in") + public void setExpiresInSeconds(String expiresInSeconds) { + this.expiresInSeconds = expiresInSeconds; + } + + public void setScope(String scope) { + this.scope = scope; + } + + @Override + public String toString() { + return "AzureDevOpsRefreshToken{" + + "accessToken='" + + accessToken + + '\'' + + ", tokenType='" + + tokenType + + '\'' + + ", refreshToken='" + + refreshToken + + '\'' + + ", expiresInSeconds='" + + expiresInSeconds + + '\'' + + ", scope='" + + scope + + '\'' + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AzureDevOpsRefreshToken that = (AzureDevOpsRefreshToken) o; + return Objects.equals(accessToken, that.accessToken) + && Objects.equals(tokenType, that.tokenType) + && Objects.equals(refreshToken, that.refreshToken) + && Objects.equals(expiresInSeconds, that.expiresInSeconds) + && Objects.equals(scope, that.scope); + } + + @Override + public int hashCode() { + return Objects.hash(accessToken, tokenType, refreshToken, expiresInSeconds, scope); + } +} diff --git a/wsmaster/che-core-api-auth-bitbucket/pom.xml b/wsmaster/che-core-api-auth-bitbucket/pom.xml index ea45faf7f29..dd1f90d71ac 100644 --- a/wsmaster/che-core-api-auth-bitbucket/pom.xml +++ b/wsmaster/che-core-api-auth-bitbucket/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-auth-bitbucket jar diff --git a/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth/BitbucketOAuthAuthenticator.java b/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth/BitbucketOAuthAuthenticator.java index 657c5c07467..1324a061f7b 100644 --- a/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth/BitbucketOAuthAuthenticator.java +++ b/wsmaster/che-core-api-auth-bitbucket/src/main/java/org/eclipse/che/security/oauth/BitbucketOAuthAuthenticator.java @@ -29,6 +29,10 @@ public class BitbucketOAuthAuthenticator extends OAuthAuthenticator { private final String bitbucketEndpoint; + private static final String BITBUCKET_CLOUD_ENDPOINT = "https://bitbucket.org"; + private static final String BITBUCKET_NAME = "bitbucket"; + private static final String BITBUCKET_SERVER_NAME = "bitbucket-server"; + public BitbucketOAuthAuthenticator( String bitbucketEndpoint, String clientId, @@ -52,12 +56,15 @@ public String getAuthenticateUrl(URL requestUrl, List scopes) { @Override public final String getOAuthProvider() { - return "bitbucket"; + // Bitbucket Cloud and Bitbucket Server have different provider names. + return BITBUCKET_CLOUD_ENDPOINT.equals(bitbucketEndpoint) + ? BITBUCKET_NAME + : BITBUCKET_SERVER_NAME; } @Override - public OAuthToken getToken(String userId) throws IOException { - final OAuthToken token = super.getToken(userId); + public OAuthToken getOrRefreshToken(String userId) throws IOException { + final OAuthToken token = super.getOrRefreshToken(userId); // Need to check if token is valid for requests, if valid - return it to caller. try { if (token == null || isNullOrEmpty(token.getToken())) { @@ -76,7 +83,7 @@ public OAuthToken getToken(String userId) throws IOException { * @return Bitbucket Cloud or Server API request URL */ private String getTestRequestUrl() { - return "https://bitbucket.org".equals(bitbucketEndpoint) + return BITBUCKET_CLOUD_ENDPOINT.equals(bitbucketEndpoint) ? "https://api.bitbucket.org/2.0/user" : bitbucketEndpoint + "/plugins/servlet/applinks/whoami"; } diff --git a/wsmaster/che-core-api-auth-github-common/pom.xml b/wsmaster/che-core-api-auth-github-common/pom.xml index b4199d6a04c..11621f03bed 100644 --- a/wsmaster/che-core-api-auth-github-common/pom.xml +++ b/wsmaster/che-core-api-auth-github-common/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-auth-github-common jar diff --git a/wsmaster/che-core-api-auth-github-common/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java b/wsmaster/che-core-api-auth-github-common/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java index 7efc36db63d..4482f44a49d 100644 --- a/wsmaster/che-core-api-auth-github-common/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java +++ b/wsmaster/che-core-api-auth-github-common/src/main/java/org/eclipse/che/security/oauth/GitHubOAuthAuthenticator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -85,8 +85,8 @@ public boolean invalidateToken(String token) throws IOException { } @Override - public OAuthToken getToken(String oauthProvider) throws IOException { - final OAuthToken token = super.getToken(oauthProvider); + public OAuthToken getOrRefreshToken(String oauthProvider) throws IOException { + final OAuthToken token = super.getOrRefreshToken(oauthProvider); // Need to check if token which is stored is valid for requests, then if valid - we returns it // to // caller diff --git a/wsmaster/che-core-api-auth-github/pom.xml b/wsmaster/che-core-api-auth-github/pom.xml index 92e58c99994..da3f5473204 100644 --- a/wsmaster/che-core-api-auth-github/pom.xml +++ b/wsmaster/che-core-api-auth-github/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-auth-github jar diff --git a/wsmaster/che-core-api-auth-gitlab-common/pom.xml b/wsmaster/che-core-api-auth-gitlab-common/pom.xml new file mode 100644 index 00000000000..5466a134978 --- /dev/null +++ b/wsmaster/che-core-api-auth-gitlab-common/pom.xml @@ -0,0 +1,69 @@ + + + + 4.0.0 + + che-master-parent + org.eclipse.che.core + 7.96.0-SNAPSHOT + + che-core-api-auth-gitlab-common + jar + Che Core :: API :: Authentication GitLab Common + + + com.google.guava + guava + + + com.google.http-client + google-http-client + + + jakarta.inject + jakarta.inject-api + + + org.eclipse.che.core + che-core-api-auth + + + org.eclipse.che.core + che-core-api-auth-shared + + + org.eclipse.che.core + che-core-commons-json + + + org.eclipse.che.core + che-core-commons-lang + + + org.slf4j + slf4j-api + + + org.testng + testng + test + + + org.wiremock + wiremock-standalone + test + + + diff --git a/wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/AbstractGitLabOAuthAuthenticatorProvider.java b/wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/AbstractGitLabOAuthAuthenticatorProvider.java new file mode 100644 index 00000000000..50389add71f --- /dev/null +++ b/wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/AbstractGitLabOAuthAuthenticatorProvider.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2012-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.security.oauth; + +import static com.google.common.base.Strings.isNullOrEmpty; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import javax.inject.Provider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provides implementation of GitLab {@link OAuthAuthenticator} based on available configuration. + * + * @author Pavol Baran + */ +public class AbstractGitLabOAuthAuthenticatorProvider implements Provider { + private static final Logger LOG = + LoggerFactory.getLogger(AbstractGitLabOAuthAuthenticatorProvider.class); + private final OAuthAuthenticator authenticator; + private final String providerName; + + public AbstractGitLabOAuthAuthenticatorProvider( + String clientIdPath, + String clientSecretPath, + String gitlabEndpoint, + String cheApiEndpoint, + String providerName) + throws IOException { + this.providerName = providerName; + authenticator = + getOAuthAuthenticator(clientIdPath, clientSecretPath, gitlabEndpoint, cheApiEndpoint); + LOG.debug("{} GitLab OAuth Authenticator is used.", authenticator); + } + + @Override + public OAuthAuthenticator get() { + return authenticator; + } + + private OAuthAuthenticator getOAuthAuthenticator( + String clientIdPath, String clientSecretPath, String gitlabEndpoint, String cheApiEndpoint) + throws IOException { + if (!isNullOrEmpty(clientIdPath) + && !isNullOrEmpty(clientSecretPath) + && !isNullOrEmpty(gitlabEndpoint)) { + String clientId = Files.readString(Path.of(clientIdPath)); + String clientSecret = Files.readString(Path.of(clientSecretPath)); + if (!isNullOrEmpty(clientId) && !isNullOrEmpty(clientSecret)) { + return new GitLabOAuthAuthenticator( + clientId, clientSecret, gitlabEndpoint, cheApiEndpoint, providerName); + } + } + return new NoopOAuthAuthenticator(); + } + + static class NoopOAuthAuthenticator extends OAuthAuthenticator { + + @Override + public String getOAuthProvider() { + return "Noop"; + } + + @Override + public String getEndpointUrl() { + return "Noop"; + } + } +} diff --git a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticator.java b/wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticator.java similarity index 91% rename from wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticator.java rename to wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticator.java index ebba4a2021c..6e78b7281b7 100644 --- a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticator.java +++ b/wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticator.java @@ -40,13 +40,19 @@ public class GitLabOAuthAuthenticator extends OAuthAuthenticator { private final String clientId; private final String clientSecret; private final String gitlabEndpoint; + private final String providerName; public GitLabOAuthAuthenticator( - String clientId, String clientSecret, String gitlabEndpoint, String cheApiEndpoint) + String clientId, + String clientSecret, + String gitlabEndpoint, + String cheApiEndpoint, + String providerName) throws IOException { this.clientId = clientId; this.clientSecret = clientSecret; this.gitlabEndpoint = trimEnd(gitlabEndpoint, '/'); + this.providerName = providerName; String trimmedGitlabEndpoint = trimEnd(gitlabEndpoint, '/'); this.gitlabUserEndpoint = trimmedGitlabEndpoint + "/api/v4/user"; this.cheApiEndpoint = cheApiEndpoint; @@ -61,7 +67,7 @@ public GitLabOAuthAuthenticator( @Override public String getOAuthProvider() { - return "gitlab"; + return providerName; } @Override @@ -88,8 +94,8 @@ protected O getJson(String getUserUrl, String accessToken, Class userClas } @Override - public OAuthToken getToken(String userId) throws IOException { - final OAuthToken token = super.getToken(userId); + public OAuthToken getOrRefreshToken(String userId) throws IOException { + final OAuthToken token = super.getOrRefreshToken(userId); try { if (token == null || token.getToken() == null || token.getToken().isEmpty()) { return null; diff --git a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabUser.java b/wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/GitLabUser.java similarity index 96% rename from wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabUser.java rename to wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/GitLabUser.java index 078f847ef4a..1b6c9f70652 100644 --- a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabUser.java +++ b/wsmaster/che-core-api-auth-gitlab-common/src/main/java/org/eclipse/che/security/oauth/GitLabUser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ diff --git a/wsmaster/che-core-api-auth-gitlab/pom.xml b/wsmaster/che-core-api-auth-gitlab/pom.xml index 7f35152783d..542fb36eada 100644 --- a/wsmaster/che-core-api-auth-gitlab/pom.xml +++ b/wsmaster/che-core-api-auth-gitlab/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-auth-gitlab jar @@ -49,23 +49,15 @@ org.eclipse.che.core - che-core-api-auth-shared - - - org.eclipse.che.core - che-core-commons-annotations + che-core-api-auth-gitlab-common org.eclipse.che.core - che-core-commons-json + che-core-api-auth-shared org.eclipse.che.core - che-core-commons-lang - - - org.slf4j - slf4j-api + che-core-commons-annotations org.testng diff --git a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabModule.java b/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabModule.java index ef3951a3dc5..29b3c190716 100644 --- a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabModule.java +++ b/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabModule.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -25,5 +25,6 @@ protected void configure() { Multibinder oAuthAuthenticators = Multibinder.newSetBinder(binder(), OAuthAuthenticator.class); oAuthAuthenticators.addBinding().toProvider(GitLabOAuthAuthenticatorProvider.class); + oAuthAuthenticators.addBinding().toProvider(GitLabOAuthAuthenticatorProviderSecond.class); } } diff --git a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProvider.java b/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProvider.java index a8a11015ec5..17965bb3170 100644 --- a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProvider.java +++ b/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -11,18 +11,11 @@ */ package org.eclipse.che.security.oauth; -import static com.google.common.base.Strings.isNullOrEmpty; - import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import javax.inject.Inject; import javax.inject.Named; -import javax.inject.Provider; import javax.inject.Singleton; import org.eclipse.che.commons.annotation.Nullable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * Provides implementation of GitLab {@link OAuthAuthenticator} based on available configuration. @@ -30,9 +23,8 @@ * @author Pavol Baran */ @Singleton -public class GitLabOAuthAuthenticatorProvider implements Provider { - private static final Logger LOG = LoggerFactory.getLogger(GitLabOAuthAuthenticatorProvider.class); - private final OAuthAuthenticator authenticator; +public class GitLabOAuthAuthenticatorProvider extends AbstractGitLabOAuthAuthenticatorProvider { + private static final String PROVIDER_NAME = "gitlab"; @Inject public GitLabOAuthAuthenticatorProvider( @@ -41,41 +33,6 @@ public GitLabOAuthAuthenticatorProvider( @Nullable @Named("che.integration.gitlab.oauth_endpoint") String gitlabEndpoint, @Named("che.api") String cheApiEndpoint) throws IOException { - authenticator = - getOAuthAuthenticator(clientIdPath, clientSecretPath, gitlabEndpoint, cheApiEndpoint); - LOG.debug("{} GitLab OAuth Authenticator is used.", authenticator); - } - - @Override - public OAuthAuthenticator get() { - return authenticator; - } - - private OAuthAuthenticator getOAuthAuthenticator( - String clientIdPath, String clientSecretPath, String gitlabEndpoint, String cheApiEndpoint) - throws IOException { - if (!isNullOrEmpty(clientIdPath) - && !isNullOrEmpty(clientSecretPath) - && !isNullOrEmpty(gitlabEndpoint)) { - String clientId = Files.readString(Path.of(clientIdPath)); - String clientSecret = Files.readString(Path.of(clientSecretPath)); - if (!isNullOrEmpty(clientId) && !isNullOrEmpty(clientSecret)) { - return new GitLabOAuthAuthenticator(clientId, clientSecret, gitlabEndpoint, cheApiEndpoint); - } - } - return new NoopOAuthAuthenticator(); - } - - static class NoopOAuthAuthenticator extends OAuthAuthenticator { - - @Override - public String getOAuthProvider() { - return "Noop"; - } - - @Override - public String getEndpointUrl() { - return "Noop"; - } + super(clientIdPath, clientSecretPath, gitlabEndpoint, cheApiEndpoint, PROVIDER_NAME); } } diff --git a/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProviderSecond.java b/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProviderSecond.java new file mode 100644 index 00000000000..42abe5d5427 --- /dev/null +++ b/wsmaster/che-core-api-auth-gitlab/src/main/java/org/eclipse/che/security/oauth/GitLabOAuthAuthenticatorProviderSecond.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2012-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.security.oauth; + +import java.io.IOException; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Singleton; +import org.eclipse.che.commons.annotation.Nullable; + +/** + * Provides implementation of GitLab {@link OAuthAuthenticator} based on available configuration. + * + * @author Pavol Baran + */ +@Singleton +public class GitLabOAuthAuthenticatorProviderSecond + extends AbstractGitLabOAuthAuthenticatorProvider { + private static final String PROVIDER_NAME = "gitlab_2"; + + @Inject + public GitLabOAuthAuthenticatorProviderSecond( + @Nullable @Named("che.oauth2.gitlab.clientid_filepath_2") String clientIdPath, + @Nullable @Named("che.oauth2.gitlab.clientsecret_filepath_2") String clientSecretPath, + @Nullable @Named("che.integration.gitlab.oauth_endpoint_2") String gitlabEndpoint, + @Named("che.api") String cheApiEndpoint) + throws IOException { + super(clientIdPath, clientSecretPath, gitlabEndpoint, cheApiEndpoint, PROVIDER_NAME); + } +} diff --git a/wsmaster/che-core-api-auth-gitlab/src/test/java/org/eclipse/che/security/oauth/GitLabAuthenticatorTest.java b/wsmaster/che-core-api-auth-gitlab/src/test/java/org/eclipse/che/security/oauth/GitLabAuthenticatorTest.java index cae662aeae8..2fcb9bfe5aa 100644 --- a/wsmaster/che-core-api-auth-gitlab/src/test/java/org/eclipse/che/security/oauth/GitLabAuthenticatorTest.java +++ b/wsmaster/che-core-api-auth-gitlab/src/test/java/org/eclipse/che/security/oauth/GitLabAuthenticatorTest.java @@ -49,7 +49,7 @@ public void shouldGetToken() throws Exception { // given GitLabOAuthAuthenticator gitLabOAuthAuthenticator = new GitLabOAuthAuthenticator( - "id", "secret", wireMockServer.url("/"), "https://che.api.com"); + "id", "secret", wireMockServer.url("/"), "https://che.api.com", "gitlab"); Field flowField = OAuthAuthenticator.class.getDeclaredField("flow"); Field credentialDataStoreField = ((Class) flowField.getGenericType()).getDeclaredField("credentialDataStore"); @@ -64,7 +64,7 @@ public void shouldGetToken() throws Exception { .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token")) .willReturn(aResponse().withBody("{\"id\": \"testId\"}"))); // when - OAuthToken token = gitLabOAuthAuthenticator.getToken("userId"); + OAuthToken token = gitLabOAuthAuthenticator.getOrRefreshToken("userId"); // then assertEquals(token.getToken(), "token"); } @@ -74,7 +74,7 @@ public void shouldGetEmptyToken() throws Exception { // given GitLabOAuthAuthenticator gitLabOAuthAuthenticator = new GitLabOAuthAuthenticator( - "id", "secret", wireMockServer.url("/"), "https://che.api.com"); + "id", "secret", wireMockServer.url("/"), "https://che.api.com", "gitlab"); Field flowField = OAuthAuthenticator.class.getDeclaredField("flow"); Field credentialDataStoreField = ((Class) flowField.getGenericType()).getDeclaredField("credentialDataStore"); @@ -89,7 +89,7 @@ public void shouldGetEmptyToken() throws Exception { .withHeader(HttpHeaders.AUTHORIZATION, equalTo("Bearer token")) .willReturn(aResponse().withBody("{}"))); // when - OAuthToken token = gitLabOAuthAuthenticator.getToken("userId"); + OAuthToken token = gitLabOAuthAuthenticator.getOrRefreshToken("userId"); // then assertNull(token); } diff --git a/wsmaster/che-core-api-auth-openshift/pom.xml b/wsmaster/che-core-api-auth-openshift/pom.xml index f61d062cec5..b96399f1905 100644 --- a/wsmaster/che-core-api-auth-openshift/pom.xml +++ b/wsmaster/che-core-api-auth-openshift/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-auth-openshift jar diff --git a/wsmaster/che-core-api-auth-openshift/src/main/java/org/eclipse/che/security/oauth/OpenShiftOAuthAuthenticator.java b/wsmaster/che-core-api-auth-openshift/src/main/java/org/eclipse/che/security/oauth/OpenShiftOAuthAuthenticator.java index 2a300de7725..af5e3c99f1b 100644 --- a/wsmaster/che-core-api-auth-openshift/src/main/java/org/eclipse/che/security/oauth/OpenShiftOAuthAuthenticator.java +++ b/wsmaster/che-core-api-auth-openshift/src/main/java/org/eclipse/che/security/oauth/OpenShiftOAuthAuthenticator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -63,8 +63,8 @@ public final String getOAuthProvider() { } @Override - public OAuthToken getToken(String userId) throws IOException { - final OAuthToken token = super.getToken(userId); + public OAuthToken getOrRefreshToken(String userId) throws IOException { + final OAuthToken token = super.getOrRefreshToken(userId); // Check if the token is valid for requests. if (!(token == null || token.getToken() == null || token.getToken().isEmpty())) { HttpURLConnection http = null; diff --git a/wsmaster/che-core-api-auth-shared/pom.xml b/wsmaster/che-core-api-auth-shared/pom.xml index 744c7035e8a..d45f6cde55a 100644 --- a/wsmaster/che-core-api-auth-shared/pom.xml +++ b/wsmaster/che-core-api-auth-shared/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-auth-shared jar diff --git a/wsmaster/che-core-api-auth/pom.xml b/wsmaster/che-core-api-auth/pom.xml index 8c751dd8343..725b9c8a6b5 100644 --- a/wsmaster/che-core-api-auth/pom.xml +++ b/wsmaster/che-core-api-auth/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-auth jar diff --git a/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/EmbeddedOAuthAPI.java b/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/EmbeddedOAuthAPI.java index 328032b3d49..fbdac336036 100644 --- a/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/EmbeddedOAuthAPI.java +++ b/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/EmbeddedOAuthAPI.java @@ -18,6 +18,7 @@ import static org.eclipse.che.commons.lang.UrlUtils.*; import static org.eclipse.che.commons.lang.UrlUtils.getParameter; import static org.eclipse.che.dto.server.DtoFactory.newDto; +import static org.eclipse.che.security.oauth.OAuthAuthenticator.SSL_ERROR_CODE; import static org.eclipse.che.security.oauth1.OAuthAuthenticationService.ERROR_QUERY_NAME; import jakarta.servlet.http.HttpServletRequest; @@ -43,6 +44,7 @@ import org.eclipse.che.api.core.util.LinksHelper; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; import org.eclipse.che.api.factory.server.scm.exception.UnsatisfiedScmPreconditionException; import org.eclipse.che.commons.annotation.Nullable; @@ -124,8 +126,16 @@ public Response callback(UriInfo uriInfo, @Nullable List errorValues) } catch (UnsatisfiedScmPreconditionException | ScmConfigurationPersistenceException e) { // Skip exception, the token will be stored in the next request. LOG.error(e.getMessage(), e); + } catch (ScmCommunicationException e) { + if (e.getStatusCode() == SSL_ERROR_CODE) { + return Response.temporaryRedirect( + URI.create(getRedirectAfterLoginUrl(params, "ssl_exception"))) + .build(); + } else { + LOG.error(e.getMessage(), e); + } } - return Response.temporaryRedirect(URI.create(getRedirectAfterLoginUrl(params))).build(); + return Response.temporaryRedirect(URI.create(getRedirectAfterLoginUrl(params, null))).build(); } /** @@ -133,9 +143,11 @@ public Response callback(UriInfo uriInfo, @Nullable List errorValues) * CSM provider, it will be decoded, to avoid unsupported characters in the URL. * * @param parameters the query parameters + * @param errorCode the error code or {@code null} * @return the redirect after login URL */ - public static String getRedirectAfterLoginUrl(Map> parameters) { + public static String getRedirectAfterLoginUrl( + Map> parameters, @Nullable String errorCode) { String redirectAfterLogin = getParameter(parameters, "redirect_after_login"); try { URI.create(redirectAfterLogin); @@ -143,6 +155,9 @@ public static String getRedirectAfterLoginUrl(Map> paramete // the redirectUrl was decoded by the CSM provider, so we need to encode it back. redirectAfterLogin = encodeRedirectUrl(redirectAfterLogin); } + if (errorCode != null) { + redirectAfterLogin += String.format("&%s=%s", ERROR_QUERY_NAME, errorCode); + } return redirectAfterLogin; } @@ -216,14 +231,14 @@ public Set getRegisteredAuthenticators(UriInfo uri } @Override - public OAuthToken getToken(String oauthProvider) + public OAuthToken getOrRefreshToken(String oauthProvider) throws NotFoundException, UnauthorizedException, ServerException { OAuthAuthenticator provider = getAuthenticator(oauthProvider); Subject subject = EnvironmentContext.getCurrent().getSubject(); try { - OAuthToken token = provider.getToken(subject.getUserId()); + OAuthToken token = provider.getOrRefreshToken(subject.getUserId()); if (token == null) { - token = provider.getToken(subject.getUserName()); + token = provider.getOrRefreshToken(subject.getUserName()); } if (token != null) { return token; @@ -237,7 +252,7 @@ public OAuthToken getToken(String oauthProvider) if (tokenOptional.isPresent()) { return newDto(OAuthToken.class).withToken(tokenOptional.get().getToken()); } - } catch (ScmConfigurationPersistenceException e) { + } catch (ScmConfigurationPersistenceException | ScmCommunicationException e) { throw new RuntimeException(e); } } @@ -248,11 +263,33 @@ public OAuthToken getToken(String oauthProvider) } } + @Override + public OAuthToken refreshToken(String oauthProvider) + throws NotFoundException, UnauthorizedException, ServerException { + OAuthAuthenticator provider = getAuthenticator(oauthProvider); + Subject subject = EnvironmentContext.getCurrent().getSubject(); + try { + OAuthToken token = provider.refreshToken(subject.getUserId()); + if (token == null) { + token = provider.refreshToken(subject.getUserName()); + } + + if (token != null) { + return token; + } else { + throw new UnauthorizedException( + "OAuth token for user " + subject.getUserId() + " was not found"); + } + } catch (IOException e) { + throw new ServerException(e.getLocalizedMessage(), e); + } + } + @Override public void invalidateToken(String oauthProvider) throws NotFoundException, UnauthorizedException, ServerException { OAuthAuthenticator oauth = getAuthenticator(oauthProvider); - OAuthToken oauthToken = getToken(oauthProvider); + OAuthToken oauthToken = getOrRefreshToken(oauthProvider); try { if (!oauth.invalidateToken(oauthToken.getToken())) { throw new UnauthorizedException( diff --git a/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAPI.java b/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAPI.java index 3ee63802c43..f7bc8345251 100644 --- a/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAPI.java +++ b/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAPI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2021 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -49,10 +49,19 @@ Set getRegisteredAuthenticators(UriInfo uriInfo) throws ForbiddenException; /** Implementation of method {@link OAuthAuthenticationService#token(String)} */ - OAuthToken getToken(String oauthProvider) + OAuthToken getOrRefreshToken(String oauthProvider) throws NotFoundException, UnauthorizedException, ServerException, ForbiddenException, BadRequestException, ConflictException; + /** + * Refreshes the token for the given OAuth provider. + * + * @param oauthProvider - the OAuth provider name + * @return the refreshed token + */ + OAuthToken refreshToken(String oauthProvider) + throws NotFoundException, UnauthorizedException, ServerException, ForbiddenException; + /** Implementation of method {@link OAuthAuthenticationService#invalidate(String)}} */ void invalidateToken(String oauthProvider) throws NotFoundException, UnauthorizedException, ServerException, ForbiddenException; diff --git a/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticationService.java b/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticationService.java index cf2cfe0f71e..6bc13b28133 100644 --- a/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticationService.java +++ b/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticationService.java @@ -96,7 +96,7 @@ public Set getRegisteredAuthenticators() throws Fo public OAuthToken token(@Required @QueryParam("oauth_provider") String oauthProvider) throws ServerException, UnauthorizedException, NotFoundException, ForbiddenException, BadRequestException, ConflictException { - return oAuthAPI.getToken(oauthProvider); + return oAuthAPI.getOrRefreshToken(oauthProvider); } /** diff --git a/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticator.java b/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticator.java index 7206188cb64..1846416fb3e 100644 --- a/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticator.java +++ b/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticator.java @@ -33,7 +33,9 @@ import java.util.List; import java.util.Map; import java.util.regex.Pattern; +import javax.net.ssl.SSLHandshakeException; import org.eclipse.che.api.auth.shared.dto.OAuthToken; +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.json.JsonHelper; import org.eclipse.che.commons.json.JsonParseException; @@ -43,7 +45,8 @@ /** Authentication service which allow get access token from OAuth provider site. */ public abstract class OAuthAuthenticator { - private static final String AUTHENTICATOR_IS_NOT_CONFIGURED = "Authenticator is not configured"; + protected static final String AUTHENTICATOR_IS_NOT_CONFIGURED = "Authenticator is not configured"; + protected static final int SSL_ERROR_CODE = 495; private static final Logger LOG = LoggerFactory.getLogger(OAuthAuthenticator.class); @@ -177,8 +180,10 @@ protected String findRedirectUrl(URL requestUrl) { * @return access token * @throws OAuthAuthenticationException if authentication failed or requestUrl does * not contain required parameters, e.g. 'code' + * @throws ScmCommunicationException if communication with SCM failed */ - public String callback(URL requestUrl, List scopes) throws OAuthAuthenticationException { + public String callback(URL requestUrl, List scopes) + throws OAuthAuthenticationException, ScmCommunicationException { if (!isConfigured()) { throw new OAuthAuthenticationException(AUTHENTICATOR_IS_NOT_CONFIGURED); } @@ -204,6 +209,10 @@ public String callback(URL requestUrl, List scopes) throws OAuthAuthenti flow.createAndStoreCredential(tokenResponse, userId); return tokenResponse.getAccessToken(); } catch (IOException ioe) { + if (ioe instanceof SSLHandshakeException) { + throw new ScmCommunicationException( + "SSL handshake failed. Please contact your administrator.", SSL_ERROR_CODE); + } throw new OAuthAuthenticationException(ioe.getMessage()); } } @@ -288,7 +297,7 @@ protected O getJson(String getUserUrl, String accessToken, Class userClas * @see OAuthTokenProvider#getToken(String, String) TODO: return Optional to avoid * returning null. */ - public OAuthToken getToken(String userId) throws IOException { + public OAuthToken getOrRefreshToken(String userId) throws IOException { if (!isConfigured()) { throw new IOException(AUTHENTICATOR_IS_NOT_CONFIGURED); } @@ -319,6 +328,44 @@ public OAuthToken getToken(String userId) throws IOException { return newDto(OAuthToken.class).withToken(credential.getAccessToken()); } + /** + * Refresh personal access token. + * + * @param userId user identifier + * @return a refreshed token value or {@code null} + * @throws IOException when error occurs during token loading + */ + public OAuthToken refreshToken(String userId) throws IOException { + if (!isConfigured()) { + throw new IOException(AUTHENTICATOR_IS_NOT_CONFIGURED); + } + + Credential credential = flow.loadCredential(userId); + if (credential == null) { + return null; + } + + boolean tokenRefreshed; + try { + tokenRefreshed = credential.refreshToken(); + } catch (IOException ioEx) { + tokenRefreshed = false; + } + + if (tokenRefreshed) { + credential = flow.loadCredential(userId); + } else { + // if token is not refreshed then old value should be invalidated + // and null result should be returned + try { + invalidateTokenByUser(userId); + } catch (IOException ignored) { + } + return null; + } + return newDto(OAuthToken.class).withToken(credential.getAccessToken()); + } + /** * Invalidate OAuth token for specified user. * diff --git a/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticatorTokenProvider.java b/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticatorTokenProvider.java index fccd2ffce32..bbccbed5896 100644 --- a/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticatorTokenProvider.java +++ b/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth/OAuthAuthenticatorTokenProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2021 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -32,7 +32,8 @@ public OAuthToken getToken(String oauthProviderName, String userId) throws IOExc OAuthAuthenticator oAuthAuthenticator = oAuthAuthenticatorProvider.getAuthenticator(oauthProviderName); OAuthToken token; - if (oAuthAuthenticator != null && (token = oAuthAuthenticator.getToken(userId)) != null) { + if (oAuthAuthenticator != null + && (token = oAuthAuthenticator.getOrRefreshToken(userId)) != null) { return token; } return null; diff --git a/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth1/OAuthAuthenticationService.java b/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth1/OAuthAuthenticationService.java index c98c62d92fa..1b27cdd5d9d 100644 --- a/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth1/OAuthAuthenticationService.java +++ b/wsmaster/che-core-api-auth/src/main/java/org/eclipse/che/security/oauth1/OAuthAuthenticationService.java @@ -74,7 +74,7 @@ public Response callback() throws OAuthAuthenticationException, BadRequestExcept final Map> parameters = getQueryParametersFromState(getState(requestUrl)); final String providerName = getParameter(parameters, "oauth_provider"); - final String redirectAfterLogin = getRedirectAfterLoginUrl(parameters); + final String redirectAfterLogin = getRedirectAfterLoginUrl(parameters, null); UriBuilder redirectUriBuilder = UriBuilder.fromUri(redirectAfterLogin); diff --git a/wsmaster/che-core-api-auth/src/test/java/org/eclipse/che/security/oauth/EmbeddedOAuthAPITest.java b/wsmaster/che-core-api-auth/src/test/java/org/eclipse/che/security/oauth/EmbeddedOAuthAPITest.java index 0013181c7cc..5a19730c3cd 100644 --- a/wsmaster/che-core-api-auth/src/test/java/org/eclipse/che/security/oauth/EmbeddedOAuthAPITest.java +++ b/wsmaster/che-core-api-auth/src/test/java/org/eclipse/che/security/oauth/EmbeddedOAuthAPITest.java @@ -17,6 +17,7 @@ import static java.util.Collections.singletonList; import static org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher.OAUTH_2_PREFIX; import static org.eclipse.che.dto.server.DtoFactory.newDto; +import static org.eclipse.che.security.oauth.OAuthAuthenticator.SSL_ERROR_CODE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; @@ -38,6 +39,7 @@ import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.security.oauth.shared.dto.OAuthAuthenticatorDescriptor; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; @@ -60,7 +62,7 @@ public class EmbeddedOAuthAPITest { expectedExceptions = NotFoundException.class, expectedExceptionsMessageRegExp = "Unsupported OAuth provider unknown") public void shouldThrowExceptionIfNoSuchProviderFound() throws Exception { - embeddedOAuthAPI.getToken("unknown"); + embeddedOAuthAPI.getOrRefreshToken("unknown"); } @Test @@ -70,9 +72,10 @@ public void shouldBeAbleToGetUserToken() throws Exception { OAuthAuthenticator authenticator = mock(OAuthAuthenticator.class); when(oauth2Providers.getAuthenticator(eq(provider))).thenReturn(authenticator); - when(authenticator.getToken(anyString())).thenReturn(newDto(OAuthToken.class).withToken(token)); + when(authenticator.getOrRefreshToken(anyString())) + .thenReturn(newDto(OAuthToken.class).withToken(token)); - OAuthToken result = embeddedOAuthAPI.getToken(provider); + OAuthToken result = embeddedOAuthAPI.getOrRefreshToken(provider); assertEquals(result.getToken(), token); } @@ -115,6 +118,30 @@ public void shouldEncodeRejectErrorForRedirectUrl() throws Exception { "http://eclipse.che?quary%3Dparam%26error_code%3Daccess_denied"); } + @Test + public void shouldAddSslErrorCode() throws Exception { + // given + UriInfo uriInfo = mock(UriInfo.class); + OAuthAuthenticator authenticator = mock(OAuthAuthenticator.class); + when(authenticator.callback(any(URL.class), anyList())) + .thenThrow(new ScmCommunicationException("", SSL_ERROR_CODE)); + when(uriInfo.getRequestUri()) + .thenReturn( + new URI( + "http://eclipse.che?state=oauth_provider" + + encode( + "=github&redirect_after_login=https://redirecturl.com?params=", UTF_8))); + when(oauth2Providers.getAuthenticator("github")).thenReturn(authenticator); + + // when + Response callback = embeddedOAuthAPI.callback(uriInfo, singletonList("ssl_exception")); + + // then + assertEquals( + callback.getLocation().toString(), + "https://redirecturl.com?params=&error_code=ssl_exception"); + } + @Test public void shouldStoreTokenOnCallback() throws Exception { // given diff --git a/wsmaster/che-core-api-devfile-shared/pom.xml b/wsmaster/che-core-api-devfile-shared/pom.xml index a0f199ddb2f..13638180f48 100644 --- a/wsmaster/che-core-api-devfile-shared/pom.xml +++ b/wsmaster/che-core-api-devfile-shared/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-devfile-shared jar diff --git a/wsmaster/che-core-api-devfile/pom.xml b/wsmaster/che-core-api-devfile/pom.xml index 66e066039a3..d2f34e4a789 100644 --- a/wsmaster/che-core-api-devfile/pom.xml +++ b/wsmaster/che-core-api-devfile/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-devfile jar diff --git a/wsmaster/che-core-api-factory-azure-devops/pom.xml b/wsmaster/che-core-api-factory-azure-devops/pom.xml index b38dd821061..f909cade67d 100644 --- a/wsmaster/che-core-api-factory-azure-devops/pom.xml +++ b/wsmaster/che-core-api-factory-azure-devops/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-factory-azure-devops jar diff --git a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsApiClient.java b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsApiClient.java index a18ae884d21..87616776a7c 100644 --- a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsApiClient.java +++ b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsApiClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -147,7 +147,9 @@ private T executeRequest( throw new ScmItemNotFoundException(body); default: throw new ScmCommunicationException( - "Unexpected status code " + response.statusCode() + " " + response.toString()); + "Unexpected status code " + response.statusCode() + " " + response, + response.statusCode(), + "azure-devops"); } } } catch (IOException | InterruptedException | UncheckedIOException e) { diff --git a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsFactoryParametersResolver.java b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsFactoryParametersResolver.java index 6b3654d3841..13f6c6d4d5e 100644 --- a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsFactoryParametersResolver.java +++ b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsFactoryParametersResolver.java @@ -25,19 +25,14 @@ import org.eclipse.che.api.factory.server.FactoryParametersResolver; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; -import org.eclipse.che.api.factory.server.urlfactory.ProjectConfigDtoMerger; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.factory.shared.dto.FactoryVisitor; import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; -import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.ProjectDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.SourceDto; /** * Provides Factory Parameters resolver for Azure DevOps repositories. @@ -56,11 +51,9 @@ public class AzureDevOpsFactoryParametersResolver extends BaseFactoryParameterRe private final URLFetcher urlFetcher; private final URLFactoryBuilder urlFactoryBuilder; private final PersonalAccessTokenManager personalAccessTokenManager; - private final ProjectConfigDtoMerger projectConfigDtoMerger; @Inject public AzureDevOpsFactoryParametersResolver( - ProjectConfigDtoMerger projectConfigDtoMerger, AzureDevOpsURLParser azureDevOpsURLParser, URLFetcher urlFetcher, URLFactoryBuilder urlFactoryBuilder, @@ -71,7 +64,6 @@ public AzureDevOpsFactoryParametersResolver( this.urlFetcher = urlFetcher; this.urlFactoryBuilder = urlFactoryBuilder; this.personalAccessTokenManager = personalAccessTokenManager; - this.projectConfigDtoMerger = projectConfigDtoMerger; } @Override @@ -122,44 +114,6 @@ public FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) { .withBranch(azureDevOpsUrl.getBranch()); return factoryDto.withScmInfo(scmInfo); } - - @Override - public FactoryDto visit(FactoryDto factory) { - if (factory.getWorkspace() != null) { - return projectConfigDtoMerger.merge( - factory, - () -> { - // Compute project configuration - return newDto(ProjectConfigDto.class) - .withSource(buildWorkspaceConfigSource(azureDevOpsUrl)) - .withName(azureDevOpsUrl.getRepository()) - .withPath("/".concat(azureDevOpsUrl.getRepository())); - }); - } else if (factory.getDevfile() == null) { - factory.setDevfile(urlFactoryBuilder.buildDefaultDevfile(azureDevOpsUrl.getRepository())); - } - - updateProjects( - factory.getDevfile(), - () -> - newDto(ProjectDto.class) - .withSource( - newDto(SourceDto.class) - .withLocation(azureDevOpsUrl.getRepositoryLocation()) - .withType("git") - .withBranch(azureDevOpsUrl.getBranch()) - .withTag(azureDevOpsUrl.getTag())) - .withName(azureDevOpsUrl.getRepository()), - project -> { - final String location = project.getSource().getLocation(); - if (location.equals(azureDevOpsUrl.getRepositoryLocation())) { - project.getSource().setBranch(azureDevOpsUrl.getBranch()); - project.getSource().setTag(azureDevOpsUrl.getTag()); - } - }); - - return factory; - } } @Override diff --git a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsPersonalAccessTokenFetcher.java b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsPersonalAccessTokenFetcher.java index b9fef804eea..2c671c33ac7 100644 --- a/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsPersonalAccessTokenFetcher.java +++ b/wsmaster/che-core-api-factory-azure-devops/src/main/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsPersonalAccessTokenFetcher.java @@ -71,9 +71,21 @@ public AzureDevOpsPersonalAccessTokenFetcher( this.azureDevOpsApiClient = azureDevOpsApiClient; } + @Override + public PersonalAccessToken refreshPersonalAccessToken(Subject cheSubject, String scmServerUrl) + throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { + return fetchOrRefreshPersonalAccessToken(cheSubject, scmServerUrl, true); + } + @Override public PersonalAccessToken fetchPersonalAccessToken(Subject cheSubject, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { + return fetchOrRefreshPersonalAccessToken(cheSubject, scmServerUrl, false); + } + + private PersonalAccessToken fetchOrRefreshPersonalAccessToken( + Subject cheSubject, String scmServerUrl, boolean forceRefreshToken) + throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { OAuthToken oAuthToken; if (!isValidScmServerUrl(scmServerUrl)) { @@ -82,7 +94,10 @@ public PersonalAccessToken fetchPersonalAccessToken(Subject cheSubject, String s } try { - oAuthToken = oAuthAPI.getToken(AzureDevOps.PROVIDER_NAME); + oAuthToken = + forceRefreshToken + ? oAuthAPI.refreshToken(AzureDevOps.PROVIDER_NAME) + : oAuthAPI.getOrRefreshToken(AzureDevOps.PROVIDER_NAME); String tokenName = NameGenerator.generate(OAUTH_2_PREFIX, 5); String tokenId = NameGenerator.generate("id-", 5); Optional> valid = @@ -154,7 +169,8 @@ public Optional isValid(PersonalAccessToken personalAccessToken) { } @Override - public Optional> isValid(PersonalAccessTokenParams params) { + public Optional> isValid(PersonalAccessTokenParams params) + throws ScmCommunicationException { if (!isValidScmServerUrl(params.getScmProviderUrl())) { LOG.debug("not a valid url {} for current fetcher ", params.getScmProviderUrl()); return Optional.empty(); @@ -168,7 +184,7 @@ public Optional> isValid(PersonalAccessTokenParams params) user = azureDevOpsApiClient.getUserWithPAT(params.getToken(), params.getOrganization()); } return Optional.of(Pair.of(Boolean.TRUE, user.getEmailAddress())); - } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { + } catch (ScmItemNotFoundException | ScmBadRequestException e) { return Optional.empty(); } } diff --git a/wsmaster/che-core-api-factory-azure-devops/src/test/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsPersonalAccessTokenFetcherTest.java b/wsmaster/che-core-api-factory-azure-devops/src/test/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsPersonalAccessTokenFetcherTest.java index 3d05c00d533..ed51fe091fe 100644 --- a/wsmaster/che-core-api-factory-azure-devops/src/test/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsPersonalAccessTokenFetcherTest.java +++ b/wsmaster/che-core-api-factory-azure-devops/src/test/java/org/eclipse/che/api/factory/server/azure/devops/AzureDevOpsPersonalAccessTokenFetcherTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -94,7 +94,7 @@ public void fetchPersonalAccessTokenShouldReturnToken() throws Exception { personalAccessTokenFetcher = new AzureDevOpsPersonalAccessTokenFetcher( "localhost", "https://dev.azure.com", new String[] {}, azureDevOpsApiClient, oAuthAPI); - when(oAuthAPI.getToken(AzureDevOps.PROVIDER_NAME)).thenReturn(oAuthToken); + when(oAuthAPI.getOrRefreshToken(AzureDevOps.PROVIDER_NAME)).thenReturn(oAuthToken); when(azureDevOpsApiClient.getUserWithOAuthToken(any())).thenReturn(azureDevOpsUser); when(azureDevOpsUser.getEmailAddress()).thenReturn("user-email"); @@ -112,7 +112,7 @@ public void fetchPersonalAccessTokenShouldReturnToken() throws Exception { public void shouldThrowUnauthorizedExceptionIfTokenIsNotValid() throws Exception { Subject subject = new SubjectImpl("Username", "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(azureOauthToken).withScope(""); - when(oAuthAPI.getToken(anyString())).thenReturn(oAuthToken); + when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/_apis/profile/profiles/me?api-version=7.0")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token " + azureOauthToken)) diff --git a/wsmaster/che-core-api-factory-bitbucket-server/pom.xml b/wsmaster/che-core-api-factory-bitbucket-server/pom.xml index 26e3d084378..55953c9a99d 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/pom.xml +++ b/wsmaster/che-core-api-factory-bitbucket-server/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-factory-bitbucket-server jar @@ -94,10 +94,6 @@ org.eclipse.che.core che-core-api-workspace - - org.eclipse.che.core - che-core-api-workspace-shared - org.eclipse.che.core che-core-commons-annotations diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolver.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolver.java index 37c61bbe383..61e95ccc5a4 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolver.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolver.java @@ -27,13 +27,10 @@ import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.factory.shared.dto.FactoryVisitor; import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; -import org.eclipse.che.api.workspace.shared.dto.devfile.ProjectDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.SourceDto; /** * Provides Factory Parameters resolver for both public and private bitbucket repositories. @@ -134,34 +131,6 @@ public FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) { } return factoryDto.withScmInfo(scmInfo); } - - @Override - public FactoryDto visit(FactoryDto factory) { - if (factory.getDevfile() == null) { - // initialize default devfile - factory.setDevfile( - urlFactoryBuilder.buildDefaultDevfile(bitbucketServerUrl.getRepository())); - } - - updateProjects( - factory.getDevfile(), - () -> - newDto(ProjectDto.class) - .withSource( - newDto(SourceDto.class) - .withLocation(bitbucketServerUrl.repositoryLocation()) - .withType("git") - .withBranch(bitbucketServerUrl.getBranch())) - .withName(bitbucketServerUrl.getRepository()), - project -> { - final String location = project.getSource().getLocation(); - if (location.equals(bitbucketServerUrl.repositoryLocation())) { - project.getSource().setBranch(bitbucketServerUrl.getBranch()); - } - }); - - return factory; - } } @Override diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcher.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcher.java index 382f6c3d666..5229fd58774 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcher.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcher.java @@ -11,7 +11,6 @@ */ package org.eclipse.che.api.factory.server.bitbucket; -import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; import static java.lang.String.valueOf; @@ -70,9 +69,22 @@ public BitbucketServerPersonalAccessTokenFetcher( this.oAuthAPI = oAuthAPI; } + @Override + public PersonalAccessToken refreshPersonalAccessToken(Subject cheUser, String scmServerUrl) + throws ScmUnauthorizedException, ScmCommunicationException { + // #fetchPersonalAccessToken does the same thing as #refreshPersonalAccessToken + return fetchOrRefreshPersonalAccessToken(cheUser, scmServerUrl); + } + @Override public PersonalAccessToken fetchPersonalAccessToken(Subject cheUser, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException { + return fetchOrRefreshPersonalAccessToken(cheUser, scmServerUrl); + } + + private PersonalAccessToken fetchOrRefreshPersonalAccessToken( + Subject cheUser, String scmServerUrl) + throws ScmUnauthorizedException, ScmCommunicationException { if (!bitbucketServerApiClient.isConnected(scmServerUrl)) { LOG.debug("not a valid url {} for current fetcher ", scmServerUrl); return null; @@ -146,7 +158,8 @@ public Optional isValid(PersonalAccessToken accessToken) } @Override - public Optional> isValid(PersonalAccessTokenParams params) { + public Optional> isValid(PersonalAccessTokenParams params) + throws ScmCommunicationException { if (!bitbucketServerApiClient.isConnected(params.getScmProviderUrl())) { // If BitBucket oAuth is not configured check the manually added user namespace token. HttpBitbucketServerApiClient apiClient = @@ -166,22 +179,9 @@ public Optional> isValid(PersonalAccessTokenParams params) } } try { - // Token is added manually by a user without token id. Validate only by requesting user info. - if (isNullOrEmpty(params.getScmTokenId())) { - BitbucketUser user = bitbucketServerApiClient.getUser(params.getToken()); - return Optional.of(Pair.of(Boolean.TRUE, user.getName())); - } - // Token is added by OAuth. Token id is available. - BitbucketPersonalAccessToken bitbucketPersonalAccessToken = - bitbucketServerApiClient.getPersonalAccessToken( - params.getScmTokenId(), params.getToken()); - return Optional.of( - Pair.of( - DEFAULT_TOKEN_SCOPE.equals(bitbucketPersonalAccessToken.getPermissions()) - ? Boolean.TRUE - : Boolean.FALSE, - bitbucketPersonalAccessToken.getUser().getName())); - } catch (ScmItemNotFoundException | ScmUnauthorizedException | ScmCommunicationException e) { + BitbucketUser user = bitbucketServerApiClient.getUser(params.getToken()); + return Optional.of(Pair.of(Boolean.TRUE, user.getName())); + } catch (ScmItemNotFoundException | ScmUnauthorizedException e) { return Optional.empty(); } } diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerURLParser.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerURLParser.java index f551cdce423..93e84f6c794 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerURLParser.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerURLParser.java @@ -96,7 +96,7 @@ private boolean isUserTokenPresent(String repositoryUrl) { Optional token = personalAccessTokenManager.get(EnvironmentContext.getCurrent().getSubject(), serverUrl); return token.isPresent() && token.get().getScmTokenName().equals(OAUTH_PROVIDER_NAME); - } catch (ScmConfigurationPersistenceException exception) { + } catch (ScmConfigurationPersistenceException | ScmCommunicationException exception) { return false; } } diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClient.java b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClient.java index a579b27901e..6cf5383c715 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClient.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/main/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClient.java @@ -411,7 +411,9 @@ private T executeRequest( throw new ScmItemNotFoundException(body); default: throw new ScmCommunicationException( - "Unexpected status code " + response.statusCode() + " " + response.toString()); + "Unexpected status code " + response.statusCode() + " " + response, + response.statusCode(), + "bitbucket"); } } } catch (IOException | InterruptedException | UncheckedIOException e) { @@ -421,7 +423,7 @@ private T executeRequest( private @Nullable String getToken() throws ScmUnauthorizedException { try { - OAuthToken token = oAuthAPI.getToken("bitbucket"); + OAuthToken token = oAuthAPI.getOrRefreshToken("bitbucket-server"); return token.getToken(); } catch (NotFoundException | ServerException @@ -459,7 +461,7 @@ private ScmUnauthorizedException buildScmUnauthorizedException() { "bitbucket", authenticator instanceof NoopOAuthAuthenticator ? "2.0" : "1.0", authenticator instanceof NoopOAuthAuthenticator - ? apiEndpoint + "/oauth/authenticate?oauth_provider=bitbucket&scope=ADMIN_WRITE" + ? apiEndpoint + "/oauth/authenticate?oauth_provider=bitbucket-server&scope=ADMIN_WRITE" : authenticator.getLocalAuthenticateUrl()); } } diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolverTest.java b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolverTest.java index 86e5d9a8b3a..90b1df33a8f 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolverTest.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerAuthorizingFactoryParametersResolverTest.java @@ -14,7 +14,6 @@ import static java.util.Collections.singletonMap; import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; -import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.eclipse.che.security.oauth1.OAuthAuthenticationService.ERROR_QUERY_NAME; import static org.mockito.ArgumentMatchers.any; @@ -27,7 +26,6 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; @@ -41,11 +39,8 @@ import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; +import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; -import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.MetadataDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.SourceDto; import org.eclipse.che.security.oauth.OAuthAPI; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; @@ -112,23 +107,17 @@ public void shouldGenerateDevfileForFactoryWithNoDevfileOrJson() throws Exceptio String bitbucketUrl = "http://bitbucket.2mcl.com/scm/test/repo.git"; - FactoryDto computedFactory = generateDevfileFactory(); - - when(urlFactoryBuilder.buildDefaultDevfile(any())).thenReturn(computedFactory.getDevfile()); - when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) .thenReturn(Optional.empty()); Map params = ImmutableMap.of(URL_PARAMETER_NAME, bitbucketUrl); // when - FactoryDto factory = - (FactoryDto) bitbucketServerFactoryParametersResolver.createFactory(params); + FactoryDevfileV2Dto factory = + (FactoryDevfileV2Dto) bitbucketServerFactoryParametersResolver.createFactory(params); // then - verify(urlFactoryBuilder).buildDefaultDevfile(eq("repo")); - assertEquals(factory, computedFactory); - SourceDto source = factory.getDevfile().getProjects().get(0).getSource(); - assertEquals(source.getLocation(), bitbucketUrl); - assertNull(source.getBranch()); + ScmInfoDto scmInfo = factory.getScmInfo(); + assertEquals(scmInfo.getRepositoryUrl(), bitbucketUrl); + assertEquals(scmInfo.getBranch(), null); } @Test @@ -137,21 +126,21 @@ public void shouldSetDefaultProjectIntoDevfileIfNotSpecified() throws Exception String bitbucketUrl = "http://bitbucket.2mcl.com/users/test/repos/repo/browse?at=refs%2Fheads%2Ffoobar"; - FactoryDto computedFactory = generateDevfileFactory(); + FactoryDevfileV2Dto factoryDevfileV2Dto = generateDevfileV2Factory(); when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) - .thenReturn(Optional.of(computedFactory)); + .thenReturn(Optional.of(factoryDevfileV2Dto)); Map params = ImmutableMap.of(URL_PARAMETER_NAME, bitbucketUrl); // when - FactoryDto factory = - (FactoryDto) bitbucketServerFactoryParametersResolver.createFactory(params); + FactoryDevfileV2Dto factory = + (FactoryDevfileV2Dto) bitbucketServerFactoryParametersResolver.createFactory(params); // then assertNotNull(factory.getDevfile()); - SourceDto source = factory.getDevfile().getProjects().get(0).getSource(); - assertEquals(source.getLocation(), "http://bitbucket.2mcl.com/scm/~test/repo.git"); - assertEquals(source.getBranch(), "refs%2Fheads%2Ffoobar"); + ScmInfoDto source = factory.getScmInfo(); + assertEquals(source.getRepositoryUrl(), "http://bitbucket.2mcl.com/scm/~test/repo.git"); + assertEquals(source.getBranch(), "foobar"); } @Test @@ -178,16 +167,6 @@ public void shouldSetScmInfoIntoDevfileV2() throws Exception { assertEquals(scmInfo.getBranch(), "foobar"); } - private FactoryDto generateDevfileFactory() { - return newDto(FactoryDto.class) - .withV(CURRENT_VERSION) - .withSource("repo") - .withDevfile( - newDto(DevfileDto.class) - .withApiVersion(CURRENT_API_VERSION) - .withMetadata(newDto(MetadataDto.class).withName("che"))); - } - private FactoryDevfileV2Dto generateDevfileV2Factory() { return newDto(FactoryDevfileV2Dto.class) .withV(CURRENT_VERSION) @@ -203,7 +182,7 @@ public void shouldCreateFactoryWithoutAuthentication() throws ApiException { ImmutableMap.of(URL_PARAMETER_NAME, bitbucketServerUrl, ERROR_QUERY_NAME, "access_denied"); when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) - .thenReturn(Optional.of(generateDevfileFactory())); + .thenReturn(Optional.of(generateDevfileV2Factory())); // when bitbucketServerFactoryParametersResolver.createFactory(params); diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcherTest.java b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcherTest.java index 596ed6282d2..001d0352338 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcherTest.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketServerPersonalAccessTokenFetcherTest.java @@ -12,6 +12,7 @@ package org.eclipse.che.api.factory.server.bitbucket; import static java.lang.String.valueOf; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.verify; @@ -222,12 +223,8 @@ public void shouldBeAbleToValidateToken() // given when(personalAccessTokenParams.getScmProviderUrl()).thenReturn(someBitbucketURL); when(personalAccessTokenParams.getToken()).thenReturn(bitbucketPersonalAccessToken.getToken()); - when(personalAccessTokenParams.getScmTokenId()) - .thenReturn(bitbucketPersonalAccessToken.getId()); when(bitbucketServerApiClient.isConnected(eq(someBitbucketURL))).thenReturn(true); - when(bitbucketServerApiClient.getPersonalAccessToken( - eq(bitbucketPersonalAccessToken.getId()), eq(bitbucketPersonalAccessToken.getToken()))) - .thenReturn(bitbucketPersonalAccessToken); + when(bitbucketServerApiClient.getUser(anyString())).thenReturn(bitbucketUser); // when Optional> result = fetcher.isValid(personalAccessTokenParams); // then diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClientTest.java b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClientTest.java index 538ab2b3843..5f7bb75fff3 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClientTest.java +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/java/org/eclipse/che/api/factory/server/bitbucket/HttpBitbucketServerApiClientTest.java @@ -68,10 +68,10 @@ @Listeners(MockitoTestNGListener.class) public class HttpBitbucketServerApiClientTest { private final String AUTHORIZATION_TOKEN = - "OAuth oauth_consumer_key=\"key123321\", oauth_nonce=\"6c0eace252f8dcda\"," - + " oauth_signature=\"dPCm521TAF56FfGxabBAZDs9YTNeCg%2BiRK49afoJve8Mxk5ILlfkZKH693udqOig5k5ydeVxX%2FTso%2Flxx1pv2bqdbCqj3Nq82do1hJN5eTDLSvbHfGvjFuOGRobHTHwP6oJkaBSafjMUY8i8Vnz6hLfxToPj2ktd6ug4nKc1WGg%3D\", " + "OAuth oauth_consumer_key=\"key123321\", oauth_nonce=\"nonce\"," + + " oauth_signature=\"signature\", " + "oauth_signature_method=\"RSA-SHA1\", oauth_timestamp=\"1609250025\", " - + "oauth_token=\"JmpyDe9sgYNn6pYHP6eGLaIU0vxdKLCJ\", oauth_version=\"1.0\""; + + "oauth_token=\"token\", oauth_version=\"1.0\""; WireMockServer wireMockServer; WireMock wireMock; BitbucketServerApiClient bitbucketServer; @@ -276,7 +276,7 @@ public void shouldBeAbleToCreatePAT() "myToKen", ImmutableSet.of("PROJECT_WRITE", "REPO_WRITE")); // then assertNotNull(result); - assertEquals(result.getToken(), "MTU4OTEwNTMyOTA5Ohc88HcY8k7gWOzl2mP5TtdtY5Qs"); + assertEquals(result.getToken(), "token"); } @Test @@ -331,7 +331,7 @@ public void shouldBeAbleToGetExistedPAT() BitbucketPersonalAccessToken result = bitbucketServer.getPersonalAccessToken("5", "token"); // then assertNotNull(result); - assertEquals(result.getToken(), "MTU4OTEwNTMyOTA5Ohc88HcY8k7gWOzl2mP5TtdtY5Qs"); + assertEquals(result.getToken(), "token"); } @Test(expectedExceptions = ScmItemNotFoundException.class) @@ -394,7 +394,7 @@ public void shouldThrowScmCommunicationExceptionInNoOauthAuthenticator() NotFoundException, BadRequestException { // given - when(oAuthAPI.getToken(eq("bitbucket"))).thenReturn(mock(OAuthToken.class)); + when(oAuthAPI.getOrRefreshToken(eq("bitbucket-server"))).thenReturn(mock(OAuthToken.class)); HttpBitbucketServerApiClient localServer = new HttpBitbucketServerApiClient( wireMockServer.url("/"), new NoopOAuthAuthenticator(), oAuthAPI, apiEndpoint); @@ -411,7 +411,7 @@ public void shouldGetOauth2Token() // given OAuthToken token = mock(OAuthToken.class); when(token.getToken()).thenReturn("token"); - when(oAuthAPI.getToken(eq("bitbucket"))).thenReturn(token); + when(oAuthAPI.getOrRefreshToken(eq("bitbucket-server"))).thenReturn(token); bitbucketServer = new HttpBitbucketServerApiClient( wireMockServer.url("/"), new NoopOAuthAuthenticator(), oAuthAPI, apiEndpoint); @@ -437,6 +437,6 @@ public void shouldGetOauth2Token() bitbucketServer.getUser(); // then - verify(oAuthAPI, times(2)).getToken(eq("bitbucket")); + verify(oAuthAPI, times(2)).getOrRefreshToken(eq("bitbucket-server")); } } diff --git a/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/access-tokens/1.0/users/ksmster/newtoken.json b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/access-tokens/1.0/users/ksmster/newtoken.json index dc41054388c..6fc0a8357ba 100644 --- a/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/access-tokens/1.0/users/ksmster/newtoken.json +++ b/wsmaster/che-core-api-factory-bitbucket-server/src/test/resources/__files/bitbucket/rest/access-tokens/1.0/users/ksmster/newtoken.json @@ -23,5 +23,5 @@ ] } }, - "token": "MTU4OTEwNTMyOTA5Ohc88HcY8k7gWOzl2mP5TtdtY5Qs" -} \ No newline at end of file + "token": "token" +} diff --git a/wsmaster/che-core-api-factory-bitbucket/pom.xml b/wsmaster/che-core-api-factory-bitbucket/pom.xml index 6cea095e3f9..e9025e9897f 100644 --- a/wsmaster/che-core-api-factory-bitbucket/pom.xml +++ b/wsmaster/che-core-api-factory-bitbucket/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-factory-bitbucket jar diff --git a/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketApiClient.java b/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketApiClient.java index 1b0d72c2a0f..6f0ef8a23f3 100644 --- a/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketApiClient.java +++ b/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketApiClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -15,6 +15,7 @@ import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_NO_CONTENT; import static java.net.HttpURLConnection.HTTP_OK; +import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static java.time.Duration.ofSeconds; import com.fasterxml.jackson.databind.ObjectMapper; @@ -37,6 +38,7 @@ import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.slf4j.Logger; @@ -100,7 +102,8 @@ public BitbucketApiClient() { * @throws ScmBadRequestException */ public BitbucketUser getUser(String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final URI uri = apiServerUrl.resolve("user"); HttpRequest request = buildBitbucketApiRequest(uri, authenticationToken); LOG.trace("executeRequest={}", request); @@ -120,7 +123,8 @@ public BitbucketUser getUser(String authenticationToken) public String getFileContent( String workspace, String repository, String source, String path, String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final URI uri = apiServerUrl.resolve( String.format("repositories/%s/%s/src/%s/%s", workspace, repository, source, path)); @@ -148,7 +152,8 @@ public String getFileContent( * @throws ScmBadRequestException */ public BitbucketUserEmail getEmail(String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final URI uri = apiServerUrl.resolve("user/emails"); HttpRequest request = buildBitbucketApiRequest(uri, authenticationToken); LOG.trace("executeRequest={}", request); @@ -174,7 +179,8 @@ public BitbucketUserEmail getEmail(String authenticationToken) * scopes. */ public Pair getTokenScopes(String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final URI uri = apiServerUrl.resolve("user"); HttpRequest request = buildBitbucketApiRequest(uri, authenticationToken); LOG.trace("executeRequest={}", request); @@ -212,7 +218,8 @@ private T executeRequest( HttpClient httpClient, HttpRequest request, Function, T> responseConverter) - throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException { + throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException, + ScmUnauthorizedException { try { HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); @@ -228,13 +235,17 @@ private T executeRequest( throw new ScmBadRequestException(body); case HTTP_NOT_FOUND: throw new ScmItemNotFoundException(body); + case HTTP_UNAUTHORIZED: + throw new ScmUnauthorizedException(body, "bitbucket", "v2", ""); default: throw new ScmCommunicationException( - "Unexpected status code " + response.statusCode() + " " + response.toString()); + "Unexpected status code " + response.statusCode() + " " + response, + response.statusCode(), + "bitbucket"); } } } catch (IOException | InterruptedException | UncheckedIOException e) { - throw new ScmCommunicationException(e.getMessage(), e); + throw new ScmCommunicationException(e.getMessage(), e, "bitbucket"); } } diff --git a/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketFactoryParametersResolver.java b/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketFactoryParametersResolver.java index 7e828904f78..be0e44ab892 100644 --- a/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketFactoryParametersResolver.java +++ b/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketFactoryParametersResolver.java @@ -24,17 +24,13 @@ import org.eclipse.che.api.factory.server.FactoryParametersResolver; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; -import org.eclipse.che.api.factory.server.urlfactory.ProjectConfigDtoMerger; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.factory.shared.dto.FactoryVisitor; import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; -import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.ProjectDto; /** Provides Factory Parameters resolver for bitbucket repositories. */ @Singleton @@ -51,8 +47,6 @@ public class BitbucketFactoryParametersResolver extends BaseFactoryParameterReso private final BitbucketSourceStorageBuilder bitbucketSourceStorageBuilder; private final URLFactoryBuilder urlFactoryBuilder; - /** ProjectDtoMerger */ - private final ProjectConfigDtoMerger projectConfigDtoMerger; /** Personal Access Token manager used when fetching protected content. */ private final PersonalAccessTokenManager personalAccessTokenManager; @@ -65,7 +59,6 @@ public BitbucketFactoryParametersResolver( URLFetcher urlFetcher, BitbucketSourceStorageBuilder bitbucketSourceStorageBuilder, URLFactoryBuilder urlFactoryBuilder, - ProjectConfigDtoMerger projectConfigDtoMerger, PersonalAccessTokenManager personalAccessTokenManager, BitbucketApiClient bitbucketApiClient, AuthorisationRequestManager authorisationRequestManager) { @@ -74,7 +67,6 @@ public BitbucketFactoryParametersResolver( this.urlFetcher = urlFetcher; this.bitbucketSourceStorageBuilder = bitbucketSourceStorageBuilder; this.urlFactoryBuilder = urlFactoryBuilder; - this.projectConfigDtoMerger = projectConfigDtoMerger; this.personalAccessTokenManager = personalAccessTokenManager; this.bitbucketApiClient = bitbucketApiClient; } @@ -142,40 +134,6 @@ public FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) { } return factoryDto.withScmInfo(scmInfo); } - - @Override - public FactoryDto visit(FactoryDto factory) { - if (factory.getWorkspace() != null) { - return projectConfigDtoMerger.merge( - factory, - () -> { - // Compute project configuration - return newDto(ProjectConfigDto.class) - .withSource( - bitbucketSourceStorageBuilder.buildWorkspaceConfigSource(bitbucketUrl)) - .withName(bitbucketUrl.getRepository()) - .withPath("/".concat(bitbucketUrl.getRepository())); - }); - } else if (factory.getDevfile() == null) { - // initialize default devfile - factory.setDevfile(urlFactoryBuilder.buildDefaultDevfile(bitbucketUrl.getRepository())); - } - - updateProjects( - factory.getDevfile(), - () -> - newDto(ProjectDto.class) - .withSource(bitbucketSourceStorageBuilder.buildDevfileSource(bitbucketUrl)) - .withName(bitbucketUrl.getRepository()), - project -> { - final String location = project.getSource().getLocation(); - if (location.equals(bitbucketUrl.repositoryLocation())) { - project.getSource().setBranch(bitbucketUrl.getBranch()); - } - }); - - return factory; - } } @Override diff --git a/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcher.java b/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcher.java index 8c1653b579d..460fc95949b 100644 --- a/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcher.java +++ b/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcher.java @@ -81,9 +81,21 @@ public BitbucketPersonalAccessTokenFetcher( this.bitbucketApiClient = bitbucketApiClient; } + @Override + public PersonalAccessToken refreshPersonalAccessToken(Subject cheSubject, String scmServerUrl) + throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { + return fetchOrRefreshPersonalAccessToken(cheSubject, scmServerUrl, true); + } + @Override public PersonalAccessToken fetchPersonalAccessToken(Subject cheSubject, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { + return fetchOrRefreshPersonalAccessToken(cheSubject, scmServerUrl, false); + } + + private PersonalAccessToken fetchOrRefreshPersonalAccessToken( + Subject cheSubject, String scmServerUrl, boolean forceRefreshToken) + throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { OAuthToken oAuthToken; if (bitbucketApiClient == null || !bitbucketApiClient.isConnected(scmServerUrl)) { @@ -91,7 +103,10 @@ public PersonalAccessToken fetchPersonalAccessToken(Subject cheSubject, String s return null; } try { - oAuthToken = oAuthAPI.getToken(OAUTH_PROVIDER_NAME); + oAuthToken = + forceRefreshToken + ? oAuthAPI.refreshToken(OAUTH_PROVIDER_NAME) + : oAuthAPI.getOrRefreshToken(OAUTH_PROVIDER_NAME); String tokenName = NameGenerator.generate(OAUTH_PROVIDER_NAME, 5); String tokenId = NameGenerator.generate("id-", 5); Optional> valid = @@ -151,13 +166,17 @@ public Optional isValid(PersonalAccessToken personalAccessToken) { try { String[] scopes = bitbucketApiClient.getTokenScopes(personalAccessToken.getToken()).second; return Optional.of(isValidScope(Sets.newHashSet(scopes))); - } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { + } catch (ScmItemNotFoundException + | ScmCommunicationException + | ScmBadRequestException + | ScmUnauthorizedException e) { return Optional.of(Boolean.FALSE); } } @Override - public Optional> isValid(PersonalAccessTokenParams params) { + public Optional> isValid(PersonalAccessTokenParams params) + throws ScmCommunicationException { if (!bitbucketApiClient.isConnected(params.getScmProviderUrl())) { LOG.debug("not a valid url {} for current fetcher ", params.getScmProviderUrl()); return Optional.empty(); @@ -169,7 +188,7 @@ public Optional> isValid(PersonalAccessTokenParams params) Pair.of( isValidScope(Sets.newHashSet(pair.second)) ? Boolean.TRUE : Boolean.FALSE, pair.first)); - } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { + } catch (ScmItemNotFoundException | ScmBadRequestException | ScmUnauthorizedException e) { return Optional.empty(); } } diff --git a/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketUserDataFetcher.java b/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketUserDataFetcher.java index 6ce3e4b6703..0dd8569186b 100644 --- a/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketUserDataFetcher.java +++ b/wsmaster/che-core-api-factory-bitbucket/src/main/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketUserDataFetcher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -67,7 +67,7 @@ public BitbucketUserDataFetcher( public GitUserData fetchGitUserData() throws ScmUnauthorizedException, ScmCommunicationException { OAuthToken oAuthToken; try { - oAuthToken = oAuthAPI.getToken(OAUTH_PROVIDER_NAME); + oAuthToken = oAuthAPI.getOrRefreshToken(OAUTH_PROVIDER_NAME); // Find the user associated to the OAuth token by querying the Bitbucket API. BitbucketUser user = bitbucketApiClient.getUser(oAuthToken.getToken()); BitbucketUserEmail emailResponse = bitbucketApiClient.getEmail(oAuthToken.getToken()); diff --git a/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketFactoryParametersResolverTest.java b/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketFactoryParametersResolverTest.java index 9137d32823d..b41510c3acd 100644 --- a/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketFactoryParametersResolverTest.java +++ b/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketFactoryParametersResolverTest.java @@ -14,24 +14,20 @@ import static java.util.Collections.singletonMap; import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; -import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.eclipse.che.security.oauth1.OAuthAuthenticationService.ERROR_QUERY_NAME; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; -import java.util.Collections; import java.util.Map; import java.util.Optional; import org.eclipse.che.api.core.ApiException; @@ -39,17 +35,12 @@ import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; -import org.eclipse.che.api.factory.server.urlfactory.ProjectConfigDtoMerger; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; +import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; -import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.MetadataDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.ProjectDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.SourceDto; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; @@ -77,9 +68,6 @@ public class BitbucketFactoryParametersResolverTest { private BitbucketSourceStorageBuilder bitbucketSourceStorageBuilder = new BitbucketSourceStorageBuilder(); - /** ProjectDtoMerger */ - @Mock private ProjectConfigDtoMerger projectConfigDtoMerger; - /** Parser which will allow to check validity of URLs and create objects. */ @Mock private URLFactoryBuilder urlFactoryBuilder; @@ -88,7 +76,8 @@ public class BitbucketFactoryParametersResolverTest { /** * Capturing the location parameter when calling {@link - * URLFactoryBuilder#createFactoryFromDevfile(RemoteFactoryUrl, FileContentProvider, Map)} + * URLFactoryBuilder#createFactoryFromDevfile(RemoteFactoryUrl, FileContentProvider, Map, + * boolean)} */ @Captor private ArgumentCaptor factoryUrlArgumentCaptor; @@ -105,7 +94,6 @@ protected void init() { urlFetcher, bitbucketSourceStorageBuilder, urlFactoryBuilder, - projectConfigDtoMerger, personalAccessTokenManager, bitbucketApiClient, authorisationRequestManager); @@ -145,101 +133,17 @@ public void shouldGenerateDevfileForFactoryWithNoDevfile() throws Exception { String bitbucketUrl = "https://bitbucket.org/eclipse/che"; - FactoryDto computedFactory = generateDevfileFactory(); - - when(urlFactoryBuilder.buildDefaultDevfile(any())).thenReturn(computedFactory.getDevfile()); - when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) .thenReturn(Optional.empty()); Map params = ImmutableMap.of(URL_PARAMETER_NAME, bitbucketUrl); // when - FactoryDto factory = (FactoryDto) bitbucketFactoryParametersResolver.createFactory(params); - // then - verify(urlFactoryBuilder).buildDefaultDevfile(eq("che")); - assertEquals(factory, computedFactory); - SourceDto source = factory.getDevfile().getProjects().get(0).getSource(); - assertEquals(source.getLocation(), bitbucketUrl + ".git"); - assertEquals(source.getBranch(), null); - } - - @Test - public void shouldReturnFactoryFromRepositoryWithDevfile() throws Exception { - - when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) - .thenReturn(Collections.singletonList("devfile.yaml")); - - String bitbucketUrl = "https://bitbucket.org/eclipse/che"; - - FactoryDto computedFactory = generateDevfileFactory(); - - when(urlFactoryBuilder.createFactoryFromDevfile( - any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) - .thenReturn(Optional.of(computedFactory)); - - Map params = ImmutableMap.of(URL_PARAMETER_NAME, bitbucketUrl); - // when - FactoryDto factory = (FactoryDto) bitbucketFactoryParametersResolver.createFactory(params); - // then - assertNotNull(factory.getDevfile()); - assertNull(factory.getWorkspace()); - - // check we called the builder with the following devfile file - verify(urlFactoryBuilder) - .createFactoryFromDevfile( - factoryUrlArgumentCaptor.capture(), any(), anyMap(), anyBoolean()); - verify(urlFactoryBuilder, never()).buildDefaultDevfile(eq("che")); - assertEquals( - factoryUrlArgumentCaptor.getValue().devfileFileLocations().iterator().next().location(), - "https://bitbucket.org/eclipse/che/raw/HEAD/devfile.yaml"); - } - - @Test - public void shouldSetDefaultProjectIntoDevfileIfNotSpecified() throws Exception { - - String bitbucketUrl = "https://bitbucket.org/eclipse/che/src/foobar"; - - FactoryDto computedFactory = generateDevfileFactory(); - - when(urlFactoryBuilder.createFactoryFromDevfile( - any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) - .thenReturn(Optional.of(computedFactory)); - - Map params = ImmutableMap.of(URL_PARAMETER_NAME, bitbucketUrl); - // when - FactoryDto factory = (FactoryDto) bitbucketFactoryParametersResolver.createFactory(params); - // then - assertNotNull(factory.getDevfile()); - SourceDto source = factory.getDevfile().getProjects().get(0).getSource(); - assertEquals(source.getLocation(), "https://bitbucket.org/eclipse/che.git"); - assertEquals(source.getBranch(), "foobar"); - } - - @Test - public void shouldSetBranchIntoDevfileIfNotMatchesCurrent() throws Exception { - - String bitbucketUrl = "https://bitbucket.org/eclipse/che/src/foobranch"; - - FactoryDto computedFactory = generateDevfileFactory(); - computedFactory - .getDevfile() - .getProjects() - .add( - newDto(ProjectDto.class) - .withSource( - newDto(SourceDto.class).withLocation("https://bitbucket.org/eclipse/che.git"))); - - when(urlFactoryBuilder.createFactoryFromDevfile( - any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) - .thenReturn(Optional.of(computedFactory)); - - Map params = ImmutableMap.of(URL_PARAMETER_NAME, bitbucketUrl); - // when - FactoryDto factory = (FactoryDto) bitbucketFactoryParametersResolver.createFactory(params); + FactoryDevfileV2Dto factory = + (FactoryDevfileV2Dto) bitbucketFactoryParametersResolver.createFactory(params); // then - assertNotNull(factory.getDevfile()); - SourceDto source = factory.getDevfile().getProjects().get(0).getSource(); - assertEquals(source.getBranch(), "foobranch"); + ScmInfoDto scmInfo = factory.getScmInfo(); + assertEquals(scmInfo.getRepositoryUrl(), bitbucketUrl + ".git"); + assertEquals(scmInfo.getBranch(), null); } @Test @@ -265,16 +169,6 @@ public void shouldSetScmInfoIntoDevfileV2() throws Exception { assertEquals(scmInfo.getBranch(), "foobar"); } - private FactoryDto generateDevfileFactory() { - return newDto(FactoryDto.class) - .withV(CURRENT_VERSION) - .withSource("repo") - .withDevfile( - newDto(DevfileDto.class) - .withApiVersion(CURRENT_API_VERSION) - .withMetadata(newDto(MetadataDto.class).withName("che"))); - } - private FactoryDevfileV2Dto generateDevfileV2Factory() { return newDto(FactoryDevfileV2Dto.class) .withV(CURRENT_VERSION) @@ -290,7 +184,7 @@ public void shouldCreateFactoryWithoutAuthentication() throws ApiException { ImmutableMap.of(URL_PARAMETER_NAME, bitbucketUrl, ERROR_QUERY_NAME, "access_denied"); when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) - .thenReturn(Optional.of(generateDevfileFactory())); + .thenReturn(Optional.of(generateDevfileV2Factory())); // when bitbucketFactoryParametersResolver.createFactory(params); diff --git a/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketGitUserDataFetcherTest.java b/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketGitUserDataFetcherTest.java index f5dda63c01d..b9196ba3558 100644 --- a/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketGitUserDataFetcherTest.java +++ b/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketGitUserDataFetcherTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -83,7 +83,7 @@ void stop() { public void shouldFetchGitUserData() throws Exception { OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(bitbucketOauthToken).withScope("repo"); - when(oAuthAPI.getToken(anyString())).thenReturn(oAuthToken); + when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); GitUserData gitUserData = bitbucketUserDataFetcher.fetchGitUserData(); diff --git a/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcherTest.java b/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcherTest.java index 9210fe58dee..6bd77b2d3b9 100644 --- a/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcherTest.java +++ b/wsmaster/che-core-api-factory-bitbucket/src/test/java/org/eclipse/che/api/factory/server/bitbucket/BitbucketPersonalAccessTokenFetcherTest.java @@ -17,7 +17,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static java.net.HttpURLConnection.HTTP_FORBIDDEN; +import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher.OAUTH_2_PREFIX; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.mockito.ArgumentMatchers.anyString; @@ -105,7 +105,7 @@ public void shouldNotValidateSCMServerWithTrailingSlash() throws Exception { public void shouldThrowExceptionOnInsufficientTokenScopes() throws Exception { Subject subject = new SubjectImpl("Username", "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(bitbucketOauthToken).withScope(""); - when(oAuthAPI.getToken(anyString())).thenReturn(oAuthToken); + when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/user")) @@ -125,7 +125,7 @@ public void shouldThrowExceptionOnInsufficientTokenScopes() throws Exception { expectedExceptionsMessageRegExp = "Username is not authorized in bitbucket OAuth provider.") public void shouldThrowUnauthorizedExceptionWhenUserNotLoggedIn() throws Exception { Subject subject = new SubjectImpl("Username", "id1", "token", false); - when(oAuthAPI.getToken(anyString())).thenThrow(UnauthorizedException.class); + when(oAuthAPI.getOrRefreshToken(anyString())).thenThrow(UnauthorizedException.class); bitbucketPersonalAccessTokenFetcher.fetchPersonalAccessToken( subject, BitbucketApiClient.BITBUCKET_SERVER); @@ -136,7 +136,7 @@ public void shouldReturnToken() throws Exception { Subject subject = new SubjectImpl("Username", "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(bitbucketOauthToken).withScope("repo"); - when(oAuthAPI.getToken(anyString())).thenReturn(oAuthToken); + when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/user")) @@ -211,7 +211,7 @@ public void shouldValidateOauthToken() throws Exception { @Test public void shouldNotValidateExpiredOauthToken() throws Exception { - stubFor(get(urlEqualTo("/user")).willReturn(aResponse().withStatus(HTTP_FORBIDDEN))); + stubFor(get(urlEqualTo("/user")).willReturn(aResponse().withStatus(HTTP_UNAUTHORIZED))); PersonalAccessTokenParams params = new PersonalAccessTokenParams( @@ -231,11 +231,11 @@ public void shouldNotValidateExpiredOauthToken() throws Exception { public void shouldThrowUnauthorizedExceptionIfTokenIsNotValid() throws Exception { Subject subject = new SubjectImpl("Username", "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(bitbucketOauthToken).withScope(""); - when(oAuthAPI.getToken(anyString())).thenReturn(oAuthToken); + when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token " + bitbucketOauthToken)) - .willReturn(aResponse().withStatus(HTTP_FORBIDDEN))); + .willReturn(aResponse().withStatus(HTTP_UNAUTHORIZED))); bitbucketPersonalAccessTokenFetcher.fetchPersonalAccessToken( subject, BitbucketApiClient.BITBUCKET_SERVER); diff --git a/wsmaster/che-core-api-factory-git-ssh/pom.xml b/wsmaster/che-core-api-factory-git-ssh/pom.xml index eaa8072748b..4b234595dc2 100644 --- a/wsmaster/che-core-api-factory-git-ssh/pom.xml +++ b/wsmaster/che-core-api-factory-git-ssh/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-factory-git-ssh jar @@ -54,10 +54,6 @@ org.eclipse.che.core che-core-api-workspace - - org.eclipse.che.core - che-core-api-workspace-shared - ch.qos.logback logback-classic diff --git a/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshFactoryParametersResolver.java b/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshFactoryParametersResolver.java index cc5ae1058aa..890e3a8b255 100644 --- a/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshFactoryParametersResolver.java +++ b/wsmaster/che-core-api-factory-git-ssh/src/main/java/org/eclipse/che/api/factory/server/git/ssh/GitSshFactoryParametersResolver.java @@ -29,13 +29,10 @@ import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.factory.shared.dto.FactoryVisitor; import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; -import org.eclipse.che.api.workspace.shared.dto.devfile.ProjectDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.SourceDto; /** * Provides Factory Parameters resolver for Git Ssh repositories. @@ -93,7 +90,8 @@ public FactoryMetaDto createFactory(@NotNull final Map factoryPa gitSshUrl, urlFetcher, personalAccessTokenManager), extractOverrideParams(factoryParameters), true) - .orElseGet(() -> newDto(FactoryDto.class).withV(CURRENT_VERSION).withSource("repo")) + .orElseGet( + () -> newDto(FactoryDevfileV2Dto.class).withV(CURRENT_VERSION).withSource("repo")) .acceptVisitor(new GitSshFactoryVisitor(gitSshUrl)); } @@ -117,26 +115,6 @@ public FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) { .withRepositoryUrl(gitSshUrl.getRepositoryLocation()); return factoryDto.withScmInfo(scmInfo); } - - @Override - public FactoryDto visit(FactoryDto factory) { - if (factory.getDevfile() == null) { - factory.setDevfile(urlFactoryBuilder.buildDefaultDevfile(gitSshUrl.getRepository())); - } - - updateProjects( - factory.getDevfile(), - () -> - newDto(ProjectDto.class) - .withSource( - newDto(SourceDto.class) - .withLocation(gitSshUrl.getRepositoryLocation()) - .withType("git")) - .withName(gitSshUrl.getRepository()), - project -> {}); - - return factory; - } } @Override diff --git a/wsmaster/che-core-api-factory-github-common/pom.xml b/wsmaster/che-core-api-factory-github-common/pom.xml index bb985529e59..dd2b93add67 100644 --- a/wsmaster/che-core-api-factory-github-common/pom.xml +++ b/wsmaster/che-core-api-factory-github-common/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-factory-github-common jar diff --git a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubFactoryParametersResolver.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubFactoryParametersResolver.java index 0aed63b4929..5415bb7d10f 100644 --- a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubFactoryParametersResolver.java +++ b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubFactoryParametersResolver.java @@ -23,13 +23,10 @@ import org.eclipse.che.api.factory.server.FactoryParametersResolver; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; -import org.eclipse.che.api.factory.server.urlfactory.ProjectConfigDtoMerger; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.*; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; -import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.ProjectDto; /** * Provides Factory Parameters resolver for github repositories. @@ -49,9 +46,6 @@ public abstract class AbstractGithubFactoryParametersResolver extends BaseFactor private final URLFactoryBuilder urlFactoryBuilder; - /** ProjectDtoMerger */ - private final ProjectConfigDtoMerger projectConfigDtoMerger; - private final PersonalAccessTokenManager personalAccessTokenManager; private final String providerName; @@ -62,7 +56,6 @@ public AbstractGithubFactoryParametersResolver( GithubSourceStorageBuilder githubSourceStorageBuilder, AuthorisationRequestManager authorisationRequestManager, URLFactoryBuilder urlFactoryBuilder, - ProjectConfigDtoMerger projectConfigDtoMerger, PersonalAccessTokenManager personalAccessTokenManager, String providerName) { super(authorisationRequestManager, urlFactoryBuilder, providerName); @@ -71,7 +64,6 @@ public AbstractGithubFactoryParametersResolver( this.urlFetcher = urlFetcher; this.githubSourceStorageBuilder = githubSourceStorageBuilder; this.urlFactoryBuilder = urlFactoryBuilder; - this.projectConfigDtoMerger = projectConfigDtoMerger; this.personalAccessTokenManager = personalAccessTokenManager; } @@ -143,39 +135,6 @@ public FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) { } return factoryDto.withScmInfo(scmInfo); } - - @Override - public FactoryDto visit(FactoryDto factory) { - if (factory.getWorkspace() != null) { - return projectConfigDtoMerger.merge( - factory, - () -> { - // Compute project configuration - return newDto(ProjectConfigDto.class) - .withSource(githubSourceStorageBuilder.buildWorkspaceConfigSource(githubUrl)) - .withName(githubUrl.getRepository()) - .withPath("/".concat(githubUrl.getRepository())); - }); - } else if (factory.getDevfile() == null) { - // initialize default devfile - factory.setDevfile(urlFactoryBuilder.buildDefaultDevfile(githubUrl.getRepository())); - } - - updateProjects( - factory.getDevfile(), - () -> - newDto(ProjectDto.class) - .withSource(githubSourceStorageBuilder.buildDevfileSource(githubUrl)) - .withName(githubUrl.getRepository()), - project -> { - final String location = project.getSource().getLocation(); - if (location.equals(githubUrl.repositoryLocation())) { - project.getSource().setBranch(githubUrl.getBranch()); - } - }); - - return factory; - } } @Override diff --git a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubPersonalAccessTokenFetcher.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubPersonalAccessTokenFetcher.java index d8873549fd3..36495295170 100644 --- a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubPersonalAccessTokenFetcher.java +++ b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubPersonalAccessTokenFetcher.java @@ -125,6 +125,13 @@ public abstract class AbstractGithubPersonalAccessTokenFetcher this.providerName = providerName; } + public PersonalAccessToken refreshPersonalAccessToken(Subject cheSubject, String scmServerUrl) + throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { + // Tokens generated via GitHub OAuth app do not have an expiration date, so we don't need to + // refresh them. + return fetchPersonalAccessToken(cheSubject, scmServerUrl); + } + @Override public PersonalAccessToken fetchPersonalAccessToken(Subject cheSubject, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { @@ -135,7 +142,7 @@ public PersonalAccessToken fetchPersonalAccessToken(Subject cheSubject, String s return null; } try { - oAuthToken = oAuthAPI.getToken(providerName); + oAuthToken = oAuthAPI.getOrRefreshToken(providerName); String tokenName = NameGenerator.generate(OAUTH_2_PREFIX, 5); String tokenId = NameGenerator.generate("id-", 5); Optional> valid = @@ -208,13 +215,17 @@ public Optional isValid(PersonalAccessToken personalAccessToken) { return Optional.of(Boolean.FALSE); } } - } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { + } catch (ScmItemNotFoundException + | ScmCommunicationException + | ScmBadRequestException + | ScmUnauthorizedException e) { return Optional.of(Boolean.FALSE); } } @Override - public Optional> isValid(PersonalAccessTokenParams params) { + public Optional> isValid(PersonalAccessTokenParams params) + throws ScmCommunicationException { GithubApiClient apiClient; if (githubApiClient.isConnected(params.getScmProviderUrl())) { // The url from the token has the same url as the api client, no need to create a new one. @@ -240,7 +251,7 @@ public Optional> isValid(PersonalAccessTokenParams params) GithubUser user = apiClient.getUser(params.getToken()); return Optional.of(Pair.of(Boolean.TRUE, user.getLogin())); } - } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { + } catch (ScmItemNotFoundException | ScmBadRequestException | ScmUnauthorizedException e) { return Optional.empty(); } } diff --git a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubURLParser.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubURLParser.java index c11372d90a2..46e65e0c648 100644 --- a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubURLParser.java +++ b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubURLParser.java @@ -14,7 +14,6 @@ import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Strings.isNullOrEmpty; import static java.lang.String.format; -import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static java.util.regex.Pattern.compile; import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException; import static org.eclipse.che.api.factory.server.github.GithubApiClient.GITHUB_SAAS_ENDPOINT; @@ -63,6 +62,8 @@ public abstract class AbstractGithubURLParser { private final boolean disableSubdomainIsolation; private final String providerName; + private final String endpoint; + private final boolean isGitHubServer; /** Constructor used for testing only. */ AbstractGithubURLParser( @@ -77,26 +78,37 @@ public abstract class AbstractGithubURLParser { this.apiClient = githubApiClient; this.disableSubdomainIsolation = disableSubdomainIsolation; this.providerName = providerName; + // Check if the given OAuth endpoint is a GitHub server URL. If the OAuth endpoint is not + // defined, or it equals the GitHub SaaS endpoint, it means that the given URL is a GitHub SaaS + // URL (https://github.com). Otherwise, the given URL is a GitHub server URL. + this.isGitHubServer = + !isNullOrEmpty(oauthEndpoint) && !GITHUB_SAAS_ENDPOINT.equals(oauthEndpoint); - String endpoint = - isNullOrEmpty(oauthEndpoint) ? GITHUB_SAAS_ENDPOINT : trimEnd(oauthEndpoint, '/'); + endpoint = isNullOrEmpty(oauthEndpoint) ? GITHUB_SAAS_ENDPOINT : trimEnd(oauthEndpoint, '/'); this.githubPattern = compile(format(githubPatternTemplate, endpoint)); this.githubSSHPattern = compile(format(githubSSHPatternTemplate, URI.create(endpoint).getHost())); } + // Check if the given URL is a valid GitHub URL. public boolean isValid(@NotNull String url) { String trimmedUrl = trimEnd(url, '/'); - return githubPattern.matcher(trimmedUrl).matches() + return + // Check if the given URL matches the GitHub URL patterns. It works if OAuth is configured and + // GitHub server URL is known or the repository URL points to GitHub SaaS (https://github.com). + githubPattern.matcher(trimmedUrl).matches() || githubSSHPattern.matcher(trimmedUrl).matches() - // If the GitHub URL is not configured, try to find it in a manually added user namespace - // token. + // Check whether PAT is configured for the GitHub server URL. It is sufficient to confirm + // that the URL is a valid GitHub URL. || isUserTokenPresent(trimmedUrl) - // Try to call an API request to see if the URL matches GitHub. - || isApiRequestRelevant(trimmedUrl); + // Check if the given URL is a valid GitHub URL by reaching the endpoint of the GitHub + // server and analysing the response. This query basically only needs to be performed if the + // specified repository URL does not point to GitHub SaaS. + || (!isGitHubServer && isApiRequestRelevant(trimmedUrl)); } + // Try to find the given url in a manually added user namespace token secret. private boolean isUserTokenPresent(String repositoryUrl) { Optional serverUrlOptional = getServerUrl(repositoryUrl); if (serverUrlOptional.isPresent()) { @@ -108,13 +120,14 @@ private boolean isUserTokenPresent(String repositoryUrl) { PersonalAccessToken accessToken = token.get(); return accessToken.getScmTokenName().equals(providerName); } - } catch (ScmConfigurationPersistenceException exception) { + } catch (ScmConfigurationPersistenceException | ScmCommunicationException exception) { return false; } } return false; } + // Try to call an API request to see if the given url matches self-hosted GitHub Enterprise. private boolean isApiRequestRelevant(String repositoryUrl) { Optional serverUrlOptional = getServerUrl(repositoryUrl); if (serverUrlOptional.isPresent()) { @@ -123,14 +136,16 @@ private boolean isApiRequestRelevant(String repositoryUrl) { // If the user request catches the unauthorised error, it means that the provided url // belongs to GitHub. githubApiClient.getUser(""); - } catch (ScmCommunicationException e) { - return e.getStatusCode() == HTTP_UNAUTHORIZED - // Check the error message as well, because other providers might also return 401 - // for such requests. - && e.getMessage().contains("Requires authentication") + } catch (ScmUnauthorizedException e) { + // Check the error message as well, because other providers might also return 401 + // for such requests. + return e.getMessage().contains("Requires authentication") || // for older GitHub Enterprise versions e.getMessage().contains("Must authenticate to access this API."); - } catch (ScmItemNotFoundException | ScmBadRequestException | IllegalArgumentException e) { + } catch (ScmItemNotFoundException + | ScmBadRequestException + | IllegalArgumentException + | ScmCommunicationException e) { return false; } } @@ -275,7 +290,8 @@ private GithubPullRequest getPullRequest( return apiClient.getPullRequest(pullRequestId, repoUser, repoName, null); } catch (ScmItemNotFoundException | ScmCommunicationException - | ScmBadRequestException exception) { + | ScmBadRequestException + | ScmUnauthorizedException exception) { LOG.error("Failed to authenticate to GitHub", e); } @@ -326,7 +342,8 @@ private GithubCommit getLatestCommit( } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException - | URISyntaxException exception) { + | URISyntaxException + | ScmUnauthorizedException exception) { LOG.error("Failed to authenticate to GitHub", e); } } catch (ScmCommunicationException diff --git a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubUserDataFetcher.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubUserDataFetcher.java index 1ce559486dd..54c8ddb45c6 100644 --- a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubUserDataFetcher.java +++ b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/AbstractGithubUserDataFetcher.java @@ -23,6 +23,7 @@ import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; /** GitHub user data retriever. */ public abstract class AbstractGithubUserDataFetcher extends AbstractGitUserDataFetcher { @@ -53,7 +54,8 @@ public AbstractGithubUserDataFetcher( @Override protected GitUserData fetchGitUserDataWithOAuthToken(String token) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { GithubUser user = githubApiClient.getUser(token); if (isNullOrEmpty(user.getName()) || isNullOrEmpty(user.getEmail())) { throw new ScmItemNotFoundException(NO_USERNAME_AND_EMAIL_ERROR_MESSAGE); @@ -65,7 +67,8 @@ protected GitUserData fetchGitUserDataWithOAuthToken(String token) @Override protected GitUserData fetchGitUserDataWithPersonalAccessToken( PersonalAccessToken personalAccessToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { GithubApiClient apiClient = githubApiClient.isConnected(personalAccessToken.getScmProviderUrl()) ? githubApiClient diff --git a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubApiClient.java b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubApiClient.java index 57907c1275b..8aa0fbf0696 100644 --- a/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubApiClient.java +++ b/wsmaster/che-core-api-factory-github-common/src/main/java/org/eclipse/che/api/factory/server/github/GithubApiClient.java @@ -16,6 +16,7 @@ import static java.net.HttpURLConnection.HTTP_NOT_FOUND; import static java.net.HttpURLConnection.HTTP_NO_CONTENT; import static java.net.HttpURLConnection.HTTP_OK; +import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static java.time.Duration.ofSeconds; import static org.eclipse.che.commons.lang.StringUtils.trimEnd; @@ -40,6 +41,7 @@ import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.commons.annotation.Nullable; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; @@ -103,7 +105,8 @@ public GithubApiClient(@Nullable String serverUrl) { * @throws ScmBadRequestException */ public GithubUser getUser(String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final URI uri = apiServerUrl.resolve("./user"); HttpRequest request = buildGithubApiRequest(uri, authenticationToken); LOG.trace("executeRequest={}", request); @@ -133,7 +136,8 @@ public GithubUser getUser(String authenticationToken) */ public GithubPullRequest getPullRequest( String id, String username, String repoName, String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final URI uri = apiServerUrl.resolve(String.format("./repos/%s/%s/pulls/%s", username, repoName, id)); HttpRequest request = buildGithubApiRequest(uri, authenticationToken); @@ -167,7 +171,7 @@ public GithubPullRequest getPullRequest( public GithubCommit getLatestCommit( String user, String repository, String branch, @Nullable String authenticationToken) throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException, - URISyntaxException { + URISyntaxException, ScmUnauthorizedException { final URI uri = apiServerUrl.resolve(String.format("./repos/%s/%s/commits", user, repository)); @@ -208,7 +212,8 @@ public GithubCommit getLatestCommit( * @throws ScmBadRequestException */ public Pair getTokenScopes(String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final URI uri = apiServerUrl.resolve("./user"); HttpRequest request = buildGithubApiRequest(uri, authenticationToken); LOG.trace("executeRequest={}", request); @@ -270,7 +275,9 @@ private T executeRequest( HttpClient httpClient, HttpRequest request, Function, T> responseConverter) - throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException { + throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException, + ScmUnauthorizedException { + String provider = GITHUB_SAAS_ENDPOINT.equals(getServerUrl()) ? "github" : "github-server"; try { HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); @@ -290,13 +297,15 @@ private T executeRequest( throw new ScmBadRequestException(body); case HTTP_NOT_FOUND: throw new ScmItemNotFoundException(body); + case HTTP_UNAUTHORIZED: + throw new ScmUnauthorizedException(body, "github", "v2", ""); default: throw new ScmCommunicationException( - "Unexpected status code " + statusCode + " " + body, statusCode); + "Unexpected status code " + statusCode + " " + body, statusCode, provider); } } } catch (IOException | InterruptedException | UncheckedIOException e) { - throw new ScmCommunicationException(e.getMessage(), e); + throw new ScmCommunicationException(e.getMessage(), e, provider); } } diff --git a/wsmaster/che-core-api-factory-github/pom.xml b/wsmaster/che-core-api-factory-github/pom.xml index 667e03ecbe1..0e0afef9159 100644 --- a/wsmaster/che-core-api-factory-github/pom.xml +++ b/wsmaster/che-core-api-factory-github/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-factory-github jar @@ -74,10 +74,6 @@ org.eclipse.che.core che-core-api-workspace - - org.eclipse.che.core - che-core-api-workspace-shared - org.eclipse.che.core che-core-commons-annotations diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolver.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolver.java index 462505bde64..cba97c04441 100644 --- a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolver.java +++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolver.java @@ -15,7 +15,6 @@ import javax.inject.Singleton; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; -import org.eclipse.che.api.factory.server.urlfactory.ProjectConfigDtoMerger; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; @@ -36,7 +35,6 @@ public GithubFactoryParametersResolver( GithubSourceStorageBuilder githubSourceStorageBuilder, AuthorisationRequestManager authorisationRequestManager, URLFactoryBuilder urlFactoryBuilder, - ProjectConfigDtoMerger projectConfigDtoMerger, PersonalAccessTokenManager personalAccessTokenManager) { super( githubUrlParser, @@ -44,7 +42,6 @@ public GithubFactoryParametersResolver( githubSourceStorageBuilder, authorisationRequestManager, urlFactoryBuilder, - projectConfigDtoMerger, personalAccessTokenManager, PROVIDER_NAME); } diff --git a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverSecond.java b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverSecond.java index 20a37cbd8d6..d2f25992f81 100644 --- a/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverSecond.java +++ b/wsmaster/che-core-api-factory-github/src/main/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverSecond.java @@ -15,7 +15,6 @@ import javax.inject.Singleton; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; -import org.eclipse.che.api.factory.server.urlfactory.ProjectConfigDtoMerger; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; @@ -36,7 +35,6 @@ public GithubFactoryParametersResolverSecond( GithubSourceStorageBuilder githubSourceStorageBuilder, AuthorisationRequestManager authorisationRequestManager, URLFactoryBuilder urlFactoryBuilder, - ProjectConfigDtoMerger projectConfigDtoMerger, PersonalAccessTokenManager personalAccessTokenManager) { super( githubUrlParser, @@ -44,7 +42,6 @@ public GithubFactoryParametersResolverSecond( githubSourceStorageBuilder, authorisationRequestManager, urlFactoryBuilder, - projectConfigDtoMerger, personalAccessTokenManager, PROVIDER_NAME); } diff --git a/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverTest.java b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverTest.java index 6f8d2d5fb5d..a8dcac141ef 100644 --- a/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverTest.java +++ b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubFactoryParametersResolverTest.java @@ -14,7 +14,6 @@ import static java.util.Collections.singletonMap; import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; -import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.eclipse.che.security.oauth1.OAuthAuthenticationService.ERROR_QUERY_NAME; import static org.mockito.ArgumentMatchers.any; @@ -29,11 +28,9 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; -import java.util.Collections; import java.util.Map; import java.util.Optional; import org.eclipse.che.api.core.ApiException; @@ -41,17 +38,12 @@ import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; -import org.eclipse.che.api.factory.server.urlfactory.ProjectConfigDtoMerger; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; +import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; -import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.MetadataDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.ProjectDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.SourceDto; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; @@ -82,9 +74,6 @@ public class GithubFactoryParametersResolverTest { @Spy private GithubSourceStorageBuilder githubSourceStorageBuilder = new GithubSourceStorageBuilder(); - /** ProjectDtoMerger */ - @Mock private ProjectConfigDtoMerger projectConfigDtoMerger; - /** Parser which will allow to check validity of URLs and create objects. */ private URLFactoryBuilder urlFactoryBuilder; @@ -94,7 +83,7 @@ public class GithubFactoryParametersResolverTest { /** * Capturing the location parameter when calling {@link * URLFactoryBuilder#createFactoryFromDevfile(RemoteFactoryUrl, FileContentProvider, Map, - * Boolean)} + * boolean)} */ @Captor private ArgumentCaptor factoryUrlArgumentCaptor; @@ -117,7 +106,6 @@ protected void init() throws Exception { githubSourceStorageBuilder, authorisationRequestManager, urlFactoryBuilder, - projectConfigDtoMerger, personalAccessTokenManager); assertNotNull(this.abstractGithubFactoryParametersResolver); } @@ -155,34 +143,23 @@ public void shouldGenerateDevfileForFactoryWithNoDevfile() throws Exception { String githubUrl = "https://github.com/eclipse/che"; - FactoryDto computedFactory = generateDevfileFactory(); - - when(urlFactoryBuilder.buildDefaultDevfile(any())).thenReturn(computedFactory.getDevfile()); - - when(urlFactoryBuilder.createFactoryFromDevfile( - any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) - .thenReturn(Optional.empty()); - when(githubApiClient.isConnected(eq("https://github.com"))).thenReturn(true); when(githubApiClient.getLatestCommit(anyString(), anyString(), anyString(), any())) .thenReturn(new GithubCommit().withSha("test-sha")); Map params = ImmutableMap.of(URL_PARAMETER_NAME, githubUrl); // when - FactoryDto factory = (FactoryDto) abstractGithubFactoryParametersResolver.createFactory(params); + FactoryDevfileV2Dto factory = + (FactoryDevfileV2Dto) abstractGithubFactoryParametersResolver.createFactory(params); // then - verify(urlFactoryBuilder).buildDefaultDevfile(eq("che")); - assertEquals(factory, computedFactory); - SourceDto source = factory.getDevfile().getProjects().get(0).getSource(); - assertEquals(source.getLocation(), githubUrl + ".git"); - assertEquals(source.getBranch(), null); + ScmInfoDto scmInfo = factory.getScmInfo(); + assertEquals(scmInfo.getRepositoryUrl(), githubUrl + ".git"); + assertEquals(scmInfo.getBranch(), null); } @Test public void shouldSkipAuthenticationWhenAccessDenied() throws Exception { // given - when(urlFactoryBuilder.buildDefaultDevfile(any())) - .thenReturn(generateDevfileFactory().getDevfile()); when(githubApiClient.isConnected(eq("https://github.com"))).thenReturn(true); when(githubApiClient.getLatestCommit(anyString(), anyString(), anyString(), any())) .thenReturn(new GithubCommit().withSha("test-sha")); @@ -204,8 +181,6 @@ public void shouldSkipAuthenticationWhenAccessDenied() throws Exception { @Test public void shouldNotSkipAuthenticationWhenNoErrorParameterPassed() throws Exception { // given - when(urlFactoryBuilder.buildDefaultDevfile(any())) - .thenReturn(generateDevfileFactory().getDevfile()); when(githubApiClient.isConnected(eq("https://github.com"))).thenReturn(true); when(githubApiClient.getLatestCommit(anyString(), anyString(), anyString(), any())) .thenReturn(new GithubCommit().withSha("test-sha")); @@ -219,94 +194,6 @@ public void shouldNotSkipAuthenticationWhenNoErrorParameterPassed() throws Excep any(RemoteFactoryUrl.class), any(FileContentProvider.class), anyMap(), eq(false)); } - @Test - public void shouldReturnFactoryFromRepositoryWithDevfile() throws Exception { - - when(devfileFilenamesProvider.getConfiguredDevfileFilenames()) - .thenReturn(Collections.singletonList("devfile.yaml")); - - String githubUrl = "https://github.com/eclipse/che"; - - FactoryDto computedFactory = generateDevfileFactory(); - - when(urlFactoryBuilder.createFactoryFromDevfile( - any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) - .thenReturn(Optional.of(computedFactory)); - when(githubApiClient.isConnected(eq("https://github.com"))).thenReturn(true); - when(githubApiClient.getLatestCommit(anyString(), anyString(), anyString(), any())) - .thenReturn(new GithubCommit().withSha("13bbd0d4605a6ed3350f7b15eb02c4d4e6f8df6e")); - - Map params = ImmutableMap.of(URL_PARAMETER_NAME, githubUrl); - // when - FactoryDto factory = (FactoryDto) abstractGithubFactoryParametersResolver.createFactory(params); - // then - assertNotNull(factory.getDevfile()); - assertNull(factory.getWorkspace()); - - // check we called the builder with the following devfile file - verify(urlFactoryBuilder) - .createFactoryFromDevfile( - factoryUrlArgumentCaptor.capture(), any(), anyMap(), anyBoolean()); - verify(urlFactoryBuilder, never()).buildDefaultDevfile(eq("che")); - assertEquals( - factoryUrlArgumentCaptor.getValue().devfileFileLocations().iterator().next().location(), - "https://raw.githubusercontent.com/eclipse/che/13bbd0d4605a6ed3350f7b15eb02c4d4e6f8df6e/devfile.yaml"); - } - - @Test - public void shouldSetDefaultProjectIntoDevfileIfNotSpecified() throws Exception { - - String githubUrl = "https://github.com/eclipse/che/tree/foobar"; - - FactoryDto computedFactory = generateDevfileFactory(); - - when(urlFactoryBuilder.createFactoryFromDevfile( - any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) - .thenReturn(Optional.of(computedFactory)); - when(githubApiClient.isConnected(eq("https://github.com"))).thenReturn(true); - when(githubApiClient.getLatestCommit(anyString(), anyString(), anyString(), any())) - .thenReturn(new GithubCommit().withSha("test-sha")); - - Map params = ImmutableMap.of(URL_PARAMETER_NAME, githubUrl); - // when - FactoryDto factory = (FactoryDto) abstractGithubFactoryParametersResolver.createFactory(params); - // then - assertNotNull(factory.getDevfile()); - SourceDto source = factory.getDevfile().getProjects().get(0).getSource(); - assertEquals(source.getLocation(), "https://github.com/eclipse/che.git"); - assertEquals(source.getBranch(), "foobar"); - } - - @Test - public void shouldSetBranchIntoDevfileIfNotMatchesCurrent() throws Exception { - - String githubUrl = "https://github.com/eclipse/che/tree/foobranch"; - - FactoryDto computedFactory = generateDevfileFactory(); - computedFactory - .getDevfile() - .getProjects() - .add( - newDto(ProjectDto.class) - .withSource( - newDto(SourceDto.class).withLocation("https://github.com/eclipse/che.git"))); - - when(urlFactoryBuilder.createFactoryFromDevfile( - any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) - .thenReturn(Optional.of(computedFactory)); - when(githubApiClient.isConnected(eq("https://github.com"))).thenReturn(true); - when(githubApiClient.getLatestCommit(anyString(), anyString(), anyString(), any())) - .thenReturn(new GithubCommit().withSha("test-sha")); - - Map params = ImmutableMap.of(URL_PARAMETER_NAME, githubUrl); - // when - FactoryDto factory = (FactoryDto) abstractGithubFactoryParametersResolver.createFactory(params); - // then - assertNotNull(factory.getDevfile()); - SourceDto source = factory.getDevfile().getProjects().get(0).getSource(); - assertEquals(source.getBranch(), "foobranch"); - } - @Test public void shouldSetScmInfoIntoDevfileV2() throws Exception { @@ -341,7 +228,7 @@ public void shouldCreateFactoryWithoutAuthentication() throws ApiException { ImmutableMap.of(URL_PARAMETER_NAME, githubUrl, ERROR_QUERY_NAME, "access_denied"); when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) - .thenReturn(Optional.of(generateDevfileFactory())); + .thenReturn(Optional.of(generateDevfileV2Factory())); // when abstractGithubFactoryParametersResolver.createFactory(params); @@ -367,7 +254,6 @@ public void shouldParseFactoryUrlWithAuthentication() throws Exception { githubSourceStorageBuilder, authorisationRequestManager, urlFactoryBuilder, - projectConfigDtoMerger, personalAccessTokenManager); when(authorisationRequestManager.isStored(eq("github"))).thenReturn(true); // when @@ -389,7 +275,6 @@ public void shouldParseFactoryUrlWithOutAuthentication() throws Exception { githubSourceStorageBuilder, authorisationRequestManager, urlFactoryBuilder, - projectConfigDtoMerger, personalAccessTokenManager); when(authorisationRequestManager.isStored(eq("github"))).thenReturn(false); // when @@ -399,16 +284,6 @@ public void shouldParseFactoryUrlWithOutAuthentication() throws Exception { verify(githubUrlParser, never()).parseWithoutAuthentication("url"); } - private FactoryDto generateDevfileFactory() { - return newDto(FactoryDto.class) - .withV(CURRENT_VERSION) - .withSource("repo") - .withDevfile( - newDto(DevfileDto.class) - .withApiVersion(CURRENT_API_VERSION) - .withMetadata(newDto(MetadataDto.class).withName("che"))); - } - private FactoryDevfileV2Dto generateDevfileV2Factory() { return newDto(FactoryDevfileV2Dto.class) .withV(CURRENT_VERSION) diff --git a/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcherTest.java b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcherTest.java index 316f297968c..2bf561c630a 100644 --- a/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcherTest.java +++ b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubPersonalAccessTokenFetcherTest.java @@ -17,7 +17,7 @@ import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static java.net.HttpURLConnection.HTTP_FORBIDDEN; +import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static org.eclipse.che.api.factory.server.github.GithubPersonalAccessTokenFetcher.DEFAULT_TOKEN_SCOPES; import static org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher.OAUTH_2_PREFIX; import static org.eclipse.che.dto.server.DtoFactory.newDto; @@ -142,7 +142,7 @@ public void testContainsScope() { public void shouldThrowExceptionOnInsufficientTokenScopes() throws Exception { Subject subject = new SubjectImpl("Username", "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(githubOauthToken).withScope(""); - when(oAuthAPI.getToken(anyString())).thenReturn(oAuthToken); + when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/api/v3/user")) @@ -161,7 +161,7 @@ public void shouldThrowExceptionOnInsufficientTokenScopes() throws Exception { expectedExceptionsMessageRegExp = "Username is not authorized in github OAuth provider.") public void shouldThrowUnauthorizedExceptionWhenUserNotLoggedIn() throws Exception { Subject subject = new SubjectImpl("Username", "id1", "token", false); - when(oAuthAPI.getToken(anyString())).thenThrow(UnauthorizedException.class); + when(oAuthAPI.getOrRefreshToken(anyString())).thenThrow(UnauthorizedException.class); githubPATFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/")); } @@ -172,11 +172,11 @@ public void shouldThrowUnauthorizedExceptionWhenUserNotLoggedIn() throws Excepti public void shouldThrowUnauthorizedExceptionIfTokenIsNotValid() throws Exception { Subject subject = new SubjectImpl("Username", "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(githubOauthToken).withScope(""); - when(oAuthAPI.getToken(anyString())).thenReturn(oAuthToken); + when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/api/v3/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token " + githubOauthToken)) - .willReturn(aResponse().withStatus(HTTP_FORBIDDEN))); + .willReturn(aResponse().withStatus(HTTP_UNAUTHORIZED))); githubPATFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/")); } @@ -185,7 +185,7 @@ public void shouldThrowUnauthorizedExceptionIfTokenIsNotValid() throws Exception public void shouldReturnToken() throws Exception { Subject subject = new SubjectImpl("Username", "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken(githubOauthToken); - when(oAuthAPI.getToken(anyString())).thenReturn(oAuthToken); + when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/api/v3/user")) @@ -254,7 +254,7 @@ public void shouldValidateOauthToken() throws Exception { @Test public void shouldNotValidateExpiredOauthToken() throws Exception { - stubFor(get(urlEqualTo("/api/v3/user")).willReturn(aResponse().withStatus(HTTP_FORBIDDEN))); + stubFor(get(urlEqualTo("/api/v3/user")).willReturn(aResponse().withStatus(HTTP_UNAUTHORIZED))); PersonalAccessTokenParams params = new PersonalAccessTokenParams( diff --git a/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubURLParserTest.java b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubURLParserTest.java index de6f728e632..4cc632ada87 100644 --- a/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubURLParserTest.java +++ b/wsmaster/che-core-api-factory-github/src/test/java/org/eclipse/che/api/factory/server/github/GithubURLParserTest.java @@ -31,6 +31,7 @@ import com.github.tomakehurst.wiremock.WireMockServer; import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; +import java.lang.reflect.Field; import java.util.Optional; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; @@ -336,6 +337,9 @@ public void shouldParseServerUrWithPullRequestId() throws Exception { @Test public void shouldValidateOldVersionGitHubServerUrl() throws Exception { // given + Field endpoint = AbstractGithubURLParser.class.getDeclaredField("endpoint"); + endpoint.setAccessible(true); + endpoint.set(githubUrlParser, wireMockServer.baseUrl()); String url = wireMockServer.url("/user/repo"); stubFor( get(urlEqualTo("/api/v3/user")) @@ -354,6 +358,9 @@ public void shouldValidateOldVersionGitHubServerUrl() throws Exception { @Test public void shouldValidateGitHubServerUrl() throws Exception { // given + Field endpoint = AbstractGithubURLParser.class.getDeclaredField("endpoint"); + endpoint.setAccessible(true); + endpoint.set(githubUrlParser, wireMockServer.baseUrl()); String url = wireMockServer.url("/user/repo"); stubFor( get(urlEqualTo("/api/v3/user")) @@ -368,4 +375,13 @@ public void shouldValidateGitHubServerUrl() throws Exception { // then assertTrue(valid); } + + @Test + public void shouldNotRequestGitHubSAASUrl() throws Exception { + // when + githubUrlParser.isValid("https:github.com/repo/user.git"); + + // then + verify(githubApiClient, never()).getUser(anyString()); + } } diff --git a/wsmaster/che-core-api-factory-gitlab-common/pom.xml b/wsmaster/che-core-api-factory-gitlab-common/pom.xml new file mode 100644 index 00000000000..3f6b2254d27 --- /dev/null +++ b/wsmaster/che-core-api-factory-gitlab-common/pom.xml @@ -0,0 +1,145 @@ + + + + 4.0.0 + + che-master-parent + org.eclipse.che.core + 7.96.0-SNAPSHOT + + che-core-api-factory-gitlab-common + jar + Che Core :: API :: Factory Resolver Gitlab Common + + + com.fasterxml.jackson.core + jackson-annotations + + + com.fasterxml.jackson.core + jackson-databind + + + com.google.guava + guava + + + jakarta.validation + jakarta.validation-api + + + org.eclipse.che.core + che-core-api-auth + + + org.eclipse.che.core + che-core-api-auth-shared + + + org.eclipse.che.core + che-core-api-core + + + org.eclipse.che.core + che-core-api-dto + + + org.eclipse.che.core + che-core-api-factory + + + org.eclipse.che.core + che-core-api-factory-shared + + + org.eclipse.che.core + che-core-api-workspace + + + org.eclipse.che.core + che-core-commons-annotations + + + org.eclipse.che.core + che-core-commons-lang + + + org.slf4j + slf4j-api + + + jakarta.servlet + jakarta.servlet-api + provided + + + ch.qos.logback + logback-classic + test + + + com.github.tomakehurst + wiremock-jre8-standalone + test + + + jakarta.ws.rs + jakarta.ws.rs-api + test + + + org.eclipse.che.core + che-core-commons-json + test + + + org.mockito + mockito-core + test + + + org.mockito + mockito-testng + test + + + org.slf4j + jcl-over-slf4j + test + + + org.testng + testng + test + + + org.wiremock + wiremock-standalone + test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + diff --git a/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabFactoryParametersResolver.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabFactoryParametersResolver.java new file mode 100644 index 00000000000..8a42b8420a5 --- /dev/null +++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabFactoryParametersResolver.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2012-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.gitlab; + +import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; +import static org.eclipse.che.dto.server.DtoFactory.newDto; + +import jakarta.validation.constraints.NotNull; +import java.util.Map; +import org.eclipse.che.api.core.ApiException; +import org.eclipse.che.api.core.BadRequestException; +import org.eclipse.che.api.factory.server.BaseFactoryParameterResolver; +import org.eclipse.che.api.factory.server.FactoryParametersResolver; +import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; +import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; +import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; +import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; +import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; +import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; +import org.eclipse.che.api.factory.shared.dto.FactoryVisitor; +import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; +import org.eclipse.che.api.workspace.server.devfile.URLFetcher; + +/** + * Provides Factory Parameters resolver for Gitlab repositories. + * + * @author Max Shaposhnyk + */ +public class AbstractGitlabFactoryParametersResolver extends BaseFactoryParameterResolver + implements FactoryParametersResolver { + + private final URLFetcher urlFetcher; + private final AbstractGitlabUrlParser gitlabURLParser; + private final PersonalAccessTokenManager personalAccessTokenManager; + private final String providerName; + + public AbstractGitlabFactoryParametersResolver( + URLFactoryBuilder urlFactoryBuilder, + URLFetcher urlFetcher, + AbstractGitlabUrlParser gitlabURLParser, + PersonalAccessTokenManager personalAccessTokenManager, + AuthorisationRequestManager authorisationRequestManager, + String providerName) { + super(authorisationRequestManager, urlFactoryBuilder, providerName); + this.urlFetcher = urlFetcher; + this.gitlabURLParser = gitlabURLParser; + this.personalAccessTokenManager = personalAccessTokenManager; + this.providerName = providerName; + } + + /** + * Check if this resolver can be used with the given parameters. + * + * @param factoryParameters map of parameters dedicated to factories + * @return true if it will be accepted by the resolver implementation or false if it is not + * accepted + */ + @Override + public boolean accept(@NotNull final Map factoryParameters) { + return factoryParameters.containsKey(URL_PARAMETER_NAME) + && gitlabURLParser.isValid(factoryParameters.get(URL_PARAMETER_NAME)); + } + + @Override + public String getProviderName() { + return providerName; + } + + /** + * Create factory object based on provided parameters + * + * @param factoryParameters map containing factory data parameters provided through URL + * @throws BadRequestException when data are invalid + */ + @Override + public FactoryMetaDto createFactory(@NotNull final Map factoryParameters) + throws ApiException { + // no need to check null value of url parameter as accept() method has performed the check + final GitlabUrl gitlabUrl = gitlabURLParser.parse(factoryParameters.get(URL_PARAMETER_NAME)); + // create factory from the following location if location exists, else create default factory + return createFactory( + factoryParameters, + gitlabUrl, + new GitlabFactoryVisitor(gitlabUrl), + new GitlabAuthorizingFileContentProvider( + gitlabUrl, urlFetcher, personalAccessTokenManager)); + } + + /** + * Visitor that puts the default devfile or updates devfile projects into the Gitlab Factory, if + * needed. + */ + private class GitlabFactoryVisitor implements FactoryVisitor { + + private final GitlabUrl gitlabUrl; + + private GitlabFactoryVisitor(GitlabUrl gitlabUrl) { + this.gitlabUrl = gitlabUrl; + } + + @Override + public FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) { + ScmInfoDto scmInfo = + newDto(ScmInfoDto.class) + .withScmProviderName(gitlabUrl.getProviderName()) + .withRepositoryUrl(gitlabUrl.repositoryLocation()); + if (gitlabUrl.getBranch() != null) { + scmInfo.withBranch(gitlabUrl.getBranch()); + } + return factoryDto.withScmInfo(scmInfo); + } + } + + @Override + public RemoteFactoryUrl parseFactoryUrl(String factoryUrl) throws ApiException { + return gitlabURLParser.parse(factoryUrl); + } +} diff --git a/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabOAuthTokenFetcher.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabOAuthTokenFetcher.java new file mode 100644 index 00000000000..05949fb1729 --- /dev/null +++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabOAuthTokenFetcher.java @@ -0,0 +1,223 @@ +/* + * Copyright (c) 2012-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.gitlab; + +import static java.lang.String.format; +import static org.eclipse.che.commons.lang.StringUtils.trimEnd; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; +import java.util.Optional; +import java.util.Set; +import org.eclipse.che.api.auth.shared.dto.OAuthToken; +import org.eclipse.che.api.core.BadRequestException; +import org.eclipse.che.api.core.ConflictException; +import org.eclipse.che.api.core.ForbiddenException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.core.ServerException; +import org.eclipse.che.api.core.UnauthorizedException; +import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; +import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; +import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenParams; +import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; +import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; +import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; +import org.eclipse.che.commons.lang.NameGenerator; +import org.eclipse.che.commons.lang.Pair; +import org.eclipse.che.commons.subject.Subject; +import org.eclipse.che.security.oauth.OAuthAPI; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** GitLab OAuth token retriever. */ +public class AbstractGitlabOAuthTokenFetcher implements PersonalAccessTokenFetcher { + + private static final Logger LOG = LoggerFactory.getLogger(AbstractGitlabOAuthTokenFetcher.class); + public static final Set DEFAULT_TOKEN_SCOPES = ImmutableSet.of("api", "write_repository"); + + private final OAuthAPI oAuthAPI; + private final String serverUrl; + private final String apiEndpoint; + private final String providerName; + + public AbstractGitlabOAuthTokenFetcher( + String serverUrl, String apiEndpoint, OAuthAPI oAuthAPI, String providerName) { + this.serverUrl = trimEnd(serverUrl, '/'); + this.apiEndpoint = apiEndpoint; + this.providerName = providerName; + this.oAuthAPI = oAuthAPI; + } + + @Override + public PersonalAccessToken refreshPersonalAccessToken(Subject cheSubject, String scmServerUrl) + throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { + return fetchOrRefreshPersonalAccessToken(cheSubject, scmServerUrl, true); + } + + @Override + public PersonalAccessToken fetchPersonalAccessToken(Subject cheSubject, String scmServerUrl) + throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { + return fetchOrRefreshPersonalAccessToken(cheSubject, scmServerUrl, false); + } + + private PersonalAccessToken fetchOrRefreshPersonalAccessToken( + Subject cheSubject, String scmServerUrl, boolean forceRefreshToken) + throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { + scmServerUrl = trimEnd(scmServerUrl, '/'); + GitlabApiClient gitlabApiClient = getApiClient(scmServerUrl); + if (gitlabApiClient == null || !gitlabApiClient.isConnected(scmServerUrl)) { + LOG.debug("not a valid url {} for current fetcher ", scmServerUrl); + return null; + } + if (oAuthAPI == null) { + throw new ScmCommunicationException( + format( + "OAuth 2 is not configured for SCM provider [%s]. For details, refer " + + "the documentation in section of SCM providers configuration.", + providerName)); + } + OAuthToken oAuthToken; + try { + oAuthToken = + forceRefreshToken + ? oAuthAPI.refreshToken(providerName) + : oAuthAPI.getOrRefreshToken(providerName); + String tokenName = NameGenerator.generate(OAUTH_2_PREFIX, 5); + String tokenId = NameGenerator.generate("id-", 5); + Optional> valid = + isValid( + new PersonalAccessTokenParams( + scmServerUrl, providerName, tokenName, tokenId, oAuthToken.getToken(), null)); + if (valid.isEmpty()) { + throw buildScmUnauthorizedException(cheSubject); + } else if (!valid.get().first) { + throw new ScmCommunicationException( + "Current token doesn't have the necessary privileges. Please make sure Che app scopes are correct and containing at least: " + + DEFAULT_TOKEN_SCOPES); + } + return new PersonalAccessToken( + scmServerUrl, + providerName, + cheSubject.getUserId(), + valid.get().second, + tokenName, + tokenId, + oAuthToken.getToken()); + } catch (UnauthorizedException e) { + throw buildScmUnauthorizedException(cheSubject); + } catch (NotFoundException nfe) { + throw new UnknownScmProviderException(nfe.getMessage(), scmServerUrl); + } catch (ServerException | ForbiddenException | BadRequestException | ConflictException e) { + LOG.warn(e.getMessage()); + throw new ScmCommunicationException(e.getMessage(), e); + } + } + + private ScmUnauthorizedException buildScmUnauthorizedException(Subject cheSubject) { + return new ScmUnauthorizedException( + cheSubject.getUserName() + " is not authorized in " + providerName + " OAuth provider.", + providerName, + "2.0", + getLocalAuthenticateUrl()); + } + + @Override + public Optional isValid(PersonalAccessToken personalAccessToken) { + GitlabApiClient gitlabApiClient = getApiClient(personalAccessToken.getScmProviderUrl()); + if (gitlabApiClient == null + || !gitlabApiClient.isConnected(personalAccessToken.getScmProviderUrl())) { + if (personalAccessToken.getScmTokenName().equals(providerName)) { + gitlabApiClient = new GitlabApiClient(personalAccessToken.getScmProviderUrl()); + } else { + LOG.debug( + "not a valid url {} for current fetcher ", personalAccessToken.getScmProviderUrl()); + return Optional.empty(); + } + } + if (personalAccessToken.getScmTokenName() != null + && personalAccessToken.getScmTokenName().startsWith(OAUTH_2_PREFIX)) { + // validation OAuth token by special API call + try { + GitlabOauthTokenInfo info = + gitlabApiClient.getOAuthTokenInfo(personalAccessToken.getToken()); + return Optional.of(Sets.newHashSet(info.getScope()).containsAll(DEFAULT_TOKEN_SCOPES)); + } catch (ScmItemNotFoundException | ScmCommunicationException | ScmUnauthorizedException e) { + return Optional.of(Boolean.FALSE); + } + } else { + // validating personal access token from secret. Since PAT API is accessible only in + // latest GitLab version, we just perform check by accessing something from API. + try { + GitlabUser user = gitlabApiClient.getUser(personalAccessToken.getToken()); + if (personalAccessToken.getScmUserName().equals(user.getUsername())) { + return Optional.of(Boolean.TRUE); + } else { + return Optional.of(Boolean.FALSE); + } + } catch (ScmItemNotFoundException + | ScmCommunicationException + | ScmBadRequestException + | ScmUnauthorizedException e) { + return Optional.of(Boolean.FALSE); + } + } + } + + @Override + public Optional> isValid(PersonalAccessTokenParams params) + throws ScmCommunicationException { + GitlabApiClient gitlabApiClient = getApiClient(params.getScmProviderUrl()); + if (gitlabApiClient == null || !gitlabApiClient.isConnected(params.getScmProviderUrl())) { + if (providerName.equals(params.getScmTokenName())) { + gitlabApiClient = new GitlabApiClient(params.getScmProviderUrl()); + } else { + LOG.debug("not a valid url {} for current fetcher ", params.getScmProviderUrl()); + return Optional.empty(); + } + } + try { + GitlabUser user = gitlabApiClient.getUser(params.getToken()); + if (params.getScmTokenName() != null && params.getScmTokenName().startsWith(OAUTH_2_PREFIX)) { + // validation OAuth token by special API call + GitlabOauthTokenInfo info = gitlabApiClient.getOAuthTokenInfo(params.getToken()); + return Optional.of( + Pair.of( + Sets.newHashSet(info.getScope()).containsAll(DEFAULT_TOKEN_SCOPES) + ? Boolean.TRUE + : Boolean.FALSE, + user.getUsername())); + } + // validating personal access token from secret. Since PAT API is accessible only in + // latest GitLab version, we just perform check by accessing something from API. + // TODO: add PAT scope validation + return Optional.of(Pair.of(Boolean.TRUE, user.getUsername())); + } catch (ScmItemNotFoundException | ScmBadRequestException | ScmUnauthorizedException e) { + return Optional.empty(); + } + } + + private String getLocalAuthenticateUrl() { + return apiEndpoint + + "/oauth/authenticate?oauth_provider=" + + providerName + + "&scope=" + + Joiner.on('+').join(DEFAULT_TOKEN_SCOPES) + + "&request_method=POST&signature_method=rsa"; + } + + private GitlabApiClient getApiClient(String serverUrl) { + return serverUrl.equals(this.serverUrl) ? new GitlabApiClient(serverUrl) : null; + } +} diff --git a/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabScmFileResolver.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabScmFileResolver.java new file mode 100644 index 00000000000..58d7f19e296 --- /dev/null +++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabScmFileResolver.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2012-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.gitlab; + +import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException; + +import java.io.IOException; +import org.eclipse.che.api.core.ApiException; +import org.eclipse.che.api.core.NotFoundException; +import org.eclipse.che.api.factory.server.ScmFileResolver; +import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; +import org.eclipse.che.api.workspace.server.devfile.URLFetcher; +import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; + +/** GitLab specific SCM file resolver. */ +public class AbstractGitlabScmFileResolver implements ScmFileResolver { + + private final AbstractGitlabUrlParser gitlabUrlParser; + private final URLFetcher urlFetcher; + private final PersonalAccessTokenManager personalAccessTokenManager; + + public AbstractGitlabScmFileResolver( + AbstractGitlabUrlParser gitlabUrlParser, + URLFetcher urlFetcher, + PersonalAccessTokenManager personalAccessTokenManager) { + this.gitlabUrlParser = gitlabUrlParser; + this.urlFetcher = urlFetcher; + this.personalAccessTokenManager = personalAccessTokenManager; + } + + @Override + public boolean accept(String repository) { + return gitlabUrlParser.isValid(repository); + } + + @Override + public String fileContent(String repository, String filePath) throws ApiException { + GitlabUrl gitlabUrl = gitlabUrlParser.parse(repository); + + try { + return fetchContent(gitlabUrl, filePath, false); + } catch (DevfileException exception) { + // This catch might mean that the authentication was rejected by user, try to repeat the fetch + // without authentication flow. + try { + return fetchContent(gitlabUrl, filePath, true); + } catch (DevfileException devfileException) { + throw toApiException(devfileException); + } + } + } + + private String fetchContent(GitlabUrl gitlabUrl, String filePath, boolean skipAuthentication) + throws DevfileException, NotFoundException { + try { + GitlabAuthorizingFileContentProvider contentProvider = + new GitlabAuthorizingFileContentProvider( + gitlabUrl, urlFetcher, personalAccessTokenManager); + return skipAuthentication + ? contentProvider.fetchContentWithoutAuthentication(filePath) + : contentProvider.fetchContent(filePath); + } catch (IOException e) { + throw new NotFoundException(e.getMessage()); + } + } +} diff --git a/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabUrlParser.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabUrlParser.java new file mode 100644 index 00000000000..76aa1e13270 --- /dev/null +++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabUrlParser.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2012-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.gitlab; + +import static com.google.common.base.Strings.isNullOrEmpty; +import static java.lang.String.format; +import static java.util.regex.Pattern.compile; +import static org.eclipse.che.commons.lang.StringUtils.trimEnd; + +import jakarta.validation.constraints.NotNull; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; +import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; +import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; +import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; +import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; +import org.eclipse.che.commons.env.EnvironmentContext; + +/** + * Parser of String Gitlab URLs and provide {@link GitlabUrl} objects. + * + * @author Max Shaposhnyk + */ +public class AbstractGitlabUrlParser { + + private final DevfileFilenamesProvider devfileFilenamesProvider; + private final PersonalAccessTokenManager personalAccessTokenManager; + private final String providerName; + private static final List gitlabUrlPatternTemplates = + List.of( + "^(?%s)://(?%s)/(?([^/]++/?)+)/-/tree/(?.++)(/)?", + "^(?%s)://(?%s)/(?.*)"); // a wider one, should be the last in + // the list + private final String gitlabSSHPatternTemplate = "^git@(?%s):(?.*)$"; + // list + private final List gitlabUrlPatterns = new ArrayList<>(); + + public AbstractGitlabUrlParser( + String serverUrl, + DevfileFilenamesProvider devfileFilenamesProvider, + PersonalAccessTokenManager personalAccessTokenManager, + String providerName) { + this.devfileFilenamesProvider = devfileFilenamesProvider; + this.personalAccessTokenManager = personalAccessTokenManager; + this.providerName = providerName; + if (isNullOrEmpty(serverUrl)) { + gitlabUrlPatternTemplates.forEach( + t -> gitlabUrlPatterns.add(compile(format(t, "https", "gitlab.com")))); + gitlabUrlPatterns.add(compile(format(gitlabSSHPatternTemplate, "gitlab.com"))); + } else { + String trimmedEndpoint = trimEnd(serverUrl, '/'); + URI uri = URI.create(trimmedEndpoint); + String schema = uri.getScheme(); + String host = uri.getHost(); + for (String gitlabUrlPatternTemplate : gitlabUrlPatternTemplates) { + gitlabUrlPatterns.add(compile(format(gitlabUrlPatternTemplate, schema, host))); + } + gitlabUrlPatterns.add(compile(format(gitlabSSHPatternTemplate, host))); + } + } + + private boolean isUserTokenPresent(String repositoryUrl) { + Optional serverUrlOptional = getServerUrl(repositoryUrl); + if (serverUrlOptional.isPresent()) { + String serverUrl = serverUrlOptional.get(); + try { + Optional token = + personalAccessTokenManager.get(EnvironmentContext.getCurrent().getSubject(), serverUrl); + if (token.isPresent()) { + PersonalAccessToken accessToken = token.get(); + return accessToken.getScmTokenName().equals(providerName); + } + } catch (ScmConfigurationPersistenceException | ScmCommunicationException exception) { + return false; + } + } + return false; + } + + public boolean isValid(@NotNull String url) { + return gitlabUrlPatterns.stream() + .anyMatch(pattern -> pattern.matcher(trimEnd(url, '/')).matches()) + // If the Gitlab URL is not configured, try to find it in a manually added user namespace + // token. + || isUserTokenPresent(url) + // Try to call an API request to see if the URL matches Gitlab. + || isApiRequestRelevant(url); + } + + private boolean isApiRequestRelevant(String repositoryUrl) { + Optional serverUrlOptional = getServerUrl(repositoryUrl); + if (serverUrlOptional.isPresent()) { + GitlabApiClient gitlabApiClient = new GitlabApiClient(serverUrlOptional.get()); + try { + // If the token request catches the unauthorised error, it means that the provided url + // belongs to Gitlab. + gitlabApiClient.getOAuthTokenInfo(""); + } catch (ScmUnauthorizedException e) { + return true; + } catch (ScmItemNotFoundException | IllegalArgumentException | ScmCommunicationException e) { + return false; + } + } + return false; + } + + private Optional getPatternMatcherByUrl(String url) { + URI uri = + URI.create( + url.matches(format(gitlabSSHPatternTemplate, ".*")) + ? "ssh://" + url.replace(":", "/") + : url); + String scheme = uri.getScheme(); + String host = uri.getHost(); + return gitlabUrlPatternTemplates.stream() + .map(t -> compile(format(t, scheme, host)).matcher(url)) + .filter(Matcher::matches) + .findAny() + .or( + () -> { + Matcher matcher = compile(format(gitlabSSHPatternTemplate, host)).matcher(url); + if (matcher.matches()) { + return Optional.of(matcher); + } + return Optional.empty(); + }); + } + + private Optional getServerUrl(String repositoryUrl) { + if (repositoryUrl.startsWith("git@")) { + String substring = repositoryUrl.substring(4); + return Optional.of("https://" + substring.substring(0, substring.indexOf(":"))); + } + Matcher serverUrlMatcher = compile("[^/|:]/").matcher(repositoryUrl); + if (serverUrlMatcher.find()) { + return Optional.of( + repositoryUrl.substring(0, repositoryUrl.indexOf(serverUrlMatcher.group()) + 1)); + } + return Optional.empty(); + } + + /** + * Parses url-s like https://gitlab.apps.cluster-327a.327a.example.opentlc.com/root/proj1.git into + * {@link GitlabUrl} objects. + */ + public GitlabUrl parse(String url) { + String trimmedUrl = trimEnd(url, '/'); + Optional matcherOptional = + gitlabUrlPatterns.stream() + .map(pattern -> pattern.matcher(trimmedUrl)) + .filter(Matcher::matches) + .findFirst() + .or(() -> getPatternMatcherByUrl(trimmedUrl)); + if (matcherOptional.isPresent()) { + return parse(matcherOptional.get()).withUrl(trimmedUrl); + } else { + throw new UnsupportedOperationException( + "The gitlab integration is not configured properly and cannot be used at this moment." + + "Please refer to docs to check the Gitlab integration instructions"); + } + } + + private GitlabUrl parse(Matcher matcher) { + String scheme = null; + try { + scheme = matcher.group("scheme"); + } catch (IllegalArgumentException e) { + // ok no such group + } + String host = matcher.group("host"); + String subGroups = trimEnd(matcher.group("subgroups"), '/'); + if (subGroups.endsWith(".git")) { + subGroups = subGroups.substring(0, subGroups.length() - 4); + } + + String branch = null; + try { + branch = matcher.group("branch"); + } catch (IllegalArgumentException e) { + // ok no such group + } + + return new GitlabUrl() + .withHostName(host) + .withScheme(scheme) + .withSubGroups(subGroups) + .withBranch(branch) + .withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames()); + } +} diff --git a/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabUserDataFetcher.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabUserDataFetcher.java new file mode 100644 index 00000000000..18dff4f6af2 --- /dev/null +++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/AbstractGitlabUserDataFetcher.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2012-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.gitlab; + +import static com.google.common.base.Strings.isNullOrEmpty; + +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableSet; +import java.util.Set; +import org.eclipse.che.api.factory.server.scm.*; +import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; +import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; +import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; +import org.eclipse.che.commons.annotation.Nullable; + +/** Gitlab OAuth token retriever. */ +public class AbstractGitlabUserDataFetcher extends AbstractGitUserDataFetcher { + + private final String serverUrl; + private final String apiEndpoint; + private final String providerName; + + public static final Set DEFAULT_TOKEN_SCOPES = + ImmutableSet.of("api", "write_repository", "openid"); + private static final String GITLAB_SAAS_ENDPOINT = "https://gitlab.com"; + + public AbstractGitlabUserDataFetcher( + @Nullable String serverUrl, + String apiEndpoint, + PersonalAccessTokenManager personalAccessTokenManager, + String providerName) { + super(providerName, serverUrl, personalAccessTokenManager); + this.serverUrl = isNullOrEmpty(serverUrl) ? GITLAB_SAAS_ENDPOINT : serverUrl; + this.apiEndpoint = apiEndpoint; + this.providerName = providerName; + } + + @Override + protected GitUserData fetchGitUserDataWithOAuthToken(String token) + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { + GitlabUser user = new GitlabApiClient(serverUrl).getUser(token); + return new GitUserData(user.getName(), user.getEmail()); + } + + @Override + protected GitUserData fetchGitUserDataWithPersonalAccessToken( + PersonalAccessToken personalAccessToken) + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { + GitlabUser user = + new GitlabApiClient(personalAccessToken.getScmProviderUrl()) + .getUser(personalAccessToken.getToken()); + return new GitUserData(user.getName(), user.getEmail()); + } + + protected String getLocalAuthenticateUrl() { + return apiEndpoint + + "/oauth/authenticate?oauth_provider=" + + providerName + + "&scope=" + + Joiner.on('+').join(DEFAULT_TOKEN_SCOPES) + + "&request_method=POST&signature_method=rsa"; + } +} diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabApiClient.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabApiClient.java similarity index 90% rename from wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabApiClient.java rename to wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabApiClient.java index 6f2a902e9bf..71a67fc3aec 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabApiClient.java +++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabApiClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -13,6 +13,7 @@ import static java.net.HttpURLConnection.HTTP_BAD_REQUEST; import static java.net.HttpURLConnection.HTTP_NOT_FOUND; +import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; import static java.time.Duration.ofSeconds; import com.fasterxml.jackson.databind.ObjectMapper; @@ -33,6 +34,7 @@ import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; +import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -65,7 +67,8 @@ public GitlabApiClient(String serverUrl) { } public GitlabUser getUser(String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException { final URI uri = serverUrl.resolve("/api/v4/user"); HttpRequest request = HttpRequest.newBuilder(uri) @@ -88,7 +91,7 @@ public GitlabUser getUser(String authenticationToken) } public GitlabPersonalAccessTokenInfo getPersonalAccessTokenInfo(String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmUnauthorizedException { final URI uri = serverUrl.resolve("/api/v4/personal_access_tokens/self"); HttpRequest request = HttpRequest.newBuilder(uri) @@ -115,7 +118,7 @@ public GitlabPersonalAccessTokenInfo getPersonalAccessTokenInfo(String authentic } public GitlabOauthTokenInfo getOAuthTokenInfo(String authenticationToken) - throws ScmItemNotFoundException, ScmCommunicationException { + throws ScmItemNotFoundException, ScmCommunicationException, ScmUnauthorizedException { final URI uri = serverUrl.resolve("/oauth/token/info"); HttpRequest request = HttpRequest.newBuilder(uri) @@ -143,7 +146,9 @@ public GitlabOauthTokenInfo getOAuthTokenInfo(String authenticationToken) private T executeRequest( HttpClient httpClient, HttpRequest request, Function bodyConverter) - throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException { + throws ScmBadRequestException, ScmItemNotFoundException, ScmCommunicationException, + ScmUnauthorizedException { + String provider = "http://gitlab.com".equals(serverUrl.toString()) ? "gitlab" : "gitlab-server"; try { HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); @@ -159,14 +164,17 @@ private T executeRequest( throw new ScmBadRequestException(body); case HTTP_NOT_FOUND: throw new ScmItemNotFoundException(body); + case HTTP_UNAUTHORIZED: + throw new ScmUnauthorizedException(body, "gitlab", "v2", ""); default: throw new ScmCommunicationException( "Unexpected status code " + response.statusCode() + " " + response, - response.statusCode()); + response.statusCode(), + provider); } } } catch (IOException | InterruptedException | UncheckedIOException e) { - throw new ScmCommunicationException(e.getMessage(), e); + throw new ScmCommunicationException(e.getMessage(), e, provider); } } diff --git a/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java new file mode 100644 index 00000000000..fd270923a77 --- /dev/null +++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2012-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.gitlab; + +import static java.net.HttpURLConnection.HTTP_OK; +import static java.time.Duration.ofSeconds; + +import com.google.common.util.concurrent.ThreadFactoryBuilder; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.concurrent.Executors; +import org.eclipse.che.api.factory.server.scm.AuthorizingFileContentProvider; +import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; +import org.eclipse.che.api.workspace.server.devfile.URLFetcher; +import org.eclipse.che.commons.lang.concurrent.LoggingUncaughtExceptionHandler; + +/** Gitlab specific authorizing file content provider. */ +class GitlabAuthorizingFileContentProvider extends AuthorizingFileContentProvider { + + private final HttpClient httpClient; + + private static final Duration DEFAULT_HTTP_TIMEOUT = ofSeconds(10); + + GitlabAuthorizingFileContentProvider( + GitlabUrl gitlabUrl, + URLFetcher urlFetcher, + PersonalAccessTokenManager personalAccessTokenManager) { + super(gitlabUrl, urlFetcher, personalAccessTokenManager); + this.httpClient = + HttpClient.newBuilder() + .executor( + Executors.newCachedThreadPool( + new ThreadFactoryBuilder() + .setUncaughtExceptionHandler(LoggingUncaughtExceptionHandler.getInstance()) + .setNameFormat(GitlabAuthorizingFileContentProvider.class.getName() + "-%d") + .setDaemon(true) + .build())) + .connectTimeout(DEFAULT_HTTP_TIMEOUT) + .version(HttpClient.Version.HTTP_1_1) + .build(); + } + + @Override + protected boolean isPublicRepository(GitlabUrl remoteFactoryUrl) { + HttpRequest request = + HttpRequest.newBuilder( + URI.create( + remoteFactoryUrl.getProviderUrl() + '/' + remoteFactoryUrl.getSubGroups())) + .timeout(DEFAULT_HTTP_TIMEOUT) + .build(); + try { + HttpResponse response = + httpClient.send(request, HttpResponse.BodyHandlers.ofInputStream()); + return response.statusCode() == HTTP_OK; + } catch (IOException | InterruptedException e) { + return false; + } + } +} diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOauthTokenInfo.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOauthTokenInfo.java similarity index 98% rename from wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOauthTokenInfo.java rename to wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOauthTokenInfo.java index 9a1c32d28e3..3116ae59ba6 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOauthTokenInfo.java +++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOauthTokenInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2021 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabPersonalAccessTokenInfo.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabPersonalAccessTokenInfo.java similarity index 98% rename from wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabPersonalAccessTokenInfo.java rename to wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabPersonalAccessTokenInfo.java index ebfcf700f59..0114029d876 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabPersonalAccessTokenInfo.java +++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabPersonalAccessTokenInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java similarity index 99% rename from wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java rename to wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java index f0bda151871..a30e6dbdd5f 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java +++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUser.java b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUser.java similarity index 98% rename from wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUser.java rename to wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUser.java index b0f1bc201f9..2937e4d863d 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUser.java +++ b/wsmaster/che-core-api-factory-gitlab-common/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2021 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ diff --git a/wsmaster/che-core-api-factory-gitlab/pom.xml b/wsmaster/che-core-api-factory-gitlab/pom.xml index dbd335494ce..278d29aad4f 100644 --- a/wsmaster/che-core-api-factory-gitlab/pom.xml +++ b/wsmaster/che-core-api-factory-gitlab/pom.xml @@ -17,20 +17,12 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-factory-gitlab jar Che Core :: API :: Factory Resolver Gitlab - - com.fasterxml.jackson.core - jackson-annotations - - - com.fasterxml.jackson.core - jackson-databind - com.google.guava guava @@ -43,10 +35,6 @@ jakarta.inject jakarta.inject-api - - jakarta.validation - jakarta.validation-api - org.eclipse.che.core che-core-api-auth @@ -69,36 +57,28 @@ org.eclipse.che.core - che-core-api-factory-shared + che-core-api-factory-gitlab-common org.eclipse.che.core - che-core-api-model + che-core-api-factory-shared org.eclipse.che.core - che-core-api-workspace + che-core-api-model org.eclipse.che.core - che-core-api-workspace-shared + che-core-api-workspace org.eclipse.che.core che-core-commons-annotations - - org.eclipse.che.core - che-core-commons-inject - org.eclipse.che.core che-core-commons-lang - - org.slf4j - slf4j-api - jakarta.servlet jakarta.servlet-api diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java deleted file mode 100644 index 1137407b943..00000000000 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProvider.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2012-2023 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.api.factory.server.gitlab; - -import java.io.IOException; -import org.eclipse.che.api.factory.server.scm.AuthorizingFileContentProvider; -import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; -import org.eclipse.che.api.workspace.server.devfile.URLFetcher; - -/** Gitlab specific authorizing file content provider. */ -class GitlabAuthorizingFileContentProvider extends AuthorizingFileContentProvider { - - GitlabAuthorizingFileContentProvider( - GitlabUrl gitlabUrl, - URLFetcher urlFetcher, - PersonalAccessTokenManager personalAccessTokenManager) { - super(gitlabUrl, urlFetcher, personalAccessTokenManager); - } - - @Override - protected boolean isPublicRepository(GitlabUrl remoteFactoryUrl) { - try { - urlFetcher.fetch(remoteFactoryUrl.getHostName() + '/' + remoteFactoryUrl.getSubGroups()); - return true; - } catch (IOException e) { - return false; - } - } -} diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolver.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolver.java index cca0a9b21a2..1991f90f40c 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolver.java +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolver.java @@ -11,29 +11,13 @@ */ package org.eclipse.che.api.factory.server.gitlab; -import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; -import static org.eclipse.che.dto.server.DtoFactory.newDto; - -import jakarta.validation.constraints.NotNull; -import java.util.Map; import javax.inject.Inject; import javax.inject.Singleton; -import org.eclipse.che.api.core.ApiException; -import org.eclipse.che.api.core.BadRequestException; -import org.eclipse.che.api.factory.server.BaseFactoryParameterResolver; import org.eclipse.che.api.factory.server.FactoryParametersResolver; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; -import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; -import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; -import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; -import org.eclipse.che.api.factory.shared.dto.FactoryVisitor; -import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; -import org.eclipse.che.api.workspace.shared.dto.devfile.ProjectDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.SourceDto; /** * Provides Factory Parameters resolver for Gitlab repositories. @@ -41,16 +25,11 @@ * @author Max Shaposhnyk */ @Singleton -public class GitlabFactoryParametersResolver extends BaseFactoryParameterResolver +public class GitlabFactoryParametersResolver extends AbstractGitlabFactoryParametersResolver implements FactoryParametersResolver { private static final String PROVIDER_NAME = "gitlab"; - private final URLFactoryBuilder urlFactoryBuilder; - private final URLFetcher urlFetcher; - private final GitlabUrlParser gitlabURLParser; - private final PersonalAccessTokenManager personalAccessTokenManager; - @Inject public GitlabFactoryParametersResolver( URLFactoryBuilder urlFactoryBuilder, @@ -58,106 +37,12 @@ public GitlabFactoryParametersResolver( GitlabUrlParser gitlabURLParser, PersonalAccessTokenManager personalAccessTokenManager, AuthorisationRequestManager authorisationRequestManager) { - super(authorisationRequestManager, urlFactoryBuilder, PROVIDER_NAME); - this.urlFactoryBuilder = urlFactoryBuilder; - this.urlFetcher = urlFetcher; - this.gitlabURLParser = gitlabURLParser; - this.personalAccessTokenManager = personalAccessTokenManager; - } - - /** - * Check if this resolver can be used with the given parameters. - * - * @param factoryParameters map of parameters dedicated to factories - * @return true if it will be accepted by the resolver implementation or false if it is not - * accepted - */ - @Override - public boolean accept(@NotNull final Map factoryParameters) { - return factoryParameters.containsKey(URL_PARAMETER_NAME) - && gitlabURLParser.isValid(factoryParameters.get(URL_PARAMETER_NAME)); - } - - @Override - public String getProviderName() { - return PROVIDER_NAME; - } - - /** - * Create factory object based on provided parameters - * - * @param factoryParameters map containing factory data parameters provided through URL - * @throws BadRequestException when data are invalid - */ - @Override - public FactoryMetaDto createFactory(@NotNull final Map factoryParameters) - throws ApiException { - // no need to check null value of url parameter as accept() method has performed the check - final GitlabUrl gitlabUrl = gitlabURLParser.parse(factoryParameters.get(URL_PARAMETER_NAME)); - // create factory from the following location if location exists, else create default factory - return createFactory( - factoryParameters, - gitlabUrl, - new GitlabFactoryVisitor(gitlabUrl), - new GitlabAuthorizingFileContentProvider( - gitlabUrl, urlFetcher, personalAccessTokenManager)); - } - - /** - * Visitor that puts the default devfile or updates devfile projects into the Gitlab Factory, if - * needed. - */ - private class GitlabFactoryVisitor implements FactoryVisitor { - - private final GitlabUrl gitlabUrl; - - private GitlabFactoryVisitor(GitlabUrl gitlabUrl) { - this.gitlabUrl = gitlabUrl; - } - - @Override - public FactoryDevfileV2Dto visit(FactoryDevfileV2Dto factoryDto) { - ScmInfoDto scmInfo = - newDto(ScmInfoDto.class) - .withScmProviderName(gitlabUrl.getProviderName()) - .withRepositoryUrl(gitlabUrl.repositoryLocation()); - if (gitlabUrl.getBranch() != null) { - scmInfo.withBranch(gitlabUrl.getBranch()); - } - return factoryDto.withScmInfo(scmInfo); - } - - @Override - public FactoryDto visit(FactoryDto factory) { - - if (factory.getDevfile() == null) { - // initialize default devfile - factory.setDevfile(urlFactoryBuilder.buildDefaultDevfile(gitlabUrl.getProject())); - } - - updateProjects( - factory.getDevfile(), - () -> - newDto(ProjectDto.class) - .withSource( - newDto(SourceDto.class) - .withLocation(gitlabUrl.repositoryLocation()) - .withType("git") - .withBranch(gitlabUrl.getBranch())) - .withName(gitlabUrl.getProject()), - project -> { - final String location = project.getSource().getLocation(); - if (location.equals(gitlabUrl.repositoryLocation())) { - project.getSource().setBranch(gitlabUrl.getBranch()); - } - }); - - return factory; - } - } - - @Override - public RemoteFactoryUrl parseFactoryUrl(String factoryUrl) throws ApiException { - return gitlabURLParser.parse(factoryUrl); + super( + urlFactoryBuilder, + urlFetcher, + gitlabURLParser, + personalAccessTokenManager, + authorisationRequestManager, + PROVIDER_NAME); } } diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolverSecond.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolverSecond.java new file mode 100644 index 00000000000..c9b6019adc8 --- /dev/null +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolverSecond.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2012-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.gitlab; + +import javax.inject.Inject; +import javax.inject.Singleton; +import org.eclipse.che.api.factory.server.FactoryParametersResolver; +import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; +import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; +import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; +import org.eclipse.che.api.workspace.server.devfile.URLFetcher; + +/** + * Provides Factory Parameters resolver for Gitlab repositories. + * + * @author Max Shaposhnyk + */ +@Singleton +public class GitlabFactoryParametersResolverSecond extends AbstractGitlabFactoryParametersResolver + implements FactoryParametersResolver { + + private static final String PROVIDER_NAME = "gitlab_2"; + + @Inject + public GitlabFactoryParametersResolverSecond( + URLFactoryBuilder urlFactoryBuilder, + URLFetcher urlFetcher, + GitlabUrlParserSecond gitlabURLParser, + PersonalAccessTokenManager personalAccessTokenManager, + AuthorisationRequestManager authorisationRequestManager) { + super( + urlFactoryBuilder, + urlFetcher, + gitlabURLParser, + personalAccessTokenManager, + authorisationRequestManager, + PROVIDER_NAME); + } +} diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabModule.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabModule.java index 3fbca073b94..9207f6fa17a 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabModule.java +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabModule.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -23,8 +23,11 @@ protected void configure() { Multibinder tokenFetcherMultibinder = Multibinder.newSetBinder(binder(), PersonalAccessTokenFetcher.class); tokenFetcherMultibinder.addBinding().to(GitlabOAuthTokenFetcher.class); + tokenFetcherMultibinder.addBinding().to(GitlabOAuthTokenFetcherSecond.class); + Multibinder gitUserDataMultibinder = Multibinder.newSetBinder(binder(), GitUserDataFetcher.class); gitUserDataMultibinder.addBinding().to(GitlabUserDataFetcher.class); + gitUserDataMultibinder.addBinding().to(GitlabUserDataFetcherSecond.class); } } diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcher.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcher.java index d3384a92571..747554955de 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcher.java +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcher.java @@ -11,231 +11,21 @@ */ package org.eclipse.che.api.factory.server.gitlab; -import static java.lang.String.format; -import static java.util.stream.Collectors.toList; - -import com.google.common.base.Joiner; -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Sets; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.Set; import javax.inject.Inject; import javax.inject.Named; -import org.eclipse.che.api.auth.shared.dto.OAuthToken; -import org.eclipse.che.api.core.BadRequestException; -import org.eclipse.che.api.core.ConflictException; -import org.eclipse.che.api.core.ForbiddenException; -import org.eclipse.che.api.core.NotFoundException; -import org.eclipse.che.api.core.ServerException; -import org.eclipse.che.api.core.UnauthorizedException; -import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; -import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenFetcher; -import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenParams; -import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; -import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; -import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; -import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; -import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.commons.annotation.Nullable; -import org.eclipse.che.commons.lang.NameGenerator; -import org.eclipse.che.commons.lang.Pair; -import org.eclipse.che.commons.lang.StringUtils; -import org.eclipse.che.commons.subject.Subject; -import org.eclipse.che.inject.ConfigurationException; import org.eclipse.che.security.oauth.OAuthAPI; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** GitLab OAuth token retriever. */ -public class GitlabOAuthTokenFetcher implements PersonalAccessTokenFetcher { +public class GitlabOAuthTokenFetcher extends AbstractGitlabOAuthTokenFetcher { - private static final Logger LOG = LoggerFactory.getLogger(GitlabOAuthTokenFetcher.class); private static final String OAUTH_PROVIDER_NAME = "gitlab"; - public static final Set DEFAULT_TOKEN_SCOPES = ImmutableSet.of("api", "write_repository"); - - private final List registeredGitlabEndpoints; - private final OAuthAPI oAuthAPI; - private final String apiEndpoint; @Inject public GitlabOAuthTokenFetcher( - @Nullable @Named("che.integration.gitlab.server_endpoints") String gitlabEndpoints, - @Nullable @Named("che.integration.gitlab.oauth_endpoint") String oauthEndpoint, + @Nullable @Named("che.integration.gitlab.oauth_endpoint") String serverUrl, @Named("che.api") String apiEndpoint, OAuthAPI oAuthAPI) { - this.apiEndpoint = apiEndpoint; - if (gitlabEndpoints != null) { - this.registeredGitlabEndpoints = - Splitter.on(",") - .splitToStream(gitlabEndpoints) - .map(e -> StringUtils.trimEnd(e, '/')) - .collect(toList()); - } else { - this.registeredGitlabEndpoints = Collections.emptyList(); - } - if (oauthEndpoint != null) { - if (!registeredGitlabEndpoints.contains(StringUtils.trimEnd(oauthEndpoint, '/'))) { - throw new ConfigurationException( - "GitLab OAuth integration endpoint must be present in registered GitLab endpoints list."); - } - this.oAuthAPI = oAuthAPI; - } else { - this.oAuthAPI = null; - } - } - - @Override - public PersonalAccessToken fetchPersonalAccessToken(Subject cheSubject, String scmServerUrl) - throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { - scmServerUrl = StringUtils.trimEnd(scmServerUrl, '/'); - GitlabApiClient gitlabApiClient = getApiClient(scmServerUrl); - if (gitlabApiClient == null || !gitlabApiClient.isConnected(scmServerUrl)) { - LOG.debug("not a valid url {} for current fetcher ", scmServerUrl); - return null; - } - if (oAuthAPI == null) { - throw new ScmCommunicationException( - format( - "OAuth 2 is not configured for SCM provider [%s]. For details, refer " - + "the documentation in section of SCM providers configuration.", - OAUTH_PROVIDER_NAME)); - } - OAuthToken oAuthToken; - try { - oAuthToken = oAuthAPI.getToken(OAUTH_PROVIDER_NAME); - String tokenName = NameGenerator.generate(OAUTH_2_PREFIX, 5); - String tokenId = NameGenerator.generate("id-", 5); - Optional> valid = - isValid( - new PersonalAccessTokenParams( - scmServerUrl, - OAUTH_PROVIDER_NAME, - tokenName, - tokenId, - oAuthToken.getToken(), - null)); - if (valid.isEmpty()) { - throw buildScmUnauthorizedException(cheSubject); - } else if (!valid.get().first) { - throw new ScmCommunicationException( - "Current token doesn't have the necessary privileges. Please make sure Che app scopes are correct and containing at least: " - + DEFAULT_TOKEN_SCOPES); - } - return new PersonalAccessToken( - scmServerUrl, - OAUTH_PROVIDER_NAME, - cheSubject.getUserId(), - valid.get().second, - tokenName, - tokenId, - oAuthToken.getToken()); - } catch (UnauthorizedException e) { - throw buildScmUnauthorizedException(cheSubject); - } catch (NotFoundException nfe) { - throw new UnknownScmProviderException(nfe.getMessage(), scmServerUrl); - } catch (ServerException | ForbiddenException | BadRequestException | ConflictException e) { - LOG.warn(e.getMessage()); - throw new ScmCommunicationException(e.getMessage(), e); - } - } - - private ScmUnauthorizedException buildScmUnauthorizedException(Subject cheSubject) { - return new ScmUnauthorizedException( - cheSubject.getUserName() - + " is not authorized in " - + OAUTH_PROVIDER_NAME - + " OAuth provider.", - OAUTH_PROVIDER_NAME, - "2.0", - getLocalAuthenticateUrl()); - } - - @Override - public Optional isValid(PersonalAccessToken personalAccessToken) { - GitlabApiClient gitlabApiClient = getApiClient(personalAccessToken.getScmProviderUrl()); - if (gitlabApiClient == null - || !gitlabApiClient.isConnected(personalAccessToken.getScmProviderUrl())) { - if (personalAccessToken.getScmTokenName().equals(OAUTH_PROVIDER_NAME)) { - gitlabApiClient = new GitlabApiClient(personalAccessToken.getScmProviderUrl()); - } else { - LOG.debug( - "not a valid url {} for current fetcher ", personalAccessToken.getScmProviderUrl()); - return Optional.empty(); - } - } - if (personalAccessToken.getScmTokenName() != null - && personalAccessToken.getScmTokenName().startsWith(OAUTH_2_PREFIX)) { - // validation OAuth token by special API call - try { - GitlabOauthTokenInfo info = - gitlabApiClient.getOAuthTokenInfo(personalAccessToken.getToken()); - return Optional.of(Sets.newHashSet(info.getScope()).containsAll(DEFAULT_TOKEN_SCOPES)); - } catch (ScmItemNotFoundException | ScmCommunicationException e) { - return Optional.of(Boolean.FALSE); - } - } else { - // validating personal access token from secret. Since PAT API is accessible only in - // latest GitLab version, we just perform check by accessing something from API. - try { - GitlabUser user = gitlabApiClient.getUser(personalAccessToken.getToken()); - if (personalAccessToken.getScmUserName().equals(user.getUsername())) { - return Optional.of(Boolean.TRUE); - } else { - return Optional.of(Boolean.FALSE); - } - } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { - return Optional.of(Boolean.FALSE); - } - } - } - - @Override - public Optional> isValid(PersonalAccessTokenParams params) { - GitlabApiClient gitlabApiClient = getApiClient(params.getScmProviderUrl()); - if (gitlabApiClient == null || !gitlabApiClient.isConnected(params.getScmProviderUrl())) { - if (OAUTH_PROVIDER_NAME.equals(params.getScmTokenName())) { - gitlabApiClient = new GitlabApiClient(params.getScmProviderUrl()); - } else { - LOG.debug("not a valid url {} for current fetcher ", params.getScmProviderUrl()); - return Optional.empty(); - } - } - try { - GitlabUser user = gitlabApiClient.getUser(params.getToken()); - if (params.getScmTokenName() != null && params.getScmTokenName().startsWith(OAUTH_2_PREFIX)) { - // validation OAuth token by special API call - GitlabOauthTokenInfo info = gitlabApiClient.getOAuthTokenInfo(params.getToken()); - return Optional.of( - Pair.of( - Sets.newHashSet(info.getScope()).containsAll(DEFAULT_TOKEN_SCOPES) - ? Boolean.TRUE - : Boolean.FALSE, - user.getUsername())); - } - // validating personal access token from secret. Since PAT API is accessible only in - // latest GitLab version, we just perform check by accessing something from API. - // TODO: add PAT scope validation - return Optional.of(Pair.of(Boolean.TRUE, user.getUsername())); - } catch (ScmItemNotFoundException | ScmCommunicationException | ScmBadRequestException e) { - return Optional.empty(); - } - } - - private String getLocalAuthenticateUrl() { - return apiEndpoint - + "/oauth/authenticate?oauth_provider=" - + OAUTH_PROVIDER_NAME - + "&scope=" - + Joiner.on('+').join(DEFAULT_TOKEN_SCOPES) - + "&request_method=POST&signature_method=rsa"; - } - - private GitlabApiClient getApiClient(String scmServerUrl) { - return registeredGitlabEndpoints.contains(scmServerUrl) - ? new GitlabApiClient(scmServerUrl) - : null; + super(serverUrl, apiEndpoint, oAuthAPI, OAUTH_PROVIDER_NAME); } } diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcherSecond.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcherSecond.java new file mode 100644 index 00000000000..a2cf599aab7 --- /dev/null +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcherSecond.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2012-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.gitlab; + +import javax.inject.Inject; +import javax.inject.Named; +import org.eclipse.che.commons.annotation.Nullable; +import org.eclipse.che.security.oauth.OAuthAPI; + +/** GitLab OAuth token retriever. */ +public class GitlabOAuthTokenFetcherSecond extends AbstractGitlabOAuthTokenFetcher { + + private static final String OAUTH_PROVIDER_NAME = "gitlab_2"; + + @Inject + public GitlabOAuthTokenFetcherSecond( + @Nullable @Named("che.integration.gitlab.oauth_endpoint_2") String serverUrl, + @Named("che.api") String apiEndpoint, + OAuthAPI oAuthAPI) { + super(serverUrl, apiEndpoint, oAuthAPI, OAUTH_PROVIDER_NAME); + } +} diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabScmFileResolver.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabScmFileResolver.java index 8a2ee17c7b6..4cfb64b9b4a 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabScmFileResolver.java +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabScmFileResolver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -11,67 +11,18 @@ */ package org.eclipse.che.api.factory.server.gitlab; -import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException; - -import java.io.IOException; import javax.inject.Inject; -import org.eclipse.che.api.core.ApiException; -import org.eclipse.che.api.core.NotFoundException; -import org.eclipse.che.api.factory.server.ScmFileResolver; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; -import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; /** GitLab specific SCM file resolver. */ -public class GitlabScmFileResolver implements ScmFileResolver { - - private final GitlabUrlParser gitlabUrlParser; - private final URLFetcher urlFetcher; - private final PersonalAccessTokenManager personalAccessTokenManager; +public class GitlabScmFileResolver extends AbstractGitlabScmFileResolver { @Inject public GitlabScmFileResolver( GitlabUrlParser gitlabUrlParser, URLFetcher urlFetcher, PersonalAccessTokenManager personalAccessTokenManager) { - this.gitlabUrlParser = gitlabUrlParser; - this.urlFetcher = urlFetcher; - this.personalAccessTokenManager = personalAccessTokenManager; - } - - @Override - public boolean accept(String repository) { - return gitlabUrlParser.isValid(repository); - } - - @Override - public String fileContent(String repository, String filePath) throws ApiException { - GitlabUrl gitlabUrl = gitlabUrlParser.parse(repository); - - try { - return fetchContent(gitlabUrl, filePath, false); - } catch (DevfileException exception) { - // This catch might mean that the authentication was rejected by user, try to repeat the fetch - // without authentication flow. - try { - return fetchContent(gitlabUrl, filePath, true); - } catch (DevfileException devfileException) { - throw toApiException(devfileException); - } - } - } - - private String fetchContent(GitlabUrl gitlabUrl, String filePath, boolean skipAuthentication) - throws DevfileException, NotFoundException { - try { - GitlabAuthorizingFileContentProvider contentProvider = - new GitlabAuthorizingFileContentProvider( - gitlabUrl, urlFetcher, personalAccessTokenManager); - return skipAuthentication - ? contentProvider.fetchContentWithoutAuthentication(filePath) - : contentProvider.fetchContent(filePath); - } catch (IOException e) { - throw new NotFoundException(e.getMessage()); - } + super(gitlabUrlParser, urlFetcher, personalAccessTokenManager); } } diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabScmFileResolverSecond.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabScmFileResolverSecond.java new file mode 100644 index 00000000000..aa9a0cb7d7b --- /dev/null +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabScmFileResolverSecond.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2012-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.gitlab; + +import javax.inject.Inject; +import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; +import org.eclipse.che.api.workspace.server.devfile.URLFetcher; + +/** GitLab specific SCM file resolver. */ +public class GitlabScmFileResolverSecond extends AbstractGitlabScmFileResolver { + + @Inject + public GitlabScmFileResolverSecond( + GitlabUrlParserSecond gitlabUrlParser, + URLFetcher urlFetcher, + PersonalAccessTokenManager personalAccessTokenManager) { + super(gitlabUrlParser, urlFetcher, personalAccessTokenManager); + } +} diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParser.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParser.java index d094b114a70..6d5b6a29f68 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParser.java +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParser.java @@ -11,200 +11,26 @@ */ package org.eclipse.che.api.factory.server.gitlab; -import static java.lang.String.format; -import static java.net.HttpURLConnection.HTTP_UNAUTHORIZED; -import static java.util.regex.Pattern.compile; -import static org.eclipse.che.commons.lang.StringUtils.trimEnd; - -import com.google.common.base.Splitter; -import jakarta.validation.constraints.NotNull; -import java.net.URI; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.inject.Inject; import javax.inject.Named; -import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; -import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; -import org.eclipse.che.api.factory.server.scm.exception.ScmConfigurationPersistenceException; -import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; import org.eclipse.che.commons.annotation.Nullable; -import org.eclipse.che.commons.env.EnvironmentContext; /** * Parser of String Gitlab URLs and provide {@link GitlabUrl} objects. * * @author Max Shaposhnyk */ -public class GitlabUrlParser { +public class GitlabUrlParser extends AbstractGitlabUrlParser { - private final DevfileFilenamesProvider devfileFilenamesProvider; - private final PersonalAccessTokenManager personalAccessTokenManager; - private static final List gitlabUrlPatternTemplates = - List.of( - "^(?%s)://(?%s)/(?([^/]++/?)+)/-/tree/(?.++)(/)?", - "^(?%s)://(?%s)/(?.*)"); // a wider one, should be the last in - // the list - private final String gitlabSSHPatternTemplate = "^git@(?%s):(?.*)$"; - // list - private final List gitlabUrlPatterns = new ArrayList<>(); private static final String OAUTH_PROVIDER_NAME = "gitlab"; @Inject public GitlabUrlParser( - @Nullable @Named("che.integration.gitlab.server_endpoints") String gitlabEndpoints, + @Nullable @Named("che.integration.gitlab.oauth_endpoint") String serverUrl, DevfileFilenamesProvider devfileFilenamesProvider, PersonalAccessTokenManager personalAccessTokenManager) { - this.devfileFilenamesProvider = devfileFilenamesProvider; - this.personalAccessTokenManager = personalAccessTokenManager; - if (gitlabEndpoints != null) { - for (String gitlabEndpoint : Splitter.on(",").split(gitlabEndpoints)) { - String trimmedEndpoint = trimEnd(gitlabEndpoint, '/'); - URI uri = URI.create(trimmedEndpoint); - String schema = uri.getScheme(); - String host = uri.getHost(); - for (String gitlabUrlPatternTemplate : gitlabUrlPatternTemplates) { - gitlabUrlPatterns.add(compile(format(gitlabUrlPatternTemplate, schema, host))); - } - gitlabUrlPatterns.add(compile(format(gitlabSSHPatternTemplate, host))); - } - } else { - gitlabUrlPatternTemplates.forEach( - t -> gitlabUrlPatterns.add(compile(format(t, "https", "gitlab.com")))); - gitlabUrlPatterns.add(compile(format(gitlabSSHPatternTemplate, "gitlab.com"))); - } - } - - private boolean isUserTokenPresent(String repositoryUrl) { - Optional serverUrlOptional = getServerUrl(repositoryUrl); - if (serverUrlOptional.isPresent()) { - String serverUrl = serverUrlOptional.get(); - try { - Optional token = - personalAccessTokenManager.get(EnvironmentContext.getCurrent().getSubject(), serverUrl); - if (token.isPresent()) { - PersonalAccessToken accessToken = token.get(); - return accessToken.getScmTokenName().equals(OAUTH_PROVIDER_NAME); - } - } catch (ScmConfigurationPersistenceException exception) { - return false; - } - } - return false; - } - - public boolean isValid(@NotNull String url) { - return gitlabUrlPatterns.stream() - .anyMatch(pattern -> pattern.matcher(trimEnd(url, '/')).matches()) - // If the Gitlab URL is not configured, try to find it in a manually added user namespace - // token. - || isUserTokenPresent(url) - // Try to call an API request to see if the URL matches Gitlab. - || isApiRequestRelevant(url); - } - - private boolean isApiRequestRelevant(String repositoryUrl) { - Optional serverUrlOptional = getServerUrl(repositoryUrl); - if (serverUrlOptional.isPresent()) { - GitlabApiClient gitlabApiClient = new GitlabApiClient(serverUrlOptional.get()); - try { - // If the token request catches the unauthorised error, it means that the provided url - // belongs to Gitlab. - gitlabApiClient.getOAuthTokenInfo(""); - } catch (ScmCommunicationException e) { - return e.getStatusCode() == HTTP_UNAUTHORIZED; - } catch (ScmItemNotFoundException | IllegalArgumentException e) { - return false; - } - } - return false; - } - - private Optional getPatternMatcherByUrl(String url) { - URI uri = - URI.create( - url.matches(format(gitlabSSHPatternTemplate, ".*")) - ? "ssh://" + url.replace(":", "/") - : url); - String scheme = uri.getScheme(); - String host = uri.getHost(); - return gitlabUrlPatternTemplates.stream() - .map(t -> compile(format(t, scheme, host)).matcher(url)) - .filter(Matcher::matches) - .findAny() - .or( - () -> { - Matcher matcher = compile(format(gitlabSSHPatternTemplate, host)).matcher(url); - if (matcher.matches()) { - return Optional.of(matcher); - } - return Optional.empty(); - }); - } - - private Optional getServerUrl(String repositoryUrl) { - if (repositoryUrl.startsWith("git@")) { - String substring = repositoryUrl.substring(4); - return Optional.of("https://" + substring.substring(0, substring.indexOf(":"))); - } - Matcher serverUrlMatcher = compile("[^/|:]/").matcher(repositoryUrl); - if (serverUrlMatcher.find()) { - return Optional.of( - repositoryUrl.substring(0, repositoryUrl.indexOf(serverUrlMatcher.group()) + 1)); - } - return Optional.empty(); - } - - /** - * Parses url-s like https://gitlab.apps.cluster-327a.327a.example.opentlc.com/root/proj1.git into - * {@link GitlabUrl} objects. - */ - public GitlabUrl parse(String url) { - String trimmedUrl = trimEnd(url, '/'); - Optional matcherOptional = - gitlabUrlPatterns.stream() - .map(pattern -> pattern.matcher(trimmedUrl)) - .filter(Matcher::matches) - .findFirst() - .or(() -> getPatternMatcherByUrl(trimmedUrl)); - if (matcherOptional.isPresent()) { - return parse(matcherOptional.get()).withUrl(trimmedUrl); - } else { - throw new UnsupportedOperationException( - "The gitlab integration is not configured properly and cannot be used at this moment." - + "Please refer to docs to check the Gitlab integration instructions"); - } - } - - private GitlabUrl parse(Matcher matcher) { - String scheme = null; - try { - scheme = matcher.group("scheme"); - } catch (IllegalArgumentException e) { - // ok no such group - } - String host = matcher.group("host"); - String subGroups = trimEnd(matcher.group("subgroups"), '/'); - if (subGroups.endsWith(".git")) { - subGroups = subGroups.substring(0, subGroups.length() - 4); - } - - String branch = null; - try { - branch = matcher.group("branch"); - } catch (IllegalArgumentException e) { - // ok no such group - } - - return new GitlabUrl() - .withHostName(host) - .withScheme(scheme) - .withSubGroups(subGroups) - .withBranch(branch) - .withDevfileFilenames(devfileFilenamesProvider.getConfiguredDevfileFilenames()); + super(serverUrl, devfileFilenamesProvider, personalAccessTokenManager, OAUTH_PROVIDER_NAME); } } diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserSecond.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserSecond.java new file mode 100644 index 00000000000..3480215a03d --- /dev/null +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserSecond.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2012-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.gitlab; + +import javax.inject.Inject; +import javax.inject.Named; +import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; +import org.eclipse.che.api.factory.server.urlfactory.DevfileFilenamesProvider; +import org.eclipse.che.commons.annotation.Nullable; + +/** + * Parser of String Gitlab URLs and provide {@link GitlabUrl} objects. + * + * @author Max Shaposhnyk + */ +public class GitlabUrlParserSecond extends AbstractGitlabUrlParser { + + private static final String OAUTH_PROVIDER_NAME = "gitlab_2"; + + @Inject + public GitlabUrlParserSecond( + @Nullable @Named("che.integration.gitlab.oauth_endpoint_2") String serverUrl, + DevfileFilenamesProvider devfileFilenamesProvider, + PersonalAccessTokenManager personalAccessTokenManager) { + super(serverUrl, devfileFilenamesProvider, personalAccessTokenManager, OAUTH_PROVIDER_NAME); + } +} diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcher.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcher.java index 15f2cffc7ad..028a46228c5 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcher.java +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcher.java @@ -11,91 +11,22 @@ */ package org.eclipse.che.api.factory.server.gitlab; -import static com.google.common.base.Strings.isNullOrEmpty; -import static java.util.stream.Collectors.toList; - -import com.google.common.base.Joiner; -import com.google.common.base.Splitter; -import com.google.common.collect.ImmutableSet; -import java.util.Collections; -import java.util.List; -import java.util.Set; import javax.inject.Inject; import javax.inject.Named; import org.eclipse.che.api.factory.server.scm.*; -import org.eclipse.che.api.factory.server.scm.exception.ScmBadRequestException; -import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; -import org.eclipse.che.api.factory.server.scm.exception.ScmItemNotFoundException; import org.eclipse.che.commons.annotation.Nullable; -import org.eclipse.che.commons.lang.StringUtils; -import org.eclipse.che.inject.ConfigurationException; /** Gitlab OAuth token retriever. */ -public class GitlabUserDataFetcher extends AbstractGitUserDataFetcher { - private final String apiEndpoint; +public class GitlabUserDataFetcher extends AbstractGitlabUserDataFetcher { /** Name of this OAuth provider as found in OAuthAPI. */ private static final String OAUTH_PROVIDER_NAME = "gitlab"; - private final List registeredGitlabEndpoints; - - public static final Set DEFAULT_TOKEN_SCOPES = - ImmutableSet.of("api", "write_repository", "openid"); - @Inject public GitlabUserDataFetcher( - @Nullable @Named("che.integration.gitlab.server_endpoints") String gitlabEndpoints, - @Nullable @Named("che.integration.gitlab.oauth_endpoint") String oauthEndpoint, + @Nullable @Named("che.integration.gitlab.oauth_endpoint") String serverUrl, @Named("che.api") String apiEndpoint, PersonalAccessTokenManager personalAccessTokenManager) { - super( - OAUTH_PROVIDER_NAME, - isNullOrEmpty(gitlabEndpoints) ? "https://gitlab.com" : gitlabEndpoints, - personalAccessTokenManager); - this.apiEndpoint = apiEndpoint; - if (gitlabEndpoints != null) { - this.registeredGitlabEndpoints = - Splitter.on(",") - .splitToStream(gitlabEndpoints) - .map(e -> StringUtils.trimEnd(e, '/')) - .collect(toList()); - } else { - this.registeredGitlabEndpoints = Collections.emptyList(); - } - if (oauthEndpoint != null) { - if (!registeredGitlabEndpoints.contains(StringUtils.trimEnd(oauthEndpoint, '/'))) { - throw new ConfigurationException( - "GitLab OAuth integration endpoint must be present in registered GitLab endpoints list."); - } - } - } - - @Override - protected GitUserData fetchGitUserDataWithOAuthToken(String token) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { - for (String gitlabServerEndpoint : this.registeredGitlabEndpoints) { - GitlabUser user = new GitlabApiClient(gitlabServerEndpoint).getUser(token); - return new GitUserData(user.getName(), user.getEmail()); - } - throw new ScmCommunicationException("Failed to retrieve git user data from Gitlab"); - } - - @Override - protected GitUserData fetchGitUserDataWithPersonalAccessToken( - PersonalAccessToken personalAccessToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException { - GitlabUser user = - new GitlabApiClient(personalAccessToken.getScmProviderUrl()) - .getUser(personalAccessToken.getToken()); - return new GitUserData(user.getName(), user.getEmail()); - } - - protected String getLocalAuthenticateUrl() { - return apiEndpoint - + "/oauth/authenticate?oauth_provider=" - + OAUTH_PROVIDER_NAME - + "&scope=" - + Joiner.on('+').join(DEFAULT_TOKEN_SCOPES) - + "&request_method=POST&signature_method=rsa"; + super(serverUrl, apiEndpoint, personalAccessTokenManager, OAUTH_PROVIDER_NAME); } } diff --git a/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcherSecond.java b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcherSecond.java new file mode 100644 index 00000000000..a7696cfd91f --- /dev/null +++ b/wsmaster/che-core-api-factory-gitlab/src/main/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcherSecond.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2012-2024 Red Hat, Inc. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.api.factory.server.gitlab; + +import javax.inject.Inject; +import javax.inject.Named; +import org.eclipse.che.api.factory.server.scm.*; +import org.eclipse.che.commons.annotation.Nullable; + +/** Gitlab OAuth token retriever. */ +public class GitlabUserDataFetcherSecond extends AbstractGitlabUserDataFetcher { + + /** Name of this OAuth provider as found in OAuthAPI. */ + private static final String OAUTH_PROVIDER_NAME = "gitlab_2"; + + @Inject + public GitlabUserDataFetcherSecond( + @Nullable @Named("che.integration.gitlab.oauth_endpoint_2") String serverUrl, + @Named("che.api") String apiEndpoint, + PersonalAccessTokenManager personalAccessTokenManager) { + super(serverUrl, apiEndpoint, personalAccessTokenManager, OAUTH_PROVIDER_NAME); + } +} diff --git a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProviderTest.java b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProviderTest.java index 0ba01840d00..8749e03251d 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProviderTest.java +++ b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabAuthorizingFileContentProviderTest.java @@ -11,27 +11,60 @@ */ package org.eclipse.che.api.factory.server.gitlab; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static java.lang.String.format; +import static java.net.HttpURLConnection.HTTP_MOVED_TEMP; +import static java.net.HttpURLConnection.HTTP_OK; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.github.tomakehurst.wiremock.WireMockServer; +import com.github.tomakehurst.wiremock.client.WireMock; +import com.github.tomakehurst.wiremock.common.Slf4jNotifier; +import java.io.FileNotFoundException; +import java.net.URI; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; +import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; +import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; import org.testng.annotations.Test; @Listeners(MockitoTestNGListener.class) public class GitlabAuthorizingFileContentProviderTest { @Mock private PersonalAccessTokenManager personalAccessTokenManager; + @Mock private URLFetcher urlFetcher; + + private WireMockServer wireMockServer; + private WireMock wireMock; + + @BeforeMethod + public void start() { + wireMockServer = + new WireMockServer(wireMockConfig().notifier(new Slf4jNotifier(false)).dynamicPort()); + wireMockServer.start(); + WireMock.configureFor("localhost", wireMockServer.port()); + wireMock = new WireMock("localhost", wireMockServer.port()); + } + + @AfterMethod + void stop() { + wireMockServer.stop(); + } @Test public void shouldExpandRelativePaths() throws Exception { - URLFetcher urlFetcher = Mockito.mock(URLFetcher.class); GitlabUrl gitlabUrl = new GitlabUrl().withHostName("gitlab.net").withSubGroups("eclipse/che"); FileContentProvider fileContentProvider = new GitlabAuthorizingFileContentProvider(gitlabUrl, urlFetcher, personalAccessTokenManager); @@ -47,7 +80,6 @@ public void shouldExpandRelativePaths() throws Exception { @Test public void shouldPreserveAbsolutePaths() throws Exception { - URLFetcher urlFetcher = Mockito.mock(URLFetcher.class); GitlabUrl gitlabUrl = new GitlabUrl().withHostName("gitlab.net").withSubGroups("eclipse/che"); FileContentProvider fileContentProvider = new GitlabAuthorizingFileContentProvider(gitlabUrl, urlFetcher, personalAccessTokenManager); @@ -59,4 +91,56 @@ public void shouldPreserveAbsolutePaths() throws Exception { fileContentProvider.fetchContent(url); verify(urlFetcher).fetch(eq(url), eq("Bearer my-token")); } + + @Test(expectedExceptions = FileNotFoundException.class) + public void shouldThrowFileNotFoundException() throws Exception { + // given + when(urlFetcher.fetch( + eq( + wireMockServer.url( + "/api/v4/projects/eclipse%2Fche/repository/files/devfile.yaml/raw?ref=HEAD")))) + .thenThrow(new FileNotFoundException()); + when(personalAccessTokenManager.getAndStore(anyString())) + .thenThrow(new UnknownScmProviderException("", "")); + URI uri = URI.create(wireMockServer.url("/")); + GitlabUrl gitlabUrl = + new GitlabUrl() + .withScheme("http") + .withHostName(format("%s:%s", uri.getHost(), uri.getPort())) + .withSubGroups("eclipse/che"); + FileContentProvider fileContentProvider = + new GitlabAuthorizingFileContentProvider(gitlabUrl, urlFetcher, personalAccessTokenManager); + + stubFor(get(urlEqualTo("/eclipse/che")).willReturn(aResponse().withStatus(HTTP_OK))); + + // when + fileContentProvider.fetchContent("devfile.yaml"); + } + + @Test( + expectedExceptions = DevfileException.class, + expectedExceptionsMessageRegExp = "Could not reach devfile at test path") + public void shouldThrowDevfileException() throws Exception { + // given + when(urlFetcher.fetch( + eq( + wireMockServer.url( + "/api/v4/projects/eclipse%2Fche/repository/files/devfile.yaml/raw?ref=HEAD")))) + .thenThrow(new FileNotFoundException("test path")); + when(personalAccessTokenManager.getAndStore(anyString())) + .thenThrow(new UnknownScmProviderException("", "")); + URI uri = URI.create(wireMockServer.url("/")); + GitlabUrl gitlabUrl = + new GitlabUrl() + .withScheme("http") + .withHostName(format("%s:%s", uri.getHost(), uri.getPort())) + .withSubGroups("eclipse/che"); + FileContentProvider fileContentProvider = + new GitlabAuthorizingFileContentProvider(gitlabUrl, urlFetcher, personalAccessTokenManager); + + stubFor(get(urlEqualTo("/eclipse/che")).willReturn(aResponse().withStatus(HTTP_MOVED_TEMP))); + + // when + fileContentProvider.fetchContent("devfile.yaml"); + } } diff --git a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolverTest.java b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolverTest.java index e4afa847d52..a6381aa8f08 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolverTest.java +++ b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabFactoryParametersResolverTest.java @@ -14,7 +14,6 @@ import static java.util.Collections.singletonMap; import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; -import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION; import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.eclipse.che.security.oauth1.OAuthAuthenticationService.ERROR_QUERY_NAME; import static org.mockito.ArgumentMatchers.any; @@ -27,7 +26,6 @@ import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; import static org.testng.Assert.assertTrue; import com.google.common.collect.ImmutableMap; @@ -41,11 +39,8 @@ import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; +import org.eclipse.che.api.factory.shared.dto.ScmInfoDto; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; -import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.MetadataDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.SourceDto; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; @@ -114,22 +109,17 @@ public void shouldGenerateDevfileForFactoryWithNoDevfileOrJson() throws Exceptio String gitlabUrl = "http://gitlab.2mcl.com/test/proj/repo.git"; - FactoryDto computedFactory = generateDevfileFactory(); - - when(urlFactoryBuilder.buildDefaultDevfile(any())).thenReturn(computedFactory.getDevfile()); - when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) .thenReturn(Optional.empty()); Map params = ImmutableMap.of(URL_PARAMETER_NAME, gitlabUrl); // when - FactoryDto factory = (FactoryDto) gitlabFactoryParametersResolver.createFactory(params); + FactoryDevfileV2Dto factory = + (FactoryDevfileV2Dto) gitlabFactoryParametersResolver.createFactory(params); // then - verify(urlFactoryBuilder).buildDefaultDevfile(eq("repo")); - assertEquals(factory, computedFactory); - SourceDto source = factory.getDevfile().getProjects().get(0).getSource(); - assertEquals(source.getLocation(), gitlabUrl); - assertNull(source.getBranch()); + ScmInfoDto scmInfo = factory.getScmInfo(); + assertEquals(scmInfo.getRepositoryUrl(), gitlabUrl); + assertEquals(scmInfo.getBranch(), null); } @Test @@ -137,7 +127,7 @@ public void shouldSetDefaultProjectIntoDevfileIfNotSpecified() throws Exception String gitlabUrl = "http://gitlab.2mcl.com/test/proj/repo/-/tree/foobar"; - FactoryDto computedFactory = generateDevfileFactory(); + FactoryDevfileV2Dto computedFactory = generateDevfileV2Factory(); when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) @@ -145,11 +135,12 @@ public void shouldSetDefaultProjectIntoDevfileIfNotSpecified() throws Exception Map params = ImmutableMap.of(URL_PARAMETER_NAME, gitlabUrl); // when - FactoryDto factory = (FactoryDto) gitlabFactoryParametersResolver.createFactory(params); + FactoryDevfileV2Dto factory = + (FactoryDevfileV2Dto) gitlabFactoryParametersResolver.createFactory(params); // then assertNotNull(factory.getDevfile()); - SourceDto source = factory.getDevfile().getProjects().get(0).getSource(); - assertEquals(source.getLocation(), "http://gitlab.2mcl.com/test/proj/repo.git"); + ScmInfoDto source = factory.getScmInfo(); + assertEquals(source.getRepositoryUrl(), "http://gitlab.2mcl.com/test/proj/repo.git"); assertEquals(source.getBranch(), "foobar"); } @@ -161,7 +152,7 @@ public void shouldCreateFactoryWithoutAuthentication() throws ApiException { ImmutableMap.of(URL_PARAMETER_NAME, gitlabUrl, ERROR_QUERY_NAME, "access_denied"); when(urlFactoryBuilder.createFactoryFromDevfile( any(RemoteFactoryUrl.class), any(), anyMap(), anyBoolean())) - .thenReturn(Optional.of(generateDevfileFactory())); + .thenReturn(Optional.of(generateDevfileV2Factory())); // when gitlabFactoryParametersResolver.createFactory(params); @@ -198,16 +189,6 @@ public void shouldSetScmInfoIntoDevfileV2() throws Exception { assertEquals(scmInfo.getBranch(), "foobar"); } - private FactoryDto generateDevfileFactory() { - return newDto(FactoryDto.class) - .withV(CURRENT_VERSION) - .withSource("repo") - .withDevfile( - newDto(DevfileDto.class) - .withApiVersion(CURRENT_API_VERSION) - .withMetadata(newDto(MetadataDto.class).withName("che"))); - } - private FactoryDevfileV2Dto generateDevfileV2Factory() { return newDto(FactoryDevfileV2Dto.class) .withV(CURRENT_VERSION) diff --git a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcherTest.java b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcherTest.java index 5244f2f7512..926408df4ca 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcherTest.java +++ b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabOAuthTokenFetcherTest.java @@ -38,7 +38,6 @@ import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.subject.Subject; import org.eclipse.che.commons.subject.SubjectImpl; -import org.eclipse.che.inject.ConfigurationException; import org.eclipse.che.security.oauth.OAuthAPI; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; @@ -64,8 +63,7 @@ void start() { WireMock.configureFor("localhost", wireMockServer.port()); wireMock = new WireMock("localhost", wireMockServer.port()); oAuthTokenFetcher = - new GitlabOAuthTokenFetcher( - wireMockServer.url("/"), wireMockServer.url("/"), "http://che.api", oAuthAPI); + new GitlabOAuthTokenFetcher(wireMockServer.url("/"), "http://che.api", oAuthAPI); } @AfterMethod @@ -80,7 +78,7 @@ void stop() { public void shouldThrowExceptionOnInsufficientTokenScopes() throws Exception { Subject subject = new SubjectImpl("Username", "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken("oauthtoken").withScope("api repo"); - when(oAuthAPI.getToken(anyString())).thenReturn(oAuthToken); + when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/api/v4/user")) @@ -106,7 +104,7 @@ public void shouldThrowExceptionOnInsufficientTokenScopes() throws Exception { expectedExceptionsMessageRegExp = "Username is not authorized in gitlab OAuth provider.") public void shouldThrowUnauthorizedExceptionWhenUserNotLoggedIn() throws Exception { Subject subject = new SubjectImpl("Username", "id1", "token", false); - when(oAuthAPI.getToken(anyString())).thenThrow(UnauthorizedException.class); + when(oAuthAPI.getOrRefreshToken(anyString())).thenThrow(UnauthorizedException.class); oAuthTokenFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/")); } @@ -116,7 +114,7 @@ public void shouldReturnToken() throws Exception { Subject subject = new SubjectImpl("Username", "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken("oauthtoken").withScope("api write_repository openid"); - when(oAuthAPI.getToken(anyString())).thenReturn(oAuthToken); + when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/oauth/token/info")) @@ -139,15 +137,6 @@ public void shouldReturnToken() throws Exception { assertNotNull(token); } - @Test( - expectedExceptions = ConfigurationException.class, - expectedExceptionsMessageRegExp = - "GitLab OAuth integration endpoint must be present in registered GitLab endpoints list.") - public void shouldThrowConfigurationExceptionIfOauthEndpointNotInTheList() throws Exception { - new GitlabOAuthTokenFetcher( - wireMockServer.url("/"), "http://foo.bar", "http://che.api", oAuthAPI); - } - @Test( expectedExceptions = ScmCommunicationException.class, expectedExceptionsMessageRegExp = @@ -156,7 +145,7 @@ public void shouldThrowConfigurationExceptionIfOauthEndpointNotInTheList() throw public void shouldThrowScmCommunicationExceptionWhenNoOauthIsConfigured() throws Exception { Subject subject = new SubjectImpl("Username", "id1", "token", false); GitlabOAuthTokenFetcher localFetcher = - new GitlabOAuthTokenFetcher(wireMockServer.url("/"), null, "http://che.api", oAuthAPI); + new GitlabOAuthTokenFetcher(wireMockServer.url("/"), "http://che.api", null); localFetcher.fetchPersonalAccessToken(subject, wireMockServer.url("/")); } @@ -198,7 +187,7 @@ public void shouldValidateOAuthToken() throws Exception { public void shouldThrowUnauthorizedExceptionIfTokenIsNotValid() throws Exception { Subject subject = new SubjectImpl("Username", "id1", "token", false); OAuthToken oAuthToken = newDto(OAuthToken.class).withToken("token").withScope(""); - when(oAuthAPI.getToken(anyString())).thenReturn(oAuthToken); + when(oAuthAPI.getOrRefreshToken(anyString())).thenReturn(oAuthToken); stubFor( get(urlEqualTo("/api/v4/user")) .withHeader(HttpHeaders.AUTHORIZATION, equalTo("token token")) diff --git a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserTest.java b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserTest.java index e8022fd043c..28e7159a05f 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserTest.java +++ b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUrlParserTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -58,7 +58,7 @@ public void prepare() { public void setUp() { gitlabUrlParser = new GitlabUrlParser( - "https://gitlab1.com,https://gitlab.foo.xxx", + "https://gitlab1.com", devfileFilenamesProvider, mock(PersonalAccessTokenManager.class)); } @@ -126,14 +126,14 @@ public Object[][] urls() { return new Object[][] { {"https://gitlab1.com/user/project/test1.git"}, {"https://gitlab1.com/user/project1.git"}, - {"https://gitlab.foo.xxx/scm/project/test1.git"}, + {"https://gitlab1.com/scm/project/test1.git"}, {"https://gitlab1.com/user/project/"}, {"https://gitlab1.com/user/project/repo/"}, {"https://gitlab1.com/user/project/-/tree/master/"}, {"https://gitlab1.com/user/project/repo/-/tree/master/subfolder"}, {"git@gitlab1.com:user/project/test1.git"}, {"git@gitlab1.com:user/project1.git"}, - {"git@gitlab.foo.xxx:scm/project/test1.git"}, + {"git@gitlab1.com:scm/project/test1.git"}, {"git@gitlab1.com:user/project/"}, {"git@gitlab1.com:user/project/repo/"}, }; diff --git a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcherTest.java b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcherTest.java index 5e16f9d0608..572fd70f50e 100644 --- a/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcherTest.java +++ b/wsmaster/che-core-api-factory-gitlab/src/test/java/org/eclipse/che/api/factory/server/gitlab/GitlabUserDataFetcherTest.java @@ -27,6 +27,7 @@ import com.github.tomakehurst.wiremock.client.WireMock; import com.github.tomakehurst.wiremock.common.Slf4jNotifier; import com.google.common.net.HttpHeaders; +import java.lang.reflect.Field; import java.util.Optional; import org.eclipse.che.api.factory.server.scm.GitUserData; import org.eclipse.che.api.factory.server.scm.PersonalAccessToken; @@ -60,10 +61,7 @@ void start() { wireMock = new WireMock("localhost", wireMockServer.port()); gitlabUserDataFetcher = new GitlabUserDataFetcher( - wireMockServer.url("/"), - wireMockServer.url("/"), - "http://che.api", - personalAccessTokenManager); + wireMockServer.url("/"), "http://che.api", personalAccessTokenManager); stubFor( get(urlEqualTo("/api/v4/user")) @@ -91,4 +89,17 @@ public void shouldFetchGitUserData() throws Exception { assertEquals(gitUserData.getScmUsername(), "John Smith"); assertEquals(gitUserData.getScmUserEmail(), "john@example.com"); } + + @Test + public void shouldSetSAASUrlAsDefault() throws Exception { + gitlabUserDataFetcher = + new GitlabUserDataFetcher(null, "http://che.api", personalAccessTokenManager); + + Field serverUrlField = + gitlabUserDataFetcher.getClass().getSuperclass().getDeclaredField("serverUrl"); + serverUrlField.setAccessible(true); + String serverUrl = (String) serverUrlField.get(gitlabUserDataFetcher); + + assertEquals(serverUrl, "https://gitlab.com"); + } } diff --git a/wsmaster/che-core-api-factory-shared/pom.xml b/wsmaster/che-core-api-factory-shared/pom.xml index d02750c1aac..d8b8f264a39 100644 --- a/wsmaster/che-core-api-factory-shared/pom.xml +++ b/wsmaster/che-core-api-factory-shared/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-factory-shared jar @@ -39,10 +39,6 @@ org.eclipse.che.core che-core-api-dto - - org.eclipse.che.core - che-core-api-workspace-shared - org.eclipse.che.core che-core-api-model diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/Constants.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/Constants.java index db111911877..16601b34758 100644 --- a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/Constants.java +++ b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/Constants.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2018 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -11,6 +11,8 @@ */ package org.eclipse.che.api.factory.shared; +import java.util.Map; + /** * Constants for Factory API. * @@ -29,5 +31,7 @@ public final class Constants { // url factory parameter names public static final String URL_PARAMETER_NAME = "url"; + public static final Map DEFAULT_DEVFILE = Map.of("schemaVersion", "2.3.0"); + private Constants() {} } diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryDto.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryDto.java deleted file mode 100644 index 8d845467899..00000000000 --- a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryDto.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2012-2021 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.api.factory.shared.dto; - -import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation.OPTIONAL; - -import java.util.List; -import org.eclipse.che.api.core.factory.FactoryParameter; -import org.eclipse.che.api.core.model.factory.Factory; -import org.eclipse.che.api.core.rest.shared.dto.Hyperlinks; -import org.eclipse.che.api.core.rest.shared.dto.Link; -import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; -import org.eclipse.che.dto.shared.DTO; - -/** - * Factory of version 4.0. - * - *

This 'implementation' of {@link FactoryMetaDto} is used for Devfile v1. - * - * @author Max Shaposhnik - */ -@DTO -public interface FactoryDto extends FactoryMetaDto, Factory, Hyperlinks { - - @Override - default FactoryMetaDto acceptVisitor(FactoryVisitor visitor) { - return visitor.visit(this); - } - - @FactoryParameter(obligation = OPTIONAL) - DevfileDto getDevfile(); - - void setDevfile(DevfileDto devfileDto); - - FactoryDto withDevfile(DevfileDto devfileDto); - - FactoryDto withV(String v); - - @Override - FactoryDto withName(String name); - - @Override - FactoryDto withPolicies(PoliciesDto policies); - - @Override - FactoryDto withIde(IdeDto ide); - - @Override - FactoryDto withId(String id); - - @Override - FactoryDto withSource(String source); - - @Override - FactoryDto withCreator(AuthorDto creator); - - @Override - FactoryDto withLinks(List links); - - /** because factory DTO may have devfile, in that case, workspace may be optional */ - @Override - @FactoryParameter(obligation = OPTIONAL) - WorkspaceConfigDto getWorkspace(); - - void setWorkspace(WorkspaceConfigDto workspace); - - FactoryDto withWorkspace(WorkspaceConfigDto workspace); -} diff --git a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryVisitor.java b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryVisitor.java index 3d9eead20c4..e7d9d005706 100644 --- a/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryVisitor.java +++ b/wsmaster/che-core-api-factory-shared/src/main/java/org/eclipse/che/api/factory/shared/dto/FactoryVisitor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -17,17 +17,6 @@ */ public interface FactoryVisitor { - /** - * Visit factory with devfile v1. - * - *

Implementation should update given factory with needed changes and give it back. - * - * @param factoryDto factory to visit - * @return updated factory - */ - @Deprecated - FactoryDto visit(FactoryDto factoryDto); - /** * Visit factory with devfile v2. * diff --git a/wsmaster/che-core-api-factory/pom.xml b/wsmaster/che-core-api-factory/pom.xml index f5db2c62092..1bcf0b49a10 100644 --- a/wsmaster/che-core-api-factory/pom.xml +++ b/wsmaster/che-core-api-factory/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-factory jar diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/ApiExceptionMapper.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/ApiExceptionMapper.java index 122c5b4d34f..f119ea40230 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/ApiExceptionMapper.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/ApiExceptionMapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -11,11 +11,16 @@ */ package org.eclipse.che.api.factory.server; +import static com.google.common.base.Strings.isNullOrEmpty; +import static org.eclipse.che.dto.server.DtoFactory.newDto; + +import java.util.Collections; import java.util.Map; import org.eclipse.che.api.core.ApiException; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.core.UnauthorizedException; +import org.eclipse.che.api.core.rest.shared.dto.ExtendedError; import org.eclipse.che.api.factory.server.scm.exception.ScmCommunicationException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; @@ -48,6 +53,16 @@ public static ApiException toApiException(ScmUnauthorizedException scmUnauthoriz + scmUnauthorizedException.getMessage()); } + public static ApiException toApiException(ScmCommunicationException scmCommunicationException) { + ApiException apiException = getApiException(scmCommunicationException); + return (apiException != null) + ? apiException + : new ServerException( + "Error occurred during SCM communication." + + "Cause: " + + scmCommunicationException.getMessage()); + } + public static ApiException toApiException( DevfileException devfileException, DevfileLocation location) { ApiException cause = getApiException(devfileException.getCause()); @@ -70,15 +85,28 @@ private static ApiException getApiException(Throwable throwable) { "oauth_version", scmCause.getOauthVersion(), "oauth_provider", scmCause.getOauthProvider(), "oauth_authentication_url", scmCause.getAuthenticateUrl())); - } else if (throwable instanceof UnknownScmProviderException) { - return new ServerException( - "Provided location is unknown or misconfigured on the server side. Error message: " - + throwable.getMessage()); - } else if (throwable instanceof ScmCommunicationException) { - return new ServerException( - "There is an error happened when communicate with SCM server. Error message: " - + throwable.getMessage()); + } else { + if (throwable instanceof UnknownScmProviderException) { + return new ServerException( + appendErrorMessage( + "Provided location is unknown or misconfigured on the server side", + throwable.getMessage())); + } else if (throwable instanceof ScmCommunicationException) { + return new ServerException( + newDto(ExtendedError.class) + .withMessage( + appendErrorMessage( + "Error occurred during SCM communication.", throwable.getMessage())) + .withErrorCode(404) + .withAttributes( + Collections.singletonMap( + "provider", ((ScmCommunicationException) throwable).getProvider()))); + } } return null; } + + private static String appendErrorMessage(String message, String errorMessage) { + return message + (isNullOrEmpty(errorMessage) ? "" : " Error message: " + errorMessage); + } } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/BaseFactoryParameterResolver.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/BaseFactoryParameterResolver.java index b5b80980449..08efe28e43e 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/BaseFactoryParameterResolver.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/BaseFactoryParameterResolver.java @@ -13,6 +13,7 @@ import static java.util.stream.Collectors.toMap; import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION; +import static org.eclipse.che.api.factory.shared.Constants.DEFAULT_DEVFILE; import static org.eclipse.che.dto.server.DtoFactory.newDto; import java.util.Collections; @@ -24,7 +25,7 @@ import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; +import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.factory.shared.dto.FactoryVisitor; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; @@ -60,7 +61,12 @@ protected FactoryMetaDto createFactory( contentProvider, extractOverrideParams(factoryParameters), getSkipAuthorisation(factoryParameters)) - .orElseGet(() -> newDto(FactoryDto.class).withV(CURRENT_VERSION).withSource("repo")) + .orElseGet( + () -> + newDto(FactoryDevfileV2Dto.class) + .withDevfile(DEFAULT_DEVFILE) + .withV(CURRENT_VERSION) + .withSource("repo")) .acceptVisitor(factoryVisitor); } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/DtoConverter.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/DtoConverter.java index cf9b5972260..526ba6c18e7 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/DtoConverter.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/DtoConverter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2021 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -17,7 +17,6 @@ import java.util.List; import org.eclipse.che.api.core.model.factory.Action; import org.eclipse.che.api.core.model.factory.Author; -import org.eclipse.che.api.core.model.factory.Factory; import org.eclipse.che.api.core.model.factory.Ide; import org.eclipse.che.api.core.model.factory.OnAppClosed; import org.eclipse.che.api.core.model.factory.OnAppLoaded; @@ -25,7 +24,6 @@ import org.eclipse.che.api.core.model.factory.Policies; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.factory.shared.dto.AuthorDto; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.factory.shared.dto.IdeActionDto; import org.eclipse.che.api.factory.shared.dto.IdeDto; import org.eclipse.che.api.factory.shared.dto.OnAppClosedDto; @@ -40,29 +38,6 @@ */ public final class DtoConverter { - public static FactoryDto asDto(Factory factory, User user) { - final FactoryDto factoryDto = - newDto(FactoryDto.class) - .withId(factory.getId()) - .withName(factory.getName()) - .withV(factory.getV()); - - if (factory.getWorkspace() != null) { - factoryDto.withWorkspace( - org.eclipse.che.api.workspace.server.DtoConverter.asDto(factory.getWorkspace())); - } - if (factory.getCreator() != null) { - factoryDto.withCreator(asDto(factory.getCreator(), user)); - } - if (factory.getIde() != null) { - factoryDto.withIde(asDto(factory.getIde())); - } - if (factory.getPolicies() != null) { - factoryDto.withPolicies(asDto(factory.getPolicies())); - } - return factoryDto; - } - public static IdeDto asDto(Ide ide) { final IdeDto ideDto = newDto(IdeDto.class); final OnAppClosed onAppClosed = ide.getOnAppClosed(); diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryCreateValidator.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryCreateValidator.java deleted file mode 100644 index 63b191fc382..00000000000 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryCreateValidator.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2012-2018 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.api.factory.server; - -import org.eclipse.che.api.core.BadRequestException; -import org.eclipse.che.api.core.ForbiddenException; -import org.eclipse.che.api.core.NotFoundException; -import org.eclipse.che.api.core.ServerException; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; - -/** - * Interface for validations of factory creation stage. - * - * @author Alexander Garagatyi - */ -public interface FactoryCreateValidator { - - /** - * Validates factory object on creation stage. Implementation should throw exception if factory - * object is invalid. - * - * @param factory factory object to validate - * @throws BadRequestException in case if factory is not valid - * @throws ServerException when any server error occurs - * @throws ForbiddenException when user have no access rights for factory creation - */ - void validateOnCreate(FactoryDto factory) - throws BadRequestException, ServerException, ForbiddenException, NotFoundException; -} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryEditValidator.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryEditValidator.java deleted file mode 100644 index e99da51e50c..00000000000 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryEditValidator.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2012-2018 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.api.factory.server; - -import org.eclipse.che.api.core.ForbiddenException; -import org.eclipse.che.api.core.ServerException; -import org.eclipse.che.api.core.model.factory.Factory; - -/** - * This validator ensures that a factory can be edited by a user that has the associated rights - * (author or account owner) - * - * @author Florent Benoit - */ -public interface FactoryEditValidator { - - /** - * Validates given factory by checking the current user is granted to edit the factory. - * - * @param factory factory object to validate - * @throws ForbiddenException when the current user is not granted to edit the factory - * @throws ServerException when any other error occurs - */ - void validate(Factory factory) throws ForbiddenException, ServerException; -} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryService.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryService.java index 2070ff1117b..86aef36214f 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryService.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/FactoryService.java @@ -152,17 +152,21 @@ public void refreshToken(@Parameter(description = "Factory url") @QueryParam("ur factoryParametersResolverHolder.getFactoryParametersResolver( singletonMap(URL_PARAMETER_NAME, url)); if (!authorisationRequestManager.isStored(factoryParametersResolver.getProviderName())) { - personalAccessTokenManager.getAndStore( - // get the provider URL from the factory URL - factoryParametersResolver.parseFactoryUrl(url).getProviderUrl()); + String scmServerUrl = factoryParametersResolver.parseFactoryUrl(url).getProviderUrl(); + if (Boolean.parseBoolean(System.getenv("CHE_FORCE_REFRESH_PERSONAL_ACCESS_TOKEN"))) { + personalAccessTokenManager.forceRefreshPersonalAccessToken(scmServerUrl); + } else { + personalAccessTokenManager.getAndStore(scmServerUrl); + } } - } catch (ScmCommunicationException - | ScmConfigurationPersistenceException - | UnknownScmProviderException - | UnsatisfiedScmPreconditionException e) { + } catch (ScmConfigurationPersistenceException | UnsatisfiedScmPreconditionException e) { throw new ApiException(e); } catch (ScmUnauthorizedException e) { throw toApiException(e); + } catch (ScmCommunicationException e) { + throw toApiException(e); + } catch (UnknownScmProviderException e) { + // ignore the exception as it is not a problem if the provider from the given URL is unknown } } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/RawDevfileUrlFactoryParameterResolver.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/RawDevfileUrlFactoryParameterResolver.java index 1896fc699b5..c26e81c490b 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/RawDevfileUrlFactoryParameterResolver.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/RawDevfileUrlFactoryParameterResolver.java @@ -45,7 +45,8 @@ public class RawDevfileUrlFactoryParameterResolver extends BaseFactoryParameterR implements FactoryParametersResolver { private static final String PROVIDER_NAME = "raw-devfile-url"; - private static final Pattern PATTERN = Pattern.compile("^https?://.*\\.ya?ml(\\?token=.*)?$"); + private static final Pattern PATTERN = + Pattern.compile("^https?://.*\\.ya?ml((\\?token=.*)|(\\?at=refs/heads/.*))?$"); protected final URLFactoryBuilder urlFactoryBuilder; protected final URLFetcher urlFetcher; diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/builder/FactoryBuilder.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/builder/FactoryBuilder.java deleted file mode 100644 index 526abd6780a..00000000000 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/builder/FactoryBuilder.java +++ /dev/null @@ -1,314 +0,0 @@ -/* - * Copyright (c) 2012-2018 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.api.factory.server.builder; - -import static org.eclipse.che.api.core.factory.FactoryParameter.Obligation; -import static org.eclipse.che.api.core.factory.FactoryParameter.Version; - -import com.google.common.base.CaseFormat; -import java.io.IOException; -import java.io.InputStream; -import java.io.Reader; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import javax.inject.Inject; -import javax.inject.Singleton; -import org.eclipse.che.api.core.ApiException; -import org.eclipse.che.api.core.ConflictException; -import org.eclipse.che.api.core.factory.FactoryParameter; -import org.eclipse.che.api.core.model.workspace.config.ProjectConfig; -import org.eclipse.che.api.core.model.workspace.config.SourceStorage; -import org.eclipse.che.api.factory.server.FactoryConstants; -import org.eclipse.che.api.factory.server.LegacyConverter; -import org.eclipse.che.api.factory.server.ValueHelper; -import org.eclipse.che.api.factory.server.impl.SourceStorageParametersValidator; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; -import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; -import org.eclipse.che.dto.server.DtoFactory; -import org.eclipse.che.dto.shared.DTO; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Tool to easy convert Factory object to json and vise versa. Also it provides factory parameters - * compatibility. - * - * @author Sergii Kabashniuk - * @author Alexander Garagatyi - */ -@Singleton -public class FactoryBuilder { - private static final Logger LOG = LoggerFactory.getLogger(FactoryBuilder.class); - - /** List contains all possible implementation of factory legacy converters. */ - static final List LEGACY_CONVERTERS; - - static { - List l = new ArrayList<>(1); - l.add(factory -> {}); - LEGACY_CONVERTERS = Collections.unmodifiableList(l); - } - - private final SourceStorageParametersValidator sourceStorageParametersValidator; - - @Inject - public FactoryBuilder(SourceStorageParametersValidator sourceStorageParametersValidator) { - this.sourceStorageParametersValidator = sourceStorageParametersValidator; - } - - /** - * Build factory from json and validate its compatibility. - * - * @param json - json Reader from encoded factory. - * @return - Factory object represented by given factory json. - */ - public FactoryDto build(Reader json) throws IOException, ApiException { - FactoryDto factory = DtoFactory.getInstance().createDtoFromJson(json, FactoryDto.class); - checkValid(factory); - return factory; - } - - /** - * Build factory from json and validate its compatibility. - * - * @param json - json string from encoded factory. - * @return - Factory object represented by given factory json. - */ - public FactoryDto build(String json) throws ApiException { - FactoryDto factory = DtoFactory.getInstance().createDtoFromJson(json, FactoryDto.class); - checkValid(factory); - return factory; - } - - /** - * Build factory from json and validate its compatibility. - * - * @param json - json InputStream from encoded factory. - * @return - Factory object represented by given factory json. - */ - public FactoryDto build(InputStream json) throws IOException, ConflictException { - FactoryDto factory = DtoFactory.getInstance().createDtoFromJson(json, FactoryDto.class); - checkValid(factory); - return factory; - } - - /** - * Validate factory compatibility at creation time. - * - * @param factory - factory object to validate - * @throws ConflictException - */ - public void checkValid(FactoryDto factory) throws ConflictException { - checkValid(factory, false); - } - - /** - * Validate factory compatibility. - * - * @param factory - factory object to validate - * @param isUpdate - indicates is validation performed on update time. Set-by-server variables are - * allowed during update. - * @throws ConflictException - */ - public void checkValid(FactoryDto factory, boolean isUpdate) throws ConflictException { - if (null == factory) { - throw new ConflictException(FactoryConstants.UNPARSABLE_FACTORY_MESSAGE); - } - if (factory.getV() == null) { - throw new ConflictException(FactoryConstants.INVALID_VERSION_MESSAGE); - } - - Version v; - try { - v = Version.fromString(factory.getV()); - } catch (IllegalArgumentException e) { - throw new ConflictException(FactoryConstants.INVALID_VERSION_MESSAGE); - } - - Class usedFactoryVersionMethodProvider; - switch (v) { - case V4_0: - usedFactoryVersionMethodProvider = FactoryDto.class; - break; - default: - throw new ConflictException(FactoryConstants.INVALID_VERSION_MESSAGE); - } - validateCompatibility( - factory, null, FactoryDto.class, usedFactoryVersionMethodProvider, v, "", isUpdate); - } - - /** - * Convert factory of given version to the latest factory format. - * - * @param factory - given factory. - * @return - factory in latest format. - * @throws org.eclipse.che.api.core.ApiException - */ - public FactoryDto convertToLatest(FactoryDto factory) throws ApiException { - FactoryDto resultFactory = DtoFactory.getInstance().clone(factory).withV("4.0"); - for (LegacyConverter converter : LEGACY_CONVERTERS) { - converter.convert(resultFactory); - } - return resultFactory; - } - - /** - * Validate compatibility of factory parameters. - * - * @param object - object to validate factory parameters - * @param parent - parent object - * @param methodsProvider - class that provides methods with {@link - * org.eclipse.che.api.core.factory.FactoryParameter} annotations - * @param allowedMethodsProvider - class that provides allowed methods - * @param version - version of factory - * @param parentName - parent parameter queryParameterName - * @throws org.eclipse.che.api.core.ConflictException - */ - void validateCompatibility( - Object object, - Object parent, - Class methodsProvider, - Class allowedMethodsProvider, - Version version, - String parentName, - boolean isUpdate) - throws ConflictException { - // validate source - if (SourceStorageDto.class.equals(methodsProvider) && !hasSubprojectInPath(parent)) { - sourceStorageParametersValidator.validate((SourceStorage) object, version); - } - - // get all methods recursively - for (Method method : methodsProvider.getMethods()) { - FactoryParameter factoryParameter = method.getAnnotation(FactoryParameter.class); - // is it factory parameter - - if (factoryParameter == null) { - continue; - } - String fullName = - (parentName.isEmpty() ? "" : (parentName + ".")) - + CaseFormat.UPPER_CAMEL.to( - CaseFormat.LOWER_CAMEL, - method.getName().startsWith("is") - ? method.getName().substring(2) - : method.getName().substring(3).toLowerCase()); - // check that field is set - Object parameterValue; - try { - parameterValue = method.invoke(object); - } catch (IllegalAccessException | InvocationTargetException | IllegalArgumentException e) { - // should never happen - LOG.error(e.getLocalizedMessage(), e); - throw new ConflictException(FactoryConstants.INVALID_PARAMETER_MESSAGE); - } - - // if value is null or empty collection or default value for primitives - if (ValueHelper.isEmpty(parameterValue)) { - // field must not be a mandatory, unless it's ignored or deprecated or doesn't suit to the - // version - if (Obligation.MANDATORY.equals(factoryParameter.obligation()) - && factoryParameter.deprecatedSince().compareTo(version) > 0 - && factoryParameter.ignoredSince().compareTo(version) > 0 - && method.getDeclaringClass().isAssignableFrom(allowedMethodsProvider)) { - throw new ConflictException( - String.format(FactoryConstants.MISSING_MANDATORY_MESSAGE, fullName)); - } - } else if (!method.getDeclaringClass().isAssignableFrom(allowedMethodsProvider)) { - throw new ConflictException( - String.format( - FactoryConstants.PARAMETRIZED_INVALID_PARAMETER_MESSAGE, fullName, version)); - } else { - // is parameter deprecated - if (factoryParameter.deprecatedSince().compareTo(version) <= 0 - || (!isUpdate && factoryParameter.setByServer())) { - throw new ConflictException( - String.format( - FactoryConstants.PARAMETRIZED_INVALID_PARAMETER_MESSAGE, fullName, version)); - } - - // use recursion if parameter is DTO object - if (method.getReturnType().isAnnotationPresent(DTO.class)) { - // validate inner objects such Git ot ProjectAttributes - validateCompatibility( - parameterValue, - object, - method.getReturnType(), - method.getReturnType(), - version, - fullName, - isUpdate); - } else if (Map.class.isAssignableFrom(method.getReturnType())) { - Type tp = ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[1]; - - Class secMapParamClass = - (tp instanceof ParameterizedType) - ? (Class) ((ParameterizedType) tp).getRawType() - : (Class) tp; - if (!String.class.equals(secMapParamClass) && !List.class.equals(secMapParamClass)) { - if (secMapParamClass.isAnnotationPresent(DTO.class)) { - Map map = (Map) parameterValue; - for (Map.Entry entry : map.entrySet()) { - validateCompatibility( - entry.getValue(), - object, - secMapParamClass, - secMapParamClass, - version, - fullName + "." + entry.getKey(), - isUpdate); - } - } else { - throw new RuntimeException("This type of fields is not supported by factory."); - } - } - } else if (List.class.isAssignableFrom(method.getReturnType())) { - Type tp = ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0]; - - Class secListParamClass = - (tp instanceof ParameterizedType) - ? (Class) ((ParameterizedType) tp).getRawType() - : (Class) tp; - if (!String.class.equals(secListParamClass) && !List.class.equals(secListParamClass)) { - if (secListParamClass.isAnnotationPresent(DTO.class)) { - List list = (List) parameterValue; - for (int i = 0; i < list.size(); i++) { - validateCompatibility( - list.get(i), - object, - secListParamClass, - secListParamClass, - version, - fullName + "[" + i + "]", - isUpdate); - } - } else { - throw new RuntimeException("This type of fields is not supported by factory."); - } - } - } - } - } - } - - private boolean hasSubprojectInPath(Object parent) { - return parent != null - && ProjectConfig.class.isAssignableFrom(parent.getClass()) - && ((ProjectConfig) parent).getPath().indexOf('/', 1) != -1; - } -} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidator.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidator.java index f7cb3a08c72..5f7876dc9ed 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidator.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2021 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -15,22 +15,18 @@ import static java.lang.String.format; import static java.lang.System.currentTimeMillis; -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.regex.Pattern; import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.factory.server.FactoryConstants; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.factory.shared.dto.IdeActionDto; import org.eclipse.che.api.factory.shared.dto.IdeDto; import org.eclipse.che.api.factory.shared.dto.OnAppLoadedDto; import org.eclipse.che.api.factory.shared.dto.OnProjectsLoadedDto; import org.eclipse.che.api.factory.shared.dto.PoliciesDto; -import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; /** * Validates values of factory parameters. @@ -42,52 +38,6 @@ public abstract class FactoryBaseValidator { private static final Pattern PROJECT_NAME_VALIDATOR = Pattern.compile("^[\\\\\\w\\\\\\d]+[\\\\\\w\\\\\\d_.-]*$"); - /** - * Validates source parameter of factory. - * - * @param factory factory to validate - * @throws BadRequestException when source projects in the factory is invalid - */ - protected void validateProjects(FactoryDto factory) throws BadRequestException { - for (ProjectConfigDto project : factory.getWorkspace().getProjects()) { - final String projectName = project.getName(); - if (null != projectName && !PROJECT_NAME_VALIDATOR.matcher(projectName).matches()) { - throw new BadRequestException( - "Project name must contain only Latin letters, " - + "digits or these following special characters -._."); - } - - if (project.getPath().indexOf('/', 1) == -1) { - - if (project.getSource() == null) { - throw new BadRequestException( - format(FactoryConstants.MISSING_MANDATORY_MESSAGE, "project.source")); - } - - final String location = project.getSource().getLocation(); - final String parameterLocationName = "project.source.location"; - - if (isNullOrEmpty(location)) { - throw new BadRequestException( - format( - FactoryConstants.PARAMETRIZED_ILLEGAL_PARAMETER_VALUE_MESSAGE, - parameterLocationName, - location)); - } - - try { - URLDecoder.decode(location, "UTF-8"); - } catch (IllegalArgumentException | UnsupportedEncodingException e) { - throw new BadRequestException( - format( - FactoryConstants.PARAMETRIZED_ILLEGAL_PARAMETER_VALUE_MESSAGE, - parameterLocationName, - location)); - } - } - } - } - /** * Validates that factory can be used at present time (used on accept) * @@ -114,36 +64,6 @@ protected void validateCurrentTimeBetweenSinceUntil(FactoryMetaDto factory) } } - /** - * Validates correct valid since and until times are used (on factory creation) - * - * @param factory factory to validate - * @throws BadRequestException if since date greater or equal than until date - * @throws BadRequestException if since date less than current date - * @throws BadRequestException if until date less than current date - */ - protected void validateCurrentTimeAfterSinceUntil(FactoryDto factory) throws BadRequestException { - final PoliciesDto policies = factory.getPolicies(); - if (policies == null) { - return; - } - - final Long since = policies.getSince() == null ? 0L : policies.getSince(); - final Long until = policies.getUntil() == null ? 0L : policies.getUntil(); - - if (since != 0 && until != 0 && since >= until) { - throw new BadRequestException(FactoryConstants.INVALID_SINCEUNTIL_MESSAGE); - } - - if (since != 0 && currentTimeMillis() > since) { - throw new BadRequestException(FactoryConstants.INVALID_SINCE_MESSAGE); - } - - if (until != 0 && currentTimeMillis() > until) { - throw new BadRequestException(FactoryConstants.INVALID_UNTIL_MESSAGE); - } - } - /** * Validates IDE actions * diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryCreateValidatorImpl.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryCreateValidatorImpl.java deleted file mode 100644 index ce6a9a10b52..00000000000 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryCreateValidatorImpl.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2012-2018 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.api.factory.server.impl; - -import javax.inject.Inject; -import javax.inject.Singleton; -import org.eclipse.che.api.core.BadRequestException; -import org.eclipse.che.api.core.ForbiddenException; -import org.eclipse.che.api.core.NotFoundException; -import org.eclipse.che.api.core.ServerException; -import org.eclipse.che.api.core.ValidationException; -import org.eclipse.che.api.factory.server.FactoryCreateValidator; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; -import org.eclipse.che.api.workspace.server.WorkspaceValidator; - -/** Factory creation stage validator. */ -@Singleton -public class FactoryCreateValidatorImpl extends FactoryBaseValidator - implements FactoryCreateValidator { - private WorkspaceValidator workspaceConfigValidator; - - @Inject - public FactoryCreateValidatorImpl(WorkspaceValidator workspaceConfigValidator) { - this.workspaceConfigValidator = workspaceConfigValidator; - } - - @Override - public void validateOnCreate(FactoryDto factory) - throws BadRequestException, ServerException, ForbiddenException, NotFoundException { - validateProjects(factory); - validateCurrentTimeAfterSinceUntil(factory); - validateProjectActions(factory); - try { - workspaceConfigValidator.validateConfig(factory.getWorkspace()); - } catch (ValidationException x) { - throw new BadRequestException(x.getMessage()); - } - } -} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryEditValidatorImpl.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryEditValidatorImpl.java deleted file mode 100644 index 2ef3b26d182..00000000000 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/impl/FactoryEditValidatorImpl.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2012-2018 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.api.factory.server.impl; - -import static java.lang.String.format; - -import javax.inject.Singleton; -import org.eclipse.che.api.core.ForbiddenException; -import org.eclipse.che.api.core.ServerException; -import org.eclipse.che.api.core.model.factory.Author; -import org.eclipse.che.api.core.model.factory.Factory; -import org.eclipse.che.api.factory.server.FactoryEditValidator; -import org.eclipse.che.commons.env.EnvironmentContext; - -/** - * This validator ensures that a factory can be edited by a user that has the associated rights - * (author or account owner) - * - * @author Florent Benoit - */ -@Singleton -public class FactoryEditValidatorImpl implements FactoryEditValidator { - - /** - * Validates given factory by checking the current user is granted to edit the factory - * - * @param factory factory object to validate - * @throws ForbiddenException occurs if the current user is not granted to edit the factory - * @throws ServerException when any server error occurs - */ - @Override - public void validate(Factory factory) throws ForbiddenException, ServerException { - // Checks if there is an author from the factory (It may be missing for some old factories) - final Author author = factory.getCreator(); - if (author == null || author.getUserId() == null) { - throw new ServerException( - format( - "Invalid factory without author stored. Please contact the support about the factory ID '%s'", - factory.getId())); - } - // ensure user has the correct permissions - final String userId = EnvironmentContext.getCurrent().getSubject().getUserId(); - if (!author.getUserId().equals(userId)) { - throw new ForbiddenException( - format("You are not authorized for the factory '%s'", factory.getId())); - } - } -} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AbstractGitUserDataFetcher.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AbstractGitUserDataFetcher.java index 8b1b94d27a9..21d92a937a8 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AbstractGitUserDataFetcher.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/AbstractGitUserDataFetcher.java @@ -56,11 +56,13 @@ public GitUserData fetchGitUserData() } protected abstract GitUserData fetchGitUserDataWithOAuthToken(String token) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException; + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException; protected abstract GitUserData fetchGitUserDataWithPersonalAccessToken( PersonalAccessToken personalAccessToken) - throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException; + throws ScmItemNotFoundException, ScmCommunicationException, ScmBadRequestException, + ScmUnauthorizedException; protected abstract String getLocalAuthenticateUrl(); } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenFetcher.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenFetcher.java index 70f8bd0a706..0abd3e095cb 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenFetcher.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenFetcher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -38,6 +38,17 @@ public interface PersonalAccessTokenFetcher { PersonalAccessToken fetchPersonalAccessToken(Subject cheUser, String scmServerUrl) throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException; + /** + * Refresh a PersonalAccessToken. + * + * @throws ScmUnauthorizedException - in case if user are not authorized che server to create new + * token. Further user interaction is needed before calling next time this method. + * @throws ScmCommunicationException - Some unexpected problem occurred during communication with + * scm provider. + */ + PersonalAccessToken refreshPersonalAccessToken(Subject cheUser, String scmServerUrl) + throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException; + /** * Checks whether the provided personal access token is valid and has expected scope of * permissions. @@ -64,6 +75,8 @@ Optional isValid(PersonalAccessToken personalAccessToken) * the token has expected scope of permissions, false if the token scopes does not match the * expected ones. Empty optional if {@link PersonalAccessTokenFetcher} is not able to confirm * or deny that token is valid. + * @throws ScmCommunicationException - problem occurred during communication with SCM server. */ - Optional> isValid(PersonalAccessTokenParams params); + Optional> isValid(PersonalAccessTokenParams params) + throws ScmCommunicationException; } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenManager.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenManager.java index 7e6572a8222..dab000515a5 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenManager.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/PersonalAccessTokenManager.java @@ -48,9 +48,10 @@ PersonalAccessToken fetchAndSave(Subject cheUser, String scmServerUrl) * @return personal access token * @throws ScmConfigurationPersistenceException - problem occurred during communication with * permanent storage. + * @throws ScmCommunicationException - problem occurred during communication with SCM server. */ Optional get(Subject cheUser, String scmServerUrl) - throws ScmConfigurationPersistenceException; + throws ScmConfigurationPersistenceException, ScmCommunicationException; /** * Gets {@link PersonalAccessToken} from permanent storage. @@ -79,10 +80,11 @@ PersonalAccessToken get(String scmServerUrl) * @return personal access token * @throws ScmConfigurationPersistenceException - problem occurred during communication with * permanent storage. + * @throws ScmCommunicationException - problem occurred during communication with SCM server. */ Optional get( Subject cheUser, String oAuthProviderName, @Nullable String scmServerUrl) - throws ScmConfigurationPersistenceException; + throws ScmConfigurationPersistenceException, ScmCommunicationException; /** * Gets {@link PersonalAccessToken} from permanent storage. If the token is not found try to fetch @@ -95,6 +97,11 @@ PersonalAccessToken getAndStore(String scmServerUrl) UnknownScmProviderException, UnsatisfiedScmPreconditionException, ScmUnauthorizedException; + /** Refresh a personal access token. */ + void forceRefreshPersonalAccessToken(String scmServerUrl) + throws UnsatisfiedScmPreconditionException, ScmConfigurationPersistenceException, + ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException; + /** * Set or update git-credentials with {@link PersonalAccessToken} from permanent storage. * diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/ScmPersonalAccessTokenFetcher.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/ScmPersonalAccessTokenFetcher.java index d24b8be5b18..cb863226f78 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/ScmPersonalAccessTokenFetcher.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/ScmPersonalAccessTokenFetcher.java @@ -33,6 +33,19 @@ public ScmPersonalAccessTokenFetcher( this.personalAccessTokenFetchers = personalAccessTokenFetchers; } + public PersonalAccessToken refreshPersonalAccessToken(Subject cheUser, String scmServerUrl) + throws ScmUnauthorizedException, ScmCommunicationException, UnknownScmProviderException { + for (PersonalAccessTokenFetcher fetcher : personalAccessTokenFetchers) { + PersonalAccessToken token = fetcher.refreshPersonalAccessToken(cheUser, scmServerUrl); + if (token != null) { + return token; + } + } + + throw new UnknownScmProviderException( + "No PersonalAccessTokenFetcher configured for " + scmServerUrl, scmServerUrl); + } + /** * Iterate over the Set declared in container and sequentially invoke * {@link PersonalAccessTokenFetcher#fetchPersonalAccessToken(Subject, String)} method. @@ -81,7 +94,7 @@ public boolean isValid(PersonalAccessToken personalAccessToken) * fetchers return an scm username, return it. Otherwise, return null. */ public Optional getScmUsername(PersonalAccessTokenParams params) - throws UnknownScmProviderException { + throws UnknownScmProviderException, ScmCommunicationException { for (PersonalAccessTokenFetcher fetcher : personalAccessTokenFetchers) { Optional> isValid = fetcher.isValid(params); if (isValid.isPresent() && isValid.get().first) { diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/exception/ScmCommunicationException.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/exception/ScmCommunicationException.java index 6bfd7fa31f9..cc6427568cd 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/exception/ScmCommunicationException.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/scm/exception/ScmCommunicationException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -14,11 +14,18 @@ /** Thrown when problem occurred during communication with scm provider */ public class ScmCommunicationException extends Exception { private int statusCode; + private String provider; public ScmCommunicationException(String message) { super(message); } + public ScmCommunicationException(String message, int statusCode, String provider) { + super(message); + this.statusCode = statusCode; + this.provider = provider; + } + public ScmCommunicationException(String message, int statusCode) { super(message); this.statusCode = statusCode; @@ -28,7 +35,20 @@ public ScmCommunicationException(String message, Throwable cause) { super(message, cause); } + public ScmCommunicationException(String message, Throwable cause, String provider) { + super(message, cause); + this.provider = provider; + } + public int getStatusCode() { return statusCode; } + + public String getProvider() { + return provider; + } + + public void setProvider(String provider) { + this.provider = provider; + } } diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/ProjectConfigDtoMerger.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/ProjectConfigDtoMerger.java deleted file mode 100644 index a2e248d999f..00000000000 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/ProjectConfigDtoMerger.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2012-2021 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.api.factory.server.urlfactory; - -import static java.util.Collections.singletonList; - -import java.util.List; -import java.util.function.Supplier; -import javax.inject.Singleton; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; -import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; - -/** - * Merge or add inside a factory the source storage dto - * - * @author Florent Benoit - */ -@Singleton -public class ProjectConfigDtoMerger { - - /** - * Apply the merging of project config dto including source storage dto into the existing factory - * - *

here are the following rules - * - *

    - *
  • no projects --> add whole project - *
  • if projects: - *
      - *
    • if there is only one project: add source if missing - *
    • if many projects: do nothing - *
    - *
- * - * @param factory source factory - * @param configSupplier supplier which can compute project config on demand - * @return factory with merged project sources - */ - public FactoryDto merge(FactoryDto factory, Supplier configSupplier) { - - if (factory.getWorkspace() == null) { - // factory is created with devfile. There is no need to provision projects - return factory; - } - - final List projects = factory.getWorkspace().getProjects(); - if (projects == null || projects.isEmpty()) { - factory.getWorkspace().setProjects(singletonList(configSupplier.get())); - return factory; - } - - // if we're here, they are projects - if (projects.size() == 1) { - ProjectConfigDto projectConfig = projects.get(0); - if (projectConfig.getSource() == null) - projectConfig.setSource(configSupplier.get().getSource()); - } - - return factory; - } -} diff --git a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java index f35564a2202..50b4975382c 100644 --- a/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java +++ b/wsmaster/che-core-api-factory/src/main/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -15,37 +15,24 @@ import static org.eclipse.che.api.factory.server.ApiExceptionMapper.toApiException; import static org.eclipse.che.api.factory.server.scm.exception.ExceptionMessages.getDevfileConnectionErrorMessage; import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION; -import static org.eclipse.che.api.workspace.server.devfile.Constants.CURRENT_API_VERSION; -import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_TOOLING_EDITOR_ATTRIBUTE; -import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE; import static org.eclipse.che.dto.server.DtoFactory.newDto; import com.fasterxml.jackson.databind.JsonNode; import java.io.IOException; -import java.util.HashMap; import java.util.Map; import java.util.Optional; import javax.inject.Inject; import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.ApiException; -import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.factory.server.scm.exception.ScmUnauthorizedException; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl.DevfileLocation; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; -import org.eclipse.che.api.workspace.server.DtoConverter; import org.eclipse.che.api.workspace.server.devfile.DevfileParser; import org.eclipse.che.api.workspace.server.devfile.DevfileVersionDetector; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; -import org.eclipse.che.api.workspace.server.devfile.exception.OverrideParameterException; -import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; -import org.eclipse.che.api.workspace.server.model.impl.devfile.MetadataImpl; -import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.DevfileDto; -import org.eclipse.che.api.workspace.shared.dto.devfile.MetadataDto; import org.eclipse.che.commons.annotation.Nullable; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -148,10 +135,7 @@ public Optional createFactoryFromDevfile( } catch (DevfileException e) { throw new ApiException(getDevfileConnectionErrorMessage(devfileLocation)); } - return Optional.of( - createFactory(parsedDevfile, overrideProperties, fileContentProvider, location)); - } catch (OverrideParameterException e) { - throw new BadRequestException("Error processing override parameter(s): " + e.getMessage()); + return Optional.of(createFactory(parsedDevfile, location)); } catch (DevfileException e) { throw toApiException(e, location); } @@ -160,86 +144,15 @@ public Optional createFactoryFromDevfile( } /** - * Converts given devfile json into factory based on the devfile version. + * Converts given devfile json into factory. * - * @param overrideProperties map of overridden properties to apply in devfile - * @param fileContentProvider service-specific devfile related file content provider * @param location devfile's location * @return new factory created from the given devfile - * @throws OverrideParameterException when any issue when overriding parameters occur - * @throws DevfileException when devfile is not valid or we can't work with it - */ - private FactoryMetaDto createFactory( - JsonNode devfileJson, - Map overrideProperties, - FileContentProvider fileContentProvider, - DevfileLocation location) - throws OverrideParameterException, DevfileException { - - if (devfileVersionDetector.devfileMajorVersion(devfileJson) == 1) { - DevfileImpl devfile = devfileParser.parseJsonNode(devfileJson, overrideProperties); - devfileParser.resolveReference(devfile, fileContentProvider); - devfile = ensureToUseGenerateName(devfile); - - return newDto(FactoryDto.class) - .withV(CURRENT_VERSION) - .withDevfile(DtoConverter.asDto(devfile)) - .withSource(location.filename().isPresent() ? location.filename().get() : null); - - } else { - return newDto(FactoryDevfileV2Dto.class) - .withV(CURRENT_VERSION) - .withDevfile(devfileParser.convertYamlToMap(devfileJson)) - .withSource(location.filename().isPresent() ? location.filename().get() : null); - } - } - - /** - * Creates devfile with only `generateName` and no `name`. We take `generateName` with precedence. - * See doc of {@link URLFactoryBuilder#createFactoryFromDevfile(RemoteFactoryUrl, - * FileContentProvider, Map, boolean)} for explanation why. - */ - private DevfileImpl ensureToUseGenerateName(DevfileImpl devfile) { - MetadataImpl devfileMetadata = new MetadataImpl(devfile.getMetadata()); - if (isNullOrEmpty(devfileMetadata.getGenerateName())) { - devfileMetadata.setGenerateName(devfileMetadata.getName()); - } - devfileMetadata.setName(null); - - DevfileImpl devfileWithProperName = new DevfileImpl(devfile); - devfileWithProperName.setMetadata(devfileMetadata); - return devfileWithProperName; - } - - /** - * Help to generate default workspace configuration - * - * @param name the name of the workspace - * @return a workspace configuration - */ - public WorkspaceConfigDto buildDefaultWorkspaceConfig(String name) { - - Map attributes = new HashMap<>(); - attributes.put(WORKSPACE_TOOLING_EDITOR_ATTRIBUTE, defaultCheEditor); - if (!isNullOrEmpty(defaultChePlugins)) { - attributes.put(WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE, defaultChePlugins); - } - - // workspace configuration using the environment - return newDto(WorkspaceConfigDto.class).withName(name).withAttributes(attributes); - } - - /** - * Help to generate default workspace devfile. Also initialise project in it - * - * @param name the name that will be used as `generateName` in the devfile - * @return a workspace devfile */ - public DevfileDto buildDefaultDevfile(String name) { - - // workspace configuration using the environment - return newDto(DevfileDto.class) - .withApiVersion(CURRENT_API_VERSION) - .withMetadata(newDto(MetadataDto.class).withGenerateName(name)); + private FactoryMetaDto createFactory(JsonNode devfileJson, DevfileLocation location) { + return newDto(FactoryDevfileV2Dto.class) + .withV(CURRENT_VERSION) + .withDevfile(devfileParser.convertYamlToMap(devfileJson)) + .withSource(location.filename().isPresent() ? location.filename().get() : null); } } diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/BaseFactoryParameterResolverTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/BaseFactoryParameterResolverTest.java index 105de8e5f3d..e9293f8617b 100644 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/BaseFactoryParameterResolverTest.java +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/BaseFactoryParameterResolverTest.java @@ -12,14 +12,20 @@ package org.eclipse.che.api.factory.server; import static java.util.Collections.emptyMap; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertTrue; import java.util.Map; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; +import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; +import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; +import org.eclipse.che.api.factory.shared.dto.FactoryVisitor; +import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; @@ -31,7 +37,9 @@ public class BaseFactoryParameterResolverTest { @Mock private AuthorisationRequestManager authorisationRequestManager; @Mock private URLFactoryBuilder urlFactoryBuilder; - + @Mock private RemoteFactoryUrl remoteFactoryUrl; + @Mock private FactoryVisitor factoryVisitor; + @Mock private FileContentProvider contentProvider; private static final String PROVIDER_NAME = "test"; private BaseFactoryParameterResolver baseFactoryParameterResolver; @@ -73,4 +81,15 @@ public void shouldReturnTrueOnGetSkipAuthorisationFromFactoryParams() { // then assertTrue(result); } + + @Test + public void shouldReturnDevfileV2() throws Exception { + // given + when(authorisationRequestManager.isStored(eq(PROVIDER_NAME))).thenReturn(false); + // when + baseFactoryParameterResolver.createFactory( + emptyMap(), remoteFactoryUrl, factoryVisitor, contentProvider); + // then + verify(factoryVisitor).visit(any(FactoryDevfileV2Dto.class)); + } } diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/FactoryServiceTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/FactoryServiceTest.java index 28ca3966467..1308f975016 100644 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/FactoryServiceTest.java +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/FactoryServiceTest.java @@ -12,24 +12,19 @@ package org.eclipse.che.api.factory.server; import static io.restassured.RestAssured.given; -import static jakarta.ws.rs.core.Response.Status.BAD_REQUEST; import static java.lang.String.valueOf; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.factory.server.FactoryResolverPriority.DEFAULT; import static org.eclipse.che.api.factory.server.FactoryResolverPriority.HIGHEST; import static org.eclipse.che.api.factory.server.FactoryResolverPriority.LOWEST; import static org.eclipse.che.api.factory.server.FactoryService.VALIDATE_QUERY_PARAMETER; -import static org.eclipse.che.api.factory.shared.Constants.CURRENT_VERSION; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; -import static org.eclipse.che.dto.server.DtoFactory.newDto; import static org.everrest.assured.JettyHttpServer.ADMIN_USER_NAME; import static org.everrest.assured.JettyHttpServer.ADMIN_USER_PASSWORD; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -47,17 +42,13 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; -import org.eclipse.che.api.core.BadRequestException; import org.eclipse.che.api.core.model.user.User; import org.eclipse.che.api.core.rest.ApiExceptionMapper; import org.eclipse.che.api.core.rest.shared.dto.ServiceError; import org.eclipse.che.api.factory.server.FactoryService.FactoryParametersResolverHolder; -import org.eclipse.che.api.factory.server.builder.FactoryBuilder; -import org.eclipse.che.api.factory.server.impl.SourceStorageParametersValidator; import org.eclipse.che.api.factory.server.scm.AuthorisationRequestManager; import org.eclipse.che.api.factory.server.scm.PersonalAccessTokenManager; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.user.server.PreferenceManager; import org.eclipse.che.api.user.server.UserManager; import org.eclipse.che.api.user.server.model.impl.UserImpl; @@ -108,8 +99,6 @@ public class FactoryServiceTest { @InjectMocks private FactoryParametersResolverHolder factoryParametersResolverHolder; private Set specificFactoryParametersResolvers; - private FactoryBuilder factoryBuilderSpy; - private User user; private FactoryService service; @@ -129,9 +118,6 @@ public void setUp() throws Exception { parametersResolvers.setAccessible(true); parametersResolvers.set(factoryParametersResolverHolder, specificFactoryParametersResolvers); specificFactoryParametersResolvers.add(rawDevfileUrlFactoryParameterResolver); - factoryBuilderSpy = spy(new FactoryBuilder(new SourceStorageParametersValidator())); - lenient().doNothing().when(factoryBuilderSpy).checkValid(any(FactoryDto.class)); - lenient().doNothing().when(factoryBuilderSpy).checkValid(any(FactoryDto.class), anyBoolean()); user = new UserImpl(USER_ID, USER_EMAIL, ADMIN_USER_NAME); lenient() .when(preferenceManager.find(USER_ID)) @@ -185,56 +171,6 @@ public void shouldThrowBadRequestWhenNoURLParameterGiven() throws Exception { "Cannot build factory with any of the provided parameters. Please check parameters correctness, and resend query."); } - @Test - public void checkValidateResolver() throws Exception { - final FactoryParametersResolverHolder dummyHolder = spy(factoryParametersResolverHolder); - doReturn(rawDevfileUrlFactoryParameterResolver) - .when(dummyHolder) - .getFactoryParametersResolver(anyMap()); - // service instance with dummy holder - service = - new FactoryService( - acceptValidator, - dummyHolder, - additionalFilenamesProvider, - personalAccessTokenManager, - authorisationRequestManager); - - // invalid factory - final String invalidFactoryMessage = "invalid factory"; - doThrow(new BadRequestException(invalidFactoryMessage)) - .when(acceptValidator) - .validateOnAccept(any()); - - // create factory - final FactoryDto expectFactory = - newDto(FactoryDto.class).withV(CURRENT_VERSION).withName("matchingResolverFactory"); - - // accept resolver - when(rawDevfileUrlFactoryParameterResolver.createFactory(anyMap())).thenReturn(expectFactory); - - // when - final Map map = new HashMap<>(); - final Response response = - given() - .contentType(ContentType.JSON) - .when() - .body(map) - .queryParam(VALIDATE_QUERY_PARAMETER, valueOf(true)) - .post(SERVICE_PATH + "/resolver"); - - // then check we have a bad request - assertEquals(response.getStatusCode(), BAD_REQUEST.getStatusCode()); - assertTrue(response.getBody().asString().contains(invalidFactoryMessage)); - - // check we call resolvers - dummyHolder.getFactoryParametersResolver(anyMap()); - verify(rawDevfileUrlFactoryParameterResolver).createFactory(anyMap()); - - // check we call validator - verify(acceptValidator).validateOnAccept(any()); - } - @Test public void checkRefreshToken() throws Exception { // given diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/RawDevfileUrlFactoryParameterResolverTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/RawDevfileUrlFactoryParameterResolverTest.java index 84c95559447..5082bace58f 100644 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/RawDevfileUrlFactoryParameterResolverTest.java +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/RawDevfileUrlFactoryParameterResolverTest.java @@ -14,14 +14,9 @@ import static java.lang.String.format; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.factory.shared.Constants.URL_PARAMETER_NAME; -import static org.eclipse.che.api.workspace.server.devfile.Constants.EDITOR_COMPONENT_TYPE; -import static org.eclipse.che.api.workspace.server.devfile.Constants.KUBERNETES_COMPONENT_TYPE; -import static org.eclipse.che.api.workspace.server.devfile.Constants.OPENSHIFT_COMPONENT_TYPE; -import static org.eclipse.che.api.workspace.server.devfile.Constants.PLUGIN_COMPONENT_TYPE; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -38,12 +33,8 @@ import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl; import org.eclipse.che.api.factory.server.urlfactory.URLFactoryBuilder; import org.eclipse.che.api.workspace.server.devfile.DevfileParser; -import org.eclipse.che.api.workspace.server.devfile.DevfileVersionDetector; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.URLFileContentProvider; -import org.eclipse.che.api.workspace.server.devfile.validator.ComponentIntegrityValidator; -import org.eclipse.che.api.workspace.server.devfile.validator.ComponentIntegrityValidator.NoopComponentIntegrityValidator; -import org.eclipse.che.api.workspace.server.devfile.validator.DevfileIntegrityValidator; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -56,53 +47,13 @@ public class RawDevfileUrlFactoryParameterResolverTest { private static final String DEVFILE = - "" - + "apiVersion: 1.0.0\n" - + "metadata:\n" - + " name: test\n" - + "components:\n" - + "- type: kubernetes\n" - + " alias: component\n" - + " reference: ../localfile\n"; + "" + "schemaVersion: 2.3.0\n" + "metadata:\n" + " name: test\n"; @Mock private URLFetcher urlFetcher; @Mock private DevfileParser devfileParser; @InjectMocks private RawDevfileUrlFactoryParameterResolver rawDevfileUrlFactoryParameterResolver; - @Test - public void shouldResolveRelativeFiles() throws Exception { - // given - Map validators = new HashMap<>(); - validators.put(EDITOR_COMPONENT_TYPE, new NoopComponentIntegrityValidator()); - validators.put(PLUGIN_COMPONENT_TYPE, new NoopComponentIntegrityValidator()); - validators.put(KUBERNETES_COMPONENT_TYPE, new NoopComponentIntegrityValidator()); - validators.put(OPENSHIFT_COMPONENT_TYPE, new NoopComponentIntegrityValidator()); - - DevfileIntegrityValidator integrityValidator = new DevfileIntegrityValidator(validators); - - DevfileParser devfileParser = new DevfileParser(integrityValidator); - - URLFactoryBuilder factoryBuilder = - new URLFactoryBuilder( - "editor", "plugin", false, devfileParser, new DevfileVersionDetector()); - - RawDevfileUrlFactoryParameterResolver res = - new RawDevfileUrlFactoryParameterResolver(factoryBuilder, urlFetcher, devfileParser); - - // set up our factory with the location of our devfile that is referencing our localfile - Map factoryParameters = new HashMap<>(); - factoryParameters.put(URL_PARAMETER_NAME, "http://myloc.com/aa/bb/devfile"); - doReturn(DEVFILE).when(urlFetcher).fetch(eq("http://myloc.com/aa/bb/devfile"), eq(null)); - doReturn("localfile").when(urlFetcher).fetch("http://myloc.com/aa/localfile", null); - - // when - res.createFactory(factoryParameters); - - // then - verify(urlFetcher).fetch(eq("http://myloc.com/aa/localfile"), eq(null)); - } - @Test @SuppressWarnings("unchecked") public void shouldFilterAndProvideOverrideParameters() throws Exception { @@ -257,7 +208,11 @@ private Object[] devfileUrls() { "https://host/path/devfile.yaml?token=TOKEN123", "https://host/path/.devfile.yaml?token=TOKEN123", "https://host/path/any-name.yaml?token=TOKEN123", - "https://host/path/any-name.yml?token=TOKEN123" + "https://host/path/any-name.yml?token=TOKEN123", + "https://host/path/devfile.yaml?at=refs/heads/branch", + "https://host/path/.devfile.yaml?at=refs/heads/branch", + "https://host/path/any-name.yaml?at=refs/heads/branch", + "https://host/path/any-name.yml?at=refs/heads/branch" }; } diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/builder/FactoryBuilderTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/builder/FactoryBuilderTest.java deleted file mode 100644 index 72fba32e280..00000000000 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/builder/FactoryBuilderTest.java +++ /dev/null @@ -1,288 +0,0 @@ -/* - * Copyright (c) 2012-2021 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.api.factory.server.builder; - -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static java.util.Collections.singletonMap; -import static java.util.Objects.requireNonNull; -import static org.eclipse.che.api.core.model.workspace.config.MachineConfig.MEMORY_LIMIT_ATTRIBUTE; -import static org.eclipse.che.dto.server.DtoFactory.newDto; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.verify; - -import com.google.common.collect.ImmutableMap; -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.net.URISyntaxException; -import org.eclipse.che.api.core.ApiException; -import org.eclipse.che.api.core.ConflictException; -import org.eclipse.che.api.core.factory.FactoryParameter; -import org.eclipse.che.api.factory.server.impl.SourceStorageParametersValidator; -import org.eclipse.che.api.factory.shared.dto.AuthorDto; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; -import org.eclipse.che.api.factory.shared.dto.IdeActionDto; -import org.eclipse.che.api.factory.shared.dto.IdeDto; -import org.eclipse.che.api.factory.shared.dto.OnAppClosedDto; -import org.eclipse.che.api.factory.shared.dto.OnAppLoadedDto; -import org.eclipse.che.api.factory.shared.dto.OnProjectsLoadedDto; -import org.eclipse.che.api.factory.shared.dto.PoliciesDto; -import org.eclipse.che.api.workspace.shared.dto.CommandDto; -import org.eclipse.che.api.workspace.shared.dto.EnvironmentDto; -import org.eclipse.che.api.workspace.shared.dto.MachineConfigDto; -import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; -import org.eclipse.che.api.workspace.shared.dto.RecipeDto; -import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; -import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; -import org.eclipse.che.dto.server.DtoFactory; -import org.mockito.Mock; -import org.mockito.testng.MockitoTestNGListener; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -/** - * Tests for {@link FactoryDto} - * - * @author Alexander Garagatyi - * @author Sergii Kabashniuk - */ -@SuppressWarnings("deprecation") -@Listeners(MockitoTestNGListener.class) -public class FactoryBuilderTest { - - private static DtoFactory dto = DtoFactory.getInstance(); - - private FactoryBuilder factoryBuilder; - - private FactoryDto actual; - - private FactoryDto expected; - - @Mock private SourceStorageParametersValidator sourceProjectParametersValidator; - - @BeforeMethod - public void setUp() throws Exception { - factoryBuilder = new FactoryBuilder(sourceProjectParametersValidator); - actual = prepareFactory(); - - expected = dto.createDto(FactoryDto.class); - } - - @Test - public void shouldBeAbleToValidateV4_0() throws Exception { - factoryBuilder.checkValid(actual); - - verify(sourceProjectParametersValidator).validate(any(), eq(FactoryParameter.Version.V4_0)); - } - - @Test(expectedExceptions = ApiException.class) - public void shouldNotValidateUnparseableFactory() throws Exception { - factoryBuilder.checkValid(null); - } - - @Test( - expectedExceptions = ApiException.class, - dataProvider = "setByServerParamsProvider", - expectedExceptionsMessageRegExp = - "You have provided an invalid parameter .* for this version of Factory parameters.*") - public void shouldNotAllowUsingParamsThatCanBeSetOnlyByServer(FactoryDto factory) - throws Exception { - factoryBuilder.checkValid(factory); - } - - @Test(dataProvider = "setByServerParamsProvider") - public void shouldAllowUsingParamsThatCanBeSetOnlyByServerDuringUpdate(FactoryDto factory) - throws Exception { - factoryBuilder.checkValid(factory, true); - } - - @DataProvider(name = "setByServerParamsProvider") - public static Object[][] setByServerParamsProvider() throws Exception { - FactoryDto factory = prepareFactory(); - return new Object[][] { - {requireNonNull(dto.clone(factory)).withId("id")}, - { - requireNonNull(dto.clone(factory)) - .withCreator(dto.createDto(AuthorDto.class).withUserId("id")) - }, - { - requireNonNull(dto.clone(factory)) - .withCreator(dto.createDto(AuthorDto.class).withCreated(123L)) - } - }; - } - - @Test(expectedExceptions = ApiException.class, dataProvider = "notValidParamsProvider") - public void shouldNotAllowUsingNotValidParams(FactoryDto factory) - throws InvocationTargetException, IllegalAccessException, ApiException, - NoSuchMethodException { - factoryBuilder.checkValid(factory); - } - - @DataProvider(name = "notValidParamsProvider") - public static Object[][] notValidParamsProvider() - throws URISyntaxException, IOException, NoSuchMethodException { - FactoryDto factory = prepareFactory(); - EnvironmentDto environmentDto = - factory.getWorkspace().getEnvironments().values().iterator().next(); - environmentDto.getRecipe().withType(null); - return new Object[][] { - { - requireNonNull(dto.clone(factory)) - .withWorkspace(factory.getWorkspace().withDefaultEnv(null)) - }, - { - requireNonNull(dto.clone(factory)) - .withWorkspace( - factory.getWorkspace().withEnvironments(singletonMap("test", environmentDto))) - } - }; - } - - @Test - public void shouldBeAbleToValidateV4_0WithTrackedParamsWithoutAccountIdIfOnPremisesIsEnabled() - throws Exception { - factoryBuilder = new FactoryBuilder(sourceProjectParametersValidator); - - FactoryDto factory = - prepareFactory() - .withPolicies( - dto.createDto(PoliciesDto.class) - .withReferer("referrer") - .withSince(123L) - .withUntil(123L)); - - factoryBuilder.checkValid(factory); - } - - @Test( - expectedExceptions = ConflictException.class, - expectedExceptionsMessageRegExp = - "You are missing a mandatory parameter \"workspace.projects\\[1\\].path\". .*") - public void shouldThrowExceptionWithMessagePointingToMissingMandatoryParameter() - throws Exception { - factoryBuilder = new FactoryBuilder(sourceProjectParametersValidator); - - ProjectConfigDto project = - dto.createDto(ProjectConfigDto.class) - .withSource( - dto.createDto(SourceStorageDto.class).withType("git").withLocation("location")) - .withType("type") - .withAttributes(singletonMap("key", singletonList("value"))) - .withDescription("description") - .withName("name") - .withPath("/path"); - - ProjectConfigDto project2 = - dto.createDto(ProjectConfigDto.class) - .withSource( - dto.createDto(SourceStorageDto.class).withType("git").withLocation("location")) - .withType("") - .withAttributes(singletonMap("key", singletonList("value"))) - .withDescription("description") - .withName("test") - .withPath(""); - FactoryDto factory = prepareFactory(); - factory.getWorkspace().setProjects(asList(project, project2)); - - factoryBuilder.checkValid(factory); - } - - private static FactoryDto prepareFactory() { - ProjectConfigDto project = - dto.createDto(ProjectConfigDto.class) - .withSource( - dto.createDto(SourceStorageDto.class).withType("git").withLocation("location")) - .withType("type") - .withAttributes(singletonMap("key", singletonList("value"))) - .withDescription("description") - .withName("name") - .withPath("/path"); - EnvironmentDto environment = - dto.createDto(EnvironmentDto.class) - .withRecipe( - newDto(RecipeDto.class) - .withType("compose") - .withContentType("application/x-yaml") - .withContent("some content")) - .withMachines( - singletonMap( - "devmachine", - newDto(MachineConfigDto.class) - .withAttributes( - singletonMap(MEMORY_LIMIT_ATTRIBUTE, "" + 512L * 1024L * 1024L)))); - - WorkspaceConfigDto workspaceConfig = - dto.createDto(WorkspaceConfigDto.class) - .withProjects(singletonList(project)) - .withCommands( - singletonList( - dto.createDto(CommandDto.class) - .withName("command1") - .withType("maven") - .withCommandLine("mvn test"))) - .withDefaultEnv("env1") - .withEnvironments(singletonMap("test", environment)); - IdeDto ide = - dto.createDto(IdeDto.class) - .withOnAppClosed( - dto.createDto(OnAppClosedDto.class) - .withActions( - singletonList(dto.createDto(IdeActionDto.class).withId("warnOnClose")))) - .withOnAppLoaded( - dto.createDto(OnAppLoadedDto.class) - .withActions( - asList( - dto.createDto(IdeActionDto.class).withId("newProject"), - dto.createDto(IdeActionDto.class) - .withId("openWelcomePage") - .withProperties( - ImmutableMap.of( - "authenticatedTitle", - "Greeting title for authenticated users", - "authenticatedContentUrl", - "http://example.com/content.url"))))) - .withOnProjectsLoaded( - dto.createDto(OnProjectsLoadedDto.class) - .withActions( - asList( - dto.createDto(IdeActionDto.class) - .withId("openFile") - .withProperties(singletonMap("file", "pom.xml")), - dto.createDto(IdeActionDto.class).withId("run"), - dto.createDto(IdeActionDto.class) - .withId("findReplace") - .withProperties( - ImmutableMap.of( - "in", - "src/main/resources/consts2.properties", - "find", - "OLD_VALUE_2", - "replace", - "NEW_VALUE_2", - "replaceMode", - "mode"))))); - return dto.createDto(FactoryDto.class) - .withV("4.0") - .withWorkspace(workspaceConfig) - .withCreator(dto.createDto(AuthorDto.class).withEmail("email").withName("name")) - .withPolicies( - dto.createDto(PoliciesDto.class) - .withReferer("referrer") - .withSince(123L) - .withUntil(123L)) - .withIde(ide); - } -} diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidatorTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidatorTest.java index f89c225bb7f..c55b96627f4 100644 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidatorTest.java +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryBaseValidatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2021 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -18,9 +18,7 @@ import static org.mockito.Mockito.lenient; import com.google.common.collect.ImmutableMap; -import jakarta.servlet.http.HttpServletRequest; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.net.URISyntaxException; import java.text.ParseException; import java.util.Date; @@ -32,9 +30,8 @@ import org.eclipse.che.api.core.NotFoundException; import org.eclipse.che.api.core.ServerException; import org.eclipse.che.api.factory.server.FactoryConstants; -import org.eclipse.che.api.factory.server.builder.FactoryBuilder; -import org.eclipse.che.api.factory.shared.dto.AuthorDto; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; +import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; +import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.factory.shared.dto.IdeActionDto; import org.eclipse.che.api.factory.shared.dto.IdeDto; import org.eclipse.che.api.factory.shared.dto.OnAppClosedDto; @@ -42,11 +39,7 @@ import org.eclipse.che.api.factory.shared.dto.OnProjectsLoadedDto; import org.eclipse.che.api.factory.shared.dto.PoliciesDto; import org.eclipse.che.api.user.server.model.impl.UserImpl; -import org.eclipse.che.api.user.server.spi.PreferenceDao; import org.eclipse.che.api.user.server.spi.UserDao; -import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; -import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; -import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; import org.eclipse.che.dto.server.DtoFactory; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; @@ -63,165 +56,19 @@ public class FactoryBaseValidatorTest { @Mock private UserDao userDao; - @Mock private PreferenceDao preferenceDao; - - @Mock private FactoryBuilder builder; - - @Mock private HttpServletRequest request; - private TesterFactoryBaseValidator validator; - private FactoryDto factory; + private FactoryDevfileV2Dto factory; @BeforeMethod public void setUp() throws ParseException, NotFoundException, ServerException { - factory = - newDto(FactoryDto.class) - .withV("4.0") - .withCreator(newDto(AuthorDto.class).withUserId("userid")); + factory = newDto(FactoryDevfileV2Dto.class).withV("4.0"); final UserImpl user = new UserImpl("userid", "email", "name"); lenient().when(userDao.getById("userid")).thenReturn(user); validator = new TesterFactoryBaseValidator(); } - @Test - public void shouldBeAbleToValidateFactoryUrlObject() throws ApiException { - factory = prepareFactoryWithGivenStorage("git", VALID_REPOSITORY_URL, VALID_PROJECT_PATH); - validator.validateProjects(factory); - validator.validateProjects(factory); - } - - @Test - public void shouldBeAbleToValidateFactoryUrlObjectIfStorageIsESBWSO2() throws ApiException { - factory = prepareFactoryWithGivenStorage("esbwso2", VALID_REPOSITORY_URL, VALID_PROJECT_PATH); - validator.validateProjects(factory); - validator.validateProjects(factory); - } - - @Test( - expectedExceptions = ApiException.class, - expectedExceptionsMessageRegExp = - "The parameter project.source.location has a value submitted http://codenvy.com/git/04%2 with a value that is " - + "unexpected. " - + "For more information, please visit https://www.eclipse.org/che/docs/workspace-data-model.html#projects") - public void shouldNotValidateIfStorageLocationContainIncorrectEncodedSymbol() - throws ApiException { - // given - factory = - prepareFactoryWithGivenStorage("git", "http://codenvy.com/git/04%2", VALID_PROJECT_PATH); - - // when, then - validator.validateProjects(factory); - } - - @Test - public void shouldValidateIfStorageLocationIsCorrectSsh() throws ApiException { - // given - factory = - prepareFactoryWithGivenStorage( - "git", - "ssh://codenvy@review.gerrithub.io:29418/codenvy/exampleProject", - "example-project"); - - // when, then - validator.validateProjects(factory); - } - - @Test - public void shouldValidateIfStorageLocationIsCorrectHttps() throws ApiException { - // given - factory = - prepareFactoryWithGivenStorage("git", "https://github.com/codenvy/example.git", "/example"); - - // when, then - validator.validateProjects(factory); - } - - @Test - public void shouldValidateSubProjectWithNoLocation() throws ApiException { - // given - factory = prepareFactoryWithGivenStorage("git", "null", "/cloudide/core"); - - // when, then - validator.validateProjects(factory); - } - - @Test(dataProvider = "badAdvancedFactoryUrlProvider", expectedExceptions = ApiException.class) - public void shouldNotValidateIfStorageOrStorageLocationIsInvalid(FactoryDto factory) - throws ApiException { - validator.validateProjects(factory); - } - - @DataProvider(name = "badAdvancedFactoryUrlProvider") - public Object[][] invalidParametersFactoryUrlProvider() throws UnsupportedEncodingException { - FactoryDto adv1 = - prepareFactoryWithGivenStorage("notagit", VALID_REPOSITORY_URL, VALID_PROJECT_PATH); - FactoryDto adv2 = prepareFactoryWithGivenStorage("git", null, VALID_PROJECT_PATH); - FactoryDto adv3 = prepareFactoryWithGivenStorage("git", "", VALID_PROJECT_PATH); - return new Object[][] { - {adv1}, // invalid vcs - {adv2}, // invalid vcsurl - {adv3} // invalid vcsurl - }; - } - - @Test( - dataProvider = "invalidProjectNamesProvider", - expectedExceptions = ApiException.class, - expectedExceptionsMessageRegExp = - "Project name must contain only Latin letters, " - + "digits or these following special characters -._.") - public void shouldThrowFactoryUrlExceptionIfProjectNameInvalid(String projectName) - throws Exception { - // given - factory.withWorkspace( - newDto(WorkspaceConfigDto.class) - .withProjects( - singletonList( - newDto(ProjectConfigDto.class).withType("type").withName(projectName)))); - // when, then - validator.validateProjects(factory); - } - - @Test(dataProvider = "validProjectNamesProvider") - public void shouldBeAbleToValidateValidProjectName(String projectName) throws Exception { - // given - prepareFactoryWithGivenStorage("git", VALID_REPOSITORY_URL, VALID_PROJECT_PATH); - factory.withWorkspace( - newDto(WorkspaceConfigDto.class) - .withProjects( - singletonList( - newDto(ProjectConfigDto.class) - .withType("type") - .withName(projectName) - .withSource( - newDto(SourceStorageDto.class) - .withType("git") - .withLocation(VALID_REPOSITORY_URL)) - .withPath(VALID_PROJECT_PATH)))); - // when, then - validator.validateProjects(factory); - } - - @DataProvider(name = "validProjectNamesProvider") - public Object[][] validProjectNames() { - return new Object[][] { - {"untitled"}, - {"Untitled"}, - {"untitled.project"}, - {"untitled-project"}, - {"untitled_project"}, - {"untitled01"}, - {"000011111"}, - {"0untitled"}, - {"UU"}, - {"untitled-proj12"}, - {"untitled.pro....111"}, - {"SampleStruts"} - }; - } - @DataProvider(name = "invalidProjectNamesProvider") public Object[][] invalidProjectNames() { return new Object[][] { @@ -229,40 +76,6 @@ public Object[][] invalidProjectNames() { }; } - @Test - public void shouldValidateIfCurrentTimeBeforeSinceUntil() throws Exception { - Long currentTime = new Date().getTime(); - - factory.withPolicies( - newDto(PoliciesDto.class).withSince(currentTime + 10000L).withUntil(currentTime + 20000L)); - validator.validateCurrentTimeAfterSinceUntil(factory); - } - - @Test( - expectedExceptions = ApiException.class, - expectedExceptionsMessageRegExp = FactoryConstants.INVALID_SINCE_MESSAGE) - public void shouldNotValidateIfSinceBeforeCurrent() throws ApiException { - factory.withPolicies(newDto(PoliciesDto.class).withSince(1L)); - validator.validateCurrentTimeAfterSinceUntil(factory); - } - - @Test( - expectedExceptions = ApiException.class, - expectedExceptionsMessageRegExp = FactoryConstants.INVALID_UNTIL_MESSAGE) - public void shouldNotValidateIfUntilBeforeCurrent() throws ApiException { - factory.withPolicies(newDto(PoliciesDto.class).withUntil(1L)); - validator.validateCurrentTimeAfterSinceUntil(factory); - } - - @Test( - expectedExceptions = ApiException.class, - expectedExceptionsMessageRegExp = FactoryConstants.INVALID_SINCEUNTIL_MESSAGE) - public void shouldNotValidateIfUntilBeforeSince() throws ApiException { - factory.withPolicies(newDto(PoliciesDto.class).withSince(2L).withUntil(1L)); - - validator.validateCurrentTimeAfterSinceUntil(factory); - } - @Test( expectedExceptions = ApiException.class, expectedExceptionsMessageRegExp = FactoryConstants.ILLEGAL_FACTORY_BY_UNTIL_MESSAGE) @@ -296,7 +109,7 @@ public void shouldNotValidateIfUntilSinceAfterCurrentTime() throws ApiException @Test public void shouldValidateTrackedParamsIfOrgIdIsMissingButOnPremisesTrue() throws Exception { final DtoFactory dtoFactory = getInstance(); - FactoryDto factory = dtoFactory.createDto(FactoryDto.class); + FactoryDevfileV2Dto factory = dtoFactory.createDto(FactoryDevfileV2Dto.class); factory .withV("4.0") .withPolicies( @@ -315,7 +128,7 @@ public void shouldNotValidateOpenfileActionIfInWrongSectionOnAppClosed() throws List actions = singletonList(newDto(IdeActionDto.class).withId("openFile")); IdeDto ide = newDto(IdeDto.class).withOnAppClosed(newDto(OnAppClosedDto.class).withActions(actions)); - FactoryDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); + FactoryMetaDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); // when validator.validateProjectActions(factoryWithAccountId); } @@ -327,7 +140,7 @@ public void shouldNotValidateFindReplaceActionIfInWrongSectionOnAppLoaded() thro List actions = singletonList(newDto(IdeActionDto.class).withId("findReplace")); IdeDto ide = newDto(IdeDto.class).withOnAppLoaded(newDto(OnAppLoadedDto.class).withActions(actions)); - FactoryDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); + FactoryMetaDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); // when validator.validateProjectActions(factoryWithAccountId); } @@ -340,7 +153,7 @@ public void shouldNotValidateIfOpenfileActionInsufficientParams() throws Excepti IdeDto ide = newDto(IdeDto.class) .withOnProjectsLoaded(newDto(OnProjectsLoadedDto.class).withActions(actions)); - FactoryDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); + FactoryMetaDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); // when validator.validateProjectActions(factoryWithAccountId); } @@ -353,7 +166,7 @@ public void shouldNotValidateIfrunCommandActionInsufficientParams() throws Excep IdeDto ide = newDto(IdeDto.class) .withOnProjectsLoaded(newDto(OnProjectsLoadedDto.class).withActions(actions)); - FactoryDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); + FactoryMetaDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); // when validator.validateProjectActions(factoryWithAccountId); } @@ -366,7 +179,7 @@ public void shouldNotValidateIfOpenWelcomePageActionInsufficientParams() throws singletonList(newDto(IdeActionDto.class).withId("openWelcomePage")); IdeDto ide = newDto(IdeDto.class).withOnAppLoaded((newDto(OnAppLoadedDto.class).withActions(actions))); - FactoryDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); + FactoryMetaDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); // when validator.validateProjectActions(factoryWithAccountId); } @@ -384,7 +197,7 @@ public void shouldNotValidateIfFindReplaceActionInsufficientParams() throws Exce IdeDto ide = newDto(IdeDto.class) .withOnProjectsLoaded(newDto(OnProjectsLoadedDto.class).withActions(actions)); - FactoryDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); + FactoryMetaDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); // when validator.validateProjectActions(factoryWithAccountId); } @@ -402,7 +215,7 @@ public void shouldValidateFindReplaceAction() throws Exception { IdeDto ide = newDto(IdeDto.class) .withOnProjectsLoaded(newDto(OnProjectsLoadedDto.class).withActions(actions)); - FactoryDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); + FactoryMetaDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); // when validator.validateProjectActions(factoryWithAccountId); } @@ -418,7 +231,7 @@ public void shouldValidateOpenfileAction() throws Exception { IdeDto ide = newDto(IdeDto.class) .withOnProjectsLoaded(newDto(OnProjectsLoadedDto.class).withActions(actions)); - FactoryDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); + FactoryMetaDto factoryWithAccountId = requireNonNull(getInstance().clone(factory)).withIde(ide); // when validator.validateProjectActions(factoryWithAccountId); } @@ -428,7 +241,7 @@ public Object[][] trackedFactoryParameterWithoutValidAccountId() throws URISyntaxException, IOException, NoSuchMethodException { return new Object[][] { { - newDto(FactoryDto.class) + newDto(FactoryMetaDto.class) .withV("4.0") .withIde( newDto(IdeDto.class) @@ -449,31 +262,20 @@ public Object[][] trackedFactoryParameterWithoutValidAccountId() .build()))))) }, { - newDto(FactoryDto.class) + newDto(FactoryMetaDto.class) .withV("4.0") .withPolicies(newDto(PoliciesDto.class).withSince(10000L)) }, { - newDto(FactoryDto.class) + newDto(FactoryMetaDto.class) .withV("4.0") .withPolicies(newDto(PoliciesDto.class).withUntil(10000L)) }, { - newDto(FactoryDto.class) + newDto(FactoryMetaDto.class) .withV("4.0") .withPolicies(newDto(PoliciesDto.class).withReferer("host")) } }; } - - private FactoryDto prepareFactoryWithGivenStorage(String type, String location, String path) { - return factory.withWorkspace( - newDto(WorkspaceConfigDto.class) - .withProjects( - singletonList( - newDto(ProjectConfigDto.class) - .withSource( - newDto(SourceStorageDto.class).withType(type).withLocation(location)) - .withPath(path)))); - } } diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryCreateAndAcceptValidatorsImplsTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryCreateAndAcceptValidatorsImplsTest.java deleted file mode 100644 index 96e74b4c87a..00000000000 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryCreateAndAcceptValidatorsImplsTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2012-2018 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.api.factory.server.impl; - -import static org.mockito.ArgumentMatchers.nullable; -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; - -import org.eclipse.che.api.core.ApiException; -import org.eclipse.che.api.core.model.workspace.WorkspaceConfig; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; -import org.eclipse.che.api.user.server.spi.PreferenceDao; -import org.eclipse.che.api.user.server.spi.UserDao; -import org.eclipse.che.api.workspace.server.WorkspaceValidator; -import org.mockito.Mock; -import org.mockito.testng.MockitoTestNGListener; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -/** - * Tests for {@link org.eclipse.che.api.factory.server.impl.FactoryAcceptValidatorImpl} and {@link - * FactoryCreateValidatorImpl} - */ -@Listeners(value = {MockitoTestNGListener.class}) -public class FactoryCreateAndAcceptValidatorsImplsTest { - - @Mock private UserDao userDao; - - @Mock private PreferenceDao preferenceDao; - - @Mock private FactoryDto factory; - - @Mock private WorkspaceValidator workspaceConfigValidator; - - private FactoryAcceptValidatorImpl acceptValidator; - - private FactoryCreateValidatorImpl createValidator; - - @BeforeMethod - public void setUp() throws Exception { - - acceptValidator = new FactoryAcceptValidatorImpl(); - createValidator = new FactoryCreateValidatorImpl(workspaceConfigValidator); - } - - @Test - public void testValidateOnCreate() throws Exception { - FactoryCreateValidatorImpl spy = spy(createValidator); - doNothing().when(spy).validateProjects(any(FactoryDto.class)); - doNothing().when(spy).validateCurrentTimeAfterSinceUntil(any(FactoryDto.class)); - doNothing().when(spy).validateProjectActions(any(FactoryDto.class)); - doNothing().when(workspaceConfigValidator).validateConfig(nullable(WorkspaceConfig.class)); - - // main invoke - spy.validateOnCreate(factory); - - verify(spy).validateProjects(any(FactoryDto.class)); - verify(spy).validateCurrentTimeAfterSinceUntil(any(FactoryDto.class)); - verify(spy).validateOnCreate(any(FactoryDto.class)); - verify(spy).validateProjectActions(any(FactoryDto.class)); - verifyNoMoreInteractions(spy); - } - - @Test - public void testOnAcceptEncoded() throws ApiException { - FactoryAcceptValidatorImpl spy = spy(acceptValidator); - doNothing().when(spy).validateCurrentTimeBetweenSinceUntil(any(FactoryDto.class)); - doNothing().when(spy).validateProjectActions(any(FactoryDto.class)); - - // main invoke - spy.validateOnAccept(factory); - - verify(spy).validateCurrentTimeBetweenSinceUntil(any(FactoryDto.class)); - verify(spy).validateOnAccept(any(FactoryDto.class)); - verify(spy).validateProjectActions(any(FactoryDto.class)); - verifyNoMoreInteractions(spy); - } -} diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryEditValidatorImplTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryEditValidatorImplTest.java deleted file mode 100644 index 31c42e375a9..00000000000 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/impl/FactoryEditValidatorImplTest.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2012-2018 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.api.factory.server.impl; - -import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import org.eclipse.che.api.core.ApiException; -import org.eclipse.che.api.core.ForbiddenException; -import org.eclipse.che.api.core.ServerException; -import org.eclipse.che.api.factory.server.FactoryEditValidator; -import org.eclipse.che.api.factory.shared.dto.AuthorDto; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; -import org.eclipse.che.commons.env.EnvironmentContext; -import org.eclipse.che.commons.subject.Subject; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.testng.MockitoTestNGListener; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -/** - * Tests for {@link FactoryEditValidator} - * - * @author Florent Benoit - */ -@Listeners(value = {MockitoTestNGListener.class}) -public class FactoryEditValidatorImplTest { - - @Mock private FactoryDto factory; - - @InjectMocks private FactoryEditValidator factoryEditValidator = new FactoryEditValidatorImpl(); - - /** - * Check missing author data - * - * @throws ApiException - */ - @Test(expectedExceptions = ServerException.class) - public void testNoAuthor() throws ApiException { - setCurrentUser(""); - factoryEditValidator.validate(factory); - } - - /** - * Check when user is not same than the one than create the factory - * - * @throws ApiException - */ - @Test(expectedExceptions = ForbiddenException.class) - public void testUserIsNotTheAuthor() throws ApiException { - String userId = "florent"; - setCurrentUser(userId); - - AuthorDto author = mock(AuthorDto.class); - doReturn(author).when(factory).getCreator(); - doReturn("john").when(author).getUserId(); - - factoryEditValidator.validate(factory); - } - - /** - * Check when user is the same than the one than create the factory - * - * @throws ApiException - */ - @Test - public void testUserIsTheAuthor() throws ApiException { - String userId = "florent"; - setCurrentUser(userId); - AuthorDto author = mock(AuthorDto.class); - doReturn(author).when(factory).getCreator(); - doReturn(userId).when(author).getUserId(); - - factoryEditValidator.validate(factory); - } - - private void setCurrentUser(String userId) { - Subject subject = mock(Subject.class); - when(subject.getUserId()).thenReturn(userId); - EnvironmentContext.getCurrent().setSubject(subject); - } -} diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/ProjectConfigDtoMergerTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/ProjectConfigDtoMergerTest.java deleted file mode 100644 index 61619615f83..00000000000 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/ProjectConfigDtoMergerTest.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2012-2018 Red Hat, Inc. - * This program and the accompanying materials are made - * available under the terms of the Eclipse Public License 2.0 - * which is available at https://www.eclipse.org/legal/epl-2.0/ - * - * SPDX-License-Identifier: EPL-2.0 - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.api.factory.server.urlfactory; - -import static org.eclipse.che.dto.server.DtoFactory.newDto; -import static org.testng.Assert.assertEquals; - -import java.util.Collections; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; -import org.eclipse.che.api.workspace.shared.dto.ProjectConfigDto; -import org.eclipse.che.api.workspace.shared.dto.SourceStorageDto; -import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; -import org.mockito.InjectMocks; -import org.mockito.testng.MockitoTestNGListener; -import org.testng.Assert; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Listeners; -import org.testng.annotations.Test; - -/** - * Testing {@link ProjectConfigDtoMerger} - * - * @author Florent Benoit - */ -@Listeners(MockitoTestNGListener.class) -public class ProjectConfigDtoMergerTest { - - /** Location */ - private static final String DUMMY_LOCATION = "dummy-location"; - - @InjectMocks private ProjectConfigDtoMerger projectConfigDtoMerger; - - private ProjectConfigDto computedProjectConfig; - - private FactoryDto factory; - - @BeforeClass - public void setup() { - WorkspaceConfigDto workspaceConfigDto = newDto(WorkspaceConfigDto.class); - this.factory = newDto(FactoryDto.class).withWorkspace(workspaceConfigDto); - - SourceStorageDto sourceStorageDto = newDto(SourceStorageDto.class).withLocation(DUMMY_LOCATION); - computedProjectConfig = newDto(ProjectConfigDto.class).withSource(sourceStorageDto); - } - - /** Check project is added when we have no project */ - @Test - public void mergeWithoutAnyProject() { - - // no project - Assert.assertTrue(factory.getWorkspace().getProjects().isEmpty()); - - // merge - projectConfigDtoMerger.merge(factory, () -> computedProjectConfig); - - // project - assertEquals(factory.getWorkspace().getProjects().size(), 1); - - assertEquals(factory.getWorkspace().getProjects().get(0), computedProjectConfig); - } - - /** Check source are added if there is only one project without source */ - @Test - public void mergeWithoutOneProjectWithoutSource() { - - // add existing project - ProjectConfigDto projectConfigDto = newDto(ProjectConfigDto.class); - factory.getWorkspace().setProjects(Collections.singletonList(projectConfigDto)); - // no source storage - Assert.assertNull(projectConfigDto.getSource()); - - // merge - projectConfigDtoMerger.merge(factory, () -> computedProjectConfig); - - // project still 1 - assertEquals(factory.getWorkspace().getProjects().size(), 1); - - SourceStorageDto sourceStorageDto = factory.getWorkspace().getProjects().get(0).getSource(); - - assertEquals(sourceStorageDto.getLocation(), DUMMY_LOCATION); - } -} diff --git a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java index 062d0e98204..0e293657a53 100644 --- a/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java +++ b/wsmaster/che-core-api-factory/src/test/java/org/eclipse/che/api/factory/server/urlfactory/URLFactoryBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012-2023 Red Hat, Inc. + * Copyright (c) 2012-2024 Red Hat, Inc. * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ @@ -15,11 +15,6 @@ import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.eclipse.che.api.workspace.server.devfile.Constants.KUBERNETES_COMPONENT_TYPE; -import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_TOOLING_EDITOR_ATTRIBUTE; -import static org.eclipse.che.api.workspace.shared.Constants.WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE; -import static org.eclipse.che.dto.server.DtoFactory.newDto; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyMap; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -34,7 +29,6 @@ import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; import java.util.Collections; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -47,20 +41,17 @@ import org.eclipse.che.api.factory.server.scm.exception.UnknownScmProviderException; import org.eclipse.che.api.factory.server.urlfactory.RemoteFactoryUrl.DevfileLocation; import org.eclipse.che.api.factory.shared.dto.FactoryDevfileV2Dto; -import org.eclipse.che.api.factory.shared.dto.FactoryDto; import org.eclipse.che.api.factory.shared.dto.FactoryMetaDto; import org.eclipse.che.api.workspace.server.devfile.DevfileParser; import org.eclipse.che.api.workspace.server.devfile.DevfileVersionDetector; import org.eclipse.che.api.workspace.server.devfile.FileContentProvider; import org.eclipse.che.api.workspace.server.devfile.URLFetcher; import org.eclipse.che.api.workspace.server.devfile.exception.DevfileException; -import org.eclipse.che.api.workspace.server.devfile.exception.OverrideParameterException; import org.eclipse.che.api.workspace.server.model.impl.EnvironmentImpl; import org.eclipse.che.api.workspace.server.model.impl.RecipeImpl; import org.eclipse.che.api.workspace.server.model.impl.WorkspaceConfigImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.DevfileImpl; import org.eclipse.che.api.workspace.server.model.impl.devfile.MetadataImpl; -import org.eclipse.che.api.workspace.shared.dto.WorkspaceConfigDto; import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; @@ -98,20 +89,6 @@ public void setUp() throws IOException, DevfileException { defaultEditor, defaultPlugin, true, devfileParser, devfileVersionDetector); } - @Test - public void checkDefaultConfiguration() throws Exception { - Map attributes = new HashMap<>(); - attributes.put(WORKSPACE_TOOLING_EDITOR_ATTRIBUTE, defaultEditor); - attributes.put(WORKSPACE_TOOLING_PLUGINS_ATTRIBUTE, defaultPlugin); - // setup environment - WorkspaceConfigDto expectedWsConfig = - newDto(WorkspaceConfigDto.class).withAttributes(attributes).withName("foo"); - - WorkspaceConfigDto actualWsConfigDto = urlFactoryBuilder.buildDefaultWorkspaceConfig("foo"); - - assertEquals(actualWsConfigDto, expectedWsConfig); - } - @Test public void checkWithCustomDevfileAndRecipe() throws Exception { @@ -126,8 +103,6 @@ public void checkWithCustomDevfileAndRecipe() throws Exception { when(devfileParser.parseYamlRaw(anyString())) .thenReturn(new ObjectNode(JsonNodeFactory.instance)); - when(devfileParser.parseJsonNode(any(JsonNode.class), anyMap())).thenReturn(devfile); - when(devfileVersionDetector.devfileMajorVersion(any(JsonNode.class))).thenReturn(1); when(fileContentProvider.fetchContent(anyString())).thenReturn("content"); FactoryMetaDto factory = @@ -141,7 +116,7 @@ public void checkWithCustomDevfileAndRecipe() throws Exception { assertNotNull(factory); assertNull(factory.getSource()); - assertTrue(factory instanceof FactoryDto); + assertTrue(factory instanceof FactoryDevfileV2Dto); } @Test @@ -152,7 +127,6 @@ public void testDevfileV2() throws ApiException, DevfileException, IOException { JsonNode devfile = new ObjectNode(JsonNodeFactory.instance); when(devfileParser.parseYamlRaw(anyString())).thenReturn(devfile); when(devfileParser.convertYamlToMap(devfile)).thenReturn(devfileAsMap); - when(devfileVersionDetector.devfileMajorVersion(devfile)).thenReturn(2); when(fileContentProvider.fetchContent(anyString())).thenReturn("content"); FactoryMetaDto factory = @@ -178,7 +152,6 @@ public void testDevfileV2WithFilename() throws ApiException, DevfileException, I JsonNode devfile = new ObjectNode(JsonNodeFactory.instance); when(devfileParser.parseYamlRaw(anyString())).thenReturn(devfile); when(devfileParser.convertYamlToMap(devfile)).thenReturn(devfileAsMap); - when(devfileVersionDetector.devfileMajorVersion(devfile)).thenReturn(2); when(fileContentProvider.fetchContent(anyString())).thenReturn("content"); RemoteFactoryUrl githubLikeRemoteUrl = @@ -252,7 +225,6 @@ public void testDevfileSpecifyingFilename() throws ApiException, DevfileExceptio JsonNode devfile = new ObjectNode(JsonNodeFactory.instance); when(devfileParser.parseYamlRaw(anyString())).thenReturn(devfile); when(devfileParser.convertYamlToMap(devfile)).thenReturn(devfileAsMap); - when(devfileVersionDetector.devfileMajorVersion(devfile)).thenReturn(2); when(fileContentProvider.fetchContent(anyString())).thenReturn("content"); RemoteFactoryUrl githubLikeRemoteUrl = @@ -337,7 +309,6 @@ public void testShouldReturnV2WithDevworkspacesDisabled() JsonNode devfile = new ObjectNode(JsonNodeFactory.instance); when(devfileParser.parseYamlRaw(anyString())).thenReturn(devfile); when(devfileParser.convertYamlToMap(devfile)).thenReturn(devfileAsMap); - when(devfileVersionDetector.devfileMajorVersion(devfile)).thenReturn(2); when(fileContentProvider.fetchContent(anyString())).thenReturn("content"); URLFactoryBuilder localUrlFactoryBuilder = @@ -383,40 +354,6 @@ public Object[][] devfiles() { return new Object[][] {{justName, NAME}, {justGenerateName, GEN_NAME}, {bothNames, GEN_NAME}}; } - @Test(dataProvider = "devfiles") - public void checkThatDtoHasCorrectNames(DevfileImpl devfile, String expectedGenerateName) - throws ApiException, IOException, OverrideParameterException, DevfileException { - DefaultFactoryUrl defaultFactoryUrl = mock(DefaultFactoryUrl.class); - FileContentProvider fileContentProvider = mock(FileContentProvider.class); - when(defaultFactoryUrl.devfileFileLocations()) - .thenReturn( - singletonList( - new DevfileLocation() { - @Override - public Optional filename() { - return Optional.empty(); - } - - @Override - public String location() { - return "http://foo.bar/anything"; - } - })); - when(fileContentProvider.fetchContent(anyString())).thenReturn("anything"); - when(devfileParser.parseYamlRaw("anything")) - .thenReturn(new ObjectNode(JsonNodeFactory.instance)); - when(devfileParser.parseJsonNode(any(JsonNode.class), anyMap())).thenReturn(devfile); - when(devfileVersionDetector.devfileMajorVersion(any(JsonNode.class))).thenReturn(1); - FactoryDto factory = - (FactoryDto) - urlFactoryBuilder - .createFactoryFromDevfile(defaultFactoryUrl, fileContentProvider, emptyMap(), false) - .get(); - - assertNull(factory.getDevfile().getMetadata().getName()); - assertEquals(factory.getDevfile().getMetadata().getGenerateName(), expectedGenerateName); - } - @Test(dataProvider = "devfileExceptions") public void checkCorrectExceptionThrownDependingOnCause( Throwable cause, diff --git a/wsmaster/che-core-api-logger-shared/pom.xml b/wsmaster/che-core-api-logger-shared/pom.xml index 5b66d7d160d..35ae6b8fd75 100644 --- a/wsmaster/che-core-api-logger-shared/pom.xml +++ b/wsmaster/che-core-api-logger-shared/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-logger-shared jar diff --git a/wsmaster/che-core-api-logger/pom.xml b/wsmaster/che-core-api-logger/pom.xml index b63e023603a..17bd21f9c99 100644 --- a/wsmaster/che-core-api-logger/pom.xml +++ b/wsmaster/che-core-api-logger/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-logger jar diff --git a/wsmaster/che-core-api-metrics/pom.xml b/wsmaster/che-core-api-metrics/pom.xml index 1b1c5ed5359..01f81bb9b21 100644 --- a/wsmaster/che-core-api-metrics/pom.xml +++ b/wsmaster/che-core-api-metrics/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-metrics jar diff --git a/wsmaster/che-core-api-ssh-shared/pom.xml b/wsmaster/che-core-api-ssh-shared/pom.xml index e03e2665c7d..5218fe27c44 100644 --- a/wsmaster/che-core-api-ssh-shared/pom.xml +++ b/wsmaster/che-core-api-ssh-shared/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-ssh-shared jar diff --git a/wsmaster/che-core-api-ssh/pom.xml b/wsmaster/che-core-api-ssh/pom.xml index ffb9430cf6d..1ae6fd0bd50 100644 --- a/wsmaster/che-core-api-ssh/pom.xml +++ b/wsmaster/che-core-api-ssh/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-ssh jar diff --git a/wsmaster/che-core-api-system-shared/pom.xml b/wsmaster/che-core-api-system-shared/pom.xml index e2a7a74de60..88c44405550 100644 --- a/wsmaster/che-core-api-system-shared/pom.xml +++ b/wsmaster/che-core-api-system-shared/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-system-shared jar diff --git a/wsmaster/che-core-api-system/pom.xml b/wsmaster/che-core-api-system/pom.xml index 546e8cc3b8d..f8cdac9b950 100644 --- a/wsmaster/che-core-api-system/pom.xml +++ b/wsmaster/che-core-api-system/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-system jar diff --git a/wsmaster/che-core-api-user-shared/pom.xml b/wsmaster/che-core-api-user-shared/pom.xml index d814730f72d..9e03f37b472 100644 --- a/wsmaster/che-core-api-user-shared/pom.xml +++ b/wsmaster/che-core-api-user-shared/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-user-shared Che Core :: API :: User :: Shared diff --git a/wsmaster/che-core-api-user/pom.xml b/wsmaster/che-core-api-user/pom.xml index 1c7d524d203..295af28fa79 100644 --- a/wsmaster/che-core-api-user/pom.xml +++ b/wsmaster/che-core-api-user/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-user Che Core :: API :: User diff --git a/wsmaster/che-core-api-workspace-activity/pom.xml b/wsmaster/che-core-api-workspace-activity/pom.xml index a609a1aff3e..0284859a1c4 100644 --- a/wsmaster/che-core-api-workspace-activity/pom.xml +++ b/wsmaster/che-core-api-workspace-activity/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-workspace-activity jar diff --git a/wsmaster/che-core-api-workspace-shared/pom.xml b/wsmaster/che-core-api-workspace-shared/pom.xml index 07092a36d0e..c5248cda27d 100644 --- a/wsmaster/che-core-api-workspace-shared/pom.xml +++ b/wsmaster/che-core-api-workspace-shared/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-workspace-shared jar diff --git a/wsmaster/che-core-api-workspace/pom.xml b/wsmaster/che-core-api-workspace/pom.xml index 4d6ce1459c1..ee468809a20 100644 --- a/wsmaster/che-core-api-workspace/pom.xml +++ b/wsmaster/che-core-api-workspace/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-api-workspace jar diff --git a/wsmaster/che-core-sql-schema/pom.xml b/wsmaster/che-core-sql-schema/pom.xml index c1d4148ca1a..8d507c2370a 100644 --- a/wsmaster/che-core-sql-schema/pom.xml +++ b/wsmaster/che-core-sql-schema/pom.xml @@ -17,7 +17,7 @@ che-master-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT che-core-sql-schema Che Core :: SQL :: Schema diff --git a/wsmaster/pom.xml b/wsmaster/pom.xml index 7ed471302e0..c417c50b3a3 100644 --- a/wsmaster/pom.xml +++ b/wsmaster/pom.xml @@ -17,7 +17,7 @@ che-core-parent org.eclipse.che.core - 7.88.0-SNAPSHOT + 7.96.0-SNAPSHOT ../core/pom.xml che-master-parent @@ -31,6 +31,7 @@ che-core-api-auth-github che-core-api-auth-github-common che-core-api-auth-gitlab + che-core-api-auth-gitlab-common che-core-api-auth-openshift che-core-api-workspace-shared che-core-api-workspace @@ -47,6 +48,7 @@ che-core-api-factory-github che-core-api-factory-github-common che-core-api-factory-gitlab + che-core-api-factory-gitlab-common che-core-api-factory-bitbucket che-core-api-factory-bitbucket-server che-core-api-ssh