diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a49947a1b72..cc51d282418 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -20,9 +20,9 @@ /jans-auth-server/ @yurem @yuriyz /jans-fido2/ @yurem /jans-scim/ @jgomer2001 -/jans-client-api/ @duttarnab @yuriyz /jans-config-api/ @pujavs @yuriyz -/jans-cli/ @mbaser -/jans-linux-setup/ @mbaser @smansoft @yuriyz +/jans-cli/ @devrimyatar +/jans-linux-setup/ @devrimyatar @smansoft @yuriyz /jans-linux-setup/jans_setup/setup_app/version.py @moabu -/jans-linux-setup/static/scripts/admin_ui_plugin.py @mbaser @duttarnab \ No newline at end of file +/jans-linux-setup/static/scripts/admin_ui_plugin.py @devrimyatar @duttarnab +/agama/ @jgomer2001 diff --git a/.github/SECURITY.md b/.github/SECURITY.md index 303d567d06e..cde7527b6c0 100644 --- a/.github/SECURITY.md +++ b/.github/SECURITY.md @@ -14,8 +14,7 @@ Security updates will typically only be applied to the latest release (at least ## Reporting a vulnerability -To report a security issue, email [security@jans.io](mailto:security@jans.io?subject=SECURITY) -and include the word "SECURITY" in the subject line. +To report a security issue, send an email to [security@jans.io](mailto:security@jans.io?subject=SECURITY) The **Janssen** team will send a response indicating the next steps in handling your report. After the initial reply to your report, the team will keep you informed of the progress towards a fix and full announcement, diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 1dea66db74a..8b002476106 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,20 +1,25 @@ ### Prepare -- [ ] Read contribution guidelines -- [ ] Read license information +- [ ] Read [PR guidelines](https://github.com/JanssenProject/jans/blob/main/docs/CONTRIBUTING.md#prs) +- [ ] Read [license information](https://github.com/JanssenProject/jans/blob/main/LICENSE) ------------------- ### Description -- Target issue # - +#### Target issue + + + + +closes #issue-number-here -- Implementation Details - +#### Implementation Details + ------------------- -### Document the changes - +### Test and Document the changes +- [ ] Static code analysis has been run locally and issues have been fixed +- [ ] Relevant unit and integration tests have been added/updated - [ ] Relevant documentation has been updated if any (i.e. user guides, installation and configuration guides, technical design docs etc) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml new file mode 100644 index 00000000000..012cb918be2 --- /dev/null +++ b/.github/workflows/backport.yml @@ -0,0 +1,52 @@ +name: Backport +on: + pull_request_target: + types: [closed, labeled] + branches: [main, release-*] + +# WARNING: +# When extending this action, be aware that $GITHUB_TOKEN allows write access to +# the GitHub repository. This means that it should not evaluate user input in a +# way that allows code injection. + +jobs: + backport: + name: Backport Pull Request + # Run the action if a PR is merged with backport labels + # OR + # when already merged PR is labeled with backport labels + if: > + github.event.pull_request.merged + && ( + github.event.action == 'closed' + || ( + github.event.action == 'labeled' + && startsWith(github.event.label.name, 'backport/') + ) + ) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + # required to find all branches + fetch-depth: 0 + token: ${{ secrets.MOAUTO_WORKFLOW_TOKEN }} + ref: ${{ github.event.pull_request.head.sha }} + - name: Create backport PRs + # should be kept in sync with `version` + uses: zeebe-io/backport-action@v0.0.8 + with: + # Config README: https://github.com/zeebe-io/backport-action#backport-action + github_token: ${{ secrets.MOAUTO_WORKFLOW_TOKEN }} + github_workspace: ${{ github.workspace }} + # should be kept in sync with `uses` + version: v0.0.8 + + # Regex pattern to match GitHub labels + # The capture group catches the target branch + # i.e. label backport/v1.0.0 will create backport PR for branch v1.0.0 + label_pattern: ^backport\/([^ ]+)$ + + pull_description: |- + Automated backport to `${target_branch}`, triggered by a label in #${pull_number}. + See ${issue_refs}. diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml new file mode 100644 index 00000000000..5004a74b76e --- /dev/null +++ b/.github/workflows/build-docs.yml @@ -0,0 +1,160 @@ +name: Publish docs via GitHub Pages +on: + push: + branches: + - main + release: + types: + - published + workflow_dispatch: + inputs: + version: + description: 'Version tag (e.g."v1.0.0")' + default: "v1.0.0" + required: false +jobs: + build: + env: + GH_TOKEN: ${{ secrets.GIT_AUTHOR_MKDOCS_DEPLOY }} + name: Deploy docs + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Python 3.10 + uses: actions/setup-python@v4 + with: + python-version: "3.10" + cache: pip + cache-dependency-path: docs/requirements.txt + + - name: Install dependencies + run: | + pip install --require-hashes -r docs/requirements.txt + cp mkdocs.yml ../ + + - name: Checkout jans ${{ github.event.inputs.version }} + if: >- + github.event_name == 'workflow_dispatch' + uses: actions/checkout@v3 + with: + ref: '${{ github.event.inputs.version }}' + fetch-depth: 0 + + - name: Copy files from main to ${{ github.event.inputs.version }} + if: >- + github.event_name == 'workflow_dispatch' + run: | + mv ../mkdocs.yml mkdocs.yml + + - name: Generate docs + run: echo "Custom work on generating docs can go here." + + - name: git config + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + - name: mike deploy ${{ github.event.inputs.version }} + if: >- + github.event_name == 'workflow_dispatch' + run: | + mike deploy --push --force ${{ github.event.inputs.version }} + + # This deploys the current docs into gh-pages/head on merges to main + # The old "main" gets deleted if it exists, head is more descriptive + - name: mike deploy head + if: contains(github.ref, 'refs/heads/main') && github.event_name != 'workflow_dispatch' + run: | + mike deploy --push head + + # If a release has been published, deploy it as a new version + - name: mike deploy new version + if: >- + github.event_name == 'release' && + github.event.action == 'published' && + startsWith(github.event.release.name, 'v') && + !github.event.release.draft && + !github.event.release.prerelease + env: + VERSION: ${{ github.event.release.tag_name }} + run: | + mike deploy --push "$VERSION" + + - name: Update mike version aliases + #if: >- + # github.event_name != 'workflow_dispatch' + id: set_versions + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + TAGS=$(gh release list -L 1000 -R ${{ github.repository }} | grep -o '^\v.*'| grep -v Draft | cut -f 1 | sed '/-/!{s/$/_/}' | sort -V | sed 's/_$//') + LATEST=$(echo "${TAGS}" | tail -1) + STABLE=$(echo "${TAGS}" | grep -v -- "-" | tail -1) + # remove below two lines after first release + LATEST="head" + STABLE="head" + mike alias -u head main + mike alias -u "${STABLE}" stable + mike set-default --push stable + echo ::set-output name=LATEST::${LATEST} + echo ::set-output name=STABLE::${STABLE} + + # Ensures the current branch is gh-pages, + # Creates / updates the "stable" and "latest" plain text files with the corresponding versions + # Commits if the files were changed + # Finally pushes if there are unpushed commits + - name: Create version files + #if: >- + # github.event_name != 'workflow_dispatch' + run: | + LATEST=${{ steps.set_versions.outputs.LATEST }} + STABLE=${{ steps.set_versions.outputs.STABLE }} + git checkout gh-pages + echo "${STABLE}" > stable.txt + git add stable.txt && git update-index --refresh + git diff-index --quiet HEAD -- || git commit -m "Set stable to ${STABLE}" + echo "${LATEST}" > latest.txt + git add latest.txt && git update-index --refresh + git diff-index --quiet HEAD -- || git commit -m "Set latest to ${LATEST}" + git push origin gh-pages + + # Because the output of the index.yaml is also in gh-pages we want to ensure the jobs run after each other + # This releases the helm chart + release-helm-chart: + if: >- + github.event_name == 'release' && + github.event.action == 'published' && + startsWith(github.event.release.name, 'v') && + !github.event.release.draft && + !github.event.release.prerelease || + github.event_name == 'workflow_dispatch' + # depending on default permission settings for your org (contents being read-only or read-write for workloads), you will have to add permissions + # see: https://docs.github.com/en/actions/security-guides/automatic-token-authentication#modifying-the-permissions-for-the-github_token + needs: build + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Configure Git + run: | + git config user.name "$GITHUB_ACTOR" + git config user.email "$GITHUB_ACTOR@users.noreply.github.com" + + - name: Install Helm + uses: azure/setup-helm@v3 + with: + version: v3.8.1 + + - name: Run chart-releaser + uses: helm/chart-releaser-action@v1.4.1 + env: + CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml index d325d57fdcc..87d868c94ad 100644 --- a/.github/workflows/build-packages.yml +++ b/.github/workflows/build-packages.yml @@ -21,18 +21,21 @@ jobs: asset_prefix: '_' asset_path: jans sign_cmd: dpkg-sig -s builder -k DE92BEF14A1A4E542F678B64DC3C790386C73900 + python_version: 3.8 - name: el8 - asset_suffix: .el8.x86_64.rpm + asset_suffix: -el8.x86_64.rpm build_files: rpm/el8 asset_prefix: '-' asset_path: jans/rpmbuild/RPMS/x86_64 sign_cmd: rpm --addsign + python_version: 3.6 - name: suse15 - asset_suffix: .suse15.x86_64.rpm + asset_suffix: -suse15.x86_64.rpm build_files: rpm/suse15 asset_prefix: '-' asset_path: jans/rpmbuild/RPMS/x86_64 sign_cmd: rpm --addsign + python_version: 3.6 steps: - name: Getting build dependencies @@ -42,11 +45,14 @@ jobs: mkdir -p jans/jans-src/opt/ cp -rp packaging/${{ matrix.build_files }}/* jans/ wget https://raw.githubusercontent.com/JanssenProject/jans/main/jans-linux-setup/jans_setup/install.py -O jans/install.py - sudo apt install -y python3-distutils python3-ldap3 build-essential devscripts debhelper rpm dpkg-sig + sudo add-apt-repository ppa:deadsnakes/ppa + sudo apt-get update + sudo apt-get install -y python${{ matrix.python_version }} + sudo apt install -y build-essential devscripts debhelper rpm dpkg-sig python3-ldap3 python3-requests python3-ruamel.yaml python3-pymysql python3-crypto python3-distutils python3-prompt-toolkit python${{ matrix.python_version }}-distutils - name: Import GPG key id: import_gpg continue-on-error: true - uses: crazy-max/ghaction-import-gpg@v4 + uses: crazy-max/ghaction-import-gpg@v5 with: gpg_private_key: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY }} passphrase: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY_PASSPHRASE }} @@ -67,11 +73,13 @@ jobs: id: run_build run: | cd jans/ - sudo python3.8 install.py --no-setup + sudo python${{ matrix.python_version }} install.py -download-exit -yes cp -r /opt/dist jans-src/opt/ cp -r /opt/jans jans-src/opt/ touch jans-src/opt/jans/jans-setup/package rm -rf install.py install jans-cli + rm -rf jans-src/opt/jans/jans-setup/logs/setup.log + rm -rf jans-src/opt/jans/jans-setup/logs/setup_error.log sed -i "s/%VERSION%/${{ steps.previoustag.outputs.version }}/g" run-build.sh cat run-build.sh sudo ./run-build.sh @@ -160,7 +168,7 @@ jobs: mv jans-cli.pyz jans-cli-linux-X86-64.pyz sha256sum jans-cli-linux-X86-64.pyz > jans-cli-linux-X86-64.pyz.sha256sum - name: Set up Python 3.6 - uses: actions/setup-python@v3 + uses: actions/setup-python@v4 with: python-version: 3.6 - name: Build with Ubuntu @@ -179,7 +187,7 @@ jobs: make zipapp mv jans-cli.pyz jans-cli-linux-ubuntu-X86-64.pyz sha256sum jans-cli-linux-ubuntu-X86-64.pyz > jans-cli-linux-ubuntu-X86-64.pyz.sha256sum - - uses: actions/cache@v2.1.7 + - uses: actions/cache@v3 id: cache-installers with: path: | @@ -206,7 +214,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v3 - - uses: actions/cache@v2.1.7 + - uses: actions/cache@v3 id: cache-installers with: path: | diff --git a/.github/workflows/build-wars.yml b/.github/workflows/build-wars.yml new file mode 100644 index 00000000000..a0a58ad6aaa --- /dev/null +++ b/.github/workflows/build-wars.yml @@ -0,0 +1,91 @@ +name: Build Wars +on: + workflow_dispatch: + pull_request: + branches: + - main + paths: + - 'agama/**' + - 'jans-auth-server/**' + - 'jans-bom/**' + - 'jans-config-api/**' + - 'jans-core/**' + - 'jans-eleven/**' + - 'jans-fido2/**' + - 'jans-notify/**' + - 'jans-orm/**' + - 'jans-scim/**' + + push: + branches: + - main + paths: + - 'agama/**' + - 'jans-auth-server/**' + - 'jans-bom/**' + - 'jans-config-api/**' + - 'jans-core/**' + - 'jans-eleven/**' + - 'jans-fido2/**' + - 'jans-notify/**' + - 'jans-orm/**' + - 'jans-scim/**' +jobs: + build: + name: Build wars + runs-on: ubuntu-latest + continue-on-error: true + strategy: + max-parallel: 11 + matrix: + fldrpath: ['jans-bom','agama','jans-fido2','jans-core','jans-eleven','jans-notify','jans-auth-server','jans-orm','jans-config-api','jans-scim'] + steps: + - name : Variables + run: | + echo ${{ github.repository }} + echo ${{ github.actor }} + + - uses: actions/checkout@v3 + with: + repository: ${{ github.repository }} + token: ${{ secrets.githubtoken }} + + - name: find changed directories + run: | + if [ $GITHUB_BASE_REF ]; then + # Pull Request + echo "Triggerring event: pull request" + echo Pull request base ref: $GITHUB_BASE_REF + git fetch origin $GITHUB_BASE_REF --depth=1 + if [ ${{ github.event.action }} = "opened" ]; then + echo "Triggerring action: opened" + echo "DIRECTORIES_CHANGED=$( git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | cut -d/ -f1 | sort -u | sed -z 's/\n/,/g;s/^/[/;s/,$/]/;s/$/\n/')" >> ${GITHUB_ENV} + fi + if [ ${{ github.event.action }} = "synchronize" ]; then + echo "Triggerring action: synchronize" + echo "DIRECTORIES_CHANGED=$( git diff --name-only ${{ github.event.before }} ${{ github.event.pull_request.head.sha }} | cut -d/ -f1 | sort -u | sed -z 's/\n/,/g;s/^/[/;s/,$/]/;s/$/\n/')" >> ${GITHUB_ENV} + fi + else + # Push + echo "Triggerring event: push" + git fetch origin ${{ github.event.before }} --depth=1 + echo "DIRECTORIES_CHANGED=$( git diff --name-only ${{ github.event.before }} ${{ github.sha }} | cut -d/ -f1 | sort -u | sed -z 's/\n/,/g;s/^/[/;s/,$/]/;s/$/\n/')" >> ${GITHUB_ENV} + fi + echo "$DIRECTORIES_CHANGED" + + - uses: actions/setup-java@v3 + if: contains(env.DIRECTORIES_CHANGED, matrix.fldrpath) + with: + java-version: '11' + distribution: 'zulu' + + - name: Publish package + if: contains(env.DIRECTORIES_CHANGED, matrix.fldrpath) + env: + GITHUB_USERNAME: ${{ github.actor }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + pwd + mvn clean install -DskipTests --file ${{ matrix.fldrpath }} + mvn -B package --file ${{ matrix.fldrpath }}/pom.xml -Dmaven.test.skip=true + mvn --file ${{ matrix.fldrpath }}/pom.xml deploy -Dmaven.test.skip=true \ No newline at end of file diff --git a/.github/workflows/central_code_quality_check.yml b/.github/workflows/central_code_quality_check.yml index 6d5b7d2eae4..c2f13cac27a 100644 --- a/.github/workflows/central_code_quality_check.yml +++ b/.github/workflows/central_code_quality_check.yml @@ -8,25 +8,45 @@ on: branches: - master - main - - "!update-pycloud-in-**" - paths-ignore: - - 'docker-jans-**/CHANGELOG.md' - - 'docker-jans-**/version.txt' - - 'jans-pycloudlib/CHANGELOG.md' - - 'jans-pycloudlib/jans/pycloudlib/version.py' - - '**.md' + - '!update-pycloud-in-**' + paths: + - 'jans-auth-server/**' + - 'jans-orm/**' + - 'jans-config-api/**' + - 'jans-scim/**' + - 'jans-core/**' + - 'jans-notify/**' + - 'jans-fido2/**' + - 'jans-eleven/**' + - 'agama/**' + - 'jans-linux-setup/**' + - 'jans-cli/**' + - 'jans-pycloudlib/**' + - '!**/CHANGELOG.md' + - '!**.txt' + # TODO: Optimize so that workflow is only triggered for `opened` and `synchronize` actions pull_request: branches: - master - main - - "!update-pycloud-in-**" - paths-ignore: - - 'docker-jans-**/CHANGELOG.md' - - 'docker-jans-**/version.txt' - - 'jans-pycloudlib/CHANGELOG.md' - - 'jans-pycloudlib/jans/pycloudlib/version.py' - - '**.md' + - '!update-pycloud-in-**' + paths: + - 'jans-auth-server/**' + - 'jans-orm/**' + - 'jans-config-api/**' + - 'jans-scim/**' + - 'jans-core/**' + - 'jans-notify/**' + - 'jans-fido2/**' + - 'jans-eleven/**' + - 'agama/**' + - 'jans-linux-setup/**' + - 'jans-cli/**' + - 'jans-pycloudlib/**' + - '!**/CHANGELOG.md' + - '!**.txt' + workflow_dispatch: jobs: @@ -36,18 +56,18 @@ jobs: strategy: fail-fast: false matrix: - module: [jans-auth-server, jans-config-api, jans-core, jans-linux-setup, jans-cli, jans-client-api, jans-fido2, jans-notify, jans-orm, jans-scim, jans-eleven, jans-pycloudlib] + module: [jans-auth-server, agama, jans-config-api, jans-core, jans-linux-setup, jans-cli, jans-fido2, jans-notify, jans-orm, jans-scim, jans-eleven, jans-pycloudlib] env: JVM_PROJECTS: | jans-auth-server jans-orm jans-config-api - jans-client-api jans-scim jans-core jans-notify jans-fido2 jans-eleven + agama NON_JVM_PROJECTS: | jans-linux-setup jans-cli @@ -63,18 +83,33 @@ jobs: run: | if [ $GITHUB_BASE_REF ]; then # Pull Request + echo "Triggering event: pull request" + echo Pull request base ref: $GITHUB_BASE_REF git fetch origin $GITHUB_BASE_REF --depth=1 - echo "CHANGED_DIR=$( git diff --name-only ${{ github.event.before }} ${{ github.event.pull_request.head.sha }} | cut -d/ -f1 | sort -u | sed -z 's/\n/,/g;s/^/[/;s/,$/]/;s/$/\n/')" >> ${GITHUB_ENV} + if [ ${{ github.event.action }} = "opened" ]; then + echo "Triggering action: opened" + echo "CHANGED_DIR=$( git diff --name-only ${{ github.event.pull_request.base.sha }} ${{ github.event.pull_request.head.sha }} | cut -d/ -f1 | sort -u | sed -z 's/\n/,/g;s/^/[/;s/,$/]/;s/$/\n/')" >> ${GITHUB_ENV} + fi + if [ ${{ github.event.action }} = "synchronize" ]; then + echo "Triggering action: synchronize" + echo "CHANGED_DIR=$( git diff --name-only ${{ github.event.before }} ${{ github.event.pull_request.head.sha }} | cut -d/ -f1 | sort -u | sed -z 's/\n/,/g;s/^/[/;s/,$/]/;s/$/\n/')" >>${GITHUB_ENV} + fi else # Push + echo "Triggerring event: push" git fetch origin ${{ github.event.before }} --depth=1 echo "CHANGED_DIR=$( git diff --name-only ${{ github.event.before }} $GITHUB_SHA | cut -d/ -f1 | sort -u | sed -z 's/\n/,/g;s/^/[/;s/,$/]/;s/$/\n/')" >> ${GITHUB_ENV} fi - name: check env run: | - echo ${{ env.CHANGED_DIR }} - echo ${{ matrix.module }} + echo changed dir list: ${{ env.CHANGED_DIR }} + echo Matrix module: ${{ matrix.module }} + echo GH event action: ${{ github.event.action }} + echo PR base sha: ${{ github.event.pull_request.base.sha }} + echo PR head sha: ${{ github.event.pull_request.head.sha }} + echo event before: ${{ github.event.before }} + echo GH sha: $GITHUB_SHA - name: Set up JDK 11 # JanssenProject/jans-cli is too similar to JanssenProject/jans-client-api as the contains function is returning it belonging to the JVM_PROJECT @@ -87,7 +122,7 @@ jobs: - name: Cache SonarCloud packages for JVM based project # JanssenProject/jans-cli is too similar to JanssenProject/jans-client-api as the contains function is returning it belonging to the JVM_PROJECT if: contains(env.CHANGED_DIR, matrix.module) && contains(env.JVM_PROJECTS, matrix.module) && matrix.module != 'jans-cli' - uses: actions/cache@v2.1.7 + uses: actions/cache@v3 with: path: ~/.sonar/cache key: ${{ runner.os }}-sonar @@ -104,8 +139,6 @@ jobs: case ${{ matrix.module }} in "jans-auth-server") ;& - "jans-client-api") - ;& "jans-scim") ;& "jans-eleven") diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index bbd239ffe65..a50dc03f2a6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -46,7 +46,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -58,7 +58,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # ℹī¸ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -72,4 +72,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/docker_build_image.yml b/.github/workflows/docker_build_image.yml index 194d839a0b6..ef0acf3d80f 100644 --- a/.github/workflows/docker_build_image.yml +++ b/.github/workflows/docker_build_image.yml @@ -28,9 +28,17 @@ on: workflow_dispatch: inputs: services: - description: 'One or set of the docker images. Format as following: "docker-jans-auth-server docker-jans-certmanager docker-jans-client-api docker-jans-config-api docker-jans-configurator docker-jans-fido2 docker-jans-persistence-loader docker-jans-scim"' + description: 'One or set of the docker images. Format as following: "docker-jans-auth-server docker-jans-certmanager docker-jans-config-api docker-jans-configurator docker-jans-fido2 docker-jans-persistence-loader docker-jans-scim docker-jans-monolith"' required: true - default: 'docker-jans-auth-server docker-jans-certmanager docker-jans-client-api docker-jans-config-api docker-jans-configurator docker-jans-fido2 docker-jans-persistence-loader docker-jans-scim' + default: 'docker-jans-auth-server docker-jans-certmanager docker-jans-config-api docker-jans-configurator docker-jans-fido2 docker-jans-persistence-loader docker-jans-scim docker-jans-monolith' + cn_version: + description: 'The war version to build the image off' + required: false + default: '' + image_tag: + description: 'The manual image tag to post' + required: false + default: '' tags: description: 'Tags' required: false @@ -41,7 +49,7 @@ jobs: strategy: max-parallel: 8 matrix: - docker-images: ["auth-server", "certmanager", "config-api", "client-api", "configurator", "fido2", "persistence-loader", "scim"] + docker-images: ["auth-server", "certmanager", "config-api", "configurator", "fido2", "persistence-loader", "scim", "monolith"] steps: - name: Checkout uses: actions/checkout@v3 @@ -55,12 +63,13 @@ jobs: DEFAULT_ALL=${{ github.event.inputs.services }} if [ -z "$DEFAULT_ALL" ] then - DEFAULT_ALL="docker-jans-auth-server docker-jans-certmanager docker-jans-client-api docker-jans-config-api docker-jans-configurator docker-jans-fido2 docker-jans-persistence-loader docker-jans-scim" + DEFAULT_ALL="docker-jans-auth-server docker-jans-certmanager docker-jans-config-api docker-jans-configurator docker-jans-fido2 docker-jans-persistence-loader docker-jans-scim docker-jans-monolith" else echo "$DEFAULT_ALL" fi # Detect actual docker folders that changed if error arises default to all images - DIRECTORIES_CHANGED=$(git diff --name-only ${{ github.event.before }} ${{ github.sha }} | cut -d/ -f1 | sort -u | grep "docker-jans" || echo "$DEFAULT_ALL") + pull_number=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH") + DIRECTORIES_CHANGED=$(gh pr view $pull_number --json files --jq '.files.[].path' | cut -d/ -f1 | sort -u | grep "docker-jans" || echo "$DEFAULT_ALL") if [[ "$DIRECTORIES_CHANGED" =~ "${{ matrix.docker-images }}" ]]; then echo "A change in this images directory has occured" echo ::set-output name=build::${BUILD} @@ -94,13 +103,31 @@ jobs: if [[ $VERSION =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\_[a-b]{1}[0-9]{1,3}$ ]]; then TAGS="$TAGS,${DOCKER_IMAGE}:${MAIN_VERSION}_dev" fi + # If the user passed a manual image tag to build a custom manual image + MANUAL_IMAGE_TAG=${{ github.event.inputs.image_tag }} + if [ ! -z "$MANUAL_IMAGE_TAG" ] + then + TAGS="$TAGS,${DOCKER_IMAGE}:${MANUAL_IMAGE_TAG}" + echo "Manual image tag has been inputted by the user" + else + echo "$TAGS" + fi + # If the user passed a war version to build off ,change this war version. + CN_VERSION=${{ github.event.inputs.cn_version }} + if [ ! -z "$CN_VERSION" ] + then + python3 -c "from dockerfile_parse import DockerfileParser ; dfparser = DockerfileParser('./docker-jans-${{ matrix.docker-images }}') ; dfparser.envs['CN_VERSION'] = '${{ github.event.inputs.cn_version }}'" + echo "War version has been modified." + else + echo "$CN_VERSION" + fi echo ::set-output name=tags::${TAGS} echo ::set-output name=build::${BUILD} # UPDATE BUILD DATES INSIDE THE DOCKERFILE BEFORE BUILDING THE DEV IMAGES TRIGGERED BY JENKINS - name: Setup Python 3.7 if: github.event_name == 'workflow_dispatch' - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v4 with: python-version: 3.7 @@ -131,11 +158,11 @@ jobs: - name: Set up Docker Buildx if: steps.build_docker_image.outputs.build && steps.prep.outputs.build id: buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v2 - name: Cache Docker layers if: steps.build_docker_image.outputs.build && steps.prep.outputs.build - uses: actions/cache@v2.1.7 + uses: actions/cache@v3 with: path: /tmp/.buildx-cache key: ${{ runner.os }}-buildx-${{ github.sha }} @@ -144,7 +171,7 @@ jobs: - name: Login to Docker Hub if: github.event_name != 'pull_request' - uses: docker/login-action@v1 + uses: docker/login-action@v2 with: username: ${{ secrets.MOAUTO_DOCKERHUB_USERNAME }} password: ${{ secrets.MOAUTO_DOCKERHUB_TOKEN }} @@ -152,7 +179,7 @@ jobs: - name: Build and push if: steps.build_docker_image.outputs.build && steps.prep.outputs.build id: docker_build - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v3 with: builder: ${{ steps.buildx.outputs.name }} context: ./docker-jans-${{ matrix.docker-images }} diff --git a/.github/workflows/docker_imagescan.yml b/.github/workflows/docker_imagescan.yml index 85fab20ed13..c6e1ad168d1 100644 --- a/.github/workflows/docker_imagescan.yml +++ b/.github/workflows/docker_imagescan.yml @@ -26,9 +26,31 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - docker-images: ["auth-server", "certmanager", "config-api", "client-api", "configurator", "fido2", "persistence-loader", "scim"] + docker-images: ["auth-server", "certmanager", "config-api", "configurator", "fido2", "persistence-loader", "scim", "monolith"] steps: - uses: actions/checkout@v3 + + # UPDATE BUILD DATES INSIDE THE DOCKERFILE BEFORE BUILDING THE DEV IMAGES TRIGGERED BY JENKINS + - name: Setup Python 3.7 + if: github.event_name == 'workflow_dispatch' + uses: actions/setup-python@v4 + with: + python-version: 3.7 + - name: Install Python dependencies + if: github.event_name == 'workflow_dispatch' + run: | + sudo apt-get update + sudo python3 -m pip install --upgrade pip + sudo pip3 install setuptools --upgrade + sudo pip3 install -r ./automation/requirements.txt + sudo apt-get update + sudo apt-get install jq + - name: Update Build date in Dockerfile + if: github.event_name == 'workflow_dispatch' + id: update_build_date_in_dockerfile + run: | + sudo python3 ./automation/auto_update_build_date.py + #END UPDATE BUILD DATES INSIDE THE DOCKERFILE BEFORE BUILDING THE DEV IMAGES TRIGGERED BY JENKINS - name: Build the Container image run: docker build ./docker-jans-${{ matrix.docker-images }} --file ./docker-jans-${{ matrix.docker-images }}/Dockerfile --tag localbuild/scanimage:latest - uses: anchore/scan-action@v3 @@ -38,7 +60,7 @@ jobs: acs-report-enable: true fail-build: false - name: upload Anchore scan SARIF report - uses: github/codeql-action/upload-sarif@v1 + uses: github/codeql-action/upload-sarif@v2 with: sarif_file: results.sarif - uses: azure/container-scan@v0 diff --git a/.github/workflows/docker_triggerdevbuild.yml b/.github/workflows/docker_triggerdevbuild.yml index 5d573600c3f..76fb12c0b2d 100644 --- a/.github/workflows/docker_triggerdevbuild.yml +++ b/.github/workflows/docker_triggerdevbuild.yml @@ -14,7 +14,7 @@ jobs: - name: Import GPG key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v4 + uses: crazy-max/ghaction-import-gpg@v5 with: gpg_private_key: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY }} passphrase: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY_PASSPHRASE }} @@ -22,7 +22,7 @@ jobs: git_commit_gpgsign: true - name: Setup Python 3.7 - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v4 with: python-version: 3.7 @@ -48,7 +48,7 @@ jobs: # Buggy behaviour with gh pr command. Will use the following action until bugs have been fixed. #PR=$(gh pr create --head $PR_DOCKER_DEV_BRANCH_NAME --assignee "moabu" --base "master" --body "Updated build date. Auto-generated." --label "enhancement,bot" --reviewer "moabu" --title "chore(Dockerfiles): updated build dates" || echo "PR Branch is already open") - name: Open PR - uses: peter-evans/create-pull-request@v3 + uses: peter-evans/create-pull-request@v4 with: token: ${{ secrets.MOAUTO_WORKFLOW_TOKEN }} committer: mo-auto <54212639+mo-auto@users.noreply.github.com> diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 00000000000..e9adc35af1a --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,116 @@ +name: documentation +on: + workflow_dispatch: + pull_request: + types: + - opened + - edited + paths: + - 'docs/**' + +jobs: + docs: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Set up Python 3.7 + uses: actions/setup-python@v4 + with: + python-version: 3.7 + + - name: Auto-merge inhouse doc prs + run: | + sudo apt-get update + sudo apt-get install jq + echo "${{ secrets.MOAUTO_WORKFLOW_TOKEN }}" > token.txt + gh auth login --with-token < token.txt + pull_number=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH") + NUMBER_OF_FOLDERS_CHANGED=$( gh pr view $pull_number --json files --jq '.files.[].path' | cut -d/ -f1 | sort -u | wc -l) + echo "The number of folders that changed are $NUMBER_OF_FOLDERS_CHANGED" + IS_USER_ORG_MEMBER=$(gh api -H "Accept: application/vnd.github.v3+json" --hostname github.com /orgs/${{ github.repository_owner }}/members?per_page=100 | jq .[].login | grep ${{ github.actor }}) + echo "checking if ${{ github.actor }} belongs to the ${{ github.repository_owner }}. Found $IS_USER_ORG_MEMBER." + PULL_NUMBER=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH") + #The number of folders changed should be 1. Otherwise the contributor has touched other folders besides /docs. + if [[ $NUMBER_OF_FOLDERS_CHANGED == "1" ]] && [[ ! -z "$IS_USER_ORG_MEMBER" ]]; then + echo "Approving PR $PULL_NUMBER" + gh pr review --approve $PULL_NUMBER + echo "Merging PR $PULL_NUMBER" + gh pr merge --squash --auto $PULL_NUMBER + echo "" + else + echo "Bot will not merge this as it does not meet the requirements." + echo "Either the developer has merged with doc changes code changes or an external contributor has requested doc changes." + fi + + check_pr: + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Install latest GH + continue-on-error: true + run: | + VERSION=`curl "https://api.github.com/repos/cli/cli/releases/latest" | grep '"tag_name"' | sed -E 's/.*"([^"]+)".*/\1/' | cut -c2-` + echo $VERSION + curl -sSL https://github.com/cli/cli/releases/download/v${VERSION}/gh_${VERSION}_linux_amd64.tar.gz -o gh_${VERSION}_linux_amd64.tar.gz + tar xvf gh_${VERSION}_linux_amd64.tar.gz + sudo cp gh_${VERSION}_linux_amd64/bin/gh /usr/local/bin/ + gh version + + - name: Check commit message + continue-on-error: true + id: check_message + run: | + PULL_NUMBER=$(echo "$GITHUB_REF" | awk -F / '{print $3}') + echo "Parsing commits from PR $PULL_NUMBER" + MESSAGE=$(gh pr view "$PULL_NUMBER" --json commits | jq '.' | grep "messageHeadline" | cut -d: -f2- | grep "docs:" || echo "") + echo "$MESSAGE" + if [[ -z "$MESSAGE" ]]; then + echo "conventional commit starting with docs: does not exist" + exit 1 + else + echo "conventional commit exists starting with docs:" + exit 0 + fi + + - name: Verify Changed files + if: steps.check_message.outcome != 'success' + id: check_files + run: | + pull_number=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH") + CHANGES=$(gh pr view $pull_number --json files --jq '.files.[].path' | cut -d/ -f1 | grep '^docs' | sort -u ) + if [ -n "$CHANGES" ]; then + echo "File changes exist" + exit 0 + else + echo "No file changes" + exit 1 + fi + + + lint_docs: + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Markdown linter + continue-on-error: true + run: | + sudo apt-get install rubygems -y + sudo gem install mdl + mdl --style automation/markdown/.mdl_style.rb docs/ + diff --git a/.github/workflows/jans_pycloud_build_package.yml b/.github/workflows/jans_pycloud_build_package.yml index bfd6b66ea47..10118c2c787 100644 --- a/.github/workflows/jans_pycloud_build_package.yml +++ b/.github/workflows/jans_pycloud_build_package.yml @@ -37,7 +37,7 @@ jobs: - name: Import GPG key id: import_gpg if: github.actor != 'mo-auto' - uses: crazy-max/ghaction-import-gpg@v4 + uses: crazy-max/ghaction-import-gpg@v5 with: gpg_private_key: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY }} passphrase: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY_PASSPHRASE }} @@ -48,7 +48,7 @@ jobs: if: github.actor != 'mo-auto' id: build_stable_reqs run: | - dockerimages="auth-server certmanager client-api config-api configurator fido2 persistence-loader scim" + dockerimages="auth-server certmanager config-api configurator fido2 persistence-loader scim" for image in $dockerimages; do sed -i '/git+https/c\git+https://github.com/${{ github.repository }}@${{ github.sha }}#egg=jans-pycloudlib&subdirectory=jans-pycloudlib' ./docker-jans-$image/requirements.txt done @@ -66,7 +66,7 @@ jobs: #PR=$(gh pr create --head $PR_DOCKER_DEV_BRANCH_NAME --assignee "moabu" --base "master" --body "Updated build date. Auto-generated." --label "enhancement,bot" --reviewer "moabu" --title "chore(Dockerfile): updated build dates" || echo "PR Branch is already open") - name: Open PR id: cpr - uses: peter-evans/create-pull-request@v3 + uses: peter-evans/create-pull-request@v4 if: github.event_name == 'pull_request' && github.actor != 'mo-auto' with: token: ${{ secrets.MOAUTO_WORKFLOW_TOKEN }} @@ -87,7 +87,7 @@ jobs: - name: Enable Pull Request Automerge if: steps.cpr.outputs.pull-request-operation == 'created' && github.actor != 'mo-auto' - uses: peter-evans/enable-pull-request-automerge@v1 + uses: peter-evans/enable-pull-request-automerge@v2 with: token: ${{ secrets.MOAUTO_WORKFLOW_TOKEN }} pull-request-number: ${{ steps.cpr.outputs.pull-request-number }} @@ -96,7 +96,7 @@ jobs: # Buggy behaviour with gh pr command. Will use the following action until bugs have been fixed. #PR=$(gh pr create --head $PR_DOCKER_DEV_BRANCH_NAME --assignee "moabu" --base "master" --body "Updated build date. Auto-generated." --label "enhancement,bot" --reviewer "moabu" --title "chore(Dockerfile): updated build dates" || echo "PR Branch is already open") - name: Open PR - uses: peter-evans/create-pull-request@v3 + uses: peter-evans/create-pull-request@v4 if: github.event_name != 'pull_request' && github.actor != 'mo-auto' with: token: ${{ secrets.MOAUTO_WORKFLOW_TOKEN }} diff --git a/.github/workflows/label_pr_issues.yml b/.github/workflows/label_pr_issues.yml new file mode 100644 index 00000000000..518b705b132 --- /dev/null +++ b/.github/workflows/label_pr_issues.yml @@ -0,0 +1,80 @@ +# Please do not attempt to edit this flow without the direct consent from the DevOps team. This file is managed centrally. +# Contact @moabu +name: Label PRs and Issues +on: + pull_request: + types: + - opened + - edited + issues: + types: + - opened + - edited + workflow_dispatch: + +jobs: + label: + name: label PR + runs-on: ubuntu-latest + steps: + - name: check out code + uses: actions/checkout@v3 + + - name: Setup Python 3.7 + uses: actions/setup-python@v4 + with: + python-version: 3.7 + + - name: Install dependencies + run: | + sudo apt-get update + sudo python3 -m pip install --upgrade pip + sudo pip3 install setuptools --upgrade + sudo pip3 install -r ./automation/requirements.txt + sudo apt-get update + sudo apt-get install jq + curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null + sudo apt update + sudo apt install gh + + - name: Update labels + run: | + git config --global user.name "mo-auto" + git config --global user.email "54212639+mo-auto@users.noreply.github.com" + echo "${{ secrets.MOAUTO_WORKFLOW_TOKEN }}" > token.txt + gh auth login --with-token < token.txt + if [[ "${{github.event_name}}" == "issues" ]]; then + echo "Activated by an issue event" + issue_or_pull_number=$(jq --raw-output .issue.number "$GITHUB_EVENT_PATH") + changed_files="NONE" + operation="issue" + branch="NONE" + title=$(jq --raw-output .issue.title "$GITHUB_EVENT_PATH") + else + echo "Activated by a PR event" + issue_or_pull_number=$(jq --raw-output .pull_request.number "$GITHUB_EVENT_PATH") + gh pr view $issue_or_pull_number --json files --jq '.files.[].path' > changed_files + changed_files=$(paste -s -d, changed_files) + operation="pr" + branch=${{github.base_ref}} + title=$(jq --raw-output .pull_request.title "$GITHUB_EVENT_PATH") + fi + sudo python3 ./automation/github-labels/label.py ${issue_or_pull_number} ${changed_files} ${branch} ${operation} ${title} + + # Remove once add-project option in gh cli support project beta. Add to labels.py + # TODO: Add PR to project + - name: Add issue to project + if: github.event_name != 'pull_request' + env: + ISSUE_ID: ${{ github.event.issue.node_id }} + PROJECT_ID: ${{ secrets.JANSSEN_ISSUE_DASHBOARD_PROJECT_ID }} + run: | + item_id="$( gh api graphql -f query=' + mutation($project:ID!, $issue:ID!) { + addProjectNextItem(input: {projectId: $project, contentId: $issue}) { + projectNextItem { + id + } + } + }' -f project=$PROJECT_ID -f issue=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')" \ No newline at end of file diff --git a/.github/workflows/microk8s.yml b/.github/workflows/microk8s.yml new file mode 100644 index 00000000000..40751228541 --- /dev/null +++ b/.github/workflows/microk8s.yml @@ -0,0 +1,38 @@ +name: microk8s +on: + push: + branches: + - master + - main + paths: + - "charts/**" + - "automation/startjanssendemo.sh" + pull_request: + branches: + - master + - main + paths: + - "charts/**" + - "automation/startjanssendemo.sh" + workflow_dispatch: +jobs: + microk8s: + strategy: + max-parallel: 6 + matrix: + istio: ["true", "false"] + # add '"pgsql" when supported + persistence-backends: ["LDAP","MYSQL"] + fail-fast: false + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Test dev setup + id: start_up_script + run: | + ip=$(ifconfig eth0 | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1') + chmod u+x automation/startjanssendemo.sh + sudo bash ./automation/startjanssendemo.sh demoexample.jans.io ${{ matrix.persistence-backends }} true $ip ${{ matrix.istio }} + sudo bash ./automation/analyze_test_microk8s_setup.sh "${{ secrets.MOAUTO_ROCKETCHAT_TOKEN }}" "${{ secrets.MOAUTO_ROCKETCHAT_ID }}" ${{ matrix.persistence-backends }} diff --git a/.github/workflows/microk8s_couchbase.yml b/.github/workflows/microk8s_couchbase.yml deleted file mode 100644 index 10ae9db13b0..00000000000 --- a/.github/workflows/microk8s_couchbase.yml +++ /dev/null @@ -1,244 +0,0 @@ -name: couchbase -on: - push: - branches: - - master - - main - paths: - - "helm/**" - pull_request: - branches: - - master - - main - paths: - - "helm/**" - workflow_dispatch: -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Set up Python 3.7 - uses: actions/setup-python@v2.3.2 - with: - python-version: 3.7 - - - name: Install dependencies - run: | - sudo apt-get update - sudo python3 -m pip install --upgrade pip - sudo pip3 install setuptools --upgrade - sudo pip3 install pyOpenSSL --upgrade - sudo pip3 install requests --upgrade - sudo apt-get update - sudo apt-get install build-essential - sudo pip3 install shiv - - - name: Install microk8s - run: | - sudo snap install microk8s --classic - sudo snap alias microk8s.kubectl kubectl - sudo microk8s.status --wait-ready - sudo microk8s.enable dns registry ingress - - - - name: Install helm - run: | - curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 get_helm.sh - ./get_helm.sh - sudo helm version - - - name: Install Couchbase - id: microk8s - run: | - git clone https://github.com/GluuFederation/cloud-native-edition.git - cp -rf ./cloud-native-edition/pygluu/kubernetes/templates/couchbase . - cp -rf ./cloud-native-edition/pygluu/kubernetes/templates/couchbase/couchbase-buckets.yaml . - cp -rf ./cloud-native-edition/pygluu/kubernetes/templates/couchbase/couchbase-ephemeral-buckets.yaml . - cd cloud-native-edition - git checkout 4.2 - cd .. - cp -rf ./couchbase ./cloud-native-edition/pygluu/kubernetes/templates - cp -rf couchbase-buckets.yaml ./cloud-native-edition - cp -rf couchbase-ephemeral-buckets.yaml ./cloud-native-edition - cd cloud-native-edition - cat ./pygluu/kubernetes/templates/couchbase/couchbase-user.yaml | sed -s "s@jans-couchbase-user-password@gluu-couchbase-user-password@g" > tmpfile.yaml && mv tmpfile.yaml ./pygluu/kubernetes/templates/couchbase/couchbase-user.yaml - ip=$(ifconfig eth0 | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1') - echo "::set-output name=ip::$ip" - cat < settings.json - { - "ACCEPT_GLUU_LICENSE": "Y", - "TEST_ENVIRONMENT": "Y", - "GLUU_VERSION": "4.2", - "GLUU_UPGRADE_TARGET_VERSION": "", - "NGINX_INGRESS_NAMESPACE": "ingress-nginx", - "GLUU_HELM_RELEASE_NAME": "jans", - "NGINX_INGRESS_RELEASE_NAME": "ningress", - "INSTALL_GLUU_GATEWAY": "N", - "INSTALL_POSTGRES": "N", - "POSTGRES_NAMESPACE": "postgres", - "KONG_NAMESPACE": "gluu-gateway", - "GLUU_GATEWAY_UI_NAMESPACE": "gg-ui", - "KONG_PG_USER": "kong", - "KONG_PG_PASSWORD": "MUs6#@", - "GLUU_GATEWAY_UI_PG_USER": "konga", - "GLUU_GATEWAY_UI_PG_PASSWORD": "MUs6#@", - "KONG_DATABASE": "kong", - "GLUU_GATEWAY_UI_DATABASE": "konga", - "POSTGRES_REPLICAS": 3, - "POSTGRES_URL": "postgres.postgres.svc.cluster.local", - "KONG_HELM_RELEASE_NAME": "kong", - "GLUU_GATEWAY_UI_HELM_RELEASE_NAME": "ggui", - "USE_ISTIO": "N", - "USE_ISTIO_INGRESS": "N", - "ISTIO_SYSTEM_NAMESPACE": "istio-system", - "NODES_IPS": [ - "$ip" - ], - "NODES_ZONES": [], - "NODES_NAMES": [], - "NODE_SSH_KEY": "", - "HOST_EXT_IP": "$ip", - "VERIFY_EXT_IP": "", - "AWS_LB_TYPE": "", - "USE_ARN": "", - "ARN_AWS_IAM": "", - "COUCHBASE_CLUSTER_FILE_OVERRIDE": "Y", - "LB_ADD": "", - "REDIS_URL": "", - "REDIS_TYPE": "", - "REDIS_PW": "", - "REDIS_USE_SSL": "false", - "DEPLOYMENT_ARCH": "microk8s", - "PERSISTENCE_BACKEND": "couchbase", - "INSTALL_JACKRABBIT": "Y", - "JACKRABBIT_STORAGE_SIZE": "4Gi", - "JACKRABBIT_URL": "http://jackrabbit:8080", - "JACKRABBIT_ADMIN_ID": "admin", - "JACKRABBIT_ADMIN_PASSWORD": "admin", - "JACKRABBIT_CLUSTER": "Y", - "JACKRABBIT_PG_USER": "jackrabbit", - "JACKRABBIT_PG_PASSWORD": "jackrabbit", - "JACKRABBIT_DATABASE": "jackrabbit", - "INSTALL_COUCHBASE": "Y", - "COUCHBASE_NAMESPACE": "cbns", - "COUCHBASE_VOLUME_TYPE": "io1", - "COUCHBASE_CLUSTER_NAME": "cbjans", - "COUCHBASE_URL": "cbjans.cbns.svc.cluster.local", - "COUCHBASE_INDEX_NUM_REPLICA": "0", - "COUCHBASE_USER": "jans", - "COUCHBASE_SUPERUSER": "admin", - "COUCHBASE_PASSWORD": "P@ssw0rd", - "COUCHBASE_SUPERUSER_PASSWORD": "P@ssw0rd", - "COUCHBASE_CRT": "", - "COUCHBASE_CN": "Couchbase CN", - "COUCHBASE_SUBJECT_ALT_NAME": "", - "COUCHBASE_BUCKET_PREFIX": "jans", - "GLUU_LDAP_MULTI_CLUSTER": "N", - "COUCHBASE_USE_LOW_RESOURCES": "Y", - "COUCHBASE_DATA_NODES": "", - "COUCHBASE_QUERY_NODES": "", - "COUCHBASE_INDEX_NODES": "", - "COUCHBASE_SEARCH_EVENTING_ANALYTICS_NODES": "", - "COUCHBASE_GENERAL_STORAGE": "", - "COUCHBASE_DATA_STORAGE": "", - "COUCHBASE_INDEX_STORAGE": "", - "COUCHBASE_QUERY_STORAGE": "", - "COUCHBASE_ANALYTICS_STORAGE": "", - "COUCHBASE_BACKUP_SCHEDULE": "*/30 * * * *", - "COUCHBASE_BACKUP_RESTORE_POINTS": 1, - "LDAP_BACKUP_SCHEDULE": "*/30 * * * *", - "NUMBER_OF_EXPECTED_USERS": "", - "EXPECTED_TRANSACTIONS_PER_SEC": "", - "USING_CODE_FLOW": "", - "USING_SCIM_FLOW": "", - "USING_RESOURCE_OWNER_PASSWORD_CRED_GRANT_FLOW": "", - "DEPLOY_MULTI_CLUSTER": "N", - "HYBRID_LDAP_HELD_DATA": "", - "LDAP_JACKRABBIT_VOLUME": "", - "APP_VOLUME_TYPE": 1, - "LDAP_STATIC_VOLUME_ID": "", - "LDAP_STATIC_DISK_URI": "", - "GLUU_CACHE_TYPE": "NATIVE_PERSISTENCE", - "GLUU_NAMESPACE": "gluu", - "GLUU_FQDN": "demoexample.gluu.org", - "COUNTRY_CODE": "US", - "STATE": "TX", - "EMAIL": "support@gluu.org", - "CITY": "Austin", - "ORG_NAME": "Gluu", - "GMAIL_ACCOUNT": "", - "GOOGLE_NODE_HOME_DIR": "", - "IS_GLUU_FQDN_REGISTERED": "N", - "LDAP_PW": "nTB5#|", - "ADMIN_PW": "@4n,Js", - "OXD_SERVER_PW": "n8H0NKuGTXsn", - "OXD_APPLICATION_KEYSTORE_CN": "oxd-server", - "OXD_ADMIN_KEYSTORE_CN": "oxd-server", - "OXD_SERVER_STORAGE": "h2", - "LDAP_STORAGE_SIZE": "4Gi", - "OXAUTH_REPLICAS": 1, - "OXTRUST_REPLICAS": 1, - "LDAP_REPLICAS": 1, - "FIDO2_REPLICAS": 1, - "SCIM_REPLICAS": 1, - "OXSHIBBOLETH_REPLICAS": 1, - "OXPASSPORT_REPLICAS": 1, - "OXD_SERVER_REPLICAS": 1, - "CASA_REPLICAS": 1, - "RADIUS_REPLICAS": 1, - "ENABLE_OXTRUST_API": "N", - "ENABLE_OXTRUST_TEST_MODE": "N", - "ENABLE_CACHE_REFRESH": "N", - "ENABLE_OXD": "Y", - "ENABLE_RADIUS": "Y", - "ENABLE_OXPASSPORT": "Y", - "ENABLE_OXSHIBBOLETH": "Y", - "ENABLE_FIDO2": "Y", - "ENABLE_SCIM": "Y", - "ENABLE_CASA": "Y", - "OXAUTH_KEYS_LIFE": 48, - "ENABLE_OXAUTH_KEY_ROTATE": "Y", - "ENABLE_OXTRUST_API_BOOLEAN": "true", - "ENABLE_OXTRUST_TEST_MODE_BOOLEAN": "false", - "ENABLE_RADIUS_BOOLEAN": "true", - "ENABLE_OXPASSPORT_BOOLEAN": "true", - "ENABLE_CASA_BOOLEAN": "true", - "ENABLE_SAML_BOOLEAN": "true", - "EDIT_IMAGE_NAMES_TAGS": "N", - "CONFIRM_PARAMS": "Y" - } - EOF - wget ${{ secrets.cbpackage }} - sudo make zipapp - wget https://gist.githubusercontent.com/moabu/9da0c5d2b5b3d297be8583c79bbba321/raw/c6e7fe5e28742cafc314280db9ee717173af9779/couchbase-cluster.yaml - sudo ./pygluu-kubernetes.pyz install-couchbase || sudo ./pygluu-kubernetes.pyz install-couchbase - crt=$(cat couchbase_crts_keys/couchbase.crt | base64 ) - echo "::set-output name=crt::$crt" - cd .. - - - name: Test Janssen authorization server with Couchbase backend - id: test_kubernetes - run: | - COUCHBASE_IP=$(sudo microk8s.kubectl get po cbjans-0000 -n cbns --template={{.status.podIP}}) - sudo microk8s.config > config || echo "" - sudo mv config ~/.kube/config || echo "" - sudo kubectl create ns jans - sudo helm install jans -f charts/jans/values.yaml charts/jans -n jans --set global.lbIp=${{ steps.microk8s.outputs.ip }} --set config.configmap.cnCouchbaseCrt=${{ steps.microk8s.outputs.crt }} --set global.provisioner="microk8s.io/hostpath" --set global.cnPersistenceType="couchbase" --set config.configmap.cnCouchbaseUrl="$COUCHBASE_IP" - sudo kubectl delete secret jans-cb-crt -n jans - sudo kubectl create secret generic jans-cb-crt -n jans --from-file=./cloud-native-edition/couchbase_crts_keys/couchbase.crt - sleep 30 - sudo kubectl get po -n jans - sudo kubectl get po -n cbns - sudo kubectl -n jans wait --for=condition=available --timeout=600s deploy/jans-auth-server || sudo kubectl logs -l APP_NAME=auth-server -c jans-auth-server -n jans || echo "Not Found" - sudo kubectl get po -n jans - sudo kubectl -n jans wait --for=condition=available --timeout=300s deploy/jans-client-api || sudo kubectl logs -l APP_NAME=client-api -c jans-client-api -n jans || echo "Not Found" - sudo kubectl get po -n jans - sudo kubectl -n jans wait --for=condition=available --timeout=300s deploy/jans-fido2 || sudo kubectl logs -l APP_NAME=fido2 -c jans-fido2 -n jans || echo "Not Found" - sudo kubectl get po -n jans - sudo kubectl -n jans wait --for=condition=available --timeout=300s deploy/jans-scim || sudo kubectl logs -l APP_NAME=scim -c jans-scim -n jans || echo "Not Found" - sudo kubectl get po -n jans diff --git a/.github/workflows/microk8s_mysql.yml b/.github/workflows/microk8s_mysql.yml deleted file mode 100644 index c7feb38a7dc..00000000000 --- a/.github/workflows/microk8s_mysql.yml +++ /dev/null @@ -1,72 +0,0 @@ -name: mysql -on: - push: - branches: - - master - - main - paths: - - "helm/**" - pull_request: - branches: - - master - - main - paths: - - "helm/**" - workflow_dispatch: -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Install microk8s - id: microk8s - run: | - ip=$(ifconfig eth0 | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1') - echo "::set-output name=ip::$ip" - sudo snap install microk8s --classic - sudo microk8s status --wait-ready - sudo microk8s enable dns registry ingress - - - name: Install helm - run: | - curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 get_helm.sh - ./get_helm.sh - sudo helm version - - - name: Install mysql - run: | - sudo microk8s config > config - sudo microk8s kubectl create ns jans --kubeconfig="$PWD/config" - sudo helm repo add bitnami https://charts.bitnami.com/bitnami - sudo microk8s.kubectl get po - sudo helm install my-release --set auth.rootPassword=Test1234#,auth.database=jans bitnami/mysql -n jans --kubeconfig="$PWD/config" - sudo microk8s kubectl get po --kubeconfig="$PWD/config" -n jans || echo "Not Found" - sleep 30 - sudo microk8s kubectl get po --kubeconfig="$PWD/config" -n jans || echo "Not Found" - sleep 30 - - - - name: Test Janssen authorization server with mysql backend - id: test_kubernetes - run: | - sudo microk8s kubectl get po -n jans --kubeconfig="$PWD/config" - sudo helm install jans -f charts/jans/values.yaml charts/jans -n jans --set global.lbIp=${{ steps.microk8s.outputs.ip }} --set global.provisioner="microk8s.io/hostpath" --set global.cnPersistenceType="sql" --set global.cnPersistenceType="sql" --set config.configmap.cnSqlDbUser="root" --set config.configmap.cnSqlDbHost="my-release-mysql.jans.svc" --set config.configmap.cnSqlDbName="jans" --kubeconfig="$PWD/config" - sleep 30 - sudo microk8s kubectl get po -n jans --kubeconfig="$PWD/config" - sudo microk8s kubectl -n jans wait --for=condition=available --timeout=600s deploy/jans-auth-server --kubeconfig="$PWD/config" || sudo microk8s kubectl logs -l APP_NAME=auth-server -c auth-server -n jans --kubeconfig="$PWD/config" || echo "Not Found" - sudo microk8s kubectl get po -n jans --kubeconfig="$PWD/config" || echo "Not Found" - sudo microk8s kubectl logs -l APP_NAME=persistence-loader -c persistence -n jans --kubeconfig="$PWD/config" - sudo microk8s kubectl get po --kubeconfig="$PWD/config" || echo "Not Found" - sudo microk8s kubectl -n jans wait --for=condition=available --timeout=300s deploy/jans-client-api --kubeconfig="$PWD/config" || sudo microk8s kubectl logs -l APP_NAME=client-api -c client-api -n jans --kubeconfig="$PWD/config" || echo "Not Found" - sudo microk8s kubectl get po -n jans --kubeconfig="$PWD/config" || echo "Not Found" - sudo microk8s kubectl logs -l APP_NAME=persistence-loader -c persistence -n jans --kubeconfig="$PWD/config" - sudo microk8s kubectl -n jans wait --for=condition=available --timeout=300s deploy/jans-fido2 --kubeconfig="$PWD/config" || sudo microk8s kubectl logs -l APP_NAME=fido2 -c fido2 -n jans --kubeconfig="$PWD/config" || echo "Not Found" - sudo microk8s kubectl get po -n jans --kubeconfig="$PWD/config" || echo "Not Found" - sudo microk8s kubectl logs -l APP_NAME=persistence-loader -c persistence -n jans --kubeconfig="$PWD/config" - sudo microk8s kubectl -n jans wait --for=condition=available --timeout=300s deploy/jans-scim --kubeconfig="$PWD/config" || sudo microk8s kubectl logs -l APP_NAME=scim -c scim -n jans --kubeconfig="$PWD/config" || echo "Not Found" - sudo microk8s kubectl get po -n jans --kubeconfig="$PWD/config" || echo "Not Found" diff --git a/.github/workflows/microk8s_opendj.yml b/.github/workflows/microk8s_opendj.yml deleted file mode 100644 index 5185d455c0f..00000000000 --- a/.github/workflows/microk8s_opendj.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: opendj -on: - push: - branches: - - master - - main - paths: - - "helm/**" - pull_request: - branches: - - master - - main - paths: - - "helm/**" - workflow_dispatch: -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Install microk8s - id: microk8s - run: | - ip=$(ifconfig eth0 | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1') - echo "::set-output name=ip::$ip" - sudo snap install microk8s --classic - sudo snap alias microk8s.kubectl kubectl - sudo microk8s.status --wait-ready - sudo microk8s.enable dns registry ingress - - - name: Install helm - run: | - curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 get_helm.sh - ./get_helm.sh - sudo helm version - - - - name: Test Janssen authorization server with LDAP backend - id: test_kubernetes - run: | - wget https://raw.githubusercontent.com/mo-auto/scripts/main/set_microk8s_kube_config.py && chmod u+x set_microk8s_kube_config.py && sudo python3 ./set_microk8s_kube_config.py - sudo kubectl create ns jans - sudo kubectl get po - sudo helm install jans -f charts/jans/values.yaml charts/jans -n jans --set global.lbIp=${{ steps.microk8s.outputs.ip }} --set global.provisioner="microk8s.io/hostpath" --set global.cnPersistenceType="ldap" - sleep 30 - sudo kubectl get po -n jans - sudo kubectl -n jans wait --for=condition=available --timeout=600s deploy/jans-auth-server || sudo kubectl logs -l APP_NAME=auth-server -c jans-auth-server -n jans || echo "Not Found" - sudo kubectl get po -n jans - sudo kubectl -n jans wait --for=condition=available --timeout=300s deploy/jans-client-api || sudo kubectl logs -l APP_NAME=client-api -c jans-client-api -n jans || echo "Not Found" - sudo kubectl get po -n jans - sudo kubectl -n jans wait --for=condition=available --timeout=300s deploy/jans-fido2 || sudo kubectl logs -l APP_NAME=fido2 -c jans-fido2 -n jans || echo "Not Found" - sudo kubectl get po -n jans - sudo kubectl -n jans wait --for=condition=available --timeout=300s deploy/jans-scim || sudo kubectl logs -l APP_NAME=scim -c jans-scim -n jans || echo "Not Found" - sudo kubectl get po -n jans - diff --git a/.github/workflows/microk8s_opendj_istio.yml b/.github/workflows/microk8s_opendj_istio.yml deleted file mode 100644 index 190013380ae..00000000000 --- a/.github/workflows/microk8s_opendj_istio.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: opendj-istio -on: - workflow_dispatch: -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Install microk8s - id: microk8s - run: | - ip=$(ifconfig eth0 | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1') - echo "::set-output name=ip::$ip" - sudo snap install microk8s --classic - sudo snap alias microk8s.kubectl kubectl - sudo microk8s.status --wait-ready - sudo microk8s.enable dns registry ingress - sudo microk8s.config > config - - sudo mv config ~/.kube/config - - - name: Install helm - run: | - curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 - chmod 700 get_helm.sh - ./get_helm.sh - sudo helm version - - - name: Install Istio - id: istio - run: | - sudo curl -L https://istio.io/downloadIstio | sh - - cd istio-1.8.0 - export PATH="$PATH:/home/runner/work/jans-cloud-native/jans-cloud-native/istio-1.8.0/bin - sudo istioctl install --set profile=demo -y - cd .. - - - name: Test Janssen authorization server with LDAP backend0 - id: test_kubernetes - run: | - sudo microk8s.kubectl create ns jans - sudo microk8s.kubectl label ns jans istio-injection=enabled - sudo helm install jans -f charts/jans/values.yaml charts/jans -n jans --set global.lbIp=${{ steps.microk8s.outputs.ip }} --set global.provisioner="microk8s.io/hostpath" --set global.cnPersistenceType="ldap" --set global.istio.enabled="true" - sleep 30 - sudo kubectl get po -n jans - sudo kubectl -n jans wait --for=condition=available --timeout=600s deploy/jans-auth-server || sudo kubectl logs -l APP_NAME=auth-server -c jans-auth-server -n jans || echo "Not Found" - sudo kubectl get po -n jans - sudo kubectl -n jans wait --for=condition=available --timeout=300s deploy/jans-client-api || sudo kubectl logs -l APP_NAME=client-api -c jans-client-api -n jans || echo "Not Found" - sudo kubectl get po -n jans - sudo kubectl -n jans wait --for=condition=available --timeout=300s deploy/jans-fido2 || sudo kubectl logs -l APP_NAME=fido2 -c jans-fido2 -n jans || echo "Not Found" - sudo kubectl get po -n jans - sudo kubectl -n jans wait --for=condition=available --timeout=300s deploy/jans-scim || sudo kubectl logs -l APP_NAME=scim -c jans-scim -n jans || echo "Not Found" - sudo kubectl get po -n jans - diff --git a/.github/workflows/post-to-rancher-partner-charts.yml b/.github/workflows/post-to-rancher-partner-charts.yml new file mode 100644 index 00000000000..0267274f275 --- /dev/null +++ b/.github/workflows/post-to-rancher-partner-charts.yml @@ -0,0 +1,72 @@ +name: rancher-partner-charts +# This posts the latest chart to rancher/partner-charts +on: + push: + tags: + - 'v*' + workflow_dispatch: +jobs: + publish: + name: Publish on rancher partner charts + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python 3.8 + uses: actions/setup-python@v4 + with: + python-version: 3.8 + + - name: Import GPG key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@v5 + with: + gpg_private_key: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY_PASSPHRASE }} + git_user_signingkey: true + git_commit_gpgsign: true + + - name: Get latest tag + id: previoustag + run: | + echo "::set-output name=tag::$(curl https://api.github.com/repos/${{ github.repository }}/releases -s | grep "tag_name" | cut -d '"' -f 4 | grep -o '^\v.*' | head -n 1)" + echo "::set-output name=version::$(curl https://api.github.com/repos/${{ github.repository }}/releases -s | grep "tag_name" | cut -d '"' -f 4 | grep -o '^\v.*' | head -n 1 | cut -d 'v' -f 2)" + - name: Configure Git + run: | + git clone https://mo-auto:${{ secrets.MOAUTO_WORKFLOW_TOKEN }}@github.com/mo-auto/partner-charts.git + git config --global user.name "mo-auto" + git config --global user.email "54212639+mo-auto@users.noreply.github.com" + git config --global user.signingkey "${{ steps.import_gpg.outputs.keyid }}" + cd partner-charts + git remote add upstream https://github.com/rancher/partner-charts.git + git fetch upstream + # ensures current branch is main-source + git checkout main-source + # pulls all new commits made to upstream/main-source + git pull upstream main-source + # this will delete all local changes to main-source + git reset --hard upstream/main-source + # take care, this will delete all changes on the forked main-source + git push origin main-source --force + mkdir ./packages/janssen || echo "directory exists!" + cp ../automation/rancher-partner-charts/package.yaml ./packages/janssen/package.yaml + export PACKAGE=janssen + make prepare + cp ../automation/rancher-partner-charts/app-readme.md ./packages/janssen/charts/app-readme.md + cp ../automation/rancher-partner-charts/questions.yaml ./packages/janssen/charts/questions.yaml + make patch + make clean + git add -A + git commit -S -s -m "feat(janssen): patch helm package" + make charts + git add -A + git commit -S -s -m "feat(janssen): chart helm package" + git push origin + MESSAGE="feat(janssen): update janssen helm partner chart" + BODY=$(<../automation/rancher-partner-charts/pull_request_body.md) + echo "${{ secrets.MOAUTO_WORKFLOW_TOKEN }}" > token.txt + gh auth login --with-token < token.txt + PR=$(gh pr create --base "main-source" --body "${BODY}" --title "${MESSAGE}") + +#TODO: Add docker test before opening a PR in rancher-partner charts. \ No newline at end of file diff --git a/.github/workflows/pr-ref-issue.yml b/.github/workflows/pr-ref-issue.yml new file mode 100644 index 00000000000..b262a1ab97c --- /dev/null +++ b/.github/workflows/pr-ref-issue.yml @@ -0,0 +1,97 @@ +# This workflow enforces each PR has an issue created. +# It makes sure each PR is targeting an open issue. +# If the PR does not target an issue , it will open an issue, write a comment and attempt to edit the PR to link it to the issue +# The author would be asked to update the issues details. + +name: issue->PR +on: + pull_request: + types: + - opened + - reopened + paths-ignore: + - "docs/**" + tags-ignore: + - "release-please-*" + workflow_dispatch: +jobs: + check-prs-issue: + name: Check PRs issue + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - name: Wait for 15 seconds so that GH can attach issue to the PR if already mentioned by author + run: sleep 15s + shell: bash + - name: Install dependencies + run: | + sudo apt-get update + curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg + sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg + echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null + sudo apt-get update + sudo apt-get -y install gh + - name: Add a comment to a PR and create an issue if the PR does not reference an open issue + env: + PR_TITLE: ${{ github.event.pull_request.title }} + PR_BODY: ${{ github.event.pull_request.body }} + PR_NUMBER: ${{ github.event.pull_request.number }} + PR_USER: ${{ github.event.pull_request.user.login }} + REPO_FULL_NAME: ${{ github.event.pull_request.base.repo.full_name }} + run: | + git config --global user.name "mo-auto" + git config --global user.email "54212639+mo-auto@users.noreply.github.com" + echo "${{ secrets.MOAUTO_WORKFLOW_TOKEN }}" | gh auth login --with-token + + issue_found="" + + for issue in $( echo "$PR_TITLE $PR_BODY" | grep -o '#[0-9][0-9]*' ); do + if check_issue="$( gh api "repos/$REPO_FULL_NAME/issues/${issue#"#"}" )" \ + && [ "$( echo "$check_issue" | python3 -c "import sys, json; data = json.load(sys.stdin); print( + 'node_id' in data and 'state' in data and data['node_id'].find('I_') == 0 and data['state'] == 'open' + )" )" = "True" ]; then + issue_found="$issue" + break + fi + done + + if [ -n "$issue_found" ]; then + echo "OK: An open issue ($issue_found) is referenced in your PR ($PR_NUMBER)" + else + echo "Failed: No open issues referenced in your PR ($PR_NUMBER)" >&2 + echo "Creating an issue..." + + if ISSUE_NUMBER="$( echo "{ \"title\": \"fix: $PR_TITLE -autocreated \", \"body\": \"This issue was created because no open issues were referenced in your PR (#$PR_NUMBER).\" }" \ + | gh api "repos/$REPO_FULL_NAME/issues" \ + -X POST \ + -H "Content-Type: application/json" \ + --input - \ + --jq '.number' + )"; then + echo "Issue number $ISSUE_NUMBER created" + else + echo "Error: Failed to create an issue" >&2 + exit 1 + fi + + echo "Creating a comment..." + printf "%b" "Error: Hi @$PR_USER, You did not reference an open issue in your [PR]($PR_NUMBER). I attempted to create an [issue](#$ISSUE_NUMBER) for you.\ + \n Please update that [issues'](#$ISSUE_NUMBER) title and body and make sure I correctly referenced it in the above PRs body." \ + | gh issue comment "$PR_NUMBER" \ + --body-file - \ + --repo "$REPO_FULL_NAME" + + if [ -z "$PR_BODY" ]; then + PR_BODY="Please edit this." + fi + + echo "Editing PR $PR_NUMBER..." + printf "%b" "$PR_BODY\nCloses #$ISSUE_NUMBER, " | gh pr edit "$PR_NUMBER" \ + --body-file - \ + --repo "$REPO_FULL_NAME" + # Report bad practice + curl -X POST -H 'Content-Type: application/json' --data '{"alias":"Mo-Auto","emoji":":robot:","text":":x: :cry: It seems like @${{github.actor}} did not follow the structure flow in GH Issue --> PR. Hi @${{github.actor}} . You did not reference an open issue in your [PR](https://github.com/${{github.repository}}/pull/${{ github.event.pull_request.number }}) body before opening it. :x: ","attachments":[{"title":"GitHub user behavior reporter","title_link":"https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue","text":"We are not too happy with your PR practices. Always open a detailed issue before opening a PR. Make sure to reference that PR.","color":"#764FA5"}]}' ${{ secrets.GITHUBUSERBEHAVIORROCKETCHATREPORTER }} + exit 0 + fi diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index fe4c90d15bb..278e39878f6 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -2,14 +2,51 @@ name: release on: workflow_dispatch: jobs: - release-chart-pr: - if: contains(github.event.head_commit.message, 'Release-As:') || github.event_name == 'workflow_dispatch' +# Currently the release of that charts is being handled by the build-docs.yml workflow +# release-chart-pr: +# if: contains(github.event.head_commit.message, 'Release-As:') || github.event_name == 'workflow_dispatch' +# runs-on: ubuntu-latest +# strategy: +# #max-parallel: 1 +# fail-fast: false +# matrix: +# charts: [ "charts/janssen" ] +# steps: +# - name: Checkout +# uses: actions/checkout@v3 +# with: +# fetch-depth: 0 +# +# - name: Import GPG key +# id: import_gpg +# uses: crazy-max/ghaction-import-gpg@v5 +# with: +# gpg_private_key: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY }} +# passphrase: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY_PASSPHRASE }} +# git_user_signingkey: true +# git_commit_gpgsign: true +# +# - name: Configure Git +# run: | +# git config user.name "mo-auto" +# git config user.email "54212639+mo-auto@users.noreply.github.com" +# git config --global user.signingkey "${{ steps.import_gpg.outputs.keyid }}" +# +# - uses: google-github-actions/release-please-action@v3.5 +# id: release-please +# with: +# path: ${{ matrix.charts }} +# token: ${{ secrets.MOAUTO_WORKFLOW_TOKEN }} +# release-type: helm +# package-name: ${{ matrix.charts }} +# monorepo-tags: true + release-simple-pr: runs-on: ubuntu-latest strategy: #max-parallel: 1 fail-fast: false matrix: - charts: [ "charts/janssen" ] + simple: [ "docs" ] steps: - name: Checkout uses: actions/checkout@v3 @@ -18,7 +55,7 @@ jobs: - name: Import GPG key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v4 + uses: crazy-max/ghaction-import-gpg@v5 with: gpg_private_key: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY }} passphrase: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY_PASSPHRASE }} @@ -31,22 +68,22 @@ jobs: git config user.email "54212639+mo-auto@users.noreply.github.com" git config --global user.signingkey "${{ steps.import_gpg.outputs.keyid }}" - - uses: google-github-actions/release-please-action@v3.1 + - uses: google-github-actions/release-please-action@v3.5 id: release-please with: - path: ${{ matrix.charts }} + path: ${{ matrix.simple }} token: ${{ secrets.MOAUTO_WORKFLOW_TOKEN }} - release-type: helm - package-name: ${{ matrix.charts }} + release-type: simple + package-name: ${{ matrix.simple }} monorepo-tags: true - release-simple-pr: - needs: release-chart-pr + release-java-pr: + needs: release-simple-pr runs-on: ubuntu-latest strategy: #max-parallel: 1 fail-fast: false matrix: - simple: [ "docs", "jans-scim", "jans-orm", "jans-notify", "jans-fido2", "jans-eleven", "jans-core", "jans-config-api", "jans-client-api", "jans-bom", "jans-auth-server" ] + maven: [ "jans-scim", "jans-orm", "jans-notify", "jans-fido2", "jans-eleven", "jans-core", "jans-config-api", "jans-bom", "jans-auth-server", "agama" ] steps: - name: Checkout uses: actions/checkout@v3 @@ -55,7 +92,7 @@ jobs: - name: Import GPG key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v4 + uses: crazy-max/ghaction-import-gpg@v5 with: gpg_private_key: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY }} passphrase: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY_PASSPHRASE }} @@ -68,22 +105,22 @@ jobs: git config user.email "54212639+mo-auto@users.noreply.github.com" git config --global user.signingkey "${{ steps.import_gpg.outputs.keyid }}" - - uses: google-github-actions/release-please-action@v3.1 + - uses: google-github-actions/release-please-action@v3.5 id: release-please with: - path: ${{ matrix.simple }} + path: ${{ matrix.maven }} token: ${{ secrets.MOAUTO_WORKFLOW_TOKEN }} - release-type: simple - package-name: ${{ matrix.simple }} + release-type: maven + package-name: ${{ matrix.maven }} monorepo-tags: true release-docker-pr: - needs: release-simple-pr + needs: release-java-pr runs-on: ubuntu-latest strategy: #max-parallel: 1 fail-fast: false matrix: - simple: [ "docker-jans-auth-server", "docker-jans-certmanager", "docker-jans-config-api", "docker-jans-client-api", "docker-jans-configurator", "docker-jans-fido2", "docker-jans-persistence-loader", "docker-jans-scim" ] + simple: [ "docker-jans-auth-server", "docker-jans-certmanager", "docker-jans-config-api", "docker-jans-configurator", "docker-jans-fido2", "docker-jans-persistence-loader", "docker-jans-scim", "docker-jans-monolith" ] steps: - name: Checkout uses: actions/checkout@v3 @@ -92,7 +129,7 @@ jobs: - name: Import GPG key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v4 + uses: crazy-max/ghaction-import-gpg@v5 with: gpg_private_key: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY }} passphrase: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY_PASSPHRASE }} @@ -105,7 +142,7 @@ jobs: git config user.email "54212639+mo-auto@users.noreply.github.com" git config --global user.signingkey "${{ steps.import_gpg.outputs.keyid }}" - - uses: google-github-actions/release-please-action@v3.1 + - uses: google-github-actions/release-please-action@v3.5 id: release-please with: path: ${{ matrix.simple }} @@ -129,7 +166,7 @@ jobs: - name: Import GPG key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v4 + uses: crazy-max/ghaction-import-gpg@v5 with: gpg_private_key: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY }} passphrase: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY_PASSPHRASE }} @@ -142,7 +179,7 @@ jobs: git config user.email "54212639+mo-auto@users.noreply.github.com" git config --global user.signingkey "${{ steps.import_gpg.outputs.keyid }}" - - uses: google-github-actions/release-please-action@v3.1 + - uses: google-github-actions/release-please-action@v3.5 id: release-please with: path: ${{ matrix.python-projects }} @@ -161,7 +198,7 @@ jobs: - name: Import GPG key id: import_gpg - uses: crazy-max/ghaction-import-gpg@v4 + uses: crazy-max/ghaction-import-gpg@v5 with: gpg_private_key: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY }} passphrase: ${{ secrets.MOAUTO_GPG_PRIVATE_KEY_PASSPHRASE }} @@ -175,7 +212,7 @@ jobs: git config --global user.signingkey "${{ steps.import_gpg.outputs.keyid }}" - - uses: google-github-actions/release-please-action@v3.1 + - uses: google-github-actions/release-please-action@v3.5 id: release-please with: token: ${{ secrets.MOAUTO3_WORKFLOW_TOKEN }} diff --git a/.github/workflows/test_docker_linux_installer.yml b/.github/workflows/test_docker_linux_installer.yml new file mode 100644 index 00000000000..f83fca7c149 --- /dev/null +++ b/.github/workflows/test_docker_linux_installer.yml @@ -0,0 +1,38 @@ +name: Test Linux installer +on: + workflow_dispatch: + push: + branches: + - main + paths: + - "jans-linux-setup/**" + pull_request: + branches: + - main + paths: + - "jans-linux-setup/**" +jobs: + build: + runs-on: ubuntu-latest + strategy: + max-parallel: 6 + matrix: + # add '"pgsql" when supported + persistence-backends: ["MYSQL"] + python-version: ["3.7"] + steps: + - uses: actions/checkout@v3 + - name: Set up Python3 + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + - name: Test Jans monolith demo + run: | + sudo apt-get update + sudo python3 -m pip install --upgrade pip + pip3 install setuptools --upgrade + pip3 install dockerfile-parse + python3 -c "from dockerfile_parse import DockerfileParser ; dfparser = DockerfileParser('./docker-jans-monolith') ; dfparser.envs['JANS_SOURCE_VERSION'] = '${{ github.sha }}'" + ip=$(ifconfig eth0 | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1') + sudo chmod u+x automation/startjanssenmonolithdemo.sh + sudo bash ./automation/startjanssenmonolithdemo.sh demoexample.jans.io ${{ matrix.persistence-backends }} $ip diff --git a/.github/workflows/testcases.yml b/.github/workflows/testcases.yml index 40d7f1e81cb..3d4ec34563b 100644 --- a/.github/workflows/testcases.yml +++ b/.github/workflows/testcases.yml @@ -28,7 +28,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2.3.2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} - name: Install dependencies @@ -40,7 +40,7 @@ jobs: cd ./jans-pycloudlib tox - name: Upload coverage to Codecov - uses: codecov/codecov-action@v2.1.0 + uses: codecov/codecov-action@v3 with: file: ./jans-pycloudlib/coverage.xml files: ./jans-pycloudlib/coverage1.xml,./jans-pycloudlib/coverage2.xml diff --git a/.gitignore b/.gitignore index 19ea6519177..4957cbcb55f 100644 --- a/.gitignore +++ b/.gitignore @@ -148,3 +148,14 @@ PyCharm .DS_STORE tmp /.metadata/ + +# DBeaver +credentials-config.json +.credentials-config.json.bak +data-sources.json +.data-sources.json.bak + +# Eclipse +.settings +.classpath +.project diff --git a/CHANGELOG.md b/CHANGELOG.md index ed2a7fa5291..4591a6116f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,562 @@ # Changelog +## 1.0.2 (2022-08-30) + + +### Features + +* add inum claim in profile scope [#2095](https://github.com/JanssenProject/jans/issues/2095) ([#2096](https://github.com/JanssenProject/jans/issues/2096)) ([f67c32e](https://github.com/JanssenProject/jans/commit/f67c32e7891f95c7a00ad0fa263444214dcaecd5)) +* add new methods to allow get/set list of custom attributes from ([#2105](https://github.com/JanssenProject/jans/issues/2105)) ([5ac23a1](https://github.com/JanssenProject/jans/commit/5ac23a18adf3b34fd41fd1199ab168bfc9602fc6)), closes [#2104](https://github.com/JanssenProject/jans/issues/2104) +* add newly redesigned jans-client-api ([#1540](https://github.com/JanssenProject/jans/issues/1540)) ([4582ae5](https://github.com/JanssenProject/jans/commit/4582ae563ddf2492c519fdbc7685af2ce3c5529d)) +* add support for date ranges in statistic client [#1575](https://github.com/JanssenProject/jans/issues/1575) ([#1653](https://github.com/JanssenProject/jans/issues/1653)) ([8048cd9](https://github.com/JanssenProject/jans/commit/8048cd9b6ab393b8a3e4a1aaf36e09abe20f605b)) +* add support for requestUriBlockList config ([#1572](https://github.com/JanssenProject/jans/issues/1572)) ([63b3b74](https://github.com/JanssenProject/jans/commit/63b3b7418861e8f4234c54a8d81fe5d21f56387f)) +* added config to disable attempt to update before insert in cache ([#1787](https://github.com/JanssenProject/jans/issues/1787)) ([d9a07ff](https://github.com/JanssenProject/jans/commit/d9a07ffb8dc1af290be6bc9b4978ad21c6797a3f)) +* **agama:** add utility classes for inbound identity ([#2204](https://github.com/JanssenProject/jans/issues/2204)) ([29f58ee](https://github.com/JanssenProject/jans/commit/29f58ee0e6c84b4af5493cabcb19167bc7ffbe40)) +* **agama:** add utility classes for inbound identity ([#2231](https://github.com/JanssenProject/jans/issues/2231)) ([96e32a4](https://github.com/JanssenProject/jans/commit/96e32a407ec6c545b73a6fd103ed2ae5876bd500)) +* **agama:** allow the config-api to perform syntax check of flows ([#1621](https://github.com/JanssenProject/jans/issues/1621)) ([2e99d3a](https://github.com/JanssenProject/jans/commit/2e99d3a9bec389f68086c606062280967ce338ce)) +* **agama:** reject usage of repeated input names ([#1484](https://github.com/JanssenProject/jans/issues/1484)) ([aed8cf3](https://github.com/JanssenProject/jans/commit/aed8cf33d89b98f0ac6aae52e145a84a0937d60e)) +* disable TLS in CB client by default ([#2167](https://github.com/JanssenProject/jans/issues/2167)) ([8ec5dd3](https://github.com/JanssenProject/jans/commit/8ec5dd3dc9818a53949468389a1918ed385c28a9)) +* **docker-jans-fido2:** allow creating initial persistence entry ([#2029](https://github.com/JanssenProject/jans/issues/2029)) ([41dfab7](https://github.com/JanssenProject/jans/commit/41dfab7a6d09d10b05954d4f5fae2437eb81a885)) +* **docker-jans-scim:** allow creating initial persistence entry ([#2035](https://github.com/JanssenProject/jans/issues/2035)) ([e485618](https://github.com/JanssenProject/jans/commit/e4856186d566f9ac7b08395b59cd75e389e5c161)) +* endpoint to get details of connected FIDO devices registered to users [#1465](https://github.com/JanssenProject/jans/issues/1465) ([#1466](https://github.com/JanssenProject/jans/issues/1466)) ([62522fe](https://github.com/JanssenProject/jans/commit/62522fe5aaa2971835c76e8e9b0d4280fee1db32)) +* expose prometheus metrics via jmx exporter ([#1573](https://github.com/JanssenProject/jans/issues/1573)) ([205e320](https://github.com/JanssenProject/jans/commit/205e3206cf87bdb7cf0908bfdd7ee777d1ab955d)) +* fix susrefire tests in filter module ([#2141](https://github.com/JanssenProject/jans/issues/2141)) ([118d77c](https://github.com/JanssenProject/jans/commit/118d77cd7025dcbe3031bc41450ec285afff4b9f)) +* fix the dependencies and code issues ([#1473](https://github.com/JanssenProject/jans/issues/1473)) ([f4824c6](https://github.com/JanssenProject/jans/commit/f4824c6c6c6a036c5d01b7a6710f51477a49a3fb)) +* introduce new hybrid persistence mapping ([#1505](https://github.com/JanssenProject/jans/issues/1505)) ([a77ab60](https://github.com/JanssenProject/jans/commit/a77ab602d15cb6bdf4751aaa11c2be9485b04a34)) +* jans linux setup enable/disable script via arg ([#1634](https://github.com/JanssenProject/jans/issues/1634)) ([0b3cf16](https://github.com/JanssenProject/jans/commit/0b3cf16f524add8f27ca321ce2d82f6d61660456)) +* jans linux setup openbanking CLI and certificate automation ([#1472](https://github.com/JanssenProject/jans/issues/1472)) ([62b5868](https://github.com/JanssenProject/jans/commit/62b5868e1e864a000be210d250602d43a2719b51)) +* **jans-auth-server:** add support for ranges in statistic endpoint (UI team request) ([fd66720](https://github.com/JanssenProject/jans/commit/fd667203564951ba4fc450bf9fb77ba0e70a75ec)) +* **jans-auth-server:** added allowSpontaneousScopes AS json config [#2074](https://github.com/JanssenProject/jans/issues/2074) ([#2111](https://github.com/JanssenProject/jans/issues/2111)) ([3083a3f](https://github.com/JanssenProject/jans/commit/3083a3f28f6d6c6a9de319f23fd745ac69477249)) +* **jans-auth-server:** added convenient method for up-scoping or down-scoping AT scopes [#1218](https://github.com/JanssenProject/jans/issues/1218) ([5d71655](https://github.com/JanssenProject/jans/commit/5d716553c6eb409c2f264864da8b65c0a0bcbe81)) +* **jans-auth-server:** added creator info to scope (time/id/type) [#1934](https://github.com/JanssenProject/jans/issues/1934) ([#2023](https://github.com/JanssenProject/jans/issues/2023)) ([ca65b24](https://github.com/JanssenProject/jans/commit/ca65b246808d30a9f8965806c4ce963cc6dea8db)) +* **jans-auth-server:** added restriction for request_uri parameter (blocklist and allowed client.request_uri) [#1503](https://github.com/JanssenProject/jans/issues/1503) ([0696d92](https://github.com/JanssenProject/jans/commit/0696d92094eeb2ed36f6b0075680634acbf8992f)) +* **jans-auth-server:** added sid and authn_time for active sessions response ([bf9b572](https://github.com/JanssenProject/jans/commit/bf9b572b835d37cc23b2c57437a3830a8ebf55f0)) +* **jans-auth-server:** if applicationType is not set during client registration AS should default to 'web' [#1687](https://github.com/JanssenProject/jans/issues/1687) ([f9695e1](https://github.com/JanssenProject/jans/commit/f9695e1c11389c6f3c0614199b0774f491de8030)) +* **jans-auth-server:** improve client assertion creation code (ClientAuthnRequest) [#1182](https://github.com/JanssenProject/jans/issues/1182) ([81946b2](https://github.com/JanssenProject/jans/commit/81946b22023e9eade94b9202adc6fb32b21652cf)) +* **jans-auth-server:** improved TokenRestWebServiceValidator and added test for it [#1591](https://github.com/JanssenProject/jans/issues/1591) ([929048e](https://github.com/JanssenProject/jans/commit/929048eb41e3c79b25c7474c0a2596b013a3e91c)) +* **jans-auth-server:** jwt "exp" must consider "keyRegenerationInterval" [#1233](https://github.com/JanssenProject/jans/issues/1233) ([023cf8a](https://github.com/JanssenProject/jans/commit/023cf8a1a1cf5ece4e0780fccd62b3acbefa768c)) +* **jans-auth-server:** make check whether user is active case insensitive [#1550](https://github.com/JanssenProject/jans/issues/1550) ([d141837](https://github.com/JanssenProject/jans/commit/d14183708a04cdc6406167acc3126f253f212efa)) +* **jans-auth-server:** persist org_id from software statement into client's "o" attribute ([021d3bd](https://github.com/JanssenProject/jans/commit/021d3bd17f8a9814e5a0d59b4f28b0c19da88ced)) +* **jans-auth-server:** removed dcrSkipSignatureValidation configuration property [#1623](https://github.com/JanssenProject/jans/issues/1623) ([6550247](https://github.com/JanssenProject/jans/commit/6550247ca727d9437ffceec3fa12b9fef93b81e4)) +* **jans-auth-server:** removed id_generation_endpoint and other claims from discovery response [#1827](https://github.com/JanssenProject/jans/issues/1827) ([4068197](https://github.com/JanssenProject/jans/commit/40681972a84d691b5d138bc603f32ec80de84fa2)) +* **jans-auth-server:** split grant validation logic into TokenRestWebServiceValidator [#1591](https://github.com/JanssenProject/jans/issues/1591) ([812e605](https://github.com/JanssenProject/jans/commit/812e605bf1c0d9041db008fba81515455ab38fab)) +* **jans-auth-server:** split validation logic to TokenRestWebServiceValidator [#1591](https://github.com/JanssenProject/jans/issues/1591) ([f9f6f49](https://github.com/JanssenProject/jans/commit/f9f6f49c8874cb0a1c71ff0bc0a75e244077feb9)) +* **jans-auth-server:** updating arquillian tests 1247 ([#2017](https://github.com/JanssenProject/jans/issues/2017)) ([ee200a7](https://github.com/JanssenProject/jans/commit/ee200a7dce5d750f3c4a9d536aa8d92a89926711)) +* jans-cli tabulate scim user list ([#1518](https://github.com/JanssenProject/jans/issues/1518)) ([d370978](https://github.com/JanssenProject/jans/commit/d370978fcdad6e6a4027af1bb2610de3513653ba)) +* **jans-client-api:** migration to Weld/Resteasy and Jetty 11 - Issue 260 ([#1319](https://github.com/JanssenProject/jans/issues/1319)) ([420ffc3](https://github.com/JanssenProject/jans/commit/420ffc3329b91c52d5c9996d7c1e600d9b6fead2)) +* **jans-client-api:** Use injectable operations and remove serviceprovider ([#1643](https://github.com/JanssenProject/jans/issues/1643)) ([982cab3](https://github.com/JanssenProject/jans/commit/982cab3bc3f499d3ec3fbefbd10cb87f58333998)) +* **jans-config-api:** added new attributes ([#1940](https://github.com/JanssenProject/jans/issues/1940)) ([757b22f](https://github.com/JanssenProject/jans/commit/757b22fcc03c28d950eb98b4503d1915fa15b025)) +* **jans-config-api:** agama configuration integration ([#1501](https://github.com/JanssenProject/jans/issues/1501)) ([e84575b](https://github.com/JanssenProject/jans/commit/e84575b018f1910860ca6fbf13f5418e8fa131f6)) +* **jans-config-api:** agama flow endpoint ([#1898](https://github.com/JanssenProject/jans/issues/1898)) ([0e73306](https://github.com/JanssenProject/jans/commit/0e73306f7642a74a3ed2cf8a8687a1ea447aa7bd)) +* **jans-config-api:** agama patch endpoint ([#2028](https://github.com/JanssenProject/jans/issues/2028)) ([0b96a95](https://github.com/JanssenProject/jans/commit/0b96a95399cac02fee614523ae5b552c99c1e254)) +* **jans-config-api:** endpoint to get UmaResource based on clientId and swagger changes ([#1912](https://github.com/JanssenProject/jans/issues/1912)) ([a3f9145](https://github.com/JanssenProject/jans/commit/a3f91453dd8fa5e9c903827b458bc58e735eda55)) +* **jans-config-api:** enhancement to agama and uma resource endpoint ([#2015](https://github.com/JanssenProject/jans/issues/2015)) ([f2c19a1](https://github.com/JanssenProject/jans/commit/f2c19a14b0b5b869eb97afd24ac7169328f22b2f)) +* **jans-config-api:** enhancement to expose user inum at root level of response ([#1477](https://github.com/JanssenProject/jans/issues/1477)) ([1e4b6bc](https://github.com/JanssenProject/jans/commit/1e4b6bc9955a0cd91d6dff000a860ca96b6bd822)) +* **jans-config-api:** fetch the associated clients_id in GET scopes api response ([#1946](https://github.com/JanssenProject/jans/issues/1946)) ([ffe743c](https://github.com/JanssenProject/jans/commit/ffe743ca007d3a1ca7011f47df9c0a4124c93e5c)) +* **jans-config-api:** fixed user management swagger spec for mandatory fields ([#1519](https://github.com/JanssenProject/jans/issues/1519)) ([29ff812](https://github.com/JanssenProject/jans/commit/29ff812c7d6cb94e98886ea7cab0ab08a44879dd)) +* **jans-config-api:** new endpoint to fetch scope by creator and type ([#2098](https://github.com/JanssenProject/jans/issues/2098)) ([cf15d67](https://github.com/JanssenProject/jans/commit/cf15d678f26de3bb2e645040ad25bcb21a03691f)) +* **jans-config-api:** new functionality and swagger fix ([#1802](https://github.com/JanssenProject/jans/issues/1802)) ([fc81d1d](https://github.com/JanssenProject/jans/commit/fc81d1d8a974350547bb6d1d22a8818140325f57)) +* **jans-config-api:** Scope object changes for creator details ([#2033](https://github.com/JanssenProject/jans/issues/2033)) ([a8b8d76](https://github.com/JanssenProject/jans/commit/a8b8d76640ff6520a462ff2bb477db50c2b60207)) +* **jans-config-api:** session management endpoint ([#2158](https://github.com/JanssenProject/jans/issues/2158)) ([30f6e1a](https://github.com/JanssenProject/jans/commit/30f6e1a4bacb90a711ed6f91bc124267d44b9d44)) +* **jans-config-api:** swagger spec change to expose user inum at root level of response ([#1483](https://github.com/JanssenProject/jans/issues/1483)) ([c202705](https://github.com/JanssenProject/jans/commit/c202705f2585c4f8f8c9259ad41b388e97f97573)) +* **jans-config-api:** user management endpoint 418 ([#1548](https://github.com/JanssenProject/jans/issues/1548)) ([b95fa7b](https://github.com/JanssenProject/jans/commit/b95fa7bcd56ef39f8478a9e879c493f815b29dd3)) +* **jans-core:** added StandaloneJavaCustomScriptManagerTest ([48ba08b](https://github.com/JanssenProject/jans/commit/48ba08b2f336c2cef1f244d1411c71859fe337a4)) +* jans-linux-setup add forgot password script ([#1587](https://github.com/JanssenProject/jans/issues/1587)) ([b2e3eb3](https://github.com/JanssenProject/jans/commit/b2e3eb3f07bfc877ee6aee9a3fdd187d7abbf52b)) +* jans-linux-setup agama ([#1486](https://github.com/JanssenProject/jans/issues/1486)) ([6b23bfe](https://github.com/JanssenProject/jans/commit/6b23bfe19ef960039f76df4de167c159312dd930)) +* jans-linux-setup debian 11 packages ([#1769](https://github.com/JanssenProject/jans/issues/1769)) ([6fbef91](https://github.com/JanssenProject/jans/commit/6fbef91dcb4e14aaa78a898d945ef2c2e38ca722)) +* jans-linux-setup Script for Keystroke Authentication ([#1853](https://github.com/JanssenProject/jans/issues/1853)) ([11a9e04](https://github.com/JanssenProject/jans/commit/11a9e040923925d2a3009bfc208321c9ea7ad33c)) +* **jans-linux-setup:** [#1731](https://github.com/JanssenProject/jans/issues/1731) ([#1732](https://github.com/JanssenProject/jans/issues/1732)) ([6fad15b](https://github.com/JanssenProject/jans/commit/6fad15b339c5e6b29055e3acf350f455c47ddc93)) +* **jans-linux-setup:** added discoveryDenyKeys [#1827](https://github.com/JanssenProject/jans/issues/1827) ([f77a6da](https://github.com/JanssenProject/jans/commit/f77a6da20a4a699998cac7c5dc098d09519c2fe4)) +* **jans-orm:** update Couchbase ORM to use SDK 3.x [#1851](https://github.com/JanssenProject/jans/issues/1851) ([#1852](https://github.com/JanssenProject/jans/issues/1852)) ([d9d5157](https://github.com/JanssenProject/jans/commit/d9d5157c3421f4995ee4abd6918c106f9a78dd5f)) +* **jans-scim:** make max no. of operations and payload size of bulks operations parameterizable ([#1872](https://github.com/JanssenProject/jans/issues/1872)) ([c27a45b](https://github.com/JanssenProject/jans/commit/c27a45bb0a19257c824c4e195f203e9b9b45ec88)) +* need to fetch the associated clients_id in GET scopes api response [#1923](https://github.com/JanssenProject/jans/issues/1923) ([#1949](https://github.com/JanssenProject/jans/issues/1949)) ([88606a5](https://github.com/JanssenProject/jans/commit/88606a5ad01b9444f533ee4ea85ea0ca57dc49d8)) +* proper plugin activation of config-api container ([#1627](https://github.com/JanssenProject/jans/issues/1627)) ([07cabb9](https://github.com/JanssenProject/jans/commit/07cabb9c310fb0b00afa419599b2e032c7cf1652)) +* update Coucbase ORM to conform SDK 3.x (config updates) [#1851](https://github.com/JanssenProject/jans/issues/1851) ([#2118](https://github.com/JanssenProject/jans/issues/2118)) ([fceec83](https://github.com/JanssenProject/jans/commit/fceec8332fb36826e5dccb797ee79b769859e126)) +* update DSL to support shorthand for finish [#1628](https://github.com/JanssenProject/jans/issues/1628) ([71e4652](https://github.com/JanssenProject/jans/commit/71e46524492d48fccf2ed2840ede3d6ae525a3e3)) + + +### Bug Fixes + +* : start_date and end_date not required in /stat reponse (swagger specs) [#1767](https://github.com/JanssenProject/jans/issues/1767) ([#1768](https://github.com/JanssenProject/jans/issues/1768)) ([c21452a](https://github.com/JanssenProject/jans/commit/c21452a95567da2f7441e57268268b8d6cb65cfb)) +* [#2143](https://github.com/JanssenProject/jans/issues/2143) ([#2144](https://github.com/JanssenProject/jans/issues/2144)) ([ff7f9f4](https://github.com/JanssenProject/jans/commit/ff7f9f4110d72b333aae0d2332b429dcbd067da3)) +* [#2157](https://github.com/JanssenProject/jans/issues/2157) ([#2159](https://github.com/JanssenProject/jans/issues/2159)) ([dc8cb60](https://github.com/JanssenProject/jans/commit/dc8cb60990052256b46842f85ebf4961beee82dd)) +* a workaround for fido2 dependency ([#1590](https://github.com/JanssenProject/jans/issues/1590)) ([527c928](https://github.com/JanssenProject/jans/commit/527c928d5769320a57d203d59175077e10c2d30a)) +* add path parameter to /fido2/registration/entries [#1465](https://github.com/JanssenProject/jans/issues/1465) ([#1508](https://github.com/JanssenProject/jans/issues/1508)) ([808d0c4](https://github.com/JanssenProject/jans/commit/808d0c4a9b2701c9238926141e22662b918e5990)) +* **agama:** template overriding not working with more than one level of nesting ([#1841](https://github.com/JanssenProject/jans/issues/1841)) ([723922a](https://github.com/JanssenProject/jans/commit/723922a17b1babc49a1135030c06db367726ab63)) +* build from source ([#1793](https://github.com/JanssenProject/jans/issues/1793)) ([e389363](https://github.com/JanssenProject/jans/commit/e389363e3fdad7149cdd73ea6fcbc4058f38819a)) +* **config-api:** fixing discrepancies in the api ([#2216](https://github.com/JanssenProject/jans/issues/2216)) ([af4d3a5](https://github.com/JanssenProject/jans/commit/af4d3a51ce2cbe8c531f8dca213d0c3ef087aad5)) +* correct the link to image ([#1660](https://github.com/JanssenProject/jans/issues/1660)) ([0943d81](https://github.com/JanssenProject/jans/commit/0943d813f782a3babaa5166f426533fd561419a5)) +* docker-jans-persistence-loader/Dockerfile to reduce vulnerabilities ([#1829](https://github.com/JanssenProject/jans/issues/1829)) ([8e4ae15](https://github.com/JanssenProject/jans/commit/8e4ae15de9c93414e3e6e03539cfebc45e71e03e)) +* don't execute next paged search if current result count less than ([#2171](https://github.com/JanssenProject/jans/issues/2171)) ([94a162f](https://github.com/JanssenProject/jans/commit/94a162f4471ec6e4798721894b4a5a583ad71370)) +* fido2-plugin throwing error during deployment [#1632](https://github.com/JanssenProject/jans/issues/1632) ([#1633](https://github.com/JanssenProject/jans/issues/1633)) ([90d2c8a](https://github.com/JanssenProject/jans/commit/90d2c8ace819b784a293df698e316c13a8548fd1)) +* fix typos and other issues in jans-config-api swagger specs [#1665](https://github.com/JanssenProject/jans/issues/1665) ([#1668](https://github.com/JanssenProject/jans/issues/1668)) ([3c3a0f4](https://github.com/JanssenProject/jans/commit/3c3a0f47f6274c8b106bebabc38df927a4238ac3)) +* **images:** conform to new couchbase persistence configuration ([#2188](https://github.com/JanssenProject/jans/issues/2188)) ([c708542](https://github.com/JanssenProject/jans/commit/c7085427fd298f74e8809ef4d6c39f780fa83776)) +* include idtoken with dynamic scopes for ciba ([#2108](https://github.com/JanssenProject/jans/issues/2108)) ([d9b5341](https://github.com/JanssenProject/jans/commit/d9b5341d50de972c910883c12785ce6d2758588f)) +* indentation ([#1821](https://github.com/JanssenProject/jans/issues/1821)) ([8353092](https://github.com/JanssenProject/jans/commit/83530920d920a6fd71bfb65545816af2e7f8511d)) +* jans app and java version ([#1492](https://github.com/JanssenProject/jans/issues/1492)) ([1257e49](https://github.com/JanssenProject/jans/commit/1257e4923eee28e20018720c8815cd518c28bd2f)) +* Jans cli user userpassword ([#1542](https://github.com/JanssenProject/jans/issues/1542)) ([d2e13a2](https://github.com/JanssenProject/jans/commit/d2e13a2bcb16a14f63f3bd99c4b3dc83d2a96d9a)) +* **jans-auth-server:** client tests expects "scope to claim" mapping which are disabled by default [#1873](https://github.com/JanssenProject/jans/issues/1873) ([958cc92](https://github.com/JanssenProject/jans/commit/958cc9232fafa618cb326c7251486f0add7a15c1)) +* **jans-auth-server:** corrected npe in JwtAuthorizationRequest ([9c9e7bf](https://github.com/JanssenProject/jans/commit/9c9e7bf6442637e9f98e9b7765eb373714130d1d)) +* **jans-auth-server:** disable surefire for jans-auth-static ([7869efa](https://github.com/JanssenProject/jans/commit/7869efabd5bc4b32fd8bf8347093fa87ab774957)) +* **jans-auth-server:** fix missing jsonobject annotation ([#1651](https://github.com/JanssenProject/jans/issues/1651)) ([be5b82a](https://github.com/JanssenProject/jans/commit/be5b82a3ccbc7a0fe9f4ebbb97fa8054657227dc)) +* **jans-auth-server:** fixed NPE during getting AT lifetime [#1233](https://github.com/JanssenProject/jans/issues/1233) ([f8be086](https://github.com/JanssenProject/jans/commit/f8be08658c1478acd59fbbfcd609d78179cb00e9)) +* **jans-auth-server:** fixing client tests effected by "scope to claim" mapping which is disabled by default [#1873](https://github.com/JanssenProject/jans/issues/1873) ([#1910](https://github.com/JanssenProject/jans/issues/1910)) ([6d81792](https://github.com/JanssenProject/jans/commit/6d81792a141ca725004c23f1bdd0a42314ffcb5f)) +* **jans-auth-server:** generate description during built-in key rotation [#1790](https://github.com/JanssenProject/jans/issues/1790) ([#2068](https://github.com/JanssenProject/jans/issues/2068)) ([cd1a77d](https://github.com/JanssenProject/jans/commit/cd1a77dd36a59b19e975c013c8081610a23106ba)) +* **jans-auth-server:** increased period of session authn time check ([#1918](https://github.com/JanssenProject/jans/issues/1918)) ([a41905a](https://github.com/JanssenProject/jans/commit/a41905abba38c051acc7e7d57131da4b7c3a1616)) +* **jans-auth-server:** sql localizedstring persistence SqlEntryManager ([#1475](https://github.com/JanssenProject/jans/issues/1475)) ([b959b94](https://github.com/JanssenProject/jans/commit/b959b94e235c8bb554fcbdc8abbc22e3df540dbe)) +* jans-cli download yaml files for build ([#1635](https://github.com/JanssenProject/jans/issues/1635)) ([31b7e49](https://github.com/JanssenProject/jans/commit/31b7e49043d86c9b266590f6437146d625412f60)) +* jans-cli help message format and prompt values (ref: [#1352](https://github.com/JanssenProject/jans/issues/1352)) ([#1478](https://github.com/JanssenProject/jans/issues/1478)) ([37a9181](https://github.com/JanssenProject/jans/commit/37a91819bb7764d2dded27d6b5eafe25de083fe9)) +* jans-cli hide menu item ([#1510](https://github.com/JanssenProject/jans/issues/1510)) ([b70fc52](https://github.com/JanssenProject/jans/commit/b70fc52073a3110c767fbc239bb10cc7924838e8)) +* jans-cli user list failing for empty customAttributes ([#1525](https://github.com/JanssenProject/jans/issues/1525)) ([7cbf10b](https://github.com/JanssenProject/jans/commit/7cbf10b85187c554bf84bc0ceea6bfcf66cb0088)) +* **jans-client-api:** minor observations PR13119 - typo transalation code-improvement ([#1806](https://github.com/JanssenProject/jans/issues/1806)) ([6df2e42](https://github.com/JanssenProject/jans/commit/6df2e422879d8726f2b1d6574fe5492355317bf9)) +* **jans-client-api:** remove jans-config-api dependency and solve wrong test dependencies ([#1737](https://github.com/JanssenProject/jans/issues/1737)) ([97dbe9c](https://github.com/JanssenProject/jans/commit/97dbe9cc3072ca17e9f092cc6d3df5a510778ac2)) +* **jans-client-api:** upgrade seleniumhq version from 3.x to 4.x ([#2110](https://github.com/JanssenProject/jans/issues/2110)) ([d48271e](https://github.com/JanssenProject/jans/commit/d48271e872de72c7085e592988ad2e4e8950116d)) +* jans-config-api add JAVA to programmingLanguage (ref: [#1656](https://github.com/JanssenProject/jans/issues/1656)) ([#1667](https://github.com/JanssenProject/jans/issues/1667)) ([a885a92](https://github.com/JanssenProject/jans/commit/a885a925cdd711158435fedd643f1dd67afad736)) +* **jans-config-api:** avoid loss of attributes in agama endpoints ([#2058](https://github.com/JanssenProject/jans/issues/2058)) ([3c8f816](https://github.com/JanssenProject/jans/commit/3c8f816b62b0efdfffc0e3f53d8371f4510d3ef6)) +* **jans-config-api:** config-api compilation failed in main [#2030](https://github.com/JanssenProject/jans/issues/2030) ([#2031](https://github.com/JanssenProject/jans/issues/2031)) ([1659da1](https://github.com/JanssenProject/jans/commit/1659da1ff4d1d930300ef9c3b3e040eabc7bc0fb)) +* **jans-config-api:** Fix to not update Metadata for PUT and PATCH agama endpoint ([#2046](https://github.com/JanssenProject/jans/issues/2046)) ([da93050](https://github.com/JanssenProject/jans/commit/da93050442d3bc1812d3a8076686ca3e02800c26)) +* **jans-config-api:** fixed due to couchbase cluster changes([#1863](https://github.com/JanssenProject/jans/issues/1863)) ([c996b51](https://github.com/JanssenProject/jans/commit/c996b516cb3f0c4880c4bc78038a5eba666a62c6)) +* **jans-config-api:** fixes for path conflict for SCIM config and spec for UMA Resource mandatory fields ([#1805](https://github.com/JanssenProject/jans/issues/1805)) ([6d8cff6](https://github.com/JanssenProject/jans/commit/6d8cff64d74634e16e100193f34b06990c356d1c)) +* **jans-config-api:** issue UMA scope request being saved as OAUTH ([#2063](https://github.com/JanssenProject/jans/issues/2063)) ([81472aa](https://github.com/JanssenProject/jans/commit/81472aa3da4b02af7ed1bd47753d6938ec0c3e01)) +* **jans-config-api:** rectified endpoint url in swagger spec for uma resource ([#1965](https://github.com/JanssenProject/jans/issues/1965)) ([0dc3b2e](https://github.com/JanssenProject/jans/commit/0dc3b2e60825f9921f28c9eeff30ffefa8bda269)) +* **jans-config-api:** removed java_script from programmingLanguages ([8b935d8](https://github.com/JanssenProject/jans/commit/8b935d8249ab97f912993a07be0a093b89e52c8b)) +* **jans-config-api:** swagger spec change to add missing attributes for Client ([#1786](https://github.com/JanssenProject/jans/issues/1786)) ([e623771](https://github.com/JanssenProject/jans/commit/e62377194616a8a39c86689b30cab92235124fc3)) +* **jans-config-api:** switch to 1.0.1-SNAPSHOT ([e8a9186](https://github.com/JanssenProject/jans/commit/e8a918647da488038ff593da875614b6d7c60cc2)) +* **jans-core:** removed redundant reference [#1927](https://github.com/JanssenProject/jans/issues/1927) ([#1928](https://github.com/JanssenProject/jans/issues/1928)) ([064cbb8](https://github.com/JanssenProject/jans/commit/064cbb8040f4d7b21ff13e5f48c7f923c38f67b1)) +* **jans-core:** switch to 1.0.1-SNAPSHOT ([dbe9355](https://github.com/JanssenProject/jans/commit/dbe9355d97618a267df1ab7aa5c0780e125a3420)) +* jans-linux-setup add dummy jansRedirectURI to scim client ([5023c02](https://github.com/JanssenProject/jans/commit/5023c0277dbf6aea969f554978e61bb833210df9)) +* jans-linux-setup add gcs module path for downloading apps ([#1538](https://github.com/JanssenProject/jans/issues/1538)) ([e540738](https://github.com/JanssenProject/jans/commit/e540738e2d0e6816562b4c927c0ce4bfbaafea56)) +* jans-linux-setup add gcs path after packages check (ref: [#1514](https://github.com/JanssenProject/jans/issues/1514)) ([#1516](https://github.com/JanssenProject/jans/issues/1516)) ([31dd609](https://github.com/JanssenProject/jans/commit/31dd609ebe3fb36213cbbafa1db74c6fc50e01a2)) +* jans-linux-setup add mod_rewrite to httpd_2.4.conf ([#1987](https://github.com/JanssenProject/jans/issues/1987)) ([b33b78e](https://github.com/JanssenProject/jans/commit/b33b78ed2e4e1c628db1ce130dfc6735ec93f83c)) +* jans-linux-setup debian11 installation ([#2160](https://github.com/JanssenProject/jans/issues/2160)) ([8b99498](https://github.com/JanssenProject/jans/commit/8b99498ad0d4b7c45b962ad459e81553a0f65d75)) +* jans-linux-setup disable script Forgot_Password_2FA_Token ([#1662](https://github.com/JanssenProject/jans/issues/1662)) ([377affc](https://github.com/JanssenProject/jans/commit/377affc238bca236324dd8eeb9d9e6750879560f)) +* jans-linux-setup displayName of forgot-password script ([#1595](https://github.com/JanssenProject/jans/issues/1595)) ([07a5ea0](https://github.com/JanssenProject/jans/commit/07a5ea017c8d120b28e2bc578045160e4d3ff0ba)) +* jans-linux-setup download jans-auth for --download-exit ([#1659](https://github.com/JanssenProject/jans/issues/1659)) ([879ed87](https://github.com/JanssenProject/jans/commit/879ed87035265f6bb714ba6283fb274fcdb2fca4)) +* jans-linux-setup enable forgot-password script ([#1597](https://github.com/JanssenProject/jans/issues/1597)) ([149d19c](https://github.com/JanssenProject/jans/commit/149d19cc358b30c4cdd9d1383ace04d911402886)) +* jans-linux-setup humanize os name ([#2066](https://github.com/JanssenProject/jans/issues/2066)) ([8c89638](https://github.com/JanssenProject/jans/commit/8c89638f27e7d440bf82c91df27cdf9d263ae63d)) +* jans-linux-setup jans and jetty version (ref: [#1792](https://github.com/JanssenProject/jans/issues/1792)) ([#1795](https://github.com/JanssenProject/jans/issues/1795)) ([58cbe20](https://github.com/JanssenProject/jans/commit/58cbe20832c2c5efae69321a2a64c06327ae4bf5)) +* jans-linux-setup multiple argument --import-ldif ([#1476](https://github.com/JanssenProject/jans/issues/1476)) ([5556f36](https://github.com/JanssenProject/jans/commit/5556f36073fab29f8d379fe763326c736f5186da)) +* jans-linux-setup no prompt for eleven installation ([#1748](https://github.com/JanssenProject/jans/issues/1748)) ([7228391](https://github.com/JanssenProject/jans/commit/7228391a3e4c8012612289cc17173996fd9670c0)) +* jans-linux-setup python executable when launching setup ([#1683](https://github.com/JanssenProject/jans/issues/1683)) ([87ac58c](https://github.com/JanssenProject/jans/commit/87ac58ca72fdeaafc230183ebe0375537d1c24be)) +* jans-linux-setup remove 101-jans.ldif and 77-customAttributes.ldif ([#1831](https://github.com/JanssenProject/jans/issues/1831)) ([bea6302](https://github.com/JanssenProject/jans/commit/bea6302dfbaf8ecb7fe4eeb53d4f129aa4494aae)) +* jans-linux-setup remove apache config when uninstall ([#1844](https://github.com/JanssenProject/jans/issues/1844)) ([4a5bc3e](https://github.com/JanssenProject/jans/commit/4a5bc3e53e711fa55288e0f118043d34244abf1f)) +* jans-linux-setup remove temporary link file ([#1495](https://github.com/JanssenProject/jans/issues/1495)) ([673859a](https://github.com/JanssenProject/jans/commit/673859a864023e7f2a0ba4a7c36d6fa4a164faaa)) +* jans-linux-setup securing files and dirs under /etc/jans ([#1782](https://github.com/JanssenProject/jans/issues/1782)) ([d64a7ae](https://github.com/JanssenProject/jans/commit/d64a7ae6c39805e61a5fa70da73f8337a8eecfe1)) +* **jans-orm:** fixed npe in filter processor and covered with tests ([ef46516](https://github.com/JanssenProject/jans/commit/ef4651677c415b92d8db01f6bf67eda4d9b9dd03)) +* **jans-orm:** switch to 1.0.1-SNAPSHOT ([0030da7](https://github.com/JanssenProject/jans/commit/0030da76d16eedbdcfc74b72b99705a9fb63cb27)) +* **login.xhtml:** add google client js ([#1666](https://github.com/JanssenProject/jans/issues/1666)) ([daf9849](https://github.com/JanssenProject/jans/commit/daf9849da1f92707b05517f73bfede1a69103365)) +* main docker version ([1220e1c](https://github.com/JanssenProject/jans/commit/1220e1c892c4462b74039bcf64f0cd0cedb80d1f)) +* **pycloudlib:** handle type mismatch for iterable ([#2004](https://github.com/JanssenProject/jans/issues/2004)) ([46e0b2e](https://github.com/JanssenProject/jans/commit/46e0b2e4aff70a97cdcdcd0102dc83d294e45fdc)) +* random password for keystores ([#2102](https://github.com/JanssenProject/jans/issues/2102)) ([b7d9af1](https://github.com/JanssenProject/jans/commit/b7d9af12ecf946498d279a5f577db0528e5522bc)) +* test data for login ([#1757](https://github.com/JanssenProject/jans/issues/1757)) ([e043949](https://github.com/JanssenProject/jans/commit/e0439497d08d09080d7ebd161cd24bcdadfee10f)) +* update chart repo ([8e347a3](https://github.com/JanssenProject/jans/commit/8e347a3c4321c1ba142c05be632630ffc8836cea)) +* update chart repo ([011af9d](https://github.com/JanssenProject/jans/commit/011af9d4ce8cff51d18005d319f5552362c94f19)) +* update error pages ([#1957](https://github.com/JanssenProject/jans/issues/1957)) ([3d63f4d](https://github.com/JanssenProject/jans/commit/3d63f4d3d58e86d499271ebafdbc0dd56f5c4e98)) +* update external modules for otp/fido2 ([#1589](https://github.com/JanssenProject/jans/issues/1589)) ([fc42181](https://github.com/JanssenProject/jans/commit/fc4218110e5130878836a663aba72e67dcefcd10)) +* use iterator to correcly remove OC attribute ([#2138](https://github.com/JanssenProject/jans/issues/2138)) ([b590981](https://github.com/JanssenProject/jans/commit/b590981c53c26f4b1a8b6a0865ddc552e0a347b8)) + + +### Miscellaneous Chores + +* prepare docker images release 1.0.1-1 ([12660a8](https://github.com/JanssenProject/jans/commit/12660a800bacb210bd3fb4b35c9156e9c5445343)) +* prepare helm chart release 1.0.1 ([ae78b76](https://github.com/JanssenProject/jans/commit/ae78b760aa536ecde3b7e7972070e144d6c3c072)) +* release 1.0.1 ([828bfe8](https://github.com/JanssenProject/jans/commit/828bfe80cee87e639839391f98ac3dc2f2d4a920)) +* release 1.0.2 ([43dead6](https://github.com/JanssenProject/jans/commit/43dead615f3508ca393c330c2db27a8fb9d1017a)) +* release 1.0.2-1 ([d01b51a](https://github.com/JanssenProject/jans/commit/d01b51a847bb2f67b52da433ebd1c5e4a66b7c1a)) + +## [1.0.1](https://github.com/JanssenProject/jans/compare/v1.0.0...v1.0.1) (2022-07-06) + + +### Features + +* add newly redesigned jans-client-api ([#1540](https://github.com/JanssenProject/jans/issues/1540)) ([4582ae5](https://github.com/JanssenProject/jans/commit/4582ae563ddf2492c519fdbc7685af2ce3c5529d)) +* add support for date ranges in statistic client [#1575](https://github.com/JanssenProject/jans/issues/1575) ([#1653](https://github.com/JanssenProject/jans/issues/1653)) ([8048cd9](https://github.com/JanssenProject/jans/commit/8048cd9b6ab393b8a3e4a1aaf36e09abe20f605b)) +* add support for requestUriBlockList config ([#1572](https://github.com/JanssenProject/jans/issues/1572)) ([63b3b74](https://github.com/JanssenProject/jans/commit/63b3b7418861e8f4234c54a8d81fe5d21f56387f)) +* **agama:** allow the config-api to perform syntax check of flows ([#1621](https://github.com/JanssenProject/jans/issues/1621)) ([2e99d3a](https://github.com/JanssenProject/jans/commit/2e99d3a9bec389f68086c606062280967ce338ce)) +* **agama:** improve flows timeout ([#1447](https://github.com/JanssenProject/jans/issues/1447)) ([ccfb62e](https://github.com/JanssenProject/jans/commit/ccfb62ec13d371c96a0d597d5a0229864f044373)) +* **agama:** reject usage of repeated input names ([#1484](https://github.com/JanssenProject/jans/issues/1484)) ([aed8cf3](https://github.com/JanssenProject/jans/commit/aed8cf33d89b98f0ac6aae52e145a84a0937d60e)) +* endpoint to get details of connected FIDO devices registered to users [#1465](https://github.com/JanssenProject/jans/issues/1465) ([#1466](https://github.com/JanssenProject/jans/issues/1466)) ([62522fe](https://github.com/JanssenProject/jans/commit/62522fe5aaa2971835c76e8e9b0d4280fee1db32)) +* enhance error handling [#1434](https://github.com/JanssenProject/jans/issues/1434) ([a3f6314](https://github.com/JanssenProject/jans/commit/a3f631421f125501f9c7cf0be207744860d856cc)) +* expose prometheus metrics via jmx exporter ([#1573](https://github.com/JanssenProject/jans/issues/1573)) ([205e320](https://github.com/JanssenProject/jans/commit/205e3206cf87bdb7cf0908bfdd7ee777d1ab955d)) +* fix the dependencies and code issues ([#1473](https://github.com/JanssenProject/jans/issues/1473)) ([f4824c6](https://github.com/JanssenProject/jans/commit/f4824c6c6c6a036c5d01b7a6710f51477a49a3fb)) +* introduce new hybrid persistence mapping ([#1505](https://github.com/JanssenProject/jans/issues/1505)) ([a77ab60](https://github.com/JanssenProject/jans/commit/a77ab602d15cb6bdf4751aaa11c2be9485b04a34)) +* jans linux setup enable/disable script via arg ([#1634](https://github.com/JanssenProject/jans/issues/1634)) ([0b3cf16](https://github.com/JanssenProject/jans/commit/0b3cf16f524add8f27ca321ce2d82f6d61660456)) +* jans linux setup openbanking CLI and certificate automation ([#1472](https://github.com/JanssenProject/jans/issues/1472)) ([62b5868](https://github.com/JanssenProject/jans/commit/62b5868e1e864a000be210d250602d43a2719b51)) +* **jans-auth-server:** add support for ranges in statistic endpoint (UI team request) ([fd66720](https://github.com/JanssenProject/jans/commit/fd667203564951ba4fc450bf9fb77ba0e70a75ec)) +* **jans-auth-server:** added convenient method for up-scoping or down-scoping AT scopes [#1218](https://github.com/JanssenProject/jans/issues/1218) ([5d71655](https://github.com/JanssenProject/jans/commit/5d716553c6eb409c2f264864da8b65c0a0bcbe81)) +* **jans-auth-server:** added restriction for request_uri parameter (blocklist and allowed client.request_uri) [#1503](https://github.com/JanssenProject/jans/issues/1503) ([0696d92](https://github.com/JanssenProject/jans/commit/0696d92094eeb2ed36f6b0075680634acbf8992f)) +* **jans-auth-server:** added sid and authn_time for active sessions response ([bf9b572](https://github.com/JanssenProject/jans/commit/bf9b572b835d37cc23b2c57437a3830a8ebf55f0)) +* **jans-auth-server:** improve client assertion creation code (ClientAuthnRequest) [#1182](https://github.com/JanssenProject/jans/issues/1182) ([81946b2](https://github.com/JanssenProject/jans/commit/81946b22023e9eade94b9202adc6fb32b21652cf)) +* **jans-auth-server:** make check whether user is active case insensitive [#1550](https://github.com/JanssenProject/jans/issues/1550) ([d141837](https://github.com/JanssenProject/jans/commit/d14183708a04cdc6406167acc3126f253f212efa)) +* **jans-auth-server:** persist org_id from software statement into client's "o" attribute ([021d3bd](https://github.com/JanssenProject/jans/commit/021d3bd17f8a9814e5a0d59b4f28b0c19da88ced)) +* **jans-auth-server:** removed dcrSkipSignatureValidation configuration property [#1623](https://github.com/JanssenProject/jans/issues/1623) ([6550247](https://github.com/JanssenProject/jans/commit/6550247ca727d9437ffceec3fa12b9fef93b81e4)) +* jans-cli --no-suggestion for automated testing ([#1437](https://github.com/JanssenProject/jans/issues/1437)) ([187cc07](https://github.com/JanssenProject/jans/commit/187cc0742102e3d1a708c6b9fffce32d1ac1ebd2)) +* jans-cli tabulate scim user list ([#1518](https://github.com/JanssenProject/jans/issues/1518)) ([d370978](https://github.com/JanssenProject/jans/commit/d370978fcdad6e6a4027af1bb2610de3513653ba)) +* **jans-client-api:** migration to Weld/Resteasy and Jetty 11 - Issue 260 ([#1319](https://github.com/JanssenProject/jans/issues/1319)) ([420ffc3](https://github.com/JanssenProject/jans/commit/420ffc3329b91c52d5c9996d7c1e600d9b6fead2)) +* **jans-config-api:** agama configuration integration ([#1501](https://github.com/JanssenProject/jans/issues/1501)) ([e84575b](https://github.com/JanssenProject/jans/commit/e84575b018f1910860ca6fbf13f5418e8fa131f6)) +* **jans-config-api:** enhancement to expose user inum at root level of response ([#1477](https://github.com/JanssenProject/jans/issues/1477)) ([1e4b6bc](https://github.com/JanssenProject/jans/commit/1e4b6bc9955a0cd91d6dff000a860ca96b6bd822)) +* **jans-config-api:** fixed user management swagger spec for mandatory fields ([#1519](https://github.com/JanssenProject/jans/issues/1519)) ([29ff812](https://github.com/JanssenProject/jans/commit/29ff812c7d6cb94e98886ea7cab0ab08a44879dd)) +* **jans-config-api:** swagger spec change to expose user inum at root level of response ([#1483](https://github.com/JanssenProject/jans/issues/1483)) ([c202705](https://github.com/JanssenProject/jans/commit/c202705f2585c4f8f8c9259ad41b388e97f97573)) +* **jans-config-api:** user management endpoint 418 ([#1548](https://github.com/JanssenProject/jans/issues/1548)) ([b95fa7b](https://github.com/JanssenProject/jans/commit/b95fa7bcd56ef39f8478a9e879c493f815b29dd3)) +* **jans-core:** added Discovery.java script and sample external service ([440f2dd](https://github.com/JanssenProject/jans/commit/440f2dd41a0dafc915fd409b21da454f8cf1e046)) +* **jans-core:** added StandaloneJavaCustomScriptManagerTest ([48ba08b](https://github.com/JanssenProject/jans/commit/48ba08b2f336c2cef1f244d1411c71859fe337a4)) +* **jans-core:** added test dependencies to scripts ([53e5f67](https://github.com/JanssenProject/jans/commit/53e5f6725648521a983a86a533f62587b902f951)) +* jans-linux-setup add forgot password script ([#1587](https://github.com/JanssenProject/jans/issues/1587)) ([b2e3eb3](https://github.com/JanssenProject/jans/commit/b2e3eb3f07bfc877ee6aee9a3fdd187d7abbf52b)) +* jans-linux-setup agama ([#1486](https://github.com/JanssenProject/jans/issues/1486)) ([6b23bfe](https://github.com/JanssenProject/jans/commit/6b23bfe19ef960039f76df4de167c159312dd930)) +* proper plugin activation of config-api container ([#1627](https://github.com/JanssenProject/jans/issues/1627)) ([07cabb9](https://github.com/JanssenProject/jans/commit/07cabb9c310fb0b00afa419599b2e032c7cf1652)) +* update DSL to support shorthand for finish [#1628](https://github.com/JanssenProject/jans/issues/1628) ([71e4652](https://github.com/JanssenProject/jans/commit/71e46524492d48fccf2ed2840ede3d6ae525a3e3)) + + +### Bug Fixes + +* a workaround for fido2 dependency ([#1590](https://github.com/JanssenProject/jans/issues/1590)) ([527c928](https://github.com/JanssenProject/jans/commit/527c928d5769320a57d203d59175077e10c2d30a)) +* add path parameter to /fido2/registration/entries [#1465](https://github.com/JanssenProject/jans/issues/1465) ([#1508](https://github.com/JanssenProject/jans/issues/1508)) ([808d0c4](https://github.com/JanssenProject/jans/commit/808d0c4a9b2701c9238926141e22662b918e5990)) +* correct the link to image ([#1660](https://github.com/JanssenProject/jans/issues/1660)) ([0943d81](https://github.com/JanssenProject/jans/commit/0943d813f782a3babaa5166f426533fd561419a5)) +* fido2-plugin throwing error during deployment [#1632](https://github.com/JanssenProject/jans/issues/1632) ([#1633](https://github.com/JanssenProject/jans/issues/1633)) ([90d2c8a](https://github.com/JanssenProject/jans/commit/90d2c8ace819b784a293df698e316c13a8548fd1)) +* fix typos and other issues in jans-config-api swagger specs [#1665](https://github.com/JanssenProject/jans/issues/1665) ([#1668](https://github.com/JanssenProject/jans/issues/1668)) ([3c3a0f4](https://github.com/JanssenProject/jans/commit/3c3a0f47f6274c8b106bebabc38df927a4238ac3)) +* jans app and java version ([#1492](https://github.com/JanssenProject/jans/issues/1492)) ([1257e49](https://github.com/JanssenProject/jans/commit/1257e4923eee28e20018720c8815cd518c28bd2f)) +* Jans cli user userpassword ([#1542](https://github.com/JanssenProject/jans/issues/1542)) ([d2e13a2](https://github.com/JanssenProject/jans/commit/d2e13a2bcb16a14f63f3bd99c4b3dc83d2a96d9a)) +* **jans-auth-server:** added SessionRestWebService to rest initializer ([f0ebf67](https://github.com/JanssenProject/jans/commit/f0ebf67703d52d35c2788b1f528a9f7081dcab6a)) +* **jans-auth-server:** corrected npe in JwtAuthorizationRequest ([9c9e7bf](https://github.com/JanssenProject/jans/commit/9c9e7bf6442637e9f98e9b7765eb373714130d1d)) +* **jans-auth-server:** disable surefire for jans-auth-static ([7869efa](https://github.com/JanssenProject/jans/commit/7869efabd5bc4b32fd8bf8347093fa87ab774957)) +* **jans-auth-server:** fix missing jsonobject annotation ([#1651](https://github.com/JanssenProject/jans/issues/1651)) ([be5b82a](https://github.com/JanssenProject/jans/commit/be5b82a3ccbc7a0fe9f4ebbb97fa8054657227dc)) +* **jans-auth-server:** sql localizedstring persistence SqlEntryManager ([#1475](https://github.com/JanssenProject/jans/issues/1475)) ([b959b94](https://github.com/JanssenProject/jans/commit/b959b94e235c8bb554fcbdc8abbc22e3df540dbe)) +* jans-cli download yaml files for build ([#1635](https://github.com/JanssenProject/jans/issues/1635)) ([31b7e49](https://github.com/JanssenProject/jans/commit/31b7e49043d86c9b266590f6437146d625412f60)) +* jans-cli help message format and prompt values (ref: [#1352](https://github.com/JanssenProject/jans/issues/1352)) ([#1478](https://github.com/JanssenProject/jans/issues/1478)) ([37a9181](https://github.com/JanssenProject/jans/commit/37a91819bb7764d2dded27d6b5eafe25de083fe9)) +* jans-cli hide menu item ([#1510](https://github.com/JanssenProject/jans/issues/1510)) ([b70fc52](https://github.com/JanssenProject/jans/commit/b70fc52073a3110c767fbc239bb10cc7924838e8)) +* jans-cli user list failing for empty customAttributes ([#1525](https://github.com/JanssenProject/jans/issues/1525)) ([7cbf10b](https://github.com/JanssenProject/jans/commit/7cbf10b85187c554bf84bc0ceea6bfcf66cb0088)) +* jans-config-api add JAVA to programmingLanguage (ref: [#1656](https://github.com/JanssenProject/jans/issues/1656)) ([#1667](https://github.com/JanssenProject/jans/issues/1667)) ([a885a92](https://github.com/JanssenProject/jans/commit/a885a925cdd711158435fedd643f1dd67afad736)) +* **jans-config-api:** removed java_script from programmingLanguages ([8b935d8](https://github.com/JanssenProject/jans/commit/8b935d8249ab97f912993a07be0a093b89e52c8b)) +* **jans-config-api:** switch to 1.0.1-SNAPSHOT ([e8a9186](https://github.com/JanssenProject/jans/commit/e8a918647da488038ff593da875614b6d7c60cc2)) +* **jans-core:** switch to 1.0.1-SNAPSHOT ([dbe9355](https://github.com/JanssenProject/jans/commit/dbe9355d97618a267df1ab7aa5c0780e125a3420)) +* jans-linux-setup add gcs module path for downloading apps ([#1538](https://github.com/JanssenProject/jans/issues/1538)) ([e540738](https://github.com/JanssenProject/jans/commit/e540738e2d0e6816562b4c927c0ce4bfbaafea56)) +* jans-linux-setup add gcs path after packages check (ref: [#1514](https://github.com/JanssenProject/jans/issues/1514)) ([#1516](https://github.com/JanssenProject/jans/issues/1516)) ([31dd609](https://github.com/JanssenProject/jans/commit/31dd609ebe3fb36213cbbafa1db74c6fc50e01a2)) +* jans-linux-setup disable script Forgot_Password_2FA_Token ([#1662](https://github.com/JanssenProject/jans/issues/1662)) ([377affc](https://github.com/JanssenProject/jans/commit/377affc238bca236324dd8eeb9d9e6750879560f)) +* jans-linux-setup displayName of forgot-password script ([#1595](https://github.com/JanssenProject/jans/issues/1595)) ([07a5ea0](https://github.com/JanssenProject/jans/commit/07a5ea017c8d120b28e2bc578045160e4d3ff0ba)) +* jans-linux-setup download jans-auth for --download-exit ([#1659](https://github.com/JanssenProject/jans/issues/1659)) ([879ed87](https://github.com/JanssenProject/jans/commit/879ed87035265f6bb714ba6283fb274fcdb2fca4)) +* jans-linux-setup enable forgot-password script ([#1597](https://github.com/JanssenProject/jans/issues/1597)) ([149d19c](https://github.com/JanssenProject/jans/commit/149d19cc358b30c4cdd9d1383ace04d911402886)) +* jans-linux-setup multiple argument --import-ldif ([#1476](https://github.com/JanssenProject/jans/issues/1476)) ([5556f36](https://github.com/JanssenProject/jans/commit/5556f36073fab29f8d379fe763326c736f5186da)) +* jans-linux-setup python executable when launching setup ([#1683](https://github.com/JanssenProject/jans/issues/1683)) ([87ac58c](https://github.com/JanssenProject/jans/commit/87ac58ca72fdeaafc230183ebe0375537d1c24be)) +* jans-linux-setup remove temporary link file ([#1495](https://github.com/JanssenProject/jans/issues/1495)) ([673859a](https://github.com/JanssenProject/jans/commit/673859a864023e7f2a0ba4a7c36d6fa4a164faaa)) +* **jans-orm:** switch to 1.0.1-SNAPSHOT ([0030da7](https://github.com/JanssenProject/jans/commit/0030da76d16eedbdcfc74b72b99705a9fb63cb27)) +* main docker version ([1220e1c](https://github.com/JanssenProject/jans/commit/1220e1c892c4462b74039bcf64f0cd0cedb80d1f)) +* remove jans-auth-common dependency [#1459](https://github.com/JanssenProject/jans/issues/1459) ([75f4fb5](https://github.com/JanssenProject/jans/commit/75f4fb5487b8adc6300c939ea9a0a3302b235d0e)) +* update external modules for otp/fido2 ([#1589](https://github.com/JanssenProject/jans/issues/1589)) ([fc42181](https://github.com/JanssenProject/jans/commit/fc4218110e5130878836a663aba72e67dcefcd10)) +* update pom [#1438](https://github.com/JanssenProject/jans/issues/1438) ([#1439](https://github.com/JanssenProject/jans/issues/1439)) ([66b9962](https://github.com/JanssenProject/jans/commit/66b996286a2285986845677ea039f177f756d962)) + + +### Miscellaneous Chores + +* prepare docker images release 1.0.1-1 ([12660a8](https://github.com/JanssenProject/jans/commit/12660a800bacb210bd3fb4b35c9156e9c5445343)) +* prepare helm chart release 1.0.1 ([ae78b76](https://github.com/JanssenProject/jans/commit/ae78b760aa536ecde3b7e7972070e144d6c3c072)) +* release 1.0.1 ([828bfe8](https://github.com/JanssenProject/jans/commit/828bfe80cee87e639839391f98ac3dc2f2d4a920)) + +## 1.0.0 (2022-05-20) + + +### Features + +* add helper to create persistence entry from LDIF file ([#1262](https://github.com/JanssenProject/jans/issues/1262)) ([f2e653e](https://github.com/JanssenProject/jans/commit/f2e653ef917efd017195f2330b64e64c333f4699)) +* add schema updates [#1390](https://github.com/JanssenProject/jans/issues/1390) ([c9023b3](https://github.com/JanssenProject/jans/commit/c9023b3435fbc8079aabe5c70de3177ec9112308)) +* add script for Google login ([#1141](https://github.com/JanssenProject/jans/issues/1141)) ([bac9144](https://github.com/JanssenProject/jans/commit/bac9144ad8a5f8f2b378aa67663caab9f19f052b)) +* add super-jans project ([1ac74d0](https://github.com/JanssenProject/jans/commit/1ac74d05a7f78bf156e7735d157559b84fad3974)) +* adding logs to debug license issues[#1258](https://github.com/JanssenProject/jans/issues/1258) ([#1281](https://github.com/JanssenProject/jans/issues/1281)) ([8a08771](https://github.com/JanssenProject/jans/commit/8a08771014e3394d4d7b0864d603a1e4b91e2d81)) +* adjust ownership and permission to avoid bloated images ([#1312](https://github.com/JanssenProject/jans/issues/1312)) ([d016682](https://github.com/JanssenProject/jans/commit/d0166821baf52665934c0eaa38de8b2f51825456)) +* allow flows to access their metadata properties [#1340](https://github.com/JanssenProject/jans/issues/1340) ([344ba04](https://github.com/JanssenProject/jans/commit/344ba0448c73e3c56e05f529eea5009b2157c1fc)) +* call id-generation script when creating user/group [#1145](https://github.com/JanssenProject/jans/issues/1145) ([3a9a03a](https://github.com/JanssenProject/jans/commit/3a9a03a101536f6616aa6b94d841bd1457815b31)) +* config-cli enumerate scope type ([275533b](https://github.com/JanssenProject/jans/commit/275533b26f4715113d83ea9cabe4a66cd283a189)) +* create apis to verify and save license api-keys in Admin UI [#1196](https://github.com/JanssenProject/jans/issues/1196) ([#1203](https://github.com/JanssenProject/jans/issues/1203)) ([315faec](https://github.com/JanssenProject/jans/commit/315faeca46045716d8aa38fa5448c7581a5e4212)) +* initial agama commit [#1322](https://github.com/JanssenProject/jans/issues/1322) ([#1323](https://github.com/JanssenProject/jans/issues/1323)) ([0148bc8](https://github.com/JanssenProject/jans/commit/0148bc8a32a005e47ba9d090e895282775148a95)) +* Jans linux setup refactor ([#1328](https://github.com/JanssenProject/jans/issues/1328)) ([79d3a75](https://github.com/JanssenProject/jans/commit/79d3a756bf0477907e4364c9887a316d4730c07a)) +* Jans linux setup ubuntu22 Installation ([#1325](https://github.com/JanssenProject/jans/issues/1325)) ([8597750](https://github.com/JanssenProject/jans/commit/85977502e307884423b4b248694cf74b9b66b96a)) +* **jans-auth-config:** user mgmt endpoint - wip ([9c8094a](https://github.com/JanssenProject/jans/commit/9c8094aaed4802d399da812898e1270fe0a0cae5)) +* **jans-auth-server:** [#808](https://github.com/JanssenProject/jans/issues/808) sign-in with apple interception script ([c21183a](https://github.com/JanssenProject/jans/commit/c21183ab6331f95531d76c6d279646cc3c0b600e)) +* **jans-auth-server:** adapted authorization ws to use authzrequest ([58c5336](https://github.com/JanssenProject/jans/commit/58c5336fe4978c3709d060cd46f1847c01782af3)) +* **jans-auth-server:** added authzrequest abstraction ([af8faf0](https://github.com/JanssenProject/jans/commit/af8faf008eec21a952c3d474169e57a9aece9152)) +* **jans-auth-server:** authorized acr values ([#1068](https://github.com/JanssenProject/jans/issues/1068)) ([26e576a](https://github.com/JanssenProject/jans/commit/26e576a5be90ac9597ed37e0d3629a2701008fcf)) +* **jans-auth-server:** changed prog lang name python->jython ([b9ba291](https://github.com/JanssenProject/jans/commit/b9ba291e576b8443f37c774088747bab09db2db9)) +* **jans-auth-server:** client registration language metadata ([#1237](https://github.com/JanssenProject/jans/issues/1237)) ([a8d0157](https://github.com/JanssenProject/jans/commit/a8d0157b0a8664e5e4d58a9524a0fa20df324381)) +* **jans-auth-server:** enable person authn script to have multiple acr names ([#1074](https://github.com/JanssenProject/jans/issues/1074)) ([1dc9250](https://github.com/JanssenProject/jans/commit/1dc9250b9140cfe2a7ea3daff6c9e0d6383c4bce)) +* **jans-auth-server:** force signed request object ([#1052](https://github.com/JanssenProject/jans/issues/1052)) ([28ebbc1](https://github.com/JanssenProject/jans/commit/28ebbc10d545ad69ceb4e9a625fbbf13e6360b75)) +* **jans-auth-server:** hide 302 redirect exception in logs [#1294](https://github.com/JanssenProject/jans/issues/1294) ([00197c7](https://github.com/JanssenProject/jans/commit/00197c720b444e50c84f49f696fd14768f8fdb08)) +* **jans-auth,jans-cli,jans-config-api:** changes to handle new attribute description in Client object and new custom script type ([d64e042](https://github.com/JanssenProject/jans/commit/d64e0424063c79e35b135f4a8bd48f04591b043c)) +* **jans-auth,jans-cli,jans-config-api:** changes to handle new attribute description in Client object and new custom script type ([a096110](https://github.com/JanssenProject/jans/commit/a096110d157dec7a0c047692e158c53872fe92fe)) +* **jans-auth,jans-cli,jans-config-api:** changes to handle new attribute description in Client object and new custom script type ([d4a9f15](https://github.com/JanssenProject/jans/commit/d4a9f15c3244961cfef6e3229c2e2e49cf85ba0d)) +* jans-cli display users in tabular form ([#1296](https://github.com/JanssenProject/jans/issues/1296)) ([7f75d39](https://github.com/JanssenProject/jans/commit/7f75d393cb2854fce58f02f40b90ac4fa9f2a100)) +* jans-cli group common items in menu (ref: [#892](https://github.com/JanssenProject/jans/issues/892)) ([#1306](https://github.com/JanssenProject/jans/issues/1306)) ([819f8f7](https://github.com/JanssenProject/jans/commit/819f8f704ab176b70b4daa9c7aca5d662e39a39f)) +* jans-cli obtain list of attrbiutes from server when creating user ([1f9b62d](https://github.com/JanssenProject/jans/commit/1f9b62dd133d442d66ef5d3ed6a8cd3ad6da5f7b)) +* jans-cli tabulate attribute list ([#1313](https://github.com/JanssenProject/jans/issues/1313)) ([a684484](https://github.com/JanssenProject/jans/commit/a684484d403f9ed52e4c6749f21bd255523a134e)) +* jans-cli use test client (ref: [#1283](https://github.com/JanssenProject/jans/issues/1283)) ([#1285](https://github.com/JanssenProject/jans/issues/1285)) ([6320af7](https://github.com/JanssenProject/jans/commit/6320af7ed82ea6fac5672c1c348aeecb7a4b5d7a)) +* **jans-config-api:** added custom script patch endpoint ([6daa4f6](https://github.com/JanssenProject/jans/commit/6daa4f61d3a72a0523912bb79566e2e62a6d84be)) +* **jans-config-api:** added patch endpoint for custom script ([e274e20](https://github.com/JanssenProject/jans/commit/e274e206188e76654de759bc687a56a80cf4bfbc)) +* **jans-config-api:** added patch endpoint for custom script ([f8da77d](https://github.com/JanssenProject/jans/commit/f8da77df201f67055ea7c23c3410e5818a170785)) +* **jans-config-api:** added scope DN validation while client creation ([#1293](https://github.com/JanssenProject/jans/issues/1293)) ([f276605](https://github.com/JanssenProject/jans/commit/f276605cb3a1cedb869733253a48d2d63be2fcdc)) +* **jans-config-api:** converting fido2 endpoint to plugin ([#1304](https://github.com/JanssenProject/jans/issues/1304)) ([88c3fff](https://github.com/JanssenProject/jans/commit/88c3fffe177840324fa5f3c4437fa3d02b9ead9b)) +* **jans-config-api:** exposed attributes at root value ([3c3df7a](https://github.com/JanssenProject/jans/commit/3c3df7a1beee57fb851a4c820fcbbffb8418bd78)) +* **jans-config-api:** exposed attributes at root value ([40570a7](https://github.com/JanssenProject/jans/commit/40570a7b9d2d03358cbfb0d8c3964a4111e15bb5)) +* **jans-config-api:** fixed build issue due to LocalizedString change ([#1329](https://github.com/JanssenProject/jans/issues/1329)) ([3b5ab78](https://github.com/JanssenProject/jans/commit/3b5ab783ed9ddf7c7b47612508cc6ab4c334375d)) +* **jans-config-api:** ignore client.customObjectClasses value for persistence type other than LDAP ([#1073](https://github.com/JanssenProject/jans/issues/1073)) ([622bcf4](https://github.com/JanssenProject/jans/commit/622bcf4afae94cddff2a19ca5178f2b8230165d5)) +* **jans-config-api:** rectified test properties file ([#1222](https://github.com/JanssenProject/jans/issues/1222)) ([5b80f67](https://github.com/JanssenProject/jans/commit/5b80f672508ea4fa6956c466bfa971684892b6ce)) +* **jans-config-api:** removed encrypttion and decryption of user password ([7f50ad0](https://github.com/JanssenProject/jans/commit/7f50ad064a68412ec67145f1b0866f136804761b)) +* **jans-config-api:** removed unused import ([8a41484](https://github.com/JanssenProject/jans/commit/8a41484412cbcf5cfc00c550506ad81fb0c70e7c)) +* **jans-config-api:** user custom attributes at root level - 1348 ([5b3f0a1](https://github.com/JanssenProject/jans/commit/5b3f0a13e25cd842e0bbd4be3d21eb48ab1d108f)) +* **jans-config-api:** user management api ([b367d44](https://github.com/JanssenProject/jans/commit/b367d440fbcc34fff923bc3040dc4e6026d165fd)) +* **jans-config-api:** user management api ([517e7f2](https://github.com/JanssenProject/jans/commit/517e7f235b536f833fec11e2f0da49b8ab0f26c8)) +* **jans-config-api:** user management api ([a034bc3](https://github.com/JanssenProject/jans/commit/a034bc3f2b80e4eaaa3ed8fba29b52692a5f91a2)) +* **jans-config-api:** user management endpoint ([f28f3b8](https://github.com/JanssenProject/jans/commit/f28f3b86d36199fc137714160cfbdad08e9265f9)) +* **jans-config-api:** user management enhancement to chk mandatory feilds ([903ba5a](https://github.com/JanssenProject/jans/commit/903ba5a9a778c87de6280332dfee0eea71eaf2f5)) +* **jans-config-api:** user management enhancement to chk mandatory feilds ([0bc2282](https://github.com/JanssenProject/jans/commit/0bc22822a390fdd82370c54ed4e15cad06064e02)) +* **jans-config-api:** user management enhancement to chk mandatory feilds ([e6e2781](https://github.com/JanssenProject/jans/commit/e6e2781c70ffbbffe92cbaf87e8607854c3d8da1)) +* **jans-config-api:** user management mandatory field chk changes ([e242ec6](https://github.com/JanssenProject/jans/commit/e242ec6c2d5edf86cc773d467fc4cd848e4bce13)) +* **jans-config-api:** user management patch endpoint ([0a7ad7d](https://github.com/JanssenProject/jans/commit/0a7ad7dba82b419d412329414f5895c22fdcaa68)) +* **jans-config-api:** user mgmt endpoint ([a093758](https://github.com/JanssenProject/jans/commit/a0937580eed7c32a0f8bf573bddb9ac8b7080e2c)) +* **jans-config-api:** user mgmt endpoint ([ad66713](https://github.com/JanssenProject/jans/commit/ad66713700b6988378c4e3c603fb3518f8ade247)) +* **jans-config-api:** user mgmt endpoint ([0f7a723](https://github.com/JanssenProject/jans/commit/0f7a723bc24dd89fdaf3f71afdb4565e1e25f7fe)) +* **jans-config-api:** user mgmt endpoint ([379ca09](https://github.com/JanssenProject/jans/commit/379ca09461c31ec9817d1094aebf5b0fa8c16148)) +* **jans-config-api:** user mgmt endpoint ([f98c59e](https://github.com/JanssenProject/jans/commit/f98c59e15bb1199037cf6ad9caa67ffff23ca451)) +* **jans-config-api:** user mgmt endpoint ([0ea10fd](https://github.com/JanssenProject/jans/commit/0ea10fd10fdd82ea2f170ecfa990c494591ba653)) +* **jans-config-api:** user mgmt endpoint - wip ([70987f6](https://github.com/JanssenProject/jans/commit/70987f65c920943a7e214b9b742cd1f83e877995)) +* **jans-config-api:** user mgmt endpoint - wip ([af30358](https://github.com/JanssenProject/jans/commit/af30358d6f8933c405e68449041d5a9e121f3b9f)) +* **jans-config-api:** user mgmt endpoint - wip ([aadbf8b](https://github.com/JanssenProject/jans/commit/aadbf8b094d42f8acbf031ead6d4324c9925bed0)) +* **jans-config-api:** user mgmt endpoint -wip ([ac35327](https://github.com/JanssenProject/jans/commit/ac35327db9183bd75d314ff162e304e70132a035)) +* **jans-config-api:** user mgmt endpoints ([1d53b2e](https://github.com/JanssenProject/jans/commit/1d53b2e2acd1d0b4f33b37088ad90506aea522f6)) +* **jans-config-api:** user mgmt endpoints ([5cd1ad5](https://github.com/JanssenProject/jans/commit/5cd1ad5fbfddf43dfdbe6f6abf557f386437133d)) +* **jans-config-api:** user mgmt patch endpoint ([1180068](https://github.com/JanssenProject/jans/commit/1180068431f92cd6c21fe024371d363cf5eedd01)) +* **jans-config-api:** user mgmt patch endpoint ([12a08e1](https://github.com/JanssenProject/jans/commit/12a08e13105467c3912680fb47dc1943590b985a)) +* **jans-config-api:** user mgmt patch endpoint ([0427186](https://github.com/JanssenProject/jans/commit/0427186a48def0f16465c96d7839134d8c7902d9)) +* **jans-config-api:** user mgmt patch endpoint ([cb7d36c](https://github.com/JanssenProject/jans/commit/cb7d36cd21ac04f683c38f73d4c9642654886c18)) +* **jans-config-api:** user mgt plugin ([ccc56f8](https://github.com/JanssenProject/jans/commit/ccc56f888cbcda657cb2b66e2915e1cae8c96f88)) +* **jans-config-api:** user mgt plugin ([ae132cf](https://github.com/JanssenProject/jans/commit/ae132cfe829a3d2ff628b22b23ea716e879c769e)) +* **jans-config-api:** user-management endpoints ([#1167](https://github.com/JanssenProject/jans/issues/1167)) ([d8e97c4](https://github.com/JanssenProject/jans/commit/d8e97c4b47b1ff38d4a0207d3f07f461fb807630)) +* **jans-core:** added more error logs if script is not loaded ([4084aeb](https://github.com/JanssenProject/jans/commit/4084aebc7076ac612f569f72478941a9f1284930)) +* **jans-core:** added pure java discovery sample custom script ([1d01ba7](https://github.com/JanssenProject/jans/commit/1d01ba7b67ca5096c987c87c7315e163d632d39a)) +* **jans-core:** compile java code on the fly for custom script ([5da6e27](https://github.com/JanssenProject/jans/commit/5da6e2743761cbdf8f06b3dca9a5cf7c8af1abe3)) +* **jans-core:** corrected StandaloneCustomScriptManager ([0a52ec8](https://github.com/JanssenProject/jans/commit/0a52ec872d5ad7cbe065fbf3868c35df6e015393)) +* **jans-core:** remove UPDATE_USER and USER_REGISTRATION scripts [#1289](https://github.com/JanssenProject/jans/issues/1289) ([c34e75d](https://github.com/JanssenProject/jans/commit/c34e75d49db89999633249376c7b42c41bb1ce24)) +* jans-linux-setup config-api fido2-plugin (ref: [#1303](https://github.com/JanssenProject/jans/issues/1303)) ([#1308](https://github.com/JanssenProject/jans/issues/1308)) ([ea929c0](https://github.com/JanssenProject/jans/commit/ea929c0637c40ee75f3adbd5377c5e08aebbe087)) +* jans-linux-setup copy site packages in case of pyz ([8a8a05e](https://github.com/JanssenProject/jans/commit/8a8a05e4990088c65692b6032d3dd8d084913b49)) +* jans-linux-setup fido metadata folder ([8e95b7a](https://github.com/JanssenProject/jans/commit/8e95b7a3891d7168b08015b9606bad651f33d94e)) +* jans-linux-setup load pure java sample custom script ([#1335](https://github.com/JanssenProject/jans/issues/1335)) ([60cb36c](https://github.com/JanssenProject/jans/commit/60cb36c1f46ac7ef383072a5ad753dc4ef6320b3)) +* jans-linux-setup refactor key reneration for all backends (ref: [#1147](https://github.com/JanssenProject/jans/issues/1147)) ([#1228](https://github.com/JanssenProject/jans/issues/1228)) ([cbe29c4](https://github.com/JanssenProject/jans/commit/cbe29c4990405d9e00f665e51791f5dda94526a2)) +* jans-linux-setup set DefaultTimeoutStartSec=300s ([#1279](https://github.com/JanssenProject/jans/issues/1279)) ([6b511c4](https://github.com/JanssenProject/jans/commit/6b511c4b504303d05e930e01a999c919ec9c7bbc)) +* jans-linux-setup show version ([b16b77d](https://github.com/JanssenProject/jans/commit/b16b77dd0db16f07e2e0de81603be74ec4eab546)) +* **jans-linux-setup:** config-api user management plugin (ref: #[#1213](https://github.com/JanssenProject/jans/issues/1213)) ([#1223](https://github.com/JanssenProject/jans/issues/1223)) ([450c78c](https://github.com/JanssenProject/jans/commit/450c78cc2097a08ea485c26e8df6eb36146ca3be)) +* **jans-linux-setup:** multivalued json enhancement ([#1102](https://github.com/JanssenProject/jans/issues/1102)) ([b8fb658](https://github.com/JanssenProject/jans/commit/b8fb658e36eb13e0d6199eeec093733bf167ca1a)) +* **jans:** jetty 11 integration ([#1123](https://github.com/JanssenProject/jans/issues/1123)) ([6c1caa1](https://github.com/JanssenProject/jans/commit/6c1caa1c4c92d28571f8589cd701e6885d4d85ef)) +* merge ORM from Gluu ([#1200](https://github.com/JanssenProject/jans/issues/1200)) ([685a159](https://github.com/JanssenProject/jans/commit/685a1593fb53e2310cfa38fcd49db94f3453042f)) +* move file downloads to setup ([2680bd0](https://github.com/JanssenProject/jans/commit/2680bd01aa9227aa517de5c7de97c51b9b123a28)) +* pre-populate role scope mapping dynamically ([#1201](https://github.com/JanssenProject/jans/issues/1201)) ([3ab6a11](https://github.com/JanssenProject/jans/commit/3ab6a1167328625c26b32d2b3b7cc46d37216261)) +* remove Jython's pip from images ([#1176](https://github.com/JanssenProject/jans/issues/1176)) ([e3f374f](https://github.com/JanssenProject/jans/commit/e3f374f5bc3c385374593455243c88e2f7dfc00d)) +* user management enhancement to chk mandatory feilds ([3ac4b19](https://github.com/JanssenProject/jans/commit/3ac4b19ada28b11a27707c56ad266ce282f13b60)) + + +### Bug Fixes + +* [#1107](https://github.com/JanssenProject/jans/issues/1107) - not required ([cf46672](https://github.com/JanssenProject/jans/commit/cf466722c5ddd70b491d79a82e080557a32ce161)) +* [#1107](https://github.com/JanssenProject/jans/issues/1107) jansCodeChallengeHash missing ([65ac184](https://github.com/JanssenProject/jans/commit/65ac1846f19e3d8e7d4833e009cb5cdd58ff2c09)) +* add issue guidelines to TOC ([#1188](https://github.com/JanssenProject/jans/issues/1188)) ([192165b](https://github.com/JanssenProject/jans/commit/192165b3eecc3cbcb03a5c9774a4dae212a9ffad)) +* add missing permission and defaultPermissionInToken attribute in role-scope mapping ([#1270](https://github.com/JanssenProject/jans/issues/1270)) ([e2c67ec](https://github.com/JanssenProject/jans/commit/e2c67ec8e662adbaab7c5d735217aa5bcbf8495c)) +* adjust beans and schema [#1107](https://github.com/JanssenProject/jans/issues/1107) ([#1248](https://github.com/JanssenProject/jans/issues/1248)) ([369129d](https://github.com/JanssenProject/jans/commit/369129d0c2614afb536d0e1329ac106fd7da187d)) +* **admin-ui:** the backend issues related to jetty 11 migration [#1258](https://github.com/JanssenProject/jans/issues/1258) ([#1259](https://github.com/JanssenProject/jans/issues/1259)) ([d61be0b](https://github.com/JanssenProject/jans/commit/d61be0bf633020c6bd989e603bb983dc7a45b78b)) +* **agama:** adjust pom version [#1402](https://github.com/JanssenProject/jans/issues/1402) ([#1403](https://github.com/JanssenProject/jans/issues/1403)) ([930f080](https://github.com/JanssenProject/jans/commit/930f0801177d516d6bfa9c536d590556144cbd61)) +* **agama:** adjust pom version [#1402](https://github.com/JanssenProject/jans/issues/1402) ([#1404](https://github.com/JanssenProject/jans/issues/1404)) ([86bf614](https://github.com/JanssenProject/jans/commit/86bf61420bf6b8d236b8200835a2ff05f430308b)) +* avoid duplicated client when re-running persistence-loader and configurator ([#1134](https://github.com/JanssenProject/jans/issues/1134)) ([5567ba9](https://github.com/JanssenProject/jans/commit/5567ba90d0484128b5a875fdc5f1406ce2c69e8a)) +* broken links ([86d0232](https://github.com/JanssenProject/jans/commit/86d023209fd8af5422153b1dd97e2a25a7b59c28)) +* bug(jans-auth-server): custom pages are not found [#1318](https://github.com/JanssenProject/jans/issues/1318) ([e1e0bf9](https://github.com/JanssenProject/jans/commit/e1e0bf943f35906430b0fae5333f3b76f05734c3)) +* change column size of jansFido2AuthnEntry.jansAuthData column ([#1066](https://github.com/JanssenProject/jans/issues/1066)) ([f1c3ffa](https://github.com/JanssenProject/jans/commit/f1c3ffa7fa72114b7e6dc2685789dade0feadf42)) +* code smells ([e5aaad7](https://github.com/JanssenProject/jans/commit/e5aaad7da310b26c4d6a6b8cfb6ed00e442b1629)) +* **config-api:** scim user management endpoint failing due to conflict with user mgmt path ([#1181](https://github.com/JanssenProject/jans/issues/1181)) ([8ee47a0](https://github.com/JanssenProject/jans/commit/8ee47a0c62ac1d13ad4a62367744e106c759bbc9)) +* Data too long for column [#1107](https://github.com/JanssenProject/jans/issues/1107) ([8eb2c70](https://github.com/JanssenProject/jans/commit/8eb2c70c95c2e60486ff1dd5a9e00acd9d70dc3b)) +* errors adding/upgrading data into couchbase persistence ([#1226](https://github.com/JanssenProject/jans/issues/1226)) ([db71324](https://github.com/JanssenProject/jans/commit/db71324ee7a94ac06a5505f7ed0993bf8f1c4f79)) +* extract directory ([fe7a3c5](https://github.com/JanssenProject/jans/commit/fe7a3c564fb867fda4b28181c3158e8d282da238)) +* fix license apis[#1258](https://github.com/JanssenProject/jans/issues/1258) ([#1271](https://github.com/JanssenProject/jans/issues/1271)) ([14c6a2b](https://github.com/JanssenProject/jans/commit/14c6a2b757bf94116faf9c0f13ab8c5e64c31f32)) +* handle index error for JSON columns ([#1205](https://github.com/JanssenProject/jans/issues/1205)) ([90f77c3](https://github.com/JanssenProject/jans/commit/90f77c39beeb8a4c30a46819a7877514fdaa4531)) +* hyperlinks ([#1209](https://github.com/JanssenProject/jans/issues/1209)) ([d1e1ed6](https://github.com/JanssenProject/jans/commit/d1e1ed63d8cea3030b23bd218241fe2275ed6e52)) +* invalid LDAP schema reading token_server client ID ([#1321](https://github.com/JanssenProject/jans/issues/1321)) ([db4f080](https://github.com/JanssenProject/jans/commit/db4f0809bb697cc2e88a7ad58917006f132ea5e5)) +* jans cli update readme ([2f4f57f](https://github.com/JanssenProject/jans/commit/2f4f57f3d1d38e2c7ca8fd2edc1da798dc36d425)) +* **jans-auth-server:** added faces context as source of locale ([#1189](https://github.com/JanssenProject/jans/issues/1189)) ([ce770ae](https://github.com/JanssenProject/jans/commit/ce770aed92c4279647d0bdd541a943ada9e6c743)) +* **jans-auth-server:** authorize page message policy ([#1096](https://github.com/JanssenProject/jans/issues/1096)) ([f10ccb1](https://github.com/JanssenProject/jans/commit/f10ccb166307cf281cbd36c757972eb3e1babf2e)) +* **jans-auth-server:** corrected fallback value of checkUserPresenceOnRefreshToken ([a822ae5](https://github.com/JanssenProject/jans/commit/a822ae5546934f4d9cabd1c3e4540b4a23d5abe0)) +* **jans-auth-server:** corrected log vulnerability ([1000a60](https://github.com/JanssenProject/jans/commit/1000a60d3a4263784250960565c73d98a52f200a)) +* **jans-auth-server:** corrected npe in response type class ([941248d](https://github.com/JanssenProject/jans/commit/941248d9deed74b82453e161ffd8d5badd00546a)) +* **jans-auth-server:** corrected signature algorithm identification with java 11 and later ([3e203f2](https://github.com/JanssenProject/jans/commit/3e203f27e4b6bdb59d25cb59f823c12675c3ffd3)) +* **jans-auth-server:** corrected thread-safety bug in ApplicationAuditLogger [#803](https://github.com/JanssenProject/jans/issues/803) ([ef73c2b](https://github.com/JanssenProject/jans/commit/ef73c2b375f021f16117b1e987a3bd487596bd5b)) +* **jans-auth-server:** disabled issuing AT by refresh token if user status=inactive ([3df72a8](https://github.com/JanssenProject/jans/commit/3df72a83a59d11b2ac32aad80ec8207560f4813e)) +* **jans-auth-server:** do not serialize jwkThumbprint ([d8634fe](https://github.com/JanssenProject/jans/commit/d8634fef2aa497787b0c7e5bb37179f8259eb415)) +* **jans-auth-server:** during encryption AS must consider client's jwks too, not only jwks_uri ([475b154](https://github.com/JanssenProject/jans/commit/475b1547dc35608925b4dc07a70130b34c355d1b)) +* **jans-auth-server:** dynamic client registration managment delete event ([911e54b](https://github.com/JanssenProject/jans/commit/911e54b0858b02d97178ee6f03192d6a5919e47d)) +* **jans-auth-server:** escape login_hint before rendering ([e1a682a](https://github.com/JanssenProject/jans/commit/e1a682aadd083e3000f51fa950dc4feb83680f1c)) +* **jans-auth-server:** fixed equals/hashcode by removing redundant dn field ([d27659d](https://github.com/JanssenProject/jans/commit/d27659d99200246de68387273c308bda012f39af)) +* **jans-auth-server:** fixed server and tests after jetty 11 migration ([#1354](https://github.com/JanssenProject/jans/issues/1354)) ([3fa19f4](https://github.com/JanssenProject/jans/commit/3fa19f491b6ef810eb679ca23551abcbdf2086cb)) +* **jans-auth-server:** gluuStatus -> jansStatus ([7f86d6d](https://github.com/JanssenProject/jans/commit/7f86d6d5d7539259d279f7f5eb1ab4320617c598)) +* **jans-auth-server:** isolate regex redirection uri validation test ([#1075](https://github.com/JanssenProject/jans/issues/1075)) ([cca0551](https://github.com/JanssenProject/jans/commit/cca055127dc57f29b6bc4e913b7a2a52ad5a1a88)) +* **jans-auth-server:** removed CONFIG_API from AS supported script types [#1286](https://github.com/JanssenProject/jans/issues/1286) ([c209868](https://github.com/JanssenProject/jans/commit/c209868c4fa94caf135e5726e3caa5b4462fd38d)) +* **jans-auth-server:** removed ThumbSignInExternalAuthenticator ([a13ca51](https://github.com/JanssenProject/jans/commit/a13ca51a753bc7f779899e0c86865c1a6bdb0374)) +* **jans-auth-server:** renamed localization resoruces files [#1198](https://github.com/JanssenProject/jans/issues/1198) ([#1199](https://github.com/JanssenProject/jans/issues/1199)) ([4561f2a](https://github.com/JanssenProject/jans/commit/4561f2a7f5194aba76e9644a3eb7627badb58c76)) +* **jans-auth-server:** restored id generator call to external custom script ([#1128](https://github.com/JanssenProject/jans/issues/1128)) ([5ba98c1](https://github.com/JanssenProject/jans/commit/5ba98c13104a8559242ba7240fe8bbfe314fc0c5)) +* **jans-auth-server:** use duration class instead of custom util to calculate seconds from date to now ([#1249](https://github.com/JanssenProject/jans/issues/1249)) ([5ae76ab](https://github.com/JanssenProject/jans/commit/5ae76ab0298995e971635f63b6c83cf455b16e14)) +* **jans-auth-server:** validate redirect_uri blank and client redirect uris single item to return by default ([#1046](https://github.com/JanssenProject/jans/issues/1046)) ([aa139e4](https://github.com/JanssenProject/jans/commit/aa139e46e6d25c6135eb05e22dbc36fe84eb3e86)) +* jans-cl update WebKeysConfiguration ([#1211](https://github.com/JanssenProject/jans/issues/1211)) ([54847bc](https://github.com/JanssenProject/jans/commit/54847bce0f066ca1ca5d3e0cf01420815c30868c)) +* jans-cli allow emptying list attrbiutes by _null ([#1166](https://github.com/JanssenProject/jans/issues/1166)) ([571c5cd](https://github.com/JanssenProject/jans/commit/571c5cd38e42871dd27605f68058b5d766e1f91e)) +* jans-cli code smells ([1dc5cb0](https://github.com/JanssenProject/jans/commit/1dc5cb0d05ab3a97c9e414d80e81f0d75586f087)) +* jans-cli do not require client if access token is provided ([6b787ec](https://github.com/JanssenProject/jans/commit/6b787ec0313794ac04b81f949d5a2c5f1a5f21dc)) +* jans-cli hardcode enums ([739a759](https://github.com/JanssenProject/jans/commit/739a7595dd98751142835957e7d006c59872c89e)) +* jans-cli scope dn/id when creating client ([518f971](https://github.com/JanssenProject/jans/commit/518f97147970c3a2465f4ef7d14481b05129f346)) +* jans-cli scope dn/id when creating client ([f056abf](https://github.com/JanssenProject/jans/commit/f056abfe98c478c76fad9c6ec1d30b5287b1e208)) +* **jans-cli:** corrected typo ([#1050](https://github.com/JanssenProject/jans/issues/1050)) ([4d93a49](https://github.com/JanssenProject/jans/commit/4d93a4926e46e7d82980a187a0be49aac0df9c1c)) +* jans-client-api replace netstat with ss in startup script ([#1246](https://github.com/JanssenProject/jans/issues/1246)) ([cde3fb1](https://github.com/JanssenProject/jans/commit/cde3fb1ef1d8f33c74983b0936485e8298e155bf)) +* **jans-config-api:** corrected typo in swagger spec ([3c11556](https://github.com/JanssenProject/jans/commit/3c115566c843e42ae9827a76496145ddc6288155)) +* **jans-config-api:** LDAP test endpoint fix ([#1320](https://github.com/JanssenProject/jans/issues/1320)) ([fb0e132](https://github.com/JanssenProject/jans/commit/fb0e13251ee645862d8f02cbade5d64a2673a0b6)) +* **jans-core:** corrected ExternalUmaClaimsGatheringService ([cfe1b6d](https://github.com/JanssenProject/jans/commit/cfe1b6d0eae75a699fc0505fea46e955a3480b57)) +* jans-linux-setup --add-module ([4f6b8a9](https://github.com/JanssenProject/jans/commit/4f6b8a9d6482f89426a82596fa1cfbc1cf12159a)) +* jans-linux-setup code smell ([09bb36e](https://github.com/JanssenProject/jans/commit/09bb36ed70620238261e39689c8d843d6d0212b7)) +* jans-linux-setup code smell ([b790c01](https://github.com/JanssenProject/jans/commit/b790c0181e50b1de22114e922b7c0788e50338d0)) +* jans-linux-setup code smell ([3c57d5e](https://github.com/JanssenProject/jans/commit/3c57d5efb5e81b12b9a52b087e35fba42eb664a3)) +* jans-linux-setup code smells ([4f362e5](https://github.com/JanssenProject/jans/commit/4f362e59e50f6598195e71f03dbe863fcb82526e)) +* jans-linux-setup code smells ([824cf1f](https://github.com/JanssenProject/jans/commit/824cf1f2821f2e365e8905da21fb353de34048d4)) +* jans-linux-setup code smells ([b2a48db](https://github.com/JanssenProject/jans/commit/b2a48db9df08d5566d8b98f2debfd3ba96b48435)) +* jans-linux-setup code smells ([e930f16](https://github.com/JanssenProject/jans/commit/e930f16f34570645357dcc29d0cf9df3cb16cf4d)) +* jans-linux-setup code smells ([45953c6](https://github.com/JanssenProject/jans/commit/45953c6daa0a2abece7fc08913bffcd76a11dce0)) +* jans-linux-setup code smells ([b01da85](https://github.com/JanssenProject/jans/commit/b01da856158d2e59cc741ce6ff9283d1a549388f)) +* jans-linux-setup config-api plugin dependencies ([#1310](https://github.com/JanssenProject/jans/issues/1310)) ([b5577dd](https://github.com/JanssenProject/jans/commit/b5577ddcec55ef9a47fca30ebd897867d5601f40)) +* jans-linux-setup copy_tree ([2c2ad3a](https://github.com/JanssenProject/jans/commit/2c2ad3a100119d3c6cfb39b1454a6c9ba4e55a9a)) +* jans-linux-setup create json index for multivalued attributes ([#1131](https://github.com/JanssenProject/jans/issues/1131)) ([be9e63c](https://github.com/JanssenProject/jans/commit/be9e63c6a96b6d91672d4e6b9700625da1196be3)) +* jans-linux-setup dependency prompt-toolkit ([865647e](https://github.com/JanssenProject/jans/commit/865647eacdf3e4c9b0a26a0d3b4980fe2c3464b7)) +* jans-linux-setup maven url ([244135d](https://github.com/JanssenProject/jans/commit/244135d0af7253896ccacbdcd59034666c7ace59)) +* jans-linux-setup move mysql-timezone to config ([31df7db](https://github.com/JanssenProject/jans/commit/31df7db5d130194b3bc65fc014c92b926f828291)) +* jans-linux-setup multivalued json mapping (ref: [#1088](https://github.com/JanssenProject/jans/issues/1088)) ([#1090](https://github.com/JanssenProject/jans/issues/1090)) ([e3d9dbf](https://github.com/JanssenProject/jans/commit/e3d9dbffdab29d58d31dab004f5d392f5ada0591)) +* jans-linux-setup openbanking setup issues ([3837dd2](https://github.com/JanssenProject/jans/commit/3837dd2fd0a48de16625df368be3a3f23f5d3625)) +* jans-linux-setup set log level to TRACE for test data ([#1345](https://github.com/JanssenProject/jans/issues/1345)) ([21a2120](https://github.com/JanssenProject/jans/commit/21a21201dcbf3b3e3d8aecf878122996b09349d1)) +* jans-linux-setup typo ([#1311](https://github.com/JanssenProject/jans/issues/1311)) ([97723d5](https://github.com/JanssenProject/jans/commit/97723d588ba736bcf6e71f9a02028708c27b4fff)) +* jans-linux-setup url of config api scim plugin ([da007f0](https://github.com/JanssenProject/jans/commit/da007f074ef35d1b613dccdb4dd29e22afe98f7a)) +* jans-linux-setup-key key-regeneration fix spanner host ([#1229](https://github.com/JanssenProject/jans/issues/1229)) ([5a472ad](https://github.com/JanssenProject/jans/commit/5a472ad4f9c1e27ca361275353944043a12a5c1e)) +* **jans-linux-setup:** copy user-mgt-plugin ([#1225](https://github.com/JanssenProject/jans/issues/1225)) ([8def41a](https://github.com/JanssenProject/jans/commit/8def41a608ad611ee2981ca790d4cc64d74d23c6)) +* **jans-linux-setup:** defaults loggingLevel to INFO ([#1346](https://github.com/JanssenProject/jans/issues/1346)) ([26b1163](https://github.com/JanssenProject/jans/commit/26b116366bc640377d22ec5ac911be6b7cc009a3)) +* **jans-linux-setup:** enable mod_auth_openidc ([#1048](https://github.com/JanssenProject/jans/issues/1048)) ([40e24ea](https://github.com/JanssenProject/jans/commit/40e24eac2f8e4b1903047afe077b2508067da7b3)) +* **jans-linux-setup:** minor typo ([#1109](https://github.com/JanssenProject/jans/issues/1109)) ([32b5af5](https://github.com/JanssenProject/jans/commit/32b5af5beff24366de0e190126ee4720ae003cd9)) +* **jans-linux-setup:** rdbm index ([#1135](https://github.com/JanssenProject/jans/issues/1135)) ([ec3bd1b](https://github.com/JanssenProject/jans/commit/ec3bd1bd2eb1c97aedd8824bad4bf90282e5fe06)) +* **jans-linux-setup:** remove attributes of size 64 from sql_data_types.json ([#1112](https://github.com/JanssenProject/jans/issues/1112)) ([1726d09](https://github.com/JanssenProject/jans/commit/1726d09299bd11a3cb6c963a89770de26e043403)) +* linux-setup don't use personCustomObjectClassList for RDBMS (ref: [#1214](https://github.com/JanssenProject/jans/issues/1214)) ([#1216](https://github.com/JanssenProject/jans/issues/1216)) ([4d8dff7](https://github.com/JanssenProject/jans/commit/4d8dff7dc3957f5aa57dee1e2a3060612196c8a4)) +* Make column wider [#1044](https://github.com/JanssenProject/jans/issues/1044) ([f3e393f](https://github.com/JanssenProject/jans/commit/f3e393fe523d1edbe1ff110eaeb4caf6fdcfc61c)) +* Security Hotspot ([4e091c4](https://github.com/JanssenProject/jans/commit/4e091c4d447d298b1d3320b8f1fd45c984ef402d)) +* Security Hotspot ([1899a39](https://github.com/JanssenProject/jans/commit/1899a39ef68aaf7f535e3caf1945ef94412a8d30)) +* set permission for jans-auth.xml explicitly ([#1315](https://github.com/JanssenProject/jans/issues/1315)) ([80f33a2](https://github.com/JanssenProject/jans/commit/80f33a23902af7498fa85d2785abe1af77a1751e)) +* submit button is missing from the Properties page [#175](https://github.com/JanssenProject/jans/issues/175) ([2424965](https://github.com/JanssenProject/jans/commit/242496594af8fd5d82960747c53078149a7e1e57)) +* the admin-ui backend issues related to jetty 11 migration [#1258](https://github.com/JanssenProject/jans/issues/1258) ([cf94d5f](https://github.com/JanssenProject/jans/commit/cf94d5f56f43b523f3bfd06429992f4705a0a4ae)) +* typo and indexing error ([#1125](https://github.com/JanssenProject/jans/issues/1125)) ([dc87dc0](https://github.com/JanssenProject/jans/commit/dc87dc01c4c63d6fcc2b967ce97a52880083b95f)) +* Typo httpLoggingExludePaths jans-auth-server jans-cli jans-config-api jans-linux-setup docker-jans-persistence-loader ([47a20ee](https://github.com/JanssenProject/jans/commit/47a20eefa781d1ca07a9aa30a5adcde3793076d1)) +* typo in jans-cli interactive mode ([25f5971](https://github.com/JanssenProject/jans/commit/25f59716aa2bccb2dcdb47a34a7039a0e83d0f5f)) +* update api-admin permissions from config api yaml ([#1183](https://github.com/JanssenProject/jans/issues/1183)) ([438c896](https://github.com/JanssenProject/jans/commit/438c8967bbd925779e8ec7b84b9021de32ec409c)) +* update mysql/spanner mappings [#1053](https://github.com/JanssenProject/jans/issues/1053) ([94fb2c6](https://github.com/JanssenProject/jans/commit/94fb2c6d0f5de061eca515c003be679f35757faa)) +* update templates [#1053](https://github.com/JanssenProject/jans/issues/1053) ([2e33a43](https://github.com/JanssenProject/jans/commit/2e33a43f1d1cc029bcb96992b7bd468956d738fc)) +* Use highest level script in case ACR script is not found. Added FF to keep existing behavior. ([#1070](https://github.com/JanssenProject/jans/issues/1070)) ([07473d9](https://github.com/JanssenProject/jans/commit/07473d9a8c3e31f6a75670a874e17341518bf0be)) +* use secure http urls for maven repositories ([#1353](https://github.com/JanssenProject/jans/issues/1353)) ([496b5b2](https://github.com/JanssenProject/jans/commit/496b5b296b85e9d9c2b87dbfb93fc6f4bb6430b5)) +* use shutil instead of zipfile ([c0a0cde](https://github.com/JanssenProject/jans/commit/c0a0cde87874a73a61bcf87efdfedada1e4f4f10)) + + +### Miscellaneous Chores + +* prepare release 1.0.0-1 ([8985928](https://github.com/JanssenProject/jans/commit/89859286d69e7de7885bd9da9f50720c8371e797)) +* release 1.0.0 ([9644d1b](https://github.com/JanssenProject/jans/commit/9644d1bd29c291e57c140b0c9ac67243c322ac35)) +* release 1.0.0 ([b2895f2](https://github.com/JanssenProject/jans/commit/b2895f224b5772c0724ea0afbdf67a417a5c537c)) +* release 1.0.0-beta.16 ([a083ad6](https://github.com/JanssenProject/jans/commit/a083ad6b1d43201126e8d4f690a55ea1b109524c)) +* release 1.0.0-beta.16 ([90e4bb2](https://github.com/JanssenProject/jans/commit/90e4bb29df040bd9fe5921a054bc4226d34ca1ef)) +* release 1.0.0-beta.16 ([eec2073](https://github.com/JanssenProject/jans/commit/eec2073be9fd25544f31087e171934afb9a71e6d)) +* release 1.0.0-beta.16 ([cd92ead](https://github.com/JanssenProject/jans/commit/cd92ead2ca654383091c4923d3de5619b70fc5b9)) +* release 1.0.0-beta.16 ([7f0a91b](https://github.com/JanssenProject/jans/commit/7f0a91bd90efc1cd7a80047f9cd6b7c6a22417a2)) +* release 1.0.0-beta.16 ([c2ad604](https://github.com/JanssenProject/jans/commit/c2ad604dc29e7401bc4cb0788feaa20e11de0440)) +* release 1.0.0-beta.16 ([a641486](https://github.com/JanssenProject/jans/commit/a6414864712789d1fcf80b823338100aebda030e)) +* release 1.0.0-beta.16 ([94d5791](https://github.com/JanssenProject/jans/commit/94d5791a23fce4ecb8913c16c940cfbbc85fed4c)) +* release 1.0.0-beta.16 ([16de429](https://github.com/JanssenProject/jans/commit/16de4299bc5e9c4a842f279ae0d3ae8282a4ff2c)) +* release 1.0.0-beta.16 ([72915c0](https://github.com/JanssenProject/jans/commit/72915c0e82b9684ac1c59934d5b9a36c2456058d)) +* release 1.0.0-beta.16 ([3ea2b37](https://github.com/JanssenProject/jans/commit/3ea2b37deac3416564614fb6a4e84b056ddbed3f)) +* release 1.0.0-beta.16 ([78a6d39](https://github.com/JanssenProject/jans/commit/78a6d39ffadf9abee18c7be0e14ad3eb6ec2ef1b)) +* release 1.0.0-beta.16 ([11bfa93](https://github.com/JanssenProject/jans/commit/11bfa9368e6ee482cc44240de08c8133d91b3f4c)) +* release 1.0.0-beta.16 ([22b180b](https://github.com/JanssenProject/jans/commit/22b180bba9a08045a6daa7ca8ee2b71abd42a973)) +* release 1.0.0-beta.16 ([b9acd0b](https://github.com/JanssenProject/jans/commit/b9acd0bceeeb54e3c47f869f11d97a22e8dc161f)) +* release 1.0.0-beta.16 ([328cd30](https://github.com/JanssenProject/jans/commit/328cd309ae1655a52709e13ca2f89441c6c965a2)) +* release 1.0.0-beta.16 ([5a84602](https://github.com/JanssenProject/jans/commit/5a84602838fb5d2e667422220fcd44dc53543e23)) +* release 1.0.0-beta.16 ([4923277](https://github.com/JanssenProject/jans/commit/4923277b100b5c814d94b27b88d1809794dfc413)) +* release 1.0.0-beta.16 ([258ba96](https://github.com/JanssenProject/jans/commit/258ba962bd93eb5be4d51e7de3a80da89c2e222f)) +* release 1.0.0-beta.16 ([77c4423](https://github.com/JanssenProject/jans/commit/77c4423d82b697fd91a0e61f40bad6bd9da0dba8)) +* release 1.0.0-beta.16 ([688b324](https://github.com/JanssenProject/jans/commit/688b32407b396917695cca787c08e95fe98269a1)) + +## [1.0.0-beta.16](https://github.com/JanssenProject/jans/compare/v1.0.0-beta.15...v1.0.0-beta.16) (2022-03-14) + + +### Features + +* add acrValues property in admin-ui configuration. [#1016](https://github.com/JanssenProject/jans/issues/1016) ([#1017](https://github.com/JanssenProject/jans/issues/1017)) ([88b591a](https://github.com/JanssenProject/jans/commit/88b591a64bf9ed0fb49942b770d9f0e334b7433c)) +* add support for role-based client (i.e. jans-cli) ([#956](https://github.com/JanssenProject/jans/issues/956)) ([306bd52](https://github.com/JanssenProject/jans/commit/306bd524bb1f3139aaed9ca3b3be91390de70fe7)) +* add support to import custom ldif ([#1002](https://github.com/JanssenProject/jans/issues/1002)) ([0b6334a](https://github.com/JanssenProject/jans/commit/0b6334acdb862ce458c628a8eb81ef0b8f7c5dcb)) +* add validity length (in days) for certs ([#981](https://github.com/JanssenProject/jans/issues/981)) ([abc89dc](https://github.com/JanssenProject/jans/commit/abc89dc6fadae5627a68a97ab4f4f5ceb56af809)) +* **jans-auth-server:** forbid plain pkce if fapi=true (fapi1-advanced-final-par-plain-pkce-rejected fail) [#946](https://github.com/JanssenProject/jans/issues/946) ([21cecb0](https://github.com/JanssenProject/jans/commit/21cecb04909a9b69da5da3a206c83ca52c9e2c8b)) +* **jans-auth-server:** new client config option defaultpromptlogin [#979](https://github.com/JanssenProject/jans/issues/979) ([4e3de26](https://github.com/JanssenProject/jans/commit/4e3de2627f676d35186877a8570de6ce8950ec57)) +* **jans-cli:** get access token from arg ([#1013](https://github.com/JanssenProject/jans/issues/1013)) ([efd718a](https://github.com/JanssenProject/jans/commit/efd718ae39cadd97f2d464572901af2b82932284)) +* **jans-config-api:** swagger spec change to add extension ([4f9d76c](https://github.com/JanssenProject/jans/commit/4f9d76cef689649f993df25e88e56526cfd26d02)) +* **jans-config-api:** swagger spec change to add extension to differentiate plugin enâ€Ļ ([4f9d76c](https://github.com/JanssenProject/jans/commit/4f9d76cef689649f993df25e88e56526cfd26d02)) +* **jans-linux-setup:** check availibility of ports for OpenDJ backend ([#949](https://github.com/JanssenProject/jans/issues/949)) ([a2944c1](https://github.com/JanssenProject/jans/commit/a2944c1ee432985c2bb8e8d52c22710ec73f7039)) +* **jans-linux-setup:** install mod_auth_openidc (ref: [#909](https://github.com/JanssenProject/jans/issues/909)) ([#952](https://github.com/JanssenProject/jans/issues/952)) ([270a7b6](https://github.com/JanssenProject/jans/commit/270a7b6e1f83f08a2a3caadb2ef1ee36e4233957)) +* **jans-linux-setup:** refactored argsp ([#969](https://github.com/JanssenProject/jans/issues/969)) ([409d364](https://github.com/JanssenProject/jans/commit/409d364383a1777ce0c5ef85fc19b432bce6c6d1)) +* support regex client attribute to validate redirect uris ([#1005](https://github.com/JanssenProject/jans/issues/1005)) ([a78ee1a](https://github.com/JanssenProject/jans/commit/a78ee1a3cfc4e7a6d08a500750edb5db0f7709a4)) +* swagger spec change to add extension to differentiate plugin endpoint ([bb3b88a](https://github.com/JanssenProject/jans/commit/bb3b88a59376ff8875e1b38048a9c360e01de8de)) + + +### Bug Fixes + +* ** jans-linux-setup:** added to extraClasspath ([#968](https://github.com/JanssenProject/jans/issues/968)) ([bfb0bfe](https://github.com/JanssenProject/jans/commit/bfb0bfe63abdc86a1384badfe15e3d985213001e)) +* add missing values for openbanking ([#939](https://github.com/JanssenProject/jans/issues/939)) ([b140892](https://github.com/JanssenProject/jans/commit/b140892d3c697226b642e18402ace6ea69b38f48)) +* avoid jetty hot-deployment issue ([#1012](https://github.com/JanssenProject/jans/issues/1012)) ([a343215](https://github.com/JanssenProject/jans/commit/a34321594055305d52aa855b32d060b113313652)) +* change in swagger spec for jwks to return missing attributes ([477643b](https://github.com/JanssenProject/jans/commit/477643bf6cc1fc6226ce7790e05c1a981324d06e)) +* **ci:** fix change identification logic ([#966](https://github.com/JanssenProject/jans/issues/966)) ([e964291](https://github.com/JanssenProject/jans/commit/e964291807b475ecf930ffb4ba86fd3501058f96)) +* jans cli build issues (update doc and fix requirements) ([#938](https://github.com/JanssenProject/jans/issues/938)) ([18d1507](https://github.com/JanssenProject/jans/commit/18d1507936a9fdcd8ee6daa46f2ca0af070ea4ba)) +* **jans-auth-server:** corrected ParValidatorTest [#946](https://github.com/JanssenProject/jans/issues/946) ([04a01fd](https://github.com/JanssenProject/jans/commit/04a01fd43e1969bc09494b2f08387bcc7d502ed7)) +* **jans-auth-server:** corrected sonar reported issue ([7c88078](https://github.com/JanssenProject/jans/commit/7c8807820a217f33c66f496b05863e9d77d8c7e8)) +* **jans-auth-server:** fix npe ([e6debb2](https://github.com/JanssenProject/jans/commit/e6debb24ea0ea1963290b543d74df7f0761efe3b)) +* **jans-auth-server:** reduce noise in logs when session can't be found ([47afc47](https://github.com/JanssenProject/jans/commit/47afc47a239c48c090591d9fa561757e7749d96d)) +* **jans-auth-server:** removed reference of removed tests [#996](https://github.com/JanssenProject/jans/issues/996) ([cabc4f2](https://github.com/JanssenProject/jans/commit/cabc4f2f6119e2aff0440fdb1bb4dd1f11dce2cd)) +* **jans-auth-server:** validate pkce after extraction data from request object ([#999](https://github.com/JanssenProject/jans/issues/999)) ([29fdfae](https://github.com/JanssenProject/jans/commit/29fdfae276b61890ed345804827aa83437acd428)) +* **jans-config-api:** create openid client throwing 502 ([#1004](https://github.com/JanssenProject/jans/issues/1004)) ([3f58aff](https://github.com/JanssenProject/jans/commit/3f58affce39a15e051a1188c619b40115607f437)) +* jans-linux-setup add dependency python3-prompt-toolkit ([#975](https://github.com/JanssenProject/jans/issues/975)) ([2d4a101](https://github.com/JanssenProject/jans/commit/2d4a101defbcddb79d6417f128a553be4c16430c)) +* jans-linux-setup flex-setup argsp ([7ee41a7](https://github.com/JanssenProject/jans/commit/7ee41a7a5745d28c9314ce0c84e894c230b9b7ae)) +* jans-linux-setup flex-setup argsp ([7ee41a7](https://github.com/JanssenProject/jans/commit/7ee41a7a5745d28c9314ce0c84e894c230b9b7ae)) +* jans-linux-setup flex-setup argsp ([9a00e93](https://github.com/JanssenProject/jans/commit/9a00e935f56da1448603e60f55e22dce6740a389)) +* jans-linux-setup getting argparser ([#974](https://github.com/JanssenProject/jans/issues/974)) ([5fc60d4](https://github.com/JanssenProject/jans/commit/5fc60d4035d42fc85566c505bfdc9e693da542b4)) +* jans-linux-setup remove fido authentication scripts from template ([#991](https://github.com/JanssenProject/jans/issues/991)) ([753ab0c](https://github.com/JanssenProject/jans/commit/753ab0c2b26fae8fc4fee14e268401588ab07a59)) +* **jans-linux-setup:** backup cli direcory if any ([#976](https://github.com/JanssenProject/jans/issues/976)) ([dc42d0f](https://github.com/JanssenProject/jans/commit/dc42d0f014514bde69666abaeffcbe79a67888ca)) +* **jans-linux-setup:** not copy duo_web.py ([#971](https://github.com/JanssenProject/jans/issues/971)) ([b5691b5](https://github.com/JanssenProject/jans/commit/b5691b51a200c2b9ba778f5a92c0d714d07e3b80)) +* **jans-linux-setup:** openbanking argparser issue ([#985](https://github.com/JanssenProject/jans/issues/985)) ([ab40173](https://github.com/JanssenProject/jans/commit/ab40173a8c0cb6ec7e2fa1398561dab5ad2a5abc)) +* **jans-linux-setup:** require python3-distutils for deb clones ([#967](https://github.com/JanssenProject/jans/issues/967)) ([9a76f23](https://github.com/JanssenProject/jans/commit/9a76f23e259e2e1b7290285f5ee9a70a66be9b0c)) +* **jans-linux-setup:** update suse15 dependency ([#980](https://github.com/JanssenProject/jans/issues/980)) ([3be0ffa](https://github.com/JanssenProject/jans/commit/3be0ffaf09001b97b1e582b6e04bf51cc4bcdbed)) + + +### Miscellaneous Chores + +* release 1.0.0-beta.16 ([a083ad6](https://github.com/JanssenProject/jans/commit/a083ad6b1d43201126e8d4f690a55ea1b109524c)) +* release 1.0.0-beta.16 ([90e4bb2](https://github.com/JanssenProject/jans/commit/90e4bb29df040bd9fe5921a054bc4226d34ca1ef)) +* release 1.0.0-beta.16 ([eec2073](https://github.com/JanssenProject/jans/commit/eec2073be9fd25544f31087e171934afb9a71e6d)) +* release 1.0.0-beta.16 ([cd92ead](https://github.com/JanssenProject/jans/commit/cd92ead2ca654383091c4923d3de5619b70fc5b9)) +* release 1.0.0-beta.16 ([7f0a91b](https://github.com/JanssenProject/jans/commit/7f0a91bd90efc1cd7a80047f9cd6b7c6a22417a2)) +* release 1.0.0-beta.16 ([c2ad604](https://github.com/JanssenProject/jans/commit/c2ad604dc29e7401bc4cb0788feaa20e11de0440)) +* release 1.0.0-beta.16 ([a641486](https://github.com/JanssenProject/jans/commit/a6414864712789d1fcf80b823338100aebda030e)) +* release 1.0.0-beta.16 ([94d5791](https://github.com/JanssenProject/jans/commit/94d5791a23fce4ecb8913c16c940cfbbc85fed4c)) +* release 1.0.0-beta.16 ([16de429](https://github.com/JanssenProject/jans/commit/16de4299bc5e9c4a842f279ae0d3ae8282a4ff2c)) +* release 1.0.0-beta.16 ([72915c0](https://github.com/JanssenProject/jans/commit/72915c0e82b9684ac1c59934d5b9a36c2456058d)) +* release 1.0.0-beta.16 ([3ea2b37](https://github.com/JanssenProject/jans/commit/3ea2b37deac3416564614fb6a4e84b056ddbed3f)) +* release 1.0.0-beta.16 ([78a6d39](https://github.com/JanssenProject/jans/commit/78a6d39ffadf9abee18c7be0e14ad3eb6ec2ef1b)) +* release 1.0.0-beta.16 ([11bfa93](https://github.com/JanssenProject/jans/commit/11bfa9368e6ee482cc44240de08c8133d91b3f4c)) +* release 1.0.0-beta.16 ([22b180b](https://github.com/JanssenProject/jans/commit/22b180bba9a08045a6daa7ca8ee2b71abd42a973)) +* release 1.0.0-beta.16 ([b9acd0b](https://github.com/JanssenProject/jans/commit/b9acd0bceeeb54e3c47f869f11d97a22e8dc161f)) +* release 1.0.0-beta.16 ([328cd30](https://github.com/JanssenProject/jans/commit/328cd309ae1655a52709e13ca2f89441c6c965a2)) +* release 1.0.0-beta.16 ([5a84602](https://github.com/JanssenProject/jans/commit/5a84602838fb5d2e667422220fcd44dc53543e23)) +* release 1.0.0-beta.16 ([4923277](https://github.com/JanssenProject/jans/commit/4923277b100b5c814d94b27b88d1809794dfc413)) +* release 1.0.0-beta.16 ([258ba96](https://github.com/JanssenProject/jans/commit/258ba962bd93eb5be4d51e7de3a80da89c2e222f)) +* release 1.0.0-beta.16 ([77c4423](https://github.com/JanssenProject/jans/commit/77c4423d82b697fd91a0e61f40bad6bd9da0dba8)) +* release 1.0.0-beta.16 ([688b324](https://github.com/JanssenProject/jans/commit/688b32407b396917695cca787c08e95fe98269a1)) +* release 1.0.0-beta.16 ([4e86f15](https://github.com/JanssenProject/jans/commit/4e86f15fc39ec89d4790ebfaa7d30e7053fef606)) +* release 1.0.0-beta.16 ([8d514ee](https://github.com/JanssenProject/jans/commit/8d514ee63d840627321de2d89e816577dd919914)) +* release 1.0.0-beta.16 ([0899898](https://github.com/JanssenProject/jans/commit/0899898e80ba9b7e6a915574737bdf0756b59a14)) + ## 1.0.0-beta.15 (2022-03-02) diff --git a/README.md b/README.md index c869b5b114d..235bdd14bc9 100644 --- a/README.md +++ b/README.md @@ -1,30 +1,123 @@ +

Janssen Project - cloud native identity and access management platform

+ +## Welcome to the Janssen Project + +Janssen enables organizations to build a scalable centralized authentication and authorization service using free open source software. The components of the project include client and server implementations of the OAuth, OpenID Connect, SCIM and FIDO standards. + +**Releases**: [Latest](https://github.com/JanssenProject/jans/releases/latest) | [All](https://github.com/JanssenProject/jans/releases) + +**Get Help**: [Discussions](https://github.com/JanssenProject/jans/discussions) | [Chat](https://gitter.im/JanssenProject/Lobby) + +**Get Started**: [Documentation](https://docs.jans.io/) | [Quick Start](#quick-start) | [User Guides](https://docs.jans.io/head/admin/recipes/) + +**Contribute**: [Contribution Guide](https://docs.jans.io/head/CONTRIBUTING/) | [Community Docs](https://docs.jans.io/head/governance/) | [Developer Guides](https://docs.jans.io/head/developer/) + +[![Artifact Hub](https://img.shields.io/endpoint?url=https://artifacthub.io/badge/repository/janssen-auth-server)](https://artifacthub.io/packages/search?repo=janssen-auth-server) [![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/4353/badge)](https://bestpractices.coreinfrastructure.org/projects/4353) +[![Hex.pm](https://img.shields.io/hexpm/l/plug)](./LICENSE) +[![GitHub contributors](https://img.shields.io/github/contributors/janssenproject/jans)](#users-and-community) +[![Conventional Commits](https://img.shields.io/badge/Conventional%20Commits-1.0.0-%23FE5196?logo=conventionalcommits&logoColor=white)](https://conventionalcommits.org) -![Janssen Logo](https://github.com/JanssenProject/jans/blob/main/docs/logo/janssen_project_transparent_630px_182px.png) +**Table of Contents** -## Welcome to the Janssen Project -Janssen enables organizations to build a scalable centralized authentication and authorization service using free open source software. The components of the project include client and server implementations of the OAuth, OpenID Connect, SCIM and FIDO standards. New digital identity components may be added as the technology evolves. +- [Janssen Modules](#janssen-modules) +- [Getting Started](#getting-started) + - [Installation](#installation) +- [Users and Community](#users-and-community) +- [Contributing](#contributing) + - [Code of Conduct](#code-of-conduct) + - [Contribution Guidelines](#contribution-guidelines) +- [Security](#security) +- [Documentation](#documentation) +- [Design](#design) + - [Design Goals](#design-goals) +- [Governance](#governance) +- [Support](#support) +- [More about Janssen Project](#more-about-janssen-project) + - [History](#history) + - [why the name Janssen](#why-the-name-janssen) + + + +## Janssen Modules + +Janssen is not a big monolith--it's a lot of services working together. Whether you deploy Janssen to a Kubernetes cluster, or you are a developer running everything on one server, it's important to understand the different parts. + +1. **[jans-auth-server](jans-auth-server)**: This component is the OAuth Authorization Server, the OpenID Connect Provider, the UMA Authorization Server--this is the main Internet facing component of Janssen. It's the service that returns tokens, JWT's and identity assertions. This service must be Internet facing. + +1. **[jans-fido2](jans-fido2)**: This component provides the server side endpoints to enroll and validate devices that use FIDO. It provides both FIDO U2F (register, authenticate) and FIDO 2 (attestation, assertion) endpoints. This service must be internet facing. + +1. **[jans-config-api](jans-config-api)**: The API to configure the auth-server and other components is consolidated in this component. This service should not be Internet-facing. + +1. **[jans-scim](jans-scim)**: [SCIM](http://www.simplecloud.info/) is JSON/REST API to manage user data. Use it to add, edit and update user information. This service should not be Internet facing. + +1. **[jans-cli](jans-cli)**: This module is a command line interface for configuring the Janssen software, providing both interactive and simple single line + options for configuration. + +1. **[jans-client-api](jans-client-api)**: Middleware API to help application developers call an OAuth, OpenID or UMA server. You may wonder why this is necessary. It makes it easier for client developers to use OpenID signing and encryption features, without becoming crypto experts. This API provides some high level endpoints to do some of the heavy lifting. + +1. **[jans-core](jans-core)**: This library has code that is shared across several janssen projects. You will most likely need this project when you build other Janssen components. + +1. **[jans-orm](jans-orm)**: This is the library for persistence and caching implementations in Janssen. Currently, LDAP and Couchbase are supported. RDBMS is coming soon. + +1. **[Agama](agama)**: Agama module offers an alternative way to build authentication flows in Janssen Server. With Agama, flows are coded in a DSL (domain specific language) designed for the sole purpose of writing web flows. + +## Getting Started + +### Quick Start + +For development and testing purposes, the Janssen Server can be quickly installed on an Ubuntu 22.04 VM by running the command below: + +``` +wget https://raw.githubusercontent.com/JanssenProject/jans/main/automation/startjanssenmonolithdemo.sh && chmod u+x startjanssenmonolithdemo.sh && sudo bash startjanssenmonolithdemo.sh demoexample.jans.io MYSQL +``` -## Quick Start +Full featured Janssen Server is now installed and ready to be used. See [Quick-Start Guide](https://docs.jans.io/head/admin/install/vm-install/quick-start-install/) to know how to access and start configuring. -Try first, ask questions later? Go to the [Janssen Project Wiki](https://github.com/JanssenProject/jans/wiki/) right now! -## Project Goal +### Installation -Using the Janssen Project components, you can build a world class cloud native identity and access management ("IAM") platform. But why should you? -1. You have economies of scale, and outsourcing this critical infrastructure to a third party does not make sense -1. You need to embed this component in your product or solution -1. The privacy or security profile of a hosted solution is not acceptable -1. You need more freedom to customize +For production environment, Janssen can be installed as cloud-native in a Kubernetes cluster or as a server on a single VM. Go to the [Janssen Documentation](https://docs.jans.io/head/admin/install/) to know all the installation options -Through the Janssen project, we can coalesce a community. Open source development results in more innovation and better code. And ultimately, more trust in the code--*trust* is foundational to digital identity infrastructure. -## Project Structure -Janssen is a Linux Foundation project, governed according to the [charter](./docs/community/charter.md). Technical oversight of the project is the responsibility of the Technical Steering Committee ("TSC"). Day to day decision making is in the hands of the Contributors. The TSC helps to guide the direction of the project and to improve the quality and security of the development process. +## Users and Community -## Design Goals +A BIG thanks to all amazing contributors!! 👏 👏 + +There are many ways you can contribute. Of course, you can contribute code. But we also need people to write documentation and guides, to help us with testing, to answer questions on the forums and chat, to review PR's, to help us with devops and CI/CD, to provide feedback on usability, and to promote the project through outreach. Also, by sharing metrics with us, we can gain valuable insights into how the software performs in the wild. + +Building a large community is our number one goal. Please let us know what we can do to make you feel more welcome, no matter what you want to contribute. + + + + + +## Contributing + +### Code of Conduct + +[Janssen code of conduct](https://docs.jans.io/head/CODE_OF_CONDUCT/) ensures that Janssen community is a welcoming place for everyone. + +### Contribution Guidelines + +[Contribution guide](https://docs.jans.io/head/CONTRIBUTING/) will give you all necessary information and `howto` to get started. Janssen community welcomes all types of contributions. Be it an interesting comment on an open issue or implementing a feature. Welcome aboard! ✈ī¸ + +## Security + +### Disclosing vulnerabilities +If you think you found a security vulnerability, please refrain from posting it publicly on the forums, the chat, or GitHub. Instead, email us on security@jans.io. + +Refer to [Janssen Security Policy](.github/SECURITY.md) + +## Documentation + +Visit [Janssen Documentation Site](https://docs.jans.io/) for documentation around current as well as previous versions. + +## Design + +### Design Goals The Janssen Project is aligned with the goals of cloud native infrastructure to enable: @@ -34,24 +127,37 @@ The Janssen Project is aligned with the goals of cloud native infrastructure to 3. Flexible while Upgradable: Open source gives you the freedom to modify the code. But having your own fork of the code might make it hard to upgrade--you'll have to merge changes. Janssen provides standard interfaces that make it possible to implement custom business logic in an upgrade-friendly manner. -## History +## Governance -The initial code was ported by [Gluu](https://gluu.org), based on version 4.2 of it's identity and access management (IAM) platform. Gluu launched in 2009 with the goal of creating an enterprise-grade open source distribution of IAM components. In 2012, Gluu started work on an OAuth Authorization Server to implement OpenID Connect, which they saw as a promising next-generation replacement for SAML. This project was called [oxAuth](https://github.com/GluuFederation/oxauth), and over time, became the core component of the Gluu Server. Gluu has submitted many [self-certifications](https://openid.net/certification/) at the OpenID Foundation. Today, it is one of the most comprehensive OpenID Connect Providers. +Janssen is a Linux Foundation project, governed according to the [charter](https://docs.jans.io/head/governance/charter/). Technical oversight of the project is the responsibility of the Technical Steering Committee ("TSC"). Day to day decision-making is in the hands of the Contributors. The TSC helps to guide the direction of the project and to improve the quality and security of the development process. -In 2020, Gluu decided to democratize the governance of the oxAuth project by moving it to the Linux Foundation. The name of the project was changed from oxAuth to Janssen, to avoid any potential trademark issues. Gluu felt that a collaboration with the Linux Foundation would help to build a larger ecosystem. +## Support -## Why the name Janssen? +Documentation currently is a work in progress and published on [Documentation site](https://docs.jans.io/). You may want to also check Gluu Server [docs](https://gluu.org/docs), which have a lot in common with Janssen. -Pigeons (or doves if you like...) are universally regarded as a symbol of peace. But they are also fast. Powered by a handful of seeds, a well trained racing pigeon can fly 1000 kilometers in a day. The Janssen brothers of Arendonk in Belgium bred the world's fastest family of racing pigeons. Complex open source infrastructure, like competitive animal husbandry, requires incremental improvement. Janssen racing pigeons revolutionized the sport. The Janssen Project seeks to revolutionize identity and access management. +We prefer to have all our discussions through [GitHub Discussions](https://github.com/JanssenProject/jans/discussion) to better facilitate faster responses. However, other means are available such as the [community chat on Gitter](https://gitter.im/JanssenProject/Lobby). You can register for free there with your GitHub identity. -## Support +If you find a bug in a Janssen project, or you would like to suggest a new feature, try the [GitHub Discussions](https://github.com/JanssenProject/jans/discussion) first. If you have a "howto" or "usage" question, [raise the question or usage](https://github.com/JanssenProject/jans/discussion)! -Documentation currently is a work in progress. Draft pages are currently available on [Janssen Project Wiki](https://github.com/JanssenProject/jans/wiki/). You may want to also check Gluu Server [docs](https://gluu.org/docs), which have a lot in common with Janssen. +## Releases -We prefer to have all our discussions through [GitHub Discussions](https://github.com/JanssenProject/jans/discussion) to better facilitate faster responses. However, other means are available such as the [community chat on Gitter](https://gitter.im/JanssenProject/Lobby). You can register for free there with your Github identity. +Below are the list of current mega releases that hold information about each single release of our services and modules: -You can also subscribe to the [Janssen Google Group](https://groups.google.com/u/2/g/janssen_project) -and post messages there. +- [v1.0.2](https://github.com/JanssenProject/jans/releases/tag/v1.0.2) +- [v1.0.1](https://github.com/JanssenProject/jans/releases/tag/v1.0.1) +- [v1.0.0](https://github.com/JanssenProject/jans/releases/tag/v1.0.0) +- [v1.0.0-beta.16](https://github.com/JanssenProject/jans/releases/tag/v1.0.0-beta.16) +- [v1.0.0-beta.15](https://github.com/JanssenProject/jans/releases/tag/v1.0.0-beta.15) -If you find a bug in a Janssen project, or you would like to suggest a new feature, try the [GitHub Discussions](https://github.com/JanssenProject/jans/discussion) first. If you have a "howto" or "usage" question, [raise the question or usage](https://github.com/JanssenProject/jans/discussion)! +## More about Janssen Project + +### History + +The initial code was ported by [Gluu](https://gluu.org), based on version 4.2 of its identity and access management (IAM) platform. Gluu launched in 2009 with the goal of creating an enterprise-grade open source distribution of IAM components. In 2012, Gluu started work on an OAuth Authorization Server to implement OpenID Connect, which they saw as a promising next-generation replacement for SAML. This project was called [oxAuth](https://github.com/GluuFederation/oxauth), and over time, became the core component of the Gluu Server. Gluu has submitted many [self-certifications](https://openid.net/certification/) at the OpenID Foundation. Today, it is one of the most comprehensive OpenID Connect Providers. + +In 2020, Gluu decided to democratize the governance of the oxAuth project by moving it to the Linux Foundation. The name of the project was changed from oxAuth to Janssen, to avoid any potential trademark issues. Gluu felt that a collaboration with the Linux Foundation would help to build a larger ecosystem. + +### Why the name Janssen? + +Pigeons (or doves if you like...) are universally regarded as a symbol of peace. But they are also fast. Powered by a handful of seeds, a well-trained racing pigeon can fly 1000 kilometers in a day. The Janssen brothers of Arendonk in Belgium bred the world's fastest family of racing pigeons. Complex open source infrastructure, like competitive animal husbandry, requires incremental improvement. Janssen racing pigeons revolutionized the sport. The Janssen Project seeks to revolutionize identity and access management. diff --git a/_config.yml b/_config.yml deleted file mode 100644 index 1d339cc278d..00000000000 --- a/_config.yml +++ /dev/null @@ -1,15 +0,0 @@ -title: 'Janssen Authorization Server' -remote_theme: benc-uk/theme-msdark -favicon: https://avatars.githubusercontent.com/u/68292770?s=200&v=4 -buttons: - - b0: - text: Main Docs - href: /jans/docs - - b1: - text: Helm Chart - href: /jans/charts - - b2: - text: Debugging Interception scripts - href: /jans/docs/interception-script-debug -gems: - - jekyll-seo-tag \ No newline at end of file diff --git a/agama/.gitignore b/agama/.gitignore new file mode 100644 index 00000000000..92e4596b0a6 --- /dev/null +++ b/agama/.gitignore @@ -0,0 +1,35 @@ +# Eclipse +.project +.classpath +.settings/ +bin/ + +# IntelliJ +.idea +*.ipr +*.iml +*.iws + +# NetBeans +nb-configuration.xml + +# Visual Studio Code +.vscode + +# OSX +.DS_Store + +# Vim +*.swp +*.swo + +# patch +*.orig +*.rej + +# Maven +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +release.properties diff --git a/agama/CHANGELOG.md b/agama/CHANGELOG.md new file mode 100644 index 00000000000..ccfb97c4994 --- /dev/null +++ b/agama/CHANGELOG.md @@ -0,0 +1,99 @@ +# Changelog + +## 1.0.2 (2022-08-30) + + +### Features + +* **agama:** add utility classes for inbound identity ([#2204](https://github.com/JanssenProject/jans/issues/2204)) ([29f58ee](https://github.com/JanssenProject/jans/commit/29f58ee0e6c84b4af5493cabcb19167bc7ffbe40)) +* **agama:** add utility classes for inbound identity ([#2231](https://github.com/JanssenProject/jans/issues/2231)) ([96e32a4](https://github.com/JanssenProject/jans/commit/96e32a407ec6c545b73a6fd103ed2ae5876bd500)) +* **agama:** allow the config-api to perform syntax check of flows ([#1621](https://github.com/JanssenProject/jans/issues/1621)) ([2e99d3a](https://github.com/JanssenProject/jans/commit/2e99d3a9bec389f68086c606062280967ce338ce)) +* **agama:** reject usage of repeated input names ([#1484](https://github.com/JanssenProject/jans/issues/1484)) ([aed8cf3](https://github.com/JanssenProject/jans/commit/aed8cf33d89b98f0ac6aae52e145a84a0937d60e)) +* **jans-config-api:** agama flow endpoint ([#1898](https://github.com/JanssenProject/jans/issues/1898)) ([0e73306](https://github.com/JanssenProject/jans/commit/0e73306f7642a74a3ed2cf8a8687a1ea447aa7bd)) +* update DSL to support shorthand for finish [#1628](https://github.com/JanssenProject/jans/issues/1628) ([71e4652](https://github.com/JanssenProject/jans/commit/71e46524492d48fccf2ed2840ede3d6ae525a3e3)) + + +### Bug Fixes + +* **agama:** template overriding not working with more than one level of nesting ([#1841](https://github.com/JanssenProject/jans/issues/1841)) ([723922a](https://github.com/JanssenProject/jans/commit/723922a17b1babc49a1135030c06db367726ab63)) + + +### Documentation + +* add email-otp flow and organize sample flow folders [#1749](https://github.com/JanssenProject/jans/issues/1749) ([#1800](https://github.com/JanssenProject/jans/issues/1800)) ([8aea2ee](https://github.com/JanssenProject/jans/commit/8aea2eeaab8d99724d48a817ff47495b5f7632d7)) +* add instructions to setup inbound identity flows [#2198](https://github.com/JanssenProject/jans/issues/2198) ([#2222](https://github.com/JanssenProject/jans/issues/2222)) ([d959b32](https://github.com/JanssenProject/jans/commit/d959b32cc9de0b8311d13ce25fcba162d0b0c5a3)) +* **agama:** add sample flows to docs ([#1750](https://github.com/JanssenProject/jans/issues/1750)) ([7d33490](https://github.com/JanssenProject/jans/commit/7d334905a147746556f984015215b7c9f5b937ef)) +* update docs wrt to language updates [#1628](https://github.com/JanssenProject/jans/issues/1628) ([ca32bd5](https://github.com/JanssenProject/jans/commit/ca32bd54ec109134dad04d4a0f15eebc6d7bdd8e)) + + +### Miscellaneous Chores + +* release 1.0.1 ([828bfe8](https://github.com/JanssenProject/jans/commit/828bfe80cee87e639839391f98ac3dc2f2d4a920)) +* release 1.0.2 ([43dead6](https://github.com/JanssenProject/jans/commit/43dead615f3508ca393c330c2db27a8fb9d1017a)) + +## [1.0.1](https://github.com/JanssenProject/jans/compare/agama-v1.0.0...agama-v1.0.1) (2022-07-06) + + +### Features + +* **agama:** allow the config-api to perform syntax check of flows ([#1621](https://github.com/JanssenProject/jans/issues/1621)) ([2e99d3a](https://github.com/JanssenProject/jans/commit/2e99d3a9bec389f68086c606062280967ce338ce)) +* **agama:** improve flows timeout ([#1447](https://github.com/JanssenProject/jans/issues/1447)) ([ccfb62e](https://github.com/JanssenProject/jans/commit/ccfb62ec13d371c96a0d597d5a0229864f044373)) +* **agama:** reject usage of repeated input names ([#1484](https://github.com/JanssenProject/jans/issues/1484)) ([aed8cf3](https://github.com/JanssenProject/jans/commit/aed8cf33d89b98f0ac6aae52e145a84a0937d60e)) +* update DSL to support shorthand for finish [#1628](https://github.com/JanssenProject/jans/issues/1628) ([71e4652](https://github.com/JanssenProject/jans/commit/71e46524492d48fccf2ed2840ede3d6ae525a3e3)) + + +### Bug Fixes + +* **agama:** adjust pom version [#1402](https://github.com/JanssenProject/jans/issues/1402) ([#1403](https://github.com/JanssenProject/jans/issues/1403)) ([930f080](https://github.com/JanssenProject/jans/commit/930f0801177d516d6bfa9c536d590556144cbd61)) +* **agama:** adjust pom version [#1402](https://github.com/JanssenProject/jans/issues/1402) ([#1404](https://github.com/JanssenProject/jans/issues/1404)) ([86bf614](https://github.com/JanssenProject/jans/commit/86bf61420bf6b8d236b8200835a2ff05f430308b)) +* update pom [#1438](https://github.com/JanssenProject/jans/issues/1438) ([#1439](https://github.com/JanssenProject/jans/issues/1439)) ([66b9962](https://github.com/JanssenProject/jans/commit/66b996286a2285986845677ea039f177f756d962)) + + +### Documentation + +* update docs wrt to language updates [#1628](https://github.com/JanssenProject/jans/issues/1628) ([ca32bd5](https://github.com/JanssenProject/jans/commit/ca32bd54ec109134dad04d4a0f15eebc6d7bdd8e)) + + +### Miscellaneous Chores + +* release 1.0.0 ([3df6f77](https://github.com/JanssenProject/jans/commit/3df6f7721a8e9d57e28d065ee29153d023dfe9ea)) +* release 1.0.0 ([9644d1b](https://github.com/JanssenProject/jans/commit/9644d1bd29c291e57c140b0c9ac67243c322ac35)) +* release 1.0.1 ([828bfe8](https://github.com/JanssenProject/jans/commit/828bfe80cee87e639839391f98ac3dc2f2d4a920)) + +## 1.0.0 (2022-05-19) + + +### Features + +* add schema updates [#1390](https://github.com/JanssenProject/jans/issues/1390) ([c9023b3](https://github.com/JanssenProject/jans/commit/c9023b3435fbc8079aabe5c70de3177ec9112308)) +* allow flows to access their metadata properties [#1340](https://github.com/JanssenProject/jans/issues/1340) ([344ba04](https://github.com/JanssenProject/jans/commit/344ba0448c73e3c56e05f529eea5009b2157c1fc)) +* initial agama commit [#1322](https://github.com/JanssenProject/jans/issues/1322) ([#1323](https://github.com/JanssenProject/jans/issues/1323)) ([0148bc8](https://github.com/JanssenProject/jans/commit/0148bc8a32a005e47ba9d090e895282775148a95)) +* **jans-config-api:** user custom attributes at root level - 1348 ([5b3f0a1](https://github.com/JanssenProject/jans/commit/5b3f0a13e25cd842e0bbd4be3d21eb48ab1d108f)) + + +### Miscellaneous Chores + +* release 1.0.0 ([b2895f2](https://github.com/JanssenProject/jans/commit/b2895f224b5772c0724ea0afbdf67a417a5c537c)) +* release 1.0.0-beta.16 ([90e4bb2](https://github.com/JanssenProject/jans/commit/90e4bb29df040bd9fe5921a054bc4226d34ca1ef)) +* release 1.0.0-beta.16 ([eec2073](https://github.com/JanssenProject/jans/commit/eec2073be9fd25544f31087e171934afb9a71e6d)) +* release 1.0.0-beta.16 ([cd92ead](https://github.com/JanssenProject/jans/commit/cd92ead2ca654383091c4923d3de5619b70fc5b9)) +* release 1.0.0-beta.16 ([7f0a91b](https://github.com/JanssenProject/jans/commit/7f0a91bd90efc1cd7a80047f9cd6b7c6a22417a2)) +* release 1.0.0-beta.16 ([c2ad604](https://github.com/JanssenProject/jans/commit/c2ad604dc29e7401bc4cb0788feaa20e11de0440)) +* release 1.0.0-beta.16 ([a641486](https://github.com/JanssenProject/jans/commit/a6414864712789d1fcf80b823338100aebda030e)) +* release 1.0.0-beta.16 ([94d5791](https://github.com/JanssenProject/jans/commit/94d5791a23fce4ecb8913c16c940cfbbc85fed4c)) +* release 1.0.0-beta.16 ([16de429](https://github.com/JanssenProject/jans/commit/16de4299bc5e9c4a842f279ae0d3ae8282a4ff2c)) +* release 1.0.0-beta.16 ([72915c0](https://github.com/JanssenProject/jans/commit/72915c0e82b9684ac1c59934d5b9a36c2456058d)) +* release 1.0.0-beta.16 ([3ea2b37](https://github.com/JanssenProject/jans/commit/3ea2b37deac3416564614fb6a4e84b056ddbed3f)) +* release 1.0.0-beta.16 ([78a6d39](https://github.com/JanssenProject/jans/commit/78a6d39ffadf9abee18c7be0e14ad3eb6ec2ef1b)) +* release 1.0.0-beta.16 ([11bfa93](https://github.com/JanssenProject/jans/commit/11bfa9368e6ee482cc44240de08c8133d91b3f4c)) +* release 1.0.0-beta.16 ([22b180b](https://github.com/JanssenProject/jans/commit/22b180bba9a08045a6daa7ca8ee2b71abd42a973)) +* release 1.0.0-beta.16 ([b9acd0b](https://github.com/JanssenProject/jans/commit/b9acd0bceeeb54e3c47f869f11d97a22e8dc161f)) +* release 1.0.0-beta.16 ([328cd30](https://github.com/JanssenProject/jans/commit/328cd309ae1655a52709e13ca2f89441c6c965a2)) +* release 1.0.0-beta.16 ([5a84602](https://github.com/JanssenProject/jans/commit/5a84602838fb5d2e667422220fcd44dc53543e23)) +* release 1.0.0-beta.16 ([4923277](https://github.com/JanssenProject/jans/commit/4923277b100b5c814d94b27b88d1809794dfc413)) +* release 1.0.0-beta.16 ([258ba96](https://github.com/JanssenProject/jans/commit/258ba962bd93eb5be4d51e7de3a80da89c2e222f)) +* release 1.0.0-beta.16 ([77c4423](https://github.com/JanssenProject/jans/commit/77c4423d82b697fd91a0e61f40bad6bd9da0dba8)) +* release 1.0.0-beta.16 ([688b324](https://github.com/JanssenProject/jans/commit/688b32407b396917695cca787c08e95fe98269a1)) +* release 1.0.0-beta.16 ([4e86f15](https://github.com/JanssenProject/jans/commit/4e86f15fc39ec89d4790ebfaa7d30e7053fef606)) +* release 1.0.0-beta.16 ([8d514ee](https://github.com/JanssenProject/jans/commit/8d514ee63d840627321de2d89e816577dd919914)) +* release 1.0.0-beta.16 ([0899898](https://github.com/JanssenProject/jans/commit/0899898e80ba9b7e6a915574737bdf0756b59a14)) diff --git a/agama/README.md b/agama/README.md new file mode 100644 index 00000000000..0026c38c9c9 --- /dev/null +++ b/agama/README.md @@ -0,0 +1,12 @@ +# Agama + +Agama is an auth-server component that offers an alternative way to build authentication flows in Janssen server. +Originally, person authentication flows are defined in the server by means of jython scripts that adhere to a predefined API. With Agama, flows are coded in a DSL (domain specific language) designed for the sole purpose of writing web flows. + +Some advantages of using Agama include: + +1. Ability to express authentication flows in a clean and concise way +2. Flow composition is supported out-of-the-box: reuse of an existing flow in another requires no effort +3. Reasoning about flows behavior is easy (as consequence of points 1 and 2). This makes flow modifications and refactoring straightforward +4. Small cognitive load. Agama DSL is a very small language with simple, non-distracting syntax +5. Friendly UI templating engine. No complexities when authoring web pages - stay focused on writing HTML markup diff --git a/agama/engine/pom.xml b/agama/engine/pom.xml new file mode 100644 index 00000000000..bb9e81908a8 --- /dev/null +++ b/agama/engine/pom.xml @@ -0,0 +1,230 @@ + + + + 4.0.0 + + agama-engine + jar + + + io.jans + agama + 1.0.3-SNAPSHOT + + + + UTF-8 + 11 + 11 + + + + + jans + Jans repository + https://maven.jans.io/maven + + + + + + profiles/${cfg}/config-agama-test.properties + + + + + src/test/resources + true + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.19.1 + + + target/test-classes/testng.xml + + + + + + + + + set-configuration-name + + + !cfg + + + + default + + + + + + + + io.jans + agama-model + ${jans.version} + + + io.jans + agama-transpiler + ${jans.version} + + + + + jakarta.servlet + jakarta.servlet-api + + + + + org.jboss.spec.javax.ws.rs + jboss-jaxrs-api_3.0_spec + + + + + org.jboss.weld.servlet + weld-servlet-core + + + jakarta.ejb + jakarta.ejb-api + + + + + org.freemarker + freemarker + 2.3.31 + + + + + org.apache.logging.log4j + log4j-slf4j-impl + + + org.apache.logging.log4j + log4j-api + + + org.apache.logging.log4j + log4j-core + + + + + com.fasterxml.jackson.core + jackson-databind + + + com.fasterxml.jackson.core + jackson-core + + + com.fasterxml.jackson.core + jackson-annotations + + + + + org.mozilla + rhino + 1.7.14 + + + + + org.apache.groovy + groovy + 4.0.3 + + + + + io.jans + jans-core-util + + + io.jans + jans-core-cdi + + + io.jans + jans-core-service + + + io.jans + jans-orm-core + + + io.jans + jans-auth-model + + + io.jans + jans-core-jsf-util + + + + + commons-beanutils + commons-beanutils + + + commons-codec + commons-codec + + + + + com.esotericsoftware + kryo + 5.3.0 + + + + + net.sourceforge.htmlunit + htmlunit + 2.65.1 + test + + + org.testng + testng + test + + + io.jans + agama-inbound + ${jans.version} + test + + + org.json + json + 20220924 + test + + + + org.apache.logging.log4j + log4j-jcl + 2.19.0 + test + + + + + \ No newline at end of file diff --git a/agama/engine/profiles/default/config-agama-test.properties b/agama/engine/profiles/default/config-agama-test.properties new file mode 100644 index 00000000000..64cf8839cdf --- /dev/null +++ b/agama/engine/profiles/default/config-agama-test.properties @@ -0,0 +1,6 @@ +#The URL of your Jans installation +server=https://jgomer2001-guiding-herring.gluu.info + +clientId=1800.5e01b3bb-1f68-4847-b9ba-d5c999a05e33 + +custParamName=customParam1 diff --git a/agama/engine/src/main/java/io/jans/agama/NativeJansFlowBridge.java b/agama/engine/src/main/java/io/jans/agama/NativeJansFlowBridge.java new file mode 100644 index 00000000000..6df66696dfe --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/NativeJansFlowBridge.java @@ -0,0 +1,96 @@ +package io.jans.agama; + +import io.jans.agama.engine.model.FlowResult; +import io.jans.agama.engine.model.FlowStatus; +import io.jans.agama.engine.service.AgamaPersistenceService; +import io.jans.agama.engine.service.FlowService; +import io.jans.agama.engine.service.WebContext; +import io.jans.agama.engine.servlet.ExecutionServlet; +import io.jans.agama.engine.script.LogUtils; +import io.jans.agama.model.EngineConfig; + +import jakarta.inject.Inject; +import jakarta.enterprise.context.RequestScoped; +import java.io.IOException; + +import org.slf4j.Logger; + +@RequestScoped +public class NativeJansFlowBridge { + + @Inject + private Logger logger; + + @Inject + private AgamaPersistenceService aps; + + @Inject + private FlowService fs; + + @Inject + private EngineConfig conf; + + @Inject + private WebContext webContext; + + public String scriptPageUrl() { + return conf.getBridgeScriptPage(); + } + + public String getTriggerUrl() { + return webContext.getContextPath() + ExecutionServlet.URL_PREFIX + + "agama" + ExecutionServlet.URL_SUFFIX; + } + + public Boolean prepareFlow(String sessionId, String qname, String jsonInput) throws Exception { + + logger.info("Preparing flow '{}'", qname); + Boolean alreadyRunning = null; + if (aps.flowEnabled(qname)) { + + FlowStatus st = aps.getFlowStatus(sessionId); + alreadyRunning = st != null; + + if (alreadyRunning && !st.getQname().equals(qname)) { + logger.warn("Flow {} is already running. Will be terminated", st.getQname()); + fs.terminateFlow(); + st = null; + } + if (st == null) { + + int timeout = aps.getEffectiveFlowTimeout(qname); + if (timeout <= 0) throw new Exception("Flow timeout negative or zero. " + + "Check your AS configuration or flow definition"); + long expireAt = System.currentTimeMillis() + 1000L * timeout; + + st = new FlowStatus(); + st.setStartedAt(FlowStatus.PREPARED); + st.setQname(qname); + st.setJsonInput(jsonInput); + st.setFinishBefore(expireAt); + aps.createFlowRun(sessionId, st, expireAt); + LogUtils.log("@w Effective timeout for this flow will be % seconds", timeout); + } + } + return alreadyRunning; + + } + + public FlowResult close() throws IOException { + + FlowStatus st = fs.getRunningFlowStatus(); + if (st == null) { + logger.error("No current flow running"); + + } else if (st.getStartedAt() != FlowStatus.FINISHED) { + logger.error("Current flow hasn't finished"); + + } else { + fs.terminateFlow(); + return st.getResult(); + } + return null; + + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/continuation/PendingException.java b/agama/engine/src/main/java/io/jans/agama/engine/continuation/PendingException.java new file mode 100644 index 00000000000..ffbbb1fd775 --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/continuation/PendingException.java @@ -0,0 +1,27 @@ +package io.jans.agama.engine.continuation; + +import org.mozilla.javascript.ContinuationPending; +import org.mozilla.javascript.NativeContinuation; + +public class PendingException extends ContinuationPending { + + private boolean allowCallbackResume; + + public PendingException(NativeContinuation continuation) { + super(continuation); + } + + @Override + public NativeContinuation getContinuation() { + return (NativeContinuation) super.getContinuation(); + } + + public boolean isAllowCallbackResume() { + return allowCallbackResume; + } + + public void setAllowCallbackResume(boolean allowCallbackResume) { + this.allowCallbackResume = allowCallbackResume; + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/continuation/PendingRedirectException.java b/agama/engine/src/main/java/io/jans/agama/engine/continuation/PendingRedirectException.java new file mode 100644 index 00000000000..490f62b88a5 --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/continuation/PendingRedirectException.java @@ -0,0 +1,21 @@ +package io.jans.agama.engine.continuation; + +import org.mozilla.javascript.NativeContinuation; + +public class PendingRedirectException extends PendingException { + + public PendingRedirectException(NativeContinuation continuation) { + super(continuation); + } + + private String location; + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/continuation/PendingRenderException.java b/agama/engine/src/main/java/io/jans/agama/engine/continuation/PendingRenderException.java new file mode 100644 index 00000000000..d7f2a863530 --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/continuation/PendingRenderException.java @@ -0,0 +1,30 @@ +package io.jans.agama.engine.continuation; + +import org.mozilla.javascript.NativeContinuation; + +public class PendingRenderException extends PendingException { + + private String templatePath; + private Object dataModel; + + public PendingRenderException(NativeContinuation continuation) { + super(continuation); + } + + public String getTemplatePath() { + return templatePath; + } + + public void setTemplatePath(String templatePath) { + this.templatePath = templatePath; + } + + public Object getDataModel() { + return dataModel; + } + + public void setDataModel(Object dataModel) { + this.dataModel = dataModel; + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/exception/FlowCrashException.java b/agama/engine/src/main/java/io/jans/agama/engine/exception/FlowCrashException.java new file mode 100644 index 00000000000..b476192080b --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/exception/FlowCrashException.java @@ -0,0 +1,13 @@ +package io.jans.agama.engine.exception; + +public class FlowCrashException extends Exception { + + public FlowCrashException(String message) { + super(message); + } + + public FlowCrashException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/exception/FlowTimeoutException.java b/agama/engine/src/main/java/io/jans/agama/engine/exception/FlowTimeoutException.java new file mode 100644 index 00000000000..857a9353a7d --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/exception/FlowTimeoutException.java @@ -0,0 +1,16 @@ +package io.jans.agama.engine.exception; + +public class FlowTimeoutException extends Exception { + + private String qname; + + public FlowTimeoutException(String message, String flowQname) { + super(message); + qname = flowQname; + } + + public String getQname() { + return qname; + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/exception/TemplateProcessingException.java b/agama/engine/src/main/java/io/jans/agama/engine/exception/TemplateProcessingException.java new file mode 100644 index 00000000000..f80dec168ca --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/exception/TemplateProcessingException.java @@ -0,0 +1,13 @@ +package io.jans.agama.engine.exception; + +public class TemplateProcessingException extends Exception { + + public TemplateProcessingException(String message) { + super(message); + } + + public TemplateProcessingException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/misc/FlowUtils.java b/agama/engine/src/main/java/io/jans/agama/engine/misc/FlowUtils.java new file mode 100644 index 00000000000..2ecdc7e0989 --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/misc/FlowUtils.java @@ -0,0 +1,93 @@ +package io.jans.agama.engine.misc; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.jans.agama.model.EngineConfig; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import java.io.IOException; +import java.io.StringReader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.codec.digest.HmacAlgorithms; +import org.apache.commons.codec.digest.HmacUtils; +import org.mozilla.javascript.Scriptable; +import org.slf4j.Logger; + +import static java.nio.charset.StandardCharsets.UTF_8; + +@ApplicationScoped +public class FlowUtils { + + private static final Path SALT_PATH = Paths.get("/etc/jans/conf/salt"); + private static final HmacAlgorithms HASH_ALG = HmacAlgorithms.HMAC_SHA_512; + + @Inject + private Logger logger; + + @Inject + private ObjectMapper mapper; + + @Inject + private EngineConfig engineConfig; + + public boolean serviceEnabled() { + return engineConfig.isEnabled(); + } + + /** + * It is assumed that values in the map are String arrays with at least one element + * @param map + * @return + * @throws JsonProcessingException + */ + public String toJsonString(Map map) throws JsonProcessingException { + + Map result = new HashMap<>(); + if (map != null) { + + for(String key : map.keySet()) { + String[] list = map.get(key); + result.put(key, list.length == 1 ? list[0] : Arrays.asList(list)); + } + } + + //TODO: implement a smarter serialization? example: + // if key starts with prefix i: try to convert to int, b: for boolean, m: map, etc. + return mapper.writeValueAsString(result); + } + + public void printScopeIds(Scriptable scope) { + List scopeIds = Stream.of(scope.getIds()).map(Object::toString).collect(Collectors.toList()); + logger.trace("Global scope has {} ids: {}", scopeIds.size(), scopeIds); + } + + public String hash(String message) throws IOException { + return new HmacUtils(HASH_ALG, sharedKey()).hmacHex(message); + } + + public String hash(byte[] message) throws IOException { + return new HmacUtils(HASH_ALG, sharedKey()).hmacHex(message); + } + + private String sharedKey() throws IOException { + + //I preferred not to have file contents in memory (static var) + Properties p = new Properties(); + p.load(new StringReader(Files.readString(SALT_PATH, UTF_8))); + return p.getProperty("encodeSalt"); + + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/misc/PrimitiveUtils.java b/agama/engine/src/main/java/io/jans/agama/engine/misc/PrimitiveUtils.java new file mode 100644 index 00000000000..3b102c6d155 --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/misc/PrimitiveUtils.java @@ -0,0 +1,90 @@ +package io.jans.agama.engine.misc; + +import java.util.Arrays; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +public class PrimitiveUtils { + + enum Primitive { + CHARACTER(Character.class, Character.TYPE), + BOOLEAN(Boolean.class, Boolean.TYPE), + BYTE(Byte.class, Byte.TYPE), + DOUBLE(Double.class, Double.TYPE), + FLOAT(Float.class, Float.TYPE), + INTEGER(Integer.class, Integer.TYPE), + LONG(Long.class, Long.TYPE), + SHORT(Short.class, Short.TYPE); + + private final Class wrapperCls; + private final Class primitiveCls; + + private final static Map, Primitive> mapW = Arrays.stream(values()) + .collect(Collectors.toMap(p -> p.wrapperCls, p -> p)); + + private final static Map, Primitive> mapP = Arrays.stream(values()) + .collect(Collectors.toMap(p -> p.primitiveCls, p -> p)); + + Primitive(Class wrapperCls, Class primitiveCls) { + this.wrapperCls = wrapperCls; + this.primitiveCls = primitiveCls; + } + + private static Primitive from(Map, Primitive> map, Class cls) { + return map.get(cls); + } + + static Primitive fromWrapperClass(Class cls) { + return from(mapW, cls); + } + + static Primitive fromPrimitiveClass(Class cls) { + return from(mapP, cls); + } + + static Primitive fromWrapperOrPrimitiveClass(Class cls) { + return Optional.ofNullable(fromWrapperClass(cls)).orElse(fromPrimitiveClass(cls)); + } + + } + + public static Boolean compatible(Class argumentCls, Class paramType) { + + Primitive p = Primitive.fromWrapperClass(argumentCls); + if (p != null) { + if (argumentCls.equals(paramType)) return true; + + return p.equals(Primitive.fromPrimitiveClass(paramType)); + } + return null; + } + + public static boolean isPrimitive(Class cls, boolean wrapperCounts) { + Primitive p = wrapperCounts ? Primitive.fromWrapperOrPrimitiveClass(cls) : + Primitive.fromPrimitiveClass(cls); + return p != null; + } + + public static Object primitiveNumberFrom(Number value, Class destination) { + + Primitive prim = Primitive.fromWrapperOrPrimitiveClass(destination); + if (prim != null) { + switch (prim) { + case BYTE: + return value.byteValue(); + case FLOAT: + return value.floatValue(); + case INTEGER: + return value.intValue(); + case LONG: + return value.longValue(); + case SHORT: + return value.shortValue(); + } + } + return null; + + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/model/FlowResult.java b/agama/engine/src/main/java/io/jans/agama/engine/model/FlowResult.java new file mode 100644 index 00000000000..d3a76cdf2fd --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/model/FlowResult.java @@ -0,0 +1,44 @@ +package io.jans.agama.engine.model; + +import java.util.Map; + +public class FlowResult { + + private boolean aborted; + private boolean success; + private String error; + private Map data; + + public boolean isAborted() { + return aborted; + } + + public void setAborted(boolean aborted) { + this.aborted = aborted; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + public Map getData() { + return data; + } + + public void setData(Map data) { + this.data = data; + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/model/FlowRun.java b/agama/engine/src/main/java/io/jans/agama/engine/model/FlowRun.java new file mode 100644 index 00000000000..0b508358238 --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/model/FlowRun.java @@ -0,0 +1,67 @@ +package io.jans.agama.engine.model; + +import io.jans.orm.annotation.AttributeName; +import io.jans.orm.annotation.DataEntry; +import io.jans.orm.annotation.ObjectClass; + +import java.util.Date; + +@DataEntry +@ObjectClass(value = FlowRun.ATTR_NAMES.OBJECT_CLASS) +public class FlowRun extends ProtoFlowRun { + + //An enum cannot be used because elements of annotations (like AttributeName) have to be constants + public class ATTR_NAMES { + public static final String OBJECT_CLASS = "agmFlowRun"; + public static final String ID = "jansId"; + public static final String STATUS = "agFlowSt"; + } + + @AttributeName(name = "agFlowEncCont") + private String encodedContinuation; + + @AttributeName(name = "jansCustomMessage") + private String hash; + + @AttributeName(name = "exp") + private Date deletableAt; + +/* + TODO: https://github.com/JanssenProject/jans/issues/1252 + When fixed, AgamaPersistenceService#saveState and getContinuation will need refactoring + @AttributeName(name = "agFlowCont") + private byte[] continuation; + + public byte[] getContinuation() { + return continuation; + } + + public void setContinuation(byte[] continuation) { + this.continuation = continuation; + } +*/ + public String getEncodedContinuation() { + return encodedContinuation; + } + + public void setEncodedContinuation(String encodedContinuation) { + this.encodedContinuation = encodedContinuation; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public Date getDeletableAt() { + return deletableAt; + } + + public void setDeletableAt(Date deletableAt) { + this.deletableAt = deletableAt; + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/model/FlowStatus.java b/agama/engine/src/main/java/io/jans/agama/engine/model/FlowStatus.java new file mode 100644 index 00000000000..dee36df5b4f --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/model/FlowStatus.java @@ -0,0 +1,110 @@ +package io.jans.agama.engine.model; + +import com.fasterxml.jackson.annotation.JsonInclude; + +import java.util.Deque; +import java.util.LinkedList; +import java.util.Map; + +@JsonInclude(JsonInclude.Include.NON_EMPTY) +public class FlowStatus { + + public static final long PREPARED = 0; + public static final long FINISHED = -1; + + private String qname; + private String templatePath; + private long startedAt; + private long finishBefore; + + @JsonInclude(JsonInclude.Include.NON_NULL) + private Object templateDataModel; + + private Deque> parentsMappings = new LinkedList<>(); + private String externalRedirectUrl; + private boolean allowCallbackResume; + private String jsonInput; + + private FlowResult result; + + public Object getTemplateDataModel() { + return templateDataModel; + } + + public void setTemplateDataModel(Object templateDataModel) { + this.templateDataModel = templateDataModel; + } + + public FlowResult getResult() { + return result; + } + + public void setResult(FlowResult result) { + this.result = result; + } + + public long getStartedAt() { + return startedAt; + } + + public void setStartedAt(long startedAt) { + this.startedAt = startedAt; + } + + public long getFinishBefore() { + return finishBefore; + } + + public void setFinishBefore(long finishBefore) { + this.finishBefore = finishBefore; + } + + public String getQname() { + return qname; + } + + public void setQname(String qname) { + this.qname = qname; + } + + public String getTemplatePath() { + return templatePath; + } + + public void setTemplatePath(String templatePath) { + this.templatePath = templatePath; + } + + public String getExternalRedirectUrl() { + return externalRedirectUrl; + } + + public void setExternalRedirectUrl(String externalRedirectUrl) { + this.externalRedirectUrl = externalRedirectUrl; + } + + public boolean isAllowCallbackResume() { + return allowCallbackResume; + } + + public void setAllowCallbackResume(boolean allowCallbackResume) { + this.allowCallbackResume = allowCallbackResume; + } + + public Deque> getParentsMappings() { + return parentsMappings; + } + + public void setParentsMappings(Deque> parentsMappings) { + this.parentsMappings = parentsMappings; + } + + public String getJsonInput() { + return jsonInput; + } + + public void setJsonInput(String jsonInput) { + this.jsonInput = jsonInput; + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/model/ProtoFlowRun.java b/agama/engine/src/main/java/io/jans/agama/engine/model/ProtoFlowRun.java new file mode 100644 index 00000000000..3f1c6160cec --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/model/ProtoFlowRun.java @@ -0,0 +1,41 @@ +package io.jans.agama.engine.model; + +/** + * This class is used as a vehicle to overcome jans-orm limitation related to data + * destruction when an update is made on a partially retrieved entity. It also helps + * to make "lighter" retrievals of FlowRuns + */ +import io.jans.orm.annotation.AttributeName; +import io.jans.orm.annotation.DataEntry; +import io.jans.orm.annotation.JsonObject; +import io.jans.orm.annotation.ObjectClass; +import io.jans.orm.model.base.Entry; + +@DataEntry +@ObjectClass(value = FlowRun.ATTR_NAMES.OBJECT_CLASS) +public class ProtoFlowRun extends Entry { + + @AttributeName(name = FlowRun.ATTR_NAMES.ID) + private String id; + + @JsonObject + @AttributeName(name = FlowRun.ATTR_NAMES.STATUS) + private FlowStatus status; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public FlowStatus getStatus() { + return status; + } + + public void setStatus(FlowStatus status) { + this.status = status; + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/page/BasicTemplateModel.java b/agama/engine/src/main/java/io/jans/agama/engine/page/BasicTemplateModel.java new file mode 100644 index 00000000000..b0d2b5870e1 --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/page/BasicTemplateModel.java @@ -0,0 +1,25 @@ +package io.jans.agama.engine.page; + +public class BasicTemplateModel { + + private String message; + private String flowName; + + public BasicTemplateModel(String message) { + this.message = message; + } + + public BasicTemplateModel(String message, String flowName) { + this.message = message; + this.flowName = flowName; + } + + public String getMessage() { + return message; + } + + public String getFlowName() { + return flowName; + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/page/Labels.java b/agama/engine/src/main/java/io/jans/agama/engine/page/Labels.java new file mode 100644 index 00000000000..601523b8927 --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/page/Labels.java @@ -0,0 +1,65 @@ +package io.jans.agama.engine.page; + +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import java.util.AbstractMap; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.ResourceBundle; +import java.util.Set; + +import io.jans.jsf2.i18n.ExtendedResourceBundle; + +import org.slf4j.Logger; + +//This is not a real Map but pretends to look like one so in Freemarker, templates expressions of the form +//msgs.KEY or msgs["KEY"] can be used. A HashMap would have led to more concise code but Weld complains +//at startup due to the presence of a final method in it. This leads to proxying issues +@RequestScoped +public class Labels extends AbstractMap { + + public static final String BUNDLE_ID = "msgs"; + + //This returns a null bundle name... + //factory = (jakarta.faces.application.ApplicationFactory) + // jakarta.faces.FactoryFinder.getFactory(FactoryFinder.APPLICATION_FACTORY); + //BUNDLE_ID = factory.getApplication().getMessageBundle(); + + private static final String BUNDLE_BASE_NAME = "jans-auth"; + + @Inject + private Logger logger; + + private ExtendedResourceBundle exrBundle; + + @Override + public Set> entrySet() { + //A dummy implementation suffices + return Collections.emptySet(); + } + + @Override + public String get(Object key) { + + try { + //See #1527 + return exrBundle.getString(key.toString()); + } catch (Exception e) { + logger.error("Failed to lookup bundle by key '{}': {}", key.toString(), e.getMessage()); + //Prefer empty string over null (null causes templates to crash by definition in freemarker) + return ""; + } + + } + + public void useLocale(Locale locale) { + //This re-creates a resource bundle instance. This is cheap because the underlying + //implementation makes use of Resource#getBundle (by default provides caching) + //and uses watchers to detect changes in properties files + exrBundle = new ExtendedResourceBundle(BUNDLE_BASE_NAME, locale); + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/page/Page.java b/agama/engine/src/main/java/io/jans/agama/engine/page/Page.java new file mode 100644 index 00000000000..49b04316f4f --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/page/Page.java @@ -0,0 +1,87 @@ +package io.jans.agama.engine.page; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.util.HashMap; +import java.util.Map; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; + +import io.jans.agama.engine.service.WebContext; + +@RequestScoped +public class Page { + + private static final String WEB_CTX_KEY = "webCtx"; + + @Inject + private WebContext webContext; + + @Inject + private ObjectMapper mapper; + + @Inject + private Labels labels; + + private String templatePath; + private Map dataModel; + private Object rawModel; + + public String getTemplatePath() { + return templatePath; + } + + public void setTemplatePath(String templatePath) { + this.templatePath = templatePath; + } + + public Object getDataModel() { + + if (rawModel == null) { + if (dataModel != null) { + + dataModel.putIfAbsent(WEB_CTX_KEY, webContext); + dataModel.putIfAbsent(Labels.BUNDLE_ID, labels); + labels.useLocale(webContext.getLocale()); + return dataModel; + + } else return new Object(); + } else return rawModel; + + } + + /** + * This call is cheaper than setDataModel, but pages won't have access to any + * contextual data + * @param object + */ + public void setRawDataModel(Object object) { + rawModel = object; + dataModel = null; + } + + public void setDataModel(Object object) { + rawModel = null; + dataModel = mapFromObject(object); + } + + public void appendToDataModel(Object object) { + if (rawModel != null) { + rawModel = null; + dataModel = new HashMap<>(); + } + dataModel.putAll(mapFromObject(object)); + } + + private Map mapFromObject(Object object) { + return mapper.convertValue(object, new TypeReference>(){}); + } + + @PostConstruct + private void init() { + dataModel = new HashMap<>(); + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/script/LogUtils.java b/agama/engine/src/main/java/io/jans/agama/engine/script/LogUtils.java new file mode 100644 index 00000000000..26f69e2f1a1 --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/script/LogUtils.java @@ -0,0 +1,246 @@ +package io.jans.agama.engine.script; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Array; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +import io.jans.agama.model.EngineConfig; +import io.jans.util.Pair; +import io.jans.service.cdi.util.CdiUtil; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class LogUtils { + + private static final Logger LOG = LoggerFactory.getLogger(LogUtils.class); + //MUST be a single character string + private static final String PLACEHOLDER = "%"; + + private static int maxIterableItems = 1; + + private enum LogLevel { + ERROR, WARN, INFO, DEBUG, TRACE; + + String getValue() { + return toString().toLowerCase(); + } + + } + + /** + * rest has at least 1 element + * @param rest + */ + public static void log(Object ...rest) { + + LogLevel level; + int dummyArgs = 0; + String sfirst; + int nargs = rest.length - 1; + + maxIterableItems = CdiUtil.bean(EngineConfig.class).getMaxItemsLoggedInCollections(); + + Object first = rest[0]; + if (first != null && first instanceof String) { + Pair p = getLogLevel(first.toString()); + level = p.getFirst(); + + if (ignoreLogStatement(level)) return; + + Pair q = getFormatString(p.getSecond(), nargs); + sfirst = q.getFirst(); + dummyArgs = q.getSecond(); + + } else { + level = LogLevel.INFO; + + if (ignoreLogStatement(level)) return; + + sfirst = asString(first) + getFormatString("", nargs).getFirst(); + } + + Object[] args = new String[nargs + dummyArgs]; + for (int i = 0; i < nargs; i++) { + args[i] = asString(rest[i + 1]); + } + Arrays.fill(args, nargs, args.length, ""); + String result = String.format(sfirst, args); + + switch (level) { + case ERROR: + LOG.error(result); + break; + case WARN: + LOG.warn(result); + break; + case INFO: + LOG.info(result); + break; + case DEBUG: + LOG.debug(result); + break; + case TRACE: + LOG.trace(result); + break; + } + + } + + private static boolean ignoreLogStatement(LogLevel logLevel) { + + switch (logLevel) { + case TRACE: return !LOG.isTraceEnabled(); + case DEBUG: return !LOG.isDebugEnabled(); + case INFO: return !LOG.isInfoEnabled(); + case WARN: return !LOG.isWarnEnabled(); + case ERROR: return !LOG.isErrorEnabled(); + } + return false; + + } + + private static Pair getLogLevel(String first) { + + LogLevel level = null; + String newFirst = null; + + String suffix = " "; + if (first.startsWith("@")) { + level = Stream.of(LogLevel.values()).filter( + l -> { + String lev = l.getValue(); + return first.startsWith("@" + lev.substring(0, 1) + suffix) + || first.startsWith("@" + lev + suffix); + } + ).findFirst().orElse(null); + + if (level != null) { + int levLen = first.substring(2).startsWith(suffix) ? 1 : level.getValue().length(); + newFirst = first.substring(1 + levLen + suffix.length()); + } + } + + if (level == null) { + newFirst = first; + level = LogLevel.INFO; + } + return new Pair<>(level, newFirst); + + } + + private static Pair getFormatString(String str, int nargs) { + + Integer dummyArgs = 0; + String tmp = str.replace(PLACEHOLDER, "%s"); + int existingPlaceHolders = tmp.length() - str.length(); + + if (existingPlaceHolders > 0) { + int excess = existingPlaceHolders - nargs; + if (excess < 0) { + tmp += " %s".repeat(-excess); + } else { + dummyArgs = excess; + } + } else { + tmp = str + " %s".repeat(nargs); + } + return new Pair<>(tmp, dummyArgs); + + } + + private static String subListAsString(List list, int originalSize) { + + StringBuilder sb = new StringBuilder("["); + list.forEach(item -> sb.append(asString(item)).append(", ")); + + if (originalSize > maxIterableItems) { + sb.append("...").append(originalSize - maxIterableItems).append(" more"); + } else { + sb.deleteCharAt(sb.length() - 1); + sb.deleteCharAt(sb.length() - 1); + } + return sb.append("]").toString(); + + } + + private static String asString(Object obj) { + + if (obj == null) return "null"; + Class objCls = obj.getClass(); + + //JS-native numeric values always come as doubles; make them look like integers if that's the case + if (objCls.equals(Double.class)) { + + Double d = (Double) obj; + if (Math.floor(d) == d && d >= 1.0*Long.MIN_VALUE && d <= 1.0*Long.MAX_VALUE) { + return Long.toString(d.longValue()); + } + + } else if (objCls.isArray()) { + + List list = new ArrayList<>(); + int len = Array.getLength(obj); + + for (int i = 0; i < Math.min(len, maxIterableItems); i++) { + list.add(Array.get(obj, i)); + } + return subListAsString(list, len); + + } else if (Collection.class.isInstance(obj)) { + + Collection col = (Collection) obj; + Iterator iterator = col.iterator(); + + List list = new ArrayList<>(); + int len = col.size(); + + for (int i = 0; i < Math.min(len, maxIterableItems); i++) { + list.add(iterator.next()); + } + return subListAsString(list, len); + + } else if (Map.class.isInstance(obj)) { + + Map map = (Map) obj; + List entries = new ArrayList<>(); + int i = 0; + + for (Object key : map.keySet()) { + entries.add(new AbstractMap.SimpleImmutableEntry(key, map.get(key))); + if (++i == maxIterableItems) break; + } + return subListAsString(entries, map.size()); + + } else if (Map.Entry.class.isInstance(obj)) { + + Map.Entry e = (Map.Entry) obj; + return String.format("(%s: %s)", asString(e.getKey()), asString(e.getValue())); + + } else if (Throwable.class.isInstance(obj)) { + + Throwable t = (Throwable) obj; + try( + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw)) { + + t.printStackTrace(pw); + return sw.toString(); + } catch(IOException e) { + //can be ignored + } + } + return obj.toString(); + + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/script/ScriptUtils.java b/agama/engine/src/main/java/io/jans/agama/engine/script/ScriptUtils.java new file mode 100644 index 00000000000..6be3da92d5c --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/script/ScriptUtils.java @@ -0,0 +1,193 @@ +package io.jans.agama.engine.script; + +import io.jans.agama.model.EngineConfig; +import io.jans.agama.engine.continuation.PendingRedirectException; +import io.jans.agama.engine.continuation.PendingRenderException; +import io.jans.agama.engine.misc.PrimitiveUtils; +import io.jans.agama.engine.service.ActionService; +import io.jans.agama.engine.service.FlowService; +import io.jans.service.cdi.util.CdiUtil; +import io.jans.util.Pair; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.AccessDeniedException; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.UnaryOperator; + +import org.mozilla.javascript.Context; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.NativeContinuation; +import org.mozilla.javascript.NativeJavaObject; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ScriptUtils { + + private static final Logger LOG = LoggerFactory.getLogger(ScriptUtils.class); + + // NOTE: do not alter this method's signature so that it returns void. The returned + // value is simulated when the continuation is resumed: see 3rd parameter in call + // to resumeContinuation (FlowService) + public static Pair pauseForRender(String page, boolean allowCallbackResume, Object data) + throws PendingRenderException, AccessDeniedException, URISyntaxException { + + page = normalize(page); + Context cx = Context.enter(); + try { + PendingRenderException pending = new PendingRenderException( + (NativeContinuation) cx.captureContinuation().getContinuation()); + pending.setTemplatePath(page); + pending.setDataModel(data); + pending.setAllowCallbackResume(allowCallbackResume); + LOG.debug("Pausing flow"); + throw pending; + } finally { + Context.exit(); + } + + } + + // NOTE: do not alter this method's signature so that it returns void. The returned + // value is simulated when the continuation is resumed: see 3rd parameter in call + // to resumeContinuation (FlowService) + public static Pair pauseForExternalRedirect(String url) throws PendingRedirectException { + + Context cx = Context.enter(); + try { + PendingRedirectException pending = new PendingRedirectException( + (NativeContinuation) cx.captureContinuation().getContinuation()); + pending.setLocation(url); + pending.setAllowCallbackResume(true); + LOG.debug("Pausing flow"); + throw pending; + } finally { + Context.exit(); + } + } + + public static boolean testEquality(Object a, Object b) { + + boolean anull = a == null; + boolean bnull = b == null; + + // Same object? + if (a == b) return true; + if (!anull && !bnull) { + + Class aClass = a.getClass(); + Class bClass = b.getClass(); + if (!aClass.equals(bClass)) { + + //Native JS numbers land as double here + if (aClass.equals(Double.class) && Number.class.isInstance(b)) { + return a.equals(((Number) b).doubleValue()); + + } else if (bClass.equals(Double.class) && Number.class.isInstance(a)) { + return b.equals(((Number) a).doubleValue()); + } + + LOG.warn("Trying to compare instances of {} and {}", aClass.getName(), bClass.getName()); + + LogUtils.log("@w Equality check between % and % is not available", + simpleName(aClass), simpleName(bClass)); + + } else if (aClass.equals(String.class) || PrimitiveUtils.isPrimitive(aClass, true)) { + return a.equals(b); + } else { + LogUtils.log("@w Equality check is only effective for numbers, strings, and boolean values. " + + "It returns false in other cases"); + } + } + return false; + + } + + //Issue a call to this method only if the request scope is active + public static Pair prepareSubflow(String qname, + Map mapping) throws IOException { + + return CdiUtil.bean(FlowService.class).prepareSubflow(qname, mapping); + + } + + public static Object callAction(Object instance, String actionClassName, String methodName, + Object[] params) throws Exception { + + return CdiUtil.bean(ActionService.class).callAction(instance, actionClassName, methodName, params); + //TODO: remove? + //if (Map.class.isInstance(value) && !NativeJavaMap.class.equals(value.getClass())) { + // Scriptable scope = CdiUtil.bean(FlowService.class).getGlobalScope(); + // return new NativeJavaMap(scope, value); + //} + + } + + //Issue a call to this method only if the request scope is active + public static void closeSubflow() throws IOException { + CdiUtil.bean(FlowService.class).closeSubflow(); + } + + public static Map templatesMapping(String parentBasepath, String[] overrides) + throws AccessDeniedException, URISyntaxException { + + String slBasepath = parentBasepath + "/"; + Map pathOverrides = new HashMap<>(); + + for (int i = 0; i < overrides.length / 2; i++) { + String original = normalize(overrides[2 * i]); + String overriden = normalize(slBasepath + overrides[2 * i + 1]); + + if (overriden.equals(slBasepath)) { + int j = original.lastIndexOf("/"); + overriden = j == -1 ? original : original.substring(j + 1); + overriden = slBasepath + overriden; + } + pathOverrides.put(original, overriden); + } + return pathOverrides; + + } + + public static boolean pathMatching(String url, Collection paths) { + + LOG.trace("Matching {} to any of {}", url, paths); + UnaryOperator unsuffixed = s -> s.substring(0, s.lastIndexOf(".")); + + String path = unsuffixed.apply(url); + return paths.stream().map(unsuffixed).anyMatch(path::equals); + + } + + private static String normalize(String sUri) throws AccessDeniedException, URISyntaxException { + + EngineConfig engineConf = CdiUtil.bean(EngineConfig.class); + String templatesFolder = engineConf.getRootDir() + engineConf.getTemplatesPath() + "/"; + + String ret = new URI(templatesFolder + sUri).normalize().toString(); + if (!ret.startsWith(templatesFolder)) + throw new AccessDeniedException(ret, null, "Access outside templates folder is not allowed"); + + return ret.substring(templatesFolder.length()); + + } + + private static String simpleName(Class cls) { + + String name; + if (List.class.isAssignableFrom(cls)) { + name = "list"; + } else if (Map.class.isAssignableFrom(cls)) { + name = "map"; + } else { + name = cls.getSimpleName(); + } + return name; + + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/serialize/ContinuationSerializer.java b/agama/engine/src/main/java/io/jans/agama/engine/serialize/ContinuationSerializer.java new file mode 100644 index 00000000000..c536186074c --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/serialize/ContinuationSerializer.java @@ -0,0 +1,97 @@ +package io.jans.agama.engine.serialize; + +import io.jans.agama.engine.service.ActionService; +import io.jans.util.Pair; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamClass; +import java.io.OutputStream; + +import org.mozilla.javascript.NativeContinuation; +import org.mozilla.javascript.NativeJavaObject; +import org.mozilla.javascript.Scriptable; + +@ApplicationScoped +public class ContinuationSerializer { + + @Inject + private ActionService actionService; + + public byte[] save(Scriptable scope, NativeContinuation continuation) throws IOException { + + class CustomObjectOutputStream extends ObjectOutputStream { + + CustomObjectOutputStream(OutputStream out) throws IOException { + super(out); + enableReplaceObject(true); + } + + @Override + protected Object replaceObject​(Object obj) throws IOException { + + if (NativeJavaObject.class.isInstance(obj)) { + return new NativeJavaBox((NativeJavaObject) obj); + } + return super.replaceObject(obj); + + } + + } + + try ( ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ObjectOutputStream sos = new CustomObjectOutputStream(baos)) { + + //Pair is not java-serializable, use a 2-length array + sos.writeObject(new Object[] { scope, continuation }); + return baos.toByteArray(); + } + + } + + public Pair restore(byte[] data) throws IOException { + + class CustomObjectInputStream extends ObjectInputStream { + + public CustomObjectInputStream(InputStream in) throws IOException { + super(in); + enableResolveObject(true); + } + + @Override + public Class resolveClass​(ObjectStreamClass desc) throws IOException, ClassNotFoundException { + return actionService.classFromName(desc.getName()); + } + + @Override + protected Object resolveObject​(Object obj) throws IOException { + + if (obj != null && obj.getClass().equals(NativeJavaBox.class)) { + return ((NativeJavaBox) obj).getRaw(); + } + return super.resolveObject(obj); + + } + + } + + try ( ByteArrayInputStream bais = new ByteArrayInputStream(data); + ObjectInputStream sis = new CustomObjectInputStream(bais)) { + + Object[] arr = (Object[]) sis.readObject(); + return new Pair<>((Scriptable) arr[0], (NativeContinuation) arr[1]); + + } catch (ClassNotFoundException e) { + throw new IOException(e); + } + + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/serialize/FstSerializer.java b/agama/engine/src/main/java/io/jans/agama/engine/serialize/FstSerializer.java new file mode 100644 index 00000000000..5bcfcf97080 --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/serialize/FstSerializer.java @@ -0,0 +1,41 @@ +package io.jans.agama.engine.serialize; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import io.jans.agama.model.serialize.Type; +import org.slf4j.Logger; + +/** + * Warning: This serialization strategy is not implemented yet + */ +@ApplicationScoped +public class FstSerializer implements ObjectSerializer { + + @Inject + private Logger logger; + + @Override + public Object deserialize(InputStream in) throws IOException { + return null; + } + + @Override + public void serialize(Object data, OutputStream out) throws IOException { + } + + @Override + public Type getType() { + return Type.FST; + } + + @PostConstruct + private void init() { + + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/serialize/KryoSerializer.java b/agama/engine/src/main/java/io/jans/agama/engine/serialize/KryoSerializer.java new file mode 100644 index 00000000000..2c50429797f --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/serialize/KryoSerializer.java @@ -0,0 +1,72 @@ +package io.jans.agama.engine.serialize; + +import com.esotericsoftware.kryo.Kryo; +import com.esotericsoftware.kryo.io.Input; +import com.esotericsoftware.kryo.io.Output; +import com.esotericsoftware.minlog.Log; + +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +import io.jans.agama.model.serialize.Type; +import io.jans.agama.engine.service.ActionService; + +import org.slf4j.Logger; + +@ApplicationScoped +public class KryoSerializer implements ObjectSerializer { + + @Inject + private Logger logger; + + @Inject + private ActionService actionService; + + private ThreadLocal kryos; + + @Override + public Object deserialize(InputStream in) throws IOException { + logger.trace("Kryodeserializing"); + Input input = new Input(in); + //If input is closed, the input's InputStream is closed + return kryos.get().readClassAndObject(input); + } + + @Override + public void serialize(Object data, OutputStream out) throws IOException { + logger.trace("Kryoserializing"); + Output output = new Output(out); + kryos.get().writeClassAndObject(output, data); + output.flush(); + } + + @Override + public Type getType() { + return Type.KRYO; + } + + @PostConstruct + private void init() { + + Log.DEBUG(); + kryos = new ThreadLocal() { + + @Override + protected Kryo initialValue() { + Kryo kryo = new Kryo(); + kryo.setRegistrationRequired(false); + kryo.setReferences(true); + kryo.setClassLoader(actionService.getClassLoader()); + kryo.setOptimizedGenerics(false); + return kryo; + } + + }; + + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/serialize/NativeJavaBox.java b/agama/engine/src/main/java/io/jans/agama/engine/serialize/NativeJavaBox.java new file mode 100644 index 00000000000..ff191a09e59 --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/serialize/NativeJavaBox.java @@ -0,0 +1,138 @@ +package io.jans.agama.engine.serialize; + +import io.jans.agama.engine.service.ActionService; +import io.jans.agama.engine.service.ManagedBeanService; +import io.jans.agama.model.serialize.Type; +import io.jans.util.Pair; +import io.jans.service.cdi.util.CdiUtil; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.Serializable; +import java.lang.annotation.Annotation; +import java.util.Set; + +import org.mozilla.javascript.NativeJavaArray; +import org.mozilla.javascript.NativeJavaClass; +import org.mozilla.javascript.NativeJavaList; +import org.mozilla.javascript.NativeJavaMap; +import org.mozilla.javascript.NativeJavaObject; +import org.mozilla.javascript.Scriptable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NativeJavaBox implements Serializable { + + private static final long serialVersionUID = 3843792598994958978L; + private static final Logger logger = LoggerFactory.getLogger(NativeJavaBox.class); + + private static final ManagedBeanService MBSRV = CdiUtil.bean(ManagedBeanService.class); + private static final SerializerFactory SERFACT = CdiUtil.bean(SerializerFactory.class); + private static final ActionService ACTSRV = CdiUtil.bean(ActionService.class); + + private NativeJavaObject raw; + private Object unwrapped; + + public NativeJavaBox(NativeJavaObject raw) { + + this.raw = raw; + unwrapped = raw.unwrap(); + + if (NativeJavaObject.class.isInstance(unwrapped)) { + throw new UnsupportedOperationException("Unexpected NativeJavaObject inside a NativeJavaObject"); + } + + logger.trace("NativeJavaBox created"); + + } + + private void writeObject(ObjectOutputStream out) throws IOException { + + String rawClsName = raw.getClass().getName(); + logger.trace("{} to the output stream", rawClsName); + + out.writeUTF(rawClsName); + out.writeObject(raw.getParentScope()); + logger.trace("Underlying object is an instance of {}", unwrapped.getClass().getName()); + + Pair, Set> metadata = MBSRV.getBeanMetadata(unwrapped); + Class cdiBeanClass = metadata.getFirst(); + + boolean cdiBean = cdiBeanClass != null; + out.writeBoolean(cdiBean); + + if (cdiBean) { + String realClassName = cdiBeanClass.getName(); + Set qualies = metadata.getSecond(); + logger.trace("Managed bean class {}", realClassName); + + //store class name and qualifiers only, not the bean itself + out.writeUTF(realClassName); + out.writeObject(qualies); //kryo fails deserializing Annotations :( + } else { + + //The object serializer instance may change at runtime. It has to be looked up every time + ObjectSerializer serializer = SERFACT.get(); + boolean useJavaOnlySerialization = serializer == null; + + out.writeObject(useJavaOnlySerialization ? null : serializer.getType()); + //unwrapped is not a managed object + if (useJavaOnlySerialization) { + out.writeObject(unwrapped); + } else { + serializer.serialize(unwrapped, out); + } + } + + } + + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + + Class rawCls = ACTSRV.classFromName(in.readUTF()); + + logger.trace("{} in the input stream", rawCls.getName()); + Scriptable parentScope = (Scriptable) in.readObject(); + + boolean cdiBean = in.readBoolean(); + if (cdiBean) { + + String realClassName = in.readUTF(); + Set qualies = (Set) in.readObject(); + + logger.trace("Managed bean class {}", realClassName); + unwrapped = ManagedBeanService.instance(ACTSRV.classFromName(realClassName), qualies); + } else { + + Type type = (Type) in.readObject(); + ObjectSerializer serializer = SERFACT.get(type); + unwrapped = serializer == null ? in.readObject() : serializer.deserialize(in); + } + + logger.trace("Underlying object is an instance of {}", unwrapped.getClass().getName()); + + if (rawCls.equals(NativeJavaObject.class)) { + raw = new NativeJavaObject(parentScope, unwrapped, unwrapped.getClass()); + + } else if (rawCls.equals(NativeJavaClass.class)) { + raw = new NativeJavaClass(parentScope, (Class) unwrapped); + + } else if (rawCls.equals(NativeJavaList.class)) { + raw = new NativeJavaList(parentScope, unwrapped); + + } else if (rawCls.equals(NativeJavaArray.class)) { + raw = NativeJavaArray.wrap(parentScope, unwrapped); + + } else if (rawCls.equals(NativeJavaMap.class)) { + raw = new NativeJavaMap(parentScope, unwrapped); + + } + + } + + public NativeJavaObject getRaw() { + logger.trace("Returning raw instance"); + return raw; + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/serialize/ObjectSerializer.java b/agama/engine/src/main/java/io/jans/agama/engine/serialize/ObjectSerializer.java new file mode 100644 index 00000000000..4138ea31e5e --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/serialize/ObjectSerializer.java @@ -0,0 +1,15 @@ +package io.jans.agama.engine.serialize; + +import io.jans.agama.model.serialize.Type; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public interface ObjectSerializer { + + Object deserialize(InputStream in) throws IOException; + void serialize(Object data, OutputStream out) throws IOException; + Type getType(); + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/serialize/SerializerFactory.java b/agama/engine/src/main/java/io/jans/agama/engine/serialize/SerializerFactory.java new file mode 100644 index 00000000000..e63bd5789be --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/serialize/SerializerFactory.java @@ -0,0 +1,35 @@ +package io.jans.agama.engine.serialize; + +import io.jans.agama.model.EngineConfig; +import io.jans.agama.model.serialize.Type; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Any; +import jakarta.enterprise.inject.Instance; +import jakarta.inject.Inject; + +@ApplicationScoped +public class SerializerFactory { + + @Inject + private EngineConfig engineConf; + + @Inject @Any + private Instance services; + + private ObjectSerializer serializer; + + public ObjectSerializer get() { + return serializer; + } + + public ObjectSerializer get(Type type) { + return services.stream().filter(s -> s.getType().equals(type)) + .findFirst().orElse(null); + } + + public void refresh() { + serializer = get(engineConf.getSerializerType()); + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/service/ActionService.java b/agama/engine/src/main/java/io/jans/agama/engine/service/ActionService.java new file mode 100644 index 00000000000..7ac0c37583f --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/service/ActionService.java @@ -0,0 +1,270 @@ +package io.jans.agama.engine.service; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; + +import groovy.lang.GroovyClassLoader; +import groovy.util.GroovyScriptEngine; +import groovy.util.ResourceException; +import groovy.util.ScriptException; + +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import java.io.File; +import java.lang.reflect.Constructor; +import java.lang.reflect.Executable; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; +import java.lang.reflect.Type; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.BiPredicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import io.jans.agama.engine.misc.PrimitiveUtils; +import io.jans.agama.model.EngineConfig; + +import org.codehaus.groovy.control.CompilerConfiguration; +import org.slf4j.Logger; + +@ApplicationScoped +public class ActionService { + + private static final List CLASS_EXTENSIONS = Arrays.asList("java", "groovy"); + + @Inject + private Logger logger; + + @Inject + private EngineConfig econf; + + private GroovyScriptEngine gse; + private ObjectMapper mapper; + private GroovyClassLoader loader; + + public Object callAction(Object instance, String className, String methodName, Object[] rhinoArgs) + throws Exception { + + boolean noInst = instance == null; + Class actionCls = null; + + if (!noInst) { + actionCls = instance.getClass(); + className = actionCls.getName(); + } else { + + try { + //logger.info("Using current classloader to load class " + className); + //Try the fastest lookup first + actionCls = Class.forName(className); + } catch (ClassNotFoundException e) { + + ResourceException rex = null; + for (String ext : CLASS_EXTENSIONS) { + try { + String classFilePath = className.replace('.', File.separatorChar) + "." + ext; + //GroovyScriptEngine classes are only really reloaded when the underlying file changes + actionCls = gse.loadScriptByName(classFilePath); + break; + } catch (ResourceException re) { + if (rex == null) rex = re; + } catch (ScriptException se) { + throw se; + } + } + + if (actionCls == null) throw new ClassNotFoundException(rex.getMessage(), rex); + } + } + logger.debug("Class {} loaded successfully", className); + int arity = rhinoArgs.length; + + BiPredicate pr = (e, staticRequired) -> { + int mod = e.getModifiers(); + return e.getParameterCount() == arity && Modifier.isPublic(mod) && + (staticRequired ? Modifier.isStatic(mod) : true); + }; + + //Search for a method/constructor matching name and arity + + if (noInst) { + if (methodName.equals("new")) { + Constructor constr = Stream.of(actionCls.getConstructors()).filter(c -> pr.test(c, false)) + .findFirst().orElse(null); + if (constr == null) { + String msg = String.format("Unable to find a constructor with arity %d in class %s", + arity, className); + logger.error(msg); + throw new InstantiationException(msg); + } + + if (logger.isDebugEnabled()) { + logger.debug("Constructor found: {}", constr.toGenericString()); + } + Object[] args = getArgsForCall(constr, arity, rhinoArgs); + + logger.debug("Creating an instance"); + return constr.newInstance(args); + + } else if (methodName.equals("class")) { + logger.debug("Returning class object"); + return actionCls; + } + } + + Method javaMethod = Stream.of(actionCls.getDeclaredMethods()) + .filter(m -> m.getName().equals(methodName)).filter(m -> pr.test(m, noInst)) + .findFirst().orElse(null); + + if (javaMethod == null) { + String msg = String.format("Unable to find a method called %s with arity %d in class %s", + methodName, arity, className); + logger.error(msg); + throw new NoSuchMethodException(msg); + } + + if (logger.isDebugEnabled()) { + logger.debug("Method found: {}", javaMethod.toGenericString()); + } + Object[] args = getArgsForCall(javaMethod, arity, rhinoArgs); + + logger.debug("Performing method call"); + return javaMethod.invoke(instance, args); + + } + + private Object[] getArgsForCall(Executable javaExec, int arity, Object[] arguments) + throws IllegalArgumentException { + + Object[] javaArgs = new Object[arity]; + int i = -1; + + for (Parameter p : javaExec.getParameters()) { + Object arg = arguments[++i]; + Class paramType = p.getType(); + String typeName = paramType.getName(); + logger.debug("Examining argument at index {}", i); + + if (arg == null) { + logger.debug("Value is null"); + if (PrimitiveUtils.isPrimitive(paramType, false)) + throw new IllegalArgumentException("null value passed for a primitive parameter of type " + + typeName); + else continue; + } + if (typeName.equals(Object.class.getName())) { + //This parameter can receive anything :( + logger.trace("Parameter is a {}", typeName); + javaArgs[i] = arg; + continue; + } + + //argClass does not carry type information due to Java type erasure + Class argClass = arg.getClass(); + + //Try to apply cheaper conversions first (in comparison to mapper-based conversion) + //Note: A numeric literal coming from Javascript code lands as a Double + Boolean primCompat = PrimitiveUtils.compatible(argClass, paramType); + if (primCompat != null) { + + if (primCompat) { + logger.trace("Parameter is a primitive (or wrapped) {}", typeName); + javaArgs[i] = arg; + + } else if (Number.class.isAssignableFrom(argClass)) { + Object number = PrimitiveUtils.primitiveNumberFrom((Number) arg, paramType); + + if (number != null) { + logger.trace("Parameter is a primitive (or wrapped) {}", typeName); + javaArgs[i] = number; + + } else mismatchError(argClass, typeName); + + } else mismatchError(argClass, typeName); + + } else if (CharSequence.class.isAssignableFrom(argClass)) { + + primCompat = PrimitiveUtils.compatible(Character.class, paramType); + + if (Optional.ofNullable(primCompat).orElse(false)) { + int len = arg.toString().length(); + if (len == 0 || len > 1) mismatchError(argClass, typeName); + + logger.trace("Parameter is a {}", typeName); + javaArgs[i] = arg.toString().charAt(0); + + } else if (paramType.isAssignableFrom(argClass)) { + logger.trace("Parameter is a {}", typeName); + javaArgs[i] = arg; + + } else mismatchError(argClass, typeName); + + } else { + //argClass should be NativeArray or NativeObject if the value was not created/derived + //from a Java call + Type parameterizedType = p.getParameterizedType(); + String ptypeName = parameterizedType.getTypeName(); + + //ptypeName and typeName are equal when there is no type information in the parameter + //(method signature). For instance: String[], List, MyBean (no parameterized types) + if (ptypeName.equals(typeName) && paramType.isInstance(arg)) { + javaArgs[i] = arg; + } else { + logger.warn("Trying to parse argument of class {} to {}", argClass.getCanonicalName(), ptypeName); + + JavaType javaType = mapper.getTypeFactory().constructType(parameterizedType); + javaArgs[i] = mapper.convertValue(arg, javaType); + } + logger.trace("Parameter is a {}", ptypeName); + } + } + return javaArgs; + + } + + public GroovyClassLoader getClassLoader() { + return loader; + } + + public Class classFromName(String qname) throws ClassNotFoundException { + return Class.forName(qname, false, loader); + } + + private void mismatchError(Class argClass, String typeName) throws IllegalArgumentException { + throw new IllegalArgumentException(argClass.getSimpleName() + " passed for a " + typeName); + } + + @PostConstruct + private void init() { + + URL url = null; + try { + url = new URL(String.format("file://%s%s/", econf.getRootDir(), econf.getScriptsPath())); + } catch(MalformedURLException e) { + logger.error(e.getMessage()); + throw new RuntimeException(e); + } + + logger.debug("Creating a Groovy Script Engine based at {}", url.toString()); + gse = new GroovyScriptEngine(new URL[]{ url }); + + CompilerConfiguration cc = gse.getConfig(); + cc.setDefaultScriptExtension(CLASS_EXTENSIONS.get(0)); + cc.setScriptExtensions(CLASS_EXTENSIONS.stream().collect(Collectors.toSet())); + + loader = gse.getGroovyClassLoader(); + loader.setShouldRecompile(true); + + mapper = new ObjectMapper(); + mapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); + + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/service/AgamaPersistenceService.java b/agama/engine/src/main/java/io/jans/agama/engine/service/AgamaPersistenceService.java new file mode 100644 index 00000000000..9c0f3c8d2fe --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/service/AgamaPersistenceService.java @@ -0,0 +1,241 @@ +package io.jans.agama.engine.service; + +import io.jans.agama.engine.misc.FlowUtils; +import io.jans.agama.engine.model.FlowResult; +import io.jans.agama.engine.model.FlowRun; +import io.jans.agama.engine.model.FlowStatus; +import io.jans.agama.engine.model.ProtoFlowRun; +import io.jans.agama.engine.serialize.ContinuationSerializer; +import io.jans.agama.model.Flow; +import io.jans.agama.model.ProtoFlow; +import io.jans.as.model.configuration.AppConfiguration; +import io.jans.orm.PersistenceEntryManager; +import io.jans.orm.search.filter.Filter; +import io.jans.util.Pair; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import java.io.IOException; +import java.util.Base64; +import java.util.Date; +import java.util.List; +import java.util.Optional; + +import org.mozilla.javascript.NativeContinuation; +import org.mozilla.javascript.Scriptable; +import org.slf4j.Logger; + +import static java.nio.charset.StandardCharsets.UTF_8; + +@ApplicationScoped +public class AgamaPersistenceService { + + private static final String AGAMA_BASE = "ou=agama,o=jans"; + + public static final String AGAMA_FLOWRUNS_BASE = "ou=runs," + AGAMA_BASE; + public static final String AGAMA_FLOWS_BASE = "ou=flows," + AGAMA_BASE; + + @Inject + private Logger logger; + + @Inject + private PersistenceEntryManager entryManager; + + @Inject + private ContinuationSerializer contSerializer; + + @Inject + private FlowUtils flowUtils; + + @Inject + private AppConfiguration appConfiguration; + + public FlowStatus getFlowStatus(String sessionId) throws IOException { + + try { + logger.debug("Retrieving current flow's status"); + ProtoFlowRun fr = entryManager.findEntries(AGAMA_FLOWRUNS_BASE, ProtoFlowRun.class, + frEqFilter(sessionId), new String[]{ FlowRun.ATTR_NAMES.STATUS }, 1).get(0); + + return fr.getStatus(); + } catch(Exception e) { + return null; + } + + } + + public void persistFlowStatus(String sessionId, FlowStatus fst) throws IOException { + + try { + ProtoFlowRun pfr = entryManager.findEntries(AGAMA_FLOWRUNS_BASE, ProtoFlowRun.class, + frEqFilter(sessionId), new String[]{ FlowRun.ATTR_NAMES.ID }, 1).get(0); + + logger.debug("Saving current flow's status"); + pfr.setStatus(fst); + entryManager.merge(pfr); + } catch(Exception e) { + throw new IOException(e); + } + + } + + public void createFlowRun(String id, FlowStatus fst, long expireAt) throws Exception { + + FlowRun fr = new FlowRun(); + fr.setBaseDn(String.format("%s=%s,%s", FlowRun.ATTR_NAMES.ID, id, AGAMA_FLOWRUNS_BASE)); + fr.setId(id); + fr.setStatus(fst); + fr.setDeletableAt(new Date(expireAt)); + + logger.info("Creating flow run"); + entryManager.persist(fr); + + } + + public boolean flowEnabled(String flowName) { + + try { + Filter filth = Filter.createANDFilter( + Filter.createEqualityFilter(Flow.ATTR_NAMES.QNAME, flowName), + Filter.createEqualityFilter("jansEnabled", true)); + + List results = entryManager.findEntries(AGAMA_FLOWS_BASE, + ProtoFlow.class, filth, new String[]{ Flow.ATTR_NAMES.QNAME }, 1); + return results.size() == 1; + + } catch(Exception e) { + logger.error(e.getMessage(), e); + logger.warn("Flow '{}' does not seem to exist!", flowName); + return false; + } + + } + + public int getEffectiveFlowTimeout(String flowName) { + + Flow fl = entryManager.findEntries(AGAMA_FLOWS_BASE, Flow.class, + Filter.createEqualityFilter(Flow.ATTR_NAMES.QNAME, flowName), + new String[]{ Flow.ATTR_NAMES.META }, 1).get(0); + + int unauth = appConfiguration.getSessionIdUnauthenticatedUnusedLifetime(); + Integer flowTimeout = fl.getMetadata().getTimeout(); + int timeout = Optional.ofNullable(flowTimeout).map(Integer::intValue).orElse(unauth); + return Math.min(unauth, timeout); + + } + + public Flow getFlow(String flowName, boolean full) throws IOException { + + try { + String[] attrs = null; + if (!full) { + attrs = new String[]{ Flow.ATTR_NAMES.QNAME, Flow.ATTR_NAMES.META, + Flow.ATTR_NAMES.TRANSPILED }; + } + + logger.debug("Retrieving {}info of flow '{}'", full ? "" : "minimal ", flowName); + List fls = entryManager.findEntries(AGAMA_FLOWS_BASE, Flow.class, + Filter.createEqualityFilter(Flow.ATTR_NAMES.QNAME, flowName), attrs, 1); + + if (fls.isEmpty()) { + logger.warn("Flow '{}' does not exist!", flowName); + } + + return fls.get(0); + } catch(Exception e) { + throw new IOException(e); + } + + } + + public Pair getContinuation(String sessionId) + throws IOException { + + FlowRun fr; + try { + fr = entryManager.findEntries(AGAMA_FLOWRUNS_BASE, FlowRun.class, frEqFilter(sessionId), + new String[] { "agFlowEncCont", "jansCustomMessage" }, 1).get(0); + } catch(Exception e) { + return null; + } + + logger.debug("Restoring continuation data..."); + byte[] cont = Base64.getDecoder().decode(fr.getEncodedContinuation()); + + if (!flowUtils.hash(cont).equals(fr.getHash())) + throw new IOException("Serialized continuation has been altered"); + + return contSerializer.restore(cont); + + } + + public void saveState(String sessionId, FlowStatus fst, NativeContinuation continuation, + Scriptable scope) throws IOException { + + byte[] bytes = contSerializer.save(scope, continuation); + logger.debug("Continuation serialized ({} bytes)", bytes.length); + + List results = entryManager.findEntries(AGAMA_FLOWRUNS_BASE, FlowRun.class, + frEqFilter(sessionId), new String[]{ FlowRun.ATTR_NAMES.ID, "exp" }, 1); + //The query above retrieves enough attributes so no data is lost after the + //update that follows below + + FlowRun run = results.get(0); + run.setEncodedContinuation(new String(Base64.getEncoder().encode(bytes), UTF_8)); + run.setHash(flowUtils.hash(bytes)); + //overwrite status + run.setStatus(fst); + + logger.debug("Saving state of current flow run"); + entryManager.merge(run); + + } + + public void finishFlow(String sessionId, FlowResult result) throws IOException { + + try { + logger.debug("Retrieving flow run {}", sessionId); + FlowRun run = entryManager.findEntries(AGAMA_FLOWRUNS_BASE, FlowRun.class, frEqFilter(sessionId), + new String[]{ FlowRun.ATTR_NAMES.ID, FlowRun.ATTR_NAMES.STATUS, "exp" }, 1).get(0); + + //The query above retrieves enough attributes so no data is lost after the + //update that follows below + + FlowStatus status = run.getStatus(); + status.setStartedAt(FlowStatus.FINISHED); + status.setResult(result); + + status.setQname(null); + status.setJsonInput(null); + status.setParentsMappings(null); + status.setTemplatePath(null); + status.setTemplateDataModel(null); + status.setExternalRedirectUrl(null); + + run.setEncodedContinuation(null); + run.setHash(null); + + logger.info("Marking flow run as finished..."); + entryManager.merge(run); + + } catch (Exception e) { + throw new IOException(e); + } + } + + public void terminateFlow(String sessionId) throws IOException { + + try { + logger.info("Removing flow run..."); + entryManager.remove(AGAMA_FLOWRUNS_BASE, FlowRun.class, frEqFilter(sessionId), 1); + } catch (Exception e) { + throw new IOException(e); + } + + } + + private Filter frEqFilter(String id) { + return Filter.createEqualityFilter(FlowRun.ATTR_NAMES.ID, id); + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/service/AppInitializer.java b/agama/engine/src/main/java/io/jans/agama/engine/service/AppInitializer.java new file mode 100644 index 00000000000..0d2ff1fb84c --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/service/AppInitializer.java @@ -0,0 +1,35 @@ +package io.jans.agama.engine.service; + +import io.jans.agama.timer.FlowRunsCleaner; +import io.jans.agama.timer.Transpilation; +import io.jans.service.cdi.event.ApplicationInitialized; +import io.jans.service.cdi.event.ApplicationInitializedEvent; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; + +import org.slf4j.Logger; + +@ApplicationScoped +public class AppInitializer { + + @Inject + private Logger logger; + + @Inject + private Transpilation trTimer; + + @Inject + private FlowRunsCleaner fcleaner; + + public void run(@Observes @ApplicationInitialized(ApplicationScoped.class) + ApplicationInitializedEvent event) { + + logger.info("Initializing Agama services"); + trTimer.initTimer(); + fcleaner.initTimer(); + + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/service/FlowService.java b/agama/engine/src/main/java/io/jans/agama/engine/service/FlowService.java new file mode 100644 index 00000000000..1929abc0825 --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/service/FlowService.java @@ -0,0 +1,429 @@ +package io.jans.agama.engine.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.jans.agama.dsl.Transpiler; +import io.jans.agama.engine.continuation.PendingException; +import io.jans.agama.engine.continuation.PendingRedirectException; +import io.jans.agama.engine.continuation.PendingRenderException; +import io.jans.agama.engine.exception.FlowCrashException; +import io.jans.agama.engine.exception.FlowTimeoutException; +import io.jans.agama.engine.misc.FlowUtils; +import io.jans.agama.engine.model.FlowResult; +import io.jans.agama.engine.model.FlowStatus; +import io.jans.agama.model.FlowMetadata; +import io.jans.agama.model.EngineConfig; +import io.jans.agama.model.Flow; +import io.jans.util.Pair; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.PreDestroy; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.Collections; +import java.util.Deque; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import org.mozilla.javascript.Context; +import org.mozilla.javascript.ContextFactory; +import org.mozilla.javascript.ContinuationPending; +import org.mozilla.javascript.Function; +import org.mozilla.javascript.NativeContinuation; +import org.mozilla.javascript.NativeJavaList; +import org.mozilla.javascript.NativeJavaMap; +import org.mozilla.javascript.NativeJavaObject; +import org.mozilla.javascript.NativeObject; +import org.mozilla.javascript.RhinoException; +import org.mozilla.javascript.Scriptable; +import org.mozilla.javascript.Undefined; +import org.slf4j.Logger; + +@RequestScoped +public class FlowService { + + private static final String SESSION_ID_COOKIE = "session_id"; + private static final String SCRIPT_SUFFIX = ".js"; + + private static final int TIMEOUT_SKEW = 8000; //millisecons + + @Inject + private Logger logger; + + @Inject + private ObjectMapper mapper; + + @Inject + private AgamaPersistenceService aps; + + @Inject + private FlowUtils flowUtils; + + @Inject + private EngineConfig engineConfig; + + @Inject + private HttpServletRequest request; + + private String sessionId; + private Context scriptCtx; + private Scriptable globalScope; + private Deque> parentsMappings; + + /** + * Obtains the status of the current flow (if any) for the current user + * @return + * @throws IOException + */ + public FlowStatus getRunningFlowStatus() throws IOException { + return aps.getFlowStatus(sessionId); + } + + public FlowStatus startFlow(FlowStatus status) throws FlowCrashException { + + try { + status.setStartedAt(System.currentTimeMillis()); + String flowName = status.getQname(); + + //retrieve the flow, execute until render/redirect is reached + Flow flow = aps.getFlow(flowName, true); + FlowMetadata fl = flow.getMetadata(); + String funcName = fl.getFuncName(); + + verifyCode(flow); + logger.info("Evaluating flow code"); + + try { + globalScope = initContext(scriptCtx); + scriptCtx.evaluateString(globalScope, Transpiler.UTIL_SCRIPT_CONTENTS, + Transpiler.UTIL_SCRIPT_NAME, 1, null); + flowUtils.printScopeIds(globalScope); + + scriptCtx.evaluateString(globalScope, flow.getTranspiled(), flowName + SCRIPT_SUFFIX, 1, null); + flowUtils.printScopeIds(globalScope); + + logger.info("Executing function {}", funcName); + Function f = (Function) globalScope.get(funcName, globalScope); + parentsMappings = status.getParentsMappings(); + + Object[] params = getFuncParams(fl, status.getJsonInput()); + Object val = scriptCtx.callFunctionWithContinuations(f, globalScope, params); + NativeObject result = checkJSReturnedValue(val); + finishFlow(result, status); + + } catch (ContinuationPending pe) { + status = processPause(pe, status); + + } catch (Exception e){ + terminateFlow(); + makeCrashException(e); + } + + //TODO: review exception handling, enable polling if needed + } catch (IOException ie) { + throw new FlowCrashException(ie.getMessage(), ie); + } + return status; + + } + + public FlowStatus continueFlow(FlowStatus status, String jsonParameters, boolean callbackResume, + String cancelUrl) throws FlowCrashException, FlowTimeoutException { + + try { + if (callbackResume) { + //disable usage of callback endpoint + status.setAllowCallbackResume(false); + aps.persistFlowStatus(sessionId, status); + } + + try { + ensureTimeNotExceeded(status); + + Pair pcont = aps.getContinuation(sessionId); + globalScope = pcont.getFirst(); + flowUtils.printScopeIds(globalScope); + + logger.debug("Resuming flow"); + parentsMappings = status.getParentsMappings(); + + Object val = scriptCtx.resumeContinuation(pcont.getSecond(), + globalScope, new Pair<>(cancelUrl, jsonParameters)); + NativeObject result = checkJSReturnedValue(val); + finishFlow(result, status); + + } catch (ContinuationPending pe) { + status = processPause(pe, status); + + } catch (FlowTimeoutException te) { + terminateFlow(); + throw te; + } catch (Exception e) { + terminateFlow(); + makeCrashException(e); + } + } catch (IOException ie) { + throw new FlowCrashException(ie.getMessage(), ie); + } + return status; + + } + + // This is called in the middle of a cx.resumeContinuation invocation (see util.js#_flowCall) + public Pair prepareSubflow(String subflowName, + Map mappings) throws IOException { + + logger.debug("Template mappings of subflow {} are {}", subflowName, mappings); + Flow flow = aps.getFlow(subflowName, false); + FlowMetadata fl = flow.getMetadata(); + String funcName = fl.getFuncName(); + + String flowCodeFileName = subflowName + SCRIPT_SUFFIX; + //strangely, scriptCtx is a bit messed at this point so initialization is required again... + initContext(scriptCtx); + + scriptCtx.evaluateString(globalScope, flow.getTranspiled(), flowCodeFileName, 1, null); + flowUtils.printScopeIds(globalScope); + + logger.info("Appending function {} to scope", funcName); + Function f = (Function) globalScope.get(funcName, globalScope); + + //This value is useful when saving the state, see method processPause + parentsMappings.push(mappings); + + logger.info("Evaluating subflow code"); + Map configs = Optional.ofNullable(fl.getProperties()).orElse(Collections.emptyMap()); + return new Pair<>(f, wrapListOrMap(configs)); + + } + + public void ensureTimeNotExceeded(FlowStatus flstatus) throws FlowTimeoutException { + + //Use some seconds to account for the potential time difference due to redirections: + //jython script -> agama, agama -> jython script. This helps agama flows to timeout + //before the unauthenticated unused time + if (System.currentTimeMillis() + TIMEOUT_SKEW > flstatus.getFinishBefore()) { + throw new FlowTimeoutException("You have exceeded the amount of time required " + + "to complete your authentication", flstatus.getQname()); + //"Your authentication attempt has run for more than " + time + " seconds" + } + + } + + public void closeSubflow() throws IOException { + parentsMappings.pop(); + } + + public void terminateFlow() throws IOException { + aps.terminateFlow(sessionId); + } + + private void finishFlow(NativeObject result, FlowStatus status) throws IOException { + + FlowResult res = flowResultFrom(result); + status.setResult(res); + aps.finishFlow(sessionId, res); + + } + + private void verifyCode(Flow fl) throws IOException { + + String code = fl.getTranspiled(); + if (code == null) { + String msg = "Source code of flow " + fl.getQname() + " "; + msg += fl.getCodeError() == null ? "has not been parsed yet" : "has errors"; + throw new IOException(msg); + } + + if (!Optional.ofNullable(engineConfig.getDisableTCHV()).orElse(false)) { + + String hash = fl.getTransHash(); + //null hash means the code is being regenerated in this moment + if (hash != null && !flowUtils.hash(code).equals(hash)) + throw new IOException("Transpiled code seems to have been altered. " + + "Restore the code by increasing this flow's jansRevision attribute"); + } + + } + + private FlowStatus processPause(ContinuationPending pending, FlowStatus status) + throws FlowCrashException, IOException { + + PendingException pe = null; + if (pending instanceof PendingRenderException) { + + PendingRenderException pre = (PendingRenderException) pending; + String templPath = computeTemplatePath(pre.getTemplatePath(), parentsMappings); + + if (!templPath.contains(".")) + throw new FlowCrashException( + "Expecting file extension for the template to render: " + templPath); + + status.setTemplatePath(templPath); + status.setTemplateDataModel(pre.getDataModel()); + status.setExternalRedirectUrl(null); + pe = pre; + + } else if (pending instanceof PendingRedirectException) { + + PendingRedirectException pre = (PendingRedirectException) pending; + + status.setTemplatePath(null); + status.setTemplateDataModel(null); + status.setExternalRedirectUrl(pre.getLocation()); + pe = pre; + + } else { + throw new IllegalArgumentException("Unexpected instance of ContinuationPending"); + } + + logger.debug("Parents mappings: {}", status.getParentsMappings()); + status.setAllowCallbackResume(pe.isAllowCallbackResume()); + //Save the state + aps.saveState(sessionId, status, pe.getContinuation(), globalScope); + + return status; + + } + + private String computeTemplatePath(String path, Deque> parentsMappings) { + + String result = path; + for (Map mapping : parentsMappings) { + String overriden = mapping.get(result); + if (overriden != null) result = overriden; + } + + logger.info("Inferred template for {} is {}", path, result); + return result; + + } + + private Object[] getFuncParams(FlowMetadata metadata, String strParams) throws JsonProcessingException { + + List inputs = Optional.ofNullable(metadata.getInputs()).orElse(Collections.emptyList()); + Map configs = Optional.ofNullable(metadata.getProperties()).orElse(Collections.emptyMap()); + + Object[] params = new Object[inputs.size() + 1]; + params[0] = wrapListOrMap(configs); + + if (strParams != null) { + Map map = mapper.readValue(strParams, new TypeReference>(){}); + for (int i = 1; i < params.length; i++) { + params[i] = map.get(inputs.get(i - 1)); + } + } + for (int i = 1; i < params.length; i++) { + String input = inputs.get(i - 1); + + if (params[i] == null) { + logger.warn("Setting parameter '{}' to null", input); + } else { + logger.debug("Setting parameter '{}' to an instance of {}", input, params[i].getClass().getName()); + + NativeJavaObject wrapped = wrapListOrMap(params[i]); + params[i] = wrapped == null ? params[i] : wrapped; + } + } + return params; + + } + + private NativeJavaObject wrapListOrMap(Object obj) { + + //This helps prevent exception "Invalid JavaScript value of type ..." + //when typeof is applied over this param in JavaScript code + if (Map.class.isInstance(obj)) { + return new NativeJavaMap(globalScope, obj); + } else if (List.class.isInstance(obj)) { + return new NativeJavaList(globalScope, obj); + } + return null; + + } + + private NativeObject checkJSReturnedValue(Object obj) throws Exception { + + try { + //obj is not null + return NativeObject.class.cast(obj); + } catch (ClassCastException e) { + if (Undefined.isUndefined(obj)) { + throw new Exception("No Finish instruction was reached"); + } else throw e; + } + + } + + private void makeCrashException(Exception e) throws FlowCrashException { + + String msg; + if (e instanceof RhinoException) { + RhinoException re = (RhinoException) e; + msg = re.details(); + logger.error(msg + re.getScriptStackTrace()); + //logger.error(re.getMessage()); + msg = "Error executing flow's code - " + msg; + } else + msg = e.getMessage(); + + throw new FlowCrashException(msg, e); + + } + + /** + * @param result + * @return + * @throws JsonProcessingException + */ + private FlowResult flowResultFrom(NativeObject result) throws JsonProcessingException { + return mapper.convertValue(result, FlowResult.class); + } + + private Scriptable initContext(Context ctx) { + ctx.setLanguageVersion(Context.VERSION_ES6); + ctx.setOptimizationLevel(-1); + return ctx.initStandardObjects(); + } + + @PostConstruct + private void init() { + + class AgamaContextFactory extends ContextFactory { + + @Override + protected boolean hasFeature(Context cx, int featureIndex) { + switch (featureIndex) { + case Context.FEATURE_ENABLE_JAVA_MAP_ACCESS: return true; + } + return super.hasFeature(cx, featureIndex); + } + } + + scriptCtx = new AgamaContextFactory().enterContext(); + sessionId = null; + Cookie[] cookies = request.getCookies(); + + if (cookies != null) { + sessionId = Stream.of(cookies).filter(coo -> coo.getName().equals(SESSION_ID_COOKIE)) + .findFirst().map(Cookie::getValue).orElse(null); + } + if (sessionId == null) { + logger.warn("Session ID not found"); + } + + } + + @PreDestroy + private void finish() { + Context.exit(); + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/service/ManagedBeanService.java b/agama/engine/src/main/java/io/jans/agama/engine/service/ManagedBeanService.java new file mode 100644 index 00000000000..aa8b75a3be1 --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/service/ManagedBeanService.java @@ -0,0 +1,90 @@ +package io.jans.agama.engine.service; + +import io.jans.util.Pair; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Any; +import jakarta.enterprise.inject.spi.Bean; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.enterprise.inject.spi.CDI; +import jakarta.enterprise.util.AnnotationLiteral; +import jakarta.inject.Inject; +import jakarta.inject.Qualifier; +import jakarta.inject.Singleton; + +import java.lang.annotation.Annotation; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.ClassCastException; +import java.util.HashSet; +import java.util.Set; + +import org.jboss.weld.proxy.WeldClientProxy; +import org.slf4j.Logger; + +@ApplicationScoped +public class ManagedBeanService { + + @Inject + private Logger logger; + + @Inject + private BeanManager beanManager; + + public static T instance(Class subtype, Set qualifiers) { + return CDI.current().select(subtype, qualifiers.toArray(new Annotation[0])).get(); + } + + public Pair, Set> getBeanMetadata(Object obj) { + + Class beanClass = null; + Set qualifiers = null; + try { + WeldClientProxy proxy = WeldClientProxy.class.cast(obj); + //it's a managed instance + Bean bean = proxy.getMetadata().getBean(); + beanClass = bean.getBeanClass(); + //Make it mutable + qualifiers = new HashSet<>(bean.getQualifiers()); + } catch (ClassCastException e) { + if (obj.getClass().isAnnotationPresent(Singleton.class)) { + //it's a managed instance. Beans with scope @Singleton don’t have a proxy object. + //Clients hold a direct reference to the singleton instance + beanClass = obj.getClass(); + qualifiers = getClassQualifiers(beanClass); + } + } + if (beanClass != null) { + qualifiers.remove(new AnnotationLiteral() {}); + logger.debug("Qualifiers found in class {}: {}", beanClass.getSimpleName(), qualifiers); + } + return new Pair<>(beanClass, qualifiers); + + } + + private Set getClassQualifiers(Class cls) { + + Set qualies = new HashSet<>(); + for (Annotation ann : cls.getAnnotations()) { + Class clazz = ann.annotationType(); + + //A qualifier annotation is annotated with @Qualifier and @Retention(RUNTIME) + if (clazz.isAnnotationPresent(Qualifier.class)) { + Retention ret = clazz.getAnnotation(Retention.class); + if (ret != null && ret.value().equals(RetentionPolicy.RUNTIME)) { + qualies.add(ann); + } + } + } + return qualies; + + } + + /* + public static void main(String ...args) throws Exception { + ManagedBeanService mbs = new ManagedBeanService(); + System.out.println(mbs.getClassQualifiers(....class)); + } + */ + +} \ No newline at end of file diff --git a/agama/engine/src/main/java/io/jans/agama/engine/service/ServicesFactory.java b/agama/engine/src/main/java/io/jans/agama/engine/service/ServicesFactory.java new file mode 100644 index 00000000000..55d43d3431b --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/service/ServicesFactory.java @@ -0,0 +1,61 @@ +package io.jans.agama.engine.service; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.jans.agama.engine.serialize.SerializerFactory; +import io.jans.agama.model.EngineConfig; +import io.jans.as.model.configuration.AppConfiguration; +import io.jans.service.cdi.event.ConfigurationUpdate; + +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Produces; +import jakarta.inject.Inject; + +import org.apache.commons.beanutils.BeanUtils; +import org.slf4j.Logger; + +@ApplicationScoped +public class ServicesFactory { + + @Inject + private Logger logger; + + @Inject + private SerializerFactory serializerFactory; + + private ObjectMapper mapper; + + private EngineConfig econfig; + + @Produces + public ObjectMapper mapperInstance() { + return mapper; + } + + @Produces + @ApplicationScoped + public EngineConfig engineConfigInstance() { + return econfig; + } + + public void updateConfiguration(@Observes @ConfigurationUpdate AppConfiguration appConfiguration) { + + try { + logger.info("Refreshing Agama configuration..."); + BeanUtils.copyProperties(econfig, appConfiguration.getAgamaConfiguration()); + serializerFactory.refresh(); + } catch (Exception e) { + logger.error(e.getMessage(), e); + } + + } + + @PostConstruct + public void init() { + mapper = new ObjectMapper(); + econfig = new EngineConfig(); + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/service/TemplatingService.java b/agama/engine/src/main/java/io/jans/agama/engine/service/TemplatingService.java new file mode 100644 index 00000000000..c54b8cc5cee --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/service/TemplatingService.java @@ -0,0 +1,85 @@ +package io.jans.agama.engine.service; + +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.Writer; +import java.nio.file.Paths; +import java.util.Collections; +import java.util.Optional; + +import freemarker.core.OutputFormat; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateExceptionHandler; + +import io.jans.agama.engine.exception.TemplateProcessingException; +import io.jans.agama.model.EngineConfig; +import io.jans.util.Pair; + +import org.slf4j.Logger; + +import static java.nio.charset.StandardCharsets.UTF_8; + +@ApplicationScoped +public class TemplatingService { + + @Inject + private Logger logger; + + @Inject + private EngineConfig econf; + + private Configuration fmConfig; + + public Pair process(String templatePath, Object dataModel, Writer writer, boolean useClassloader) + throws TemplateProcessingException { + + try { + //Get template, inject data, and write output + Template t = useClassloader ? getTemplateFromClassLoader(templatePath) : getTemplate(templatePath); + t.process(Optional.ofNullable(dataModel).orElse(Collections.emptyMap()), writer); + + String mime = Optional.ofNullable(t.getOutputFormat()).map(OutputFormat::getMimeType).orElse(null); + String encoding = Optional.ofNullable(t.getEncoding()).orElse(fmConfig.getDefaultEncoding()); + return new Pair<>(mime, encoding); + } catch (Exception e) { + logger.error(e.getMessage(), e); + throw new TemplateProcessingException(e.getMessage(), e); + } + + } + + private Template getTemplate(String path) throws IOException { + return fmConfig.getTemplate(path); + } + + private Template getTemplateFromClassLoader(String path) throws IOException { + ClassLoader loader = getClass().getClassLoader(); + Reader reader = new InputStreamReader(loader.getResourceAsStream(path), UTF_8); + return new Template(path, reader, fmConfig); + } + + @PostConstruct + private void init() { + + fmConfig = new Configuration(Configuration.VERSION_2_3_31); + fmConfig.setDefaultEncoding(UTF_8.toString()); + fmConfig.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + fmConfig.setLogTemplateExceptions(false); + fmConfig.setWrapUncheckedExceptions(true); + fmConfig.setFallbackOnNullLoopVariable(false); + + try { + fmConfig.setDirectoryForTemplateLoading(Paths.get(econf.getRootDir()).toFile()); + } catch(IOException e) { + logger.error("Error configuring directory for UI templates: {}", e.getMessage()); + throw new RuntimeException(e); + } + + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/service/WebContext.java b/agama/engine/src/main/java/io/jans/agama/engine/service/WebContext.java new file mode 100644 index 00000000000..d8e91f4a525 --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/service/WebContext.java @@ -0,0 +1,57 @@ +package io.jans.agama.engine.service; + +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.servlet.http.HttpServletRequest; + +import java.util.Locale; + +import io.jans.agama.engine.servlet.RestartServlet; + +@RequestScoped +public class WebContext { + + @Inject + private HttpServletRequest request; + + private String contextPath; + private String relativePath; + private Locale locale; + + public String getContextPath() { + return contextPath; + } + + public String getRestartUrl() { + return contextPath + RestartServlet.PATH; + } + + public String getRelativePath() { + return relativePath; + } + + public String getRequestUrl() { + + String queryString = request.getQueryString(); + if (queryString == null) { + queryString = ""; + } else { + queryString = "?" + queryString; + } + return request.getRequestURL().toString() + queryString; + + } + + public Locale getLocale() { + return locale; + } + + @PostConstruct + private void init() { + contextPath = request.getContextPath(); + relativePath = request.getServletPath(); + locale = request.getLocale(); + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/servlet/BaseServlet.java b/agama/engine/src/main/java/io/jans/agama/engine/servlet/BaseServlet.java new file mode 100644 index 00000000000..1be5ab3f0fc --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/servlet/BaseServlet.java @@ -0,0 +1,122 @@ +package io.jans.agama.engine.servlet; + +import io.jans.agama.engine.exception.TemplateProcessingException; +import io.jans.agama.engine.misc.FlowUtils; +import io.jans.agama.engine.page.BasicTemplateModel; +import io.jans.agama.engine.page.Page; +import io.jans.agama.engine.service.TemplatingService; +import io.jans.agama.model.EngineConfig; +import io.jans.util.Pair; + +import jakarta.inject.Inject; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.ws.rs.core.MediaType; +import java.io.IOException; +import java.io.StringWriter; + +import org.slf4j.Logger; + +public abstract class BaseServlet extends HttpServlet { + + @Inject + protected Logger logger; + + @Inject + protected FlowUtils flowUtils; + + @Inject + private TemplatingService templatingService; + + @Inject + protected EngineConfig engineConf; + + @Inject + protected Page page; + + @Inject + protected HttpServletRequest request; + + protected boolean isJsonRequest() { + return MediaType.APPLICATION_JSON.equals(request.getContentType()); + } + + protected void sendNotAvailable(HttpServletResponse response) throws IOException { + response.sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE, + "Flow engine not available. Check if Agama has been enabled in your configuration."); + } + + protected void sendFlowTimeout(HttpServletResponse response, String message) throws IOException { + + String errorPage = engineConf.getInterruptionErrorPage(); + page.setTemplatePath(errorPath(errorPage)); + page.setDataModel(new BasicTemplateModel(message)); + sendPageContents(response); + + } + + protected void sendFlowCrashed(HttpServletResponse response, String error) throws IOException { + + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + + String errorPage = engineConf.getCrashErrorPage(); + page.setTemplatePath(errorPath(errorPage)); + page.setRawDataModel(new BasicTemplateModel(error)); + sendPageContents(response); + + } + + protected void sendPageMismatch(HttpServletResponse response, String message, String flowQname) + throws IOException { + + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + + String errorPage = engineConf.getPageMismatchErrorPage(); + page.setTemplatePath(errorPath(errorPage)); + page.setDataModel(new BasicTemplateModel(message, flowQname)); + sendPageContents(response); + + } + + protected void sendPageContents(HttpServletResponse response) throws IOException { + + try { + processTemplate(response, page.getTemplatePath(), page.getDataModel()); + } catch (TemplateProcessingException e) { + + try { + response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); + String templatePath = errorPath(engineConf.getCrashErrorPage()); + processTemplate(response, templatePath, new BasicTemplateModel(e.getMessage())); + + } catch (TemplateProcessingException e2) { + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e2.getMessage()); + } + } + + } + + private String errorPath(String page) { + return isJsonRequest() ? engineConf.getJsonErrorPage(page) : page; + } + + private void processTemplate(HttpServletResponse response, String path, Object dataModel) + throws TemplateProcessingException, IOException { + + StringWriter sw = new StringWriter(); + Pair contentType = templatingService.process(path, dataModel, sw, false); + + //encoding MUST be set before calling getWriter + response.setCharacterEncoding(contentType.getSecond()); + response.getWriter().write(sw.toString()); + + engineConf.getDefaultResponseHeaders().forEach((h, v) -> response.setHeader(h, v)); + String mediaType = contentType.getFirst(); + if (mediaType != null) { + response.setContentType(mediaType); + } + + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/servlet/ExecutionServlet.java b/agama/engine/src/main/java/io/jans/agama/engine/servlet/ExecutionServlet.java new file mode 100644 index 00000000000..c766e28e36b --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/servlet/ExecutionServlet.java @@ -0,0 +1,226 @@ +package io.jans.agama.engine.servlet; + +import jakarta.inject.Inject; +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.HttpMethod; +import java.io.IOException; +import java.util.stream.Collectors; + +import io.jans.agama.engine.exception.FlowCrashException; +import io.jans.agama.engine.exception.FlowTimeoutException; +import io.jans.agama.engine.model.FlowResult; +import io.jans.agama.engine.model.FlowStatus; +import io.jans.agama.engine.service.FlowService; + +@WebServlet(urlPatterns = { + "*" + ExecutionServlet.URL_SUFFIX, + ExecutionServlet.CALLBACK_PATH +}) +public class ExecutionServlet extends BaseServlet { + + public static final String URL_SUFFIX = ".fls"; + public static final String URL_PREFIX = "/fl/"; + public static final String CALLBACK_PATH = URL_PREFIX + "callback"; + public static final String ABORT_PARAM = "_abort"; + + //TODO: put string in agama resource bundle + private static final String NO_ACTIVE_FLOW = "No flow running currently " + + "or your flow may have already finished/timed out."; + + @Inject + private FlowService flowService; + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + FlowStatus fstatus = flowService.getRunningFlowStatus(); + if (fstatus == null || fstatus.getStartedAt() == FlowStatus.FINISHED) { + sendPageMismatch(response, NO_ACTIVE_FLOW, null); + return; + } + + String qname = fstatus.getQname(); + + if (fstatus.getStartedAt() == FlowStatus.PREPARED) { + logger.info("Attempting to trigger flow {}", qname); + + try { + fstatus = flowService.startFlow(fstatus); + FlowResult result = fstatus.getResult(); + + if (result == null) { + sendRedirect(response, request.getContextPath(), fstatus, true); + } else { + sendFinishPage(response, result); + } + } catch (FlowCrashException e) { + logger.error(e.getMessage(), e); + sendFlowCrashed(response, e.getMessage()); + } + + } else { + String path = request.getServletPath(); + if (processCallback(response, fstatus, path)) return; + + String expectedUrl = getExpectedUrl(fstatus); + + if (path.equals(expectedUrl)) { + page.setTemplatePath(engineConf.getTemplatesPath() + "/" + fstatus.getTemplatePath()); + page.setDataModel(fstatus.getTemplateDataModel()); + sendPageContents(response); + } else { + //This is an attempt to GET a page which is not the current page of this flow + //json-based clients must explicitly pass the content-type in GET requests + sendPageMismatch(response, expectedUrl, qname); + } + } + + } + + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + FlowStatus fstatus = flowService.getRunningFlowStatus(); + if (fstatus == null || fstatus.getStartedAt() == FlowStatus.FINISHED) { + sendPageMismatch(response, NO_ACTIVE_FLOW, null); + return; + } + + String path = request.getServletPath(); + if (processCallback(response, fstatus, path)) return; + + String expectedUrl = getExpectedUrl(fstatus); + if (path.equals(expectedUrl)) { + boolean aborting = request.getParameter(ABORT_PARAM) != null; + + String cancelUrl = aborting ? path.substring(URL_PREFIX.length()) : null; + continueFlow(response, fstatus, false, cancelUrl); + } else { + //This is an attempt to POST to a URL which is not the current page of this flow + sendPageMismatch(response, expectedUrl, fstatus.getQname()); + } + + } + + @Override + public void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + if (!flowUtils.serviceEnabled()) { + sendNotAvailable(response); + return; + } + + String method = request.getMethod(); + String path = request.getServletPath(); + boolean match = path.startsWith(URL_PREFIX); + + if (match) { + logger.debug("ExecutionServlet {} {}", method, path); + + if (method.equals(HttpMethod.GET)) { + doGet(request, response); + } else if (method.equals(HttpMethod.POST)) { + doPost(request, response); + } else { + response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED); + } + } else { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + logger.debug("Unexpected path {}", path); + } + + } + + private void continueFlow(HttpServletResponse response, FlowStatus fstatus, boolean callbackResume, + String cancelUrl) throws IOException { + + try { + String jsonParams; + if (isJsonRequest()) { + //Obtain from payload + jsonParams = request.getReader().lines().collect(Collectors.joining()); + } else { + jsonParams = flowUtils.toJsonString(request.getParameterMap()); + } + + fstatus = flowService.continueFlow(fstatus, jsonParams, callbackResume, cancelUrl); + FlowResult result = fstatus.getResult(); + + if (result == null) { + sendRedirect(response, request.getContextPath(), fstatus, + request.getMethod().equals(HttpMethod.GET)); + } else { + sendFinishPage(response, result); + } + + } catch (FlowTimeoutException te) { + sendFlowTimeout(response, te.getMessage()); + + } catch (FlowCrashException ce) { + logger.error(ce.getMessage(), ce); + sendFlowCrashed(response, ce.getMessage()); + } + + } + + private boolean processCallback(HttpServletResponse response, FlowStatus fstatus, String path) + throws IOException { + + if (path.equals(CALLBACK_PATH)) { + if (fstatus.isAllowCallbackResume()) { + continueFlow(response, fstatus, true, null); + } else { + String msg = "Unexpected incoming request to flow callback endpoint"; + logger.warn(msg); + sendPageMismatch(response, msg, null); + } + return true; + } + return false; + + } + + private void sendRedirect(HttpServletResponse response, String contextPath, FlowStatus fls, + boolean currentIsGet) throws IOException { + + String newLocation = fls.getExternalRedirectUrl(); + if (newLocation == null) { + // Local redirection + newLocation = contextPath + getExpectedUrl(fls); + } + //See https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections and + //https://stackoverflow.com/questions/4764297/difference-between-http-redirect-codes + if (currentIsGet) { + //This one uses 302 (Found) redirection + response.sendRedirect(newLocation); + } else { + response.setHeader(HttpHeaders.LOCATION, newLocation); + response.setStatus(HttpServletResponse.SC_SEE_OTHER); + } + + } + + private void sendFinishPage(HttpServletResponse response, FlowResult result) throws IOException { + + String fpage = isJsonRequest() ? engineConf.getJsonFinishedFlowPage() : + engineConf.getFinishedFlowPage(); + page.setTemplatePath(fpage); + page.setDataModel(result); + sendPageContents(response); + + } + + private String getExpectedUrl(FlowStatus fls) { + String templPath = fls.getTemplatePath(); + if (templPath == null) return null; + return URL_PREFIX + templPath.substring(0, templPath.lastIndexOf(".")) + URL_SUFFIX; + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/servlet/RestartServlet.java b/agama/engine/src/main/java/io/jans/agama/engine/servlet/RestartServlet.java new file mode 100644 index 00000000000..0313778ea58 --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/servlet/RestartServlet.java @@ -0,0 +1,67 @@ +package io.jans.agama.engine.servlet; + +import jakarta.inject.Inject; +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.ws.rs.core.Response; +import java.io.IOException; + +import io.jans.agama.NativeJansFlowBridge; +import io.jans.agama.engine.exception.FlowTimeoutException; +import io.jans.agama.engine.model.FlowStatus; +import io.jans.agama.engine.service.FlowService; + +@WebServlet(urlPatterns = RestartServlet.PATH) +public class RestartServlet extends BaseServlet { + + public static final String PATH = "/fl/restart"; + + @Inject + private FlowService flowService; + + @Inject + private NativeJansFlowBridge bridge; + + @Override + public void doPost(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + if (!flowUtils.serviceEnabled()) { + sendNotAvailable(response); + return; + } + + logger.debug("Restart servlet"); + try { + FlowStatus st = flowService.getRunningFlowStatus(); + + if (st == null || st.getStartedAt() == FlowStatus.FINISHED) { + //If flow exists, ensure it is not about to be collected by cleaner job + throw new IOException("No flow to restart"); + } else { + + try { + flowService.ensureTimeNotExceeded(st); + flowService.terminateFlow(); + + logger.debug("Sending user's browser for a flow start"); + //This redirection relies on the (unauthenticated) session id being still alive + //(so the flow name and its inputs can be remembered in the bridge script) + //see AgamaBridge.py#prepareForStep + String url = bridge.scriptPageUrl().replaceFirst("\\.xhtml", ".htm"); + response.sendRedirect(request.getContextPath() + "/" + url); + + } catch (FlowTimeoutException e) { + sendFlowTimeout(response, e.getMessage()); + } + + } + } catch (IOException e) { + sendFlowCrashed(response, e.getMessage()); + } + + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/engine/servlet/StatusServlet.java b/agama/engine/src/main/java/io/jans/agama/engine/servlet/StatusServlet.java new file mode 100644 index 00000000000..072f6ee8822 --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/engine/servlet/StatusServlet.java @@ -0,0 +1,26 @@ +package io.jans.agama.engine.servlet; + +import jakarta.inject.Inject; +import jakarta.servlet.ServletException; +import jakarta.servlet.annotation.WebServlet; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import java.io.IOException; + +import org.slf4j.Logger; + +@WebServlet(urlPatterns = "/fl/status") +public class StatusServlet extends HttpServlet { + + @Inject + private Logger logger; + + @Override + public void doGet(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/timer/FlowRunsCleaner.java b/agama/engine/src/main/java/io/jans/agama/timer/FlowRunsCleaner.java new file mode 100644 index 00000000000..97953491208 --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/timer/FlowRunsCleaner.java @@ -0,0 +1,91 @@ +package io.jans.agama.timer; + +import io.jans.agama.engine.misc.FlowUtils; +import io.jans.agama.engine.model.FlowRun; +import io.jans.orm.PersistenceEntryManager; +import io.jans.orm.search.filter.Filter; +import io.jans.service.cdi.async.Asynchronous; +import io.jans.service.cdi.event.Scheduled; +import io.jans.service.timer.event.TimerEvent; +import io.jans.service.timer.schedule.TimerSchedule; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Event; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; +import java.util.Date; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.slf4j.Logger; + +import static io.jans.agama.engine.service.AgamaPersistenceService.AGAMA_FLOWRUNS_BASE; + +@ApplicationScoped +public class FlowRunsCleaner { + + private static final int DELAY = 120; //seconds + private static final int INTERVAL = 90; //seconds + private static final int GAP = 5000; //milliseconds + private static final int DEL_BATCH_SIZE = 100; + + @Inject + private Logger logger; + + @Inject + private PersistenceEntryManager entryManager; + + @Inject + private Event timerEvent; + + @Inject + private FlowUtils futils; + + private AtomicBoolean isActive; + + public void initTimer() { + + logger.info("Initializing Agama runs cleaner Timer"); + isActive = new AtomicBoolean(false); + timerEvent.fire(new TimerEvent(new TimerSchedule(DELAY, INTERVAL), + new FlowRunsCleanerEvent(), Scheduled.Literal.INSTANCE)); + + } + + @Asynchronous + public void run(@Observes @Scheduled FlowRunsCleanerEvent event) { + + if (!futils.serviceEnabled()) return; + + if (isActive.get()) return; + + if (!isActive.compareAndSet(false, true)) return; + + try { + int count = clean(); + logger.info("Flows cleaner timer has run. {} runs removed", count); + } catch (Exception e) { + logger.error("An error occurred while running flows cleaner timer", e); + } finally { + isActive.set(false); + } + + } + + private int clean() { + + //use a small delay window so flow rus are removed a bit after the expiration has occurred + Date date = new Date(System.currentTimeMillis() - GAP); + int total = 0, removed; + do { + removed = entryManager.remove(AGAMA_FLOWRUNS_BASE, FlowRun.class, + Filter.createLessOrEqualFilter("exp", entryManager.encodeTime(AGAMA_FLOWRUNS_BASE, date)), + DEL_BATCH_SIZE); + + total += removed; + logger.trace("{} entries removed", removed); + } while (removed > 0); + return total; + + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/timer/FlowRunsCleanerEvent.java b/agama/engine/src/main/java/io/jans/agama/timer/FlowRunsCleanerEvent.java new file mode 100644 index 00000000000..a9f0f41f769 --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/timer/FlowRunsCleanerEvent.java @@ -0,0 +1,3 @@ +package io.jans.agama.timer; + +public class FlowRunsCleanerEvent { } diff --git a/agama/engine/src/main/java/io/jans/agama/timer/Transpilation.java b/agama/engine/src/main/java/io/jans/agama/timer/Transpilation.java new file mode 100644 index 00000000000..f3caf0d07da --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/timer/Transpilation.java @@ -0,0 +1,209 @@ +package io.jans.agama.timer; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.jans.agama.dsl.TranspilationResult; +import io.jans.agama.dsl.Transpiler; +import io.jans.agama.dsl.TranspilerException; +import io.jans.agama.dsl.error.SyntaxException; +import io.jans.agama.engine.misc.FlowUtils; +import io.jans.agama.engine.service.AgamaPersistenceService; +import io.jans.agama.model.Flow; +import io.jans.agama.model.Flow.ATTR_NAMES; +import io.jans.agama.model.FlowMetadata; +import io.jans.agama.model.ProtoFlow; +import io.jans.orm.PersistenceEntryManager; +import io.jans.orm.search.filter.Filter; +import io.jans.service.cdi.async.Asynchronous; +import io.jans.service.cdi.event.Scheduled; +import io.jans.service.timer.event.TimerEvent; +import io.jans.service.timer.schedule.TimerSchedule; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.event.Event; +import jakarta.enterprise.event.Observes; +import jakarta.inject.Inject; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.slf4j.Logger; + +@ApplicationScoped +public class Transpilation { + + private static final int DELAY = 10 + (int) (10 * Math.random()); //seconds + private static final int INTERVAL = 30; // seconds + private static final double PR = 0.25; + + @Inject + private PersistenceEntryManager entryManager; + + @Inject + private Logger logger; + + @Inject + private Event timerEvent; + + @Inject + private ObjectMapper mapper; + + @Inject + private FlowUtils futils; + + private AtomicBoolean isActive; + + private Map traces; + + public void initTimer() { + + logger.info("Initializing Agama transpilation Timer"); + isActive = new AtomicBoolean(false); + timerEvent.fire(new TimerEvent(new TimerSchedule(DELAY, INTERVAL), + new TranspilationEvent(), Scheduled.Literal.INSTANCE)); + + } + + @Asynchronous + public void run(@Observes @Scheduled TranspilationEvent event) { + + if (!futils.serviceEnabled()) return; + + if (isActive.get()) return; + + if (!isActive.compareAndSet(false, true)) return; + + try { + process(); + logger.debug("Transpilation timer has run."); + } catch (Exception e) { + logger.error("An error occurred while running transpilation timer", e); + } finally { + isActive.set(false); + } + + } + + /** + * This method assumes that when a flow is created (eg. via an administrative tool), + * attribute revision is set to a negative value + * @throws IOException + */ + public void process() throws IOException { + + List flows = entryManager.findEntries(AgamaPersistenceService.AGAMA_FLOWS_BASE, + ProtoFlow.class, null); + + Map map = flows.stream().collect( + Collectors.toMap(ProtoFlow::getQname, Function.identity())); + + if (traces == null) { + traces = map.entrySet().stream().collect(Collectors.toMap( + Map.Entry::getKey, e -> e.getValue().getRevision())); + //make it modifiable + traces = new HashMap<>(traces); + } else { + //remove flows that were disabled/removed wrt the previous timer run + traces.keySet().retainAll(map.keySet()); + } + + List candidates = new ArrayList<>(); + for (String name : map.keySet()) { + + ProtoFlow pfl = map.get(name); + Integer rev = pfl.getRevision(); + + if (rev != null) { + if (!traces.containsKey(name)) { + //A newcomer. This script was enabled recently + candidates.add(name); + traces.put(name, rev); + } else if ( + //there might be a compilation of this script running already. + //If the node in charge of this crashed before completion, the random + //condition helps to get the job done by another node in the near future + (pfl.getTransHash() == null && Math.random() < PR) || + (rev < 0 || rev > traces.get(name))) { + candidates.add(name); + } + } + } + + int s = candidates.size(); + if (s > 0) { + //pick only one. This is helpful in a multinode environment so not all nodes try + //to work on the same script. However, in practice s will rarelly be greater than 2 + String qname = candidates.get((int)(s * Math.random())); + logger.info("Starting transpilation of flow '{}'", qname); + + ProtoFlow pfl = map.get(qname); + pfl.setTransHash(null); //This helps prevent several nodes transpiling the same flow code + if (pfl.getRevision() < 0) { + pfl.setRevision(0); + } + + logger.debug("Marking the script is under compilation"); + entryManager.merge(pfl); + traces.put(qname, pfl.getRevision()); + + //This time retrieve all attributes for the flow of interest + Flow fl = entryManager.findEntries(AgamaPersistenceService.AGAMA_FLOWS_BASE, + Flow.class, Filter.createEqualityFilter(ATTR_NAMES.QNAME, qname), null, 1).get(0); + + String error = null, shortError = null; + try { + TranspilationResult result = Transpiler.transpile(qname, map.keySet(), fl.getSource()); + logger.debug("Successful transpilation"); + + FlowMetadata meta = fl.getMetadata(); + meta.setFuncName(result.getFuncName()); + meta.setInputs(result.getInputs()); + meta.setTimeout(result.getTimeout()); + + String compiled = result.getCode(); + fl.setMetadata(meta); + fl.setTranspiled(compiled); + fl.setTransHash(futils.hash(compiled)); + fl.setCodeError(null); + + logger.debug("Persisting changes..."); + entryManager.merge(fl); + + } catch (SyntaxException se) { + try { + error = mapper.writeValueAsString(se); + shortError = se.getMessage(); + } catch(JsonProcessingException je) { + error = je.getMessage(); + } + } catch (TranspilerException te) { + error = te.getMessage(); + if (te.getCause() != null) { + error += "\n" + te.getCause().getMessage(); + } + } + + if (error != null) { + + logger.error("Transpilation failed!"); + if (shortError != null) { + logger.error(shortError); + } + + fl.setCodeError(error); + logger.debug("Persisting error details..."); + + entryManager.merge(fl); + logger.warn("Check database for errors"); + } + } + + } + +} diff --git a/agama/engine/src/main/java/io/jans/agama/timer/TranspilationEvent.java b/agama/engine/src/main/java/io/jans/agama/timer/TranspilationEvent.java new file mode 100644 index 00000000000..793105bb77d --- /dev/null +++ b/agama/engine/src/main/java/io/jans/agama/timer/TranspilationEvent.java @@ -0,0 +1,3 @@ +package io.jans.agama.timer; + +public class TranspilationEvent { } diff --git a/agama/engine/src/main/resources/META-INF/beans.xml b/agama/engine/src/main/resources/META-INF/beans.xml new file mode 100644 index 00000000000..d87ce4e5eac --- /dev/null +++ b/agama/engine/src/main/resources/META-INF/beans.xml @@ -0,0 +1,7 @@ + + + + diff --git a/agama/engine/src/test/java/io/jans/agama/test/BaseTest.java b/agama/engine/src/test/java/io/jans/agama/test/BaseTest.java new file mode 100644 index 00000000000..cbfee164568 --- /dev/null +++ b/agama/engine/src/test/java/io/jans/agama/test/BaseTest.java @@ -0,0 +1,198 @@ +package io.jans.agama.test; + +import com.gargoylesoftware.htmlunit.BrowserVersion; +import com.gargoylesoftware.htmlunit.Page; +import com.gargoylesoftware.htmlunit.WebClient; +import com.gargoylesoftware.htmlunit.WebClientOptions; +import com.gargoylesoftware.htmlunit.WebResponse; +import com.gargoylesoftware.htmlunit.html.DomElement; +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlPage; + +import io.jans.inbound.oauth2.CodeGrantUtil; +import io.jans.inbound.oauth2.OAuthParams; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.net.URISyntaxException; +import java.net.URLEncoder; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.json.JSONObject; +import org.testng.ITestContext; +import org.testng.annotations.BeforeSuite; + +import static java.nio.charset.StandardCharsets.UTF_8; + +//import org.testng.annotations.Test; +import static org.testng.Assert.*; + +public class BaseTest { + + private static String AGAMA_ACR = "agama"; + private Map map = null; + + Logger logger = LogManager.getLogger(getClass()); + WebClient client = null; + + BaseTest() { + + client = new WebClient(BrowserVersion.CHROME); + WebClientOptions options = client.getOptions(); + + options.setThrowExceptionOnFailingStatusCode(false); + //skip downloading of external resources to avoid time waste + options.setCssEnabled(false); + options.setDownloadImages(false); + //prevent the finish page to autosubmit the POST to AS's postlogin endpoint + options.setJavaScriptEnabled(false); + + } + + @BeforeSuite + public void init(ITestContext context) throws IOException { + + String propertiesFile = context.getCurrentXmlTest().getParameter("propertiesFile"); + Properties prop = new Properties(); + prop.load(Files.newBufferedReader(Paths.get(propertiesFile), UTF_8)); + + map = new Hashtable<>(); + //do not bother about empty keys... but + //If a value is found null, this will throw a NPE since we are using a Hashtable + prop.forEach((key, value) -> map.put(key.toString(), value.toString())); + context.getSuite().getXmlSuite().setParameters(map); + + } + + String authzRequestUrl(String flowQName, Map inputs) { + + OAuthParams p = new OAuthParams(); + p.setAuthzEndpoint(map.get("authzEndpoint")); + p.setClientId(map.get("clientId")); + p.setRedirectUri(map.get("redirectUri")); + p.setScopes(Collections.singletonList("openid")); + + String queryParam = URLEncoder.encode(map.get("custParamName"), UTF_8); + + StringBuilder builder = new StringBuilder(flowQName); + if (inputs != null) { + JSONObject jo = new JSONObject(inputs); + builder.append("-").append(jo.toString()); + } + + Map custParams = new HashMap<>(); + custParams.put("acr_values", AGAMA_ACR); + custParams.put(queryParam, builder.toString()); + p.setCustParamsAuthReq(custParams); + + String url = null; + CodeGrantUtil grant = new CodeGrantUtil(p); + + try { + url = grant.makeAuthzRequest().getFirst(); + logger.debug("Authentication request built is: {}", url); + } catch (URISyntaxException e) { + fail(e.getMessage(), e); + } + return url; + + } + + HtmlPage launch(String flowQName, Map parameters) { + + //Generate an authn request and launch it in the htmlUnit browser + String url = authzRequestUrl(flowQName, parameters); + logger.info("Starting flow {}", flowQName); + try { + Page p = client.getPage(url); + + //Check it is an ok web page + assertTrue(p.isHtmlPage(), "Not an html page"); + assertOK(p); + return (HtmlPage) p; + + } catch (IOException e) { + fail(e.getMessage(), e); + return null; + } + + } + + void validateFinishPage(HtmlPage page, boolean success) { + + assertOK(page); + //check we are effectively at the finish page + String title = page.getTitleText().toLowerCase(); + + if (success) { + assertTextContained(title, true, "redirect"); + + List forms = page.getForms(); + assertEquals(forms.size(), 1, "Page should have one and only one form"); + + HtmlForm form = forms.get(0); + assertTrue(form.getActionAttribute().contains("postlogin"), "Form does not have the expected action attribute"); + assertEquals(form.getMethodAttribute().toLowerCase(), "post", "Form does not use POST"); + + } else { + assertTextContained(title, true, "error"); + assertTextContained(page.getVisibleText().toLowerCase(), true, "authentication", "failed"); + } + + } + + void assertOK(Page page) { + assertEquals(page.getWebResponse().getStatusCode(), WebResponse.OK); + } + + void assertServerError(Page page) { + assertEquals(page.getWebResponse().getStatusCode(), WebResponse.INTERNAL_SERVER_ERROR); + } + + void assertTextContained(String text, String ...words) { + assertTextContained(text, false, words); + } + + void assertTextContained(String text, boolean includeMessageInAssert, String ...words) { + + List list = Arrays.asList(words); + if (includeMessageInAssert) { + list.forEach(w -> assertTrue(text.contains(w), String.format("'%s' word not found in page text", w))); + } else { + list.forEach(w -> assertTrue(text.contains(w))); + } + + } + +

P doClick(DomElement el) { + + try { + return el.click(); + } catch (IOException e) { + fail(e.getMessage(), e); + return null; + } + + } + + void typeInInputWithName(HtmlForm form, String name, String text) { + + try { + //See f1/index.ftl + form.getInputByName("something").type(text); + } catch (IOException e) { + fail(e.getMessage(), e); + } + + } + +} \ No newline at end of file diff --git a/agama/engine/src/test/java/io/jans/agama/test/CustomConfigsFlowTest.java b/agama/engine/src/test/java/io/jans/agama/test/CustomConfigsFlowTest.java new file mode 100644 index 00000000000..83ce021096a --- /dev/null +++ b/agama/engine/src/test/java/io/jans/agama/test/CustomConfigsFlowTest.java @@ -0,0 +1,61 @@ +package io.jans.agama.test; + +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput; +import com.gargoylesoftware.htmlunit.WebResponse; + +import org.testng.annotations.Parameters; +import org.testng.annotations.Test; + +import static org.testng.Assert.*; + +public class CustomConfigsFlowTest extends BaseTest { + + private static final String QNAME = "io.jans.agama.test.showConfig"; + + @Test + public void withTimeout() { + + //Waiting 10 seconds is enough due to time skew (see FlowService#ensureTimeNotExceeded) + HtmlPage page = launchAndWait(10); + //Flow should have timed out now - see flow impl + //The page currently shown may correspond to the Agama timeout template or to + //the mismatch (not found) page in case the cleaner job already wiped the flow execution + + int status = page.getWebResponse().getStatusCode(); + String text = page.getVisibleText().toLowerCase(); + + if (status == WebResponse.OK) { + //See timeout.ftlh + assertTextContained(text, "took", "more", "expected"); + } else if (status == WebResponse.NOT_FOUND) { + //See mismatch.ftlh + assertTextContained(text, "not", "found"); + } else { + fail("Unexpected status code " + status); + } + + } + + @Test + public void noTimeout() { + HtmlPage page = launchAndWait(2); + validateFinishPage(page, false); + } + + private HtmlPage launchAndWait(int wait) { + + HtmlPage page = launch(QNAME, null); + try { + Thread.sleep(1000L * wait); + } catch (InterruptedException e) { + fail(e.getMessage(), e); + } + + //click on the "Continue" button + HtmlSubmitInput button = page.getForms().get(0).getInputByValue("Continue"); + return doClick(button); + + } + +} \ No newline at end of file diff --git a/agama/engine/src/test/java/io/jans/agama/test/InexistentFlowTest.java b/agama/engine/src/test/java/io/jans/agama/test/InexistentFlowTest.java new file mode 100644 index 00000000000..a5d04dfb05c --- /dev/null +++ b/agama/engine/src/test/java/io/jans/agama/test/InexistentFlowTest.java @@ -0,0 +1,17 @@ +package io.jans.agama.test; + +import com.gargoylesoftware.htmlunit.html.HtmlPage; + +import org.testng.annotations.Test; + +import static org.testng.Assert.*; + +public class InexistentFlowTest extends BaseTest { + + @Test + public void test() { + HtmlPage page = launch("flow" + Math.random(), null); + assertFalse(page.getUrl().toString().endsWith(".fls")); + } + +} diff --git a/agama/engine/src/test/java/io/jans/agama/test/MathFlowTest.java b/agama/engine/src/test/java/io/jans/agama/test/MathFlowTest.java new file mode 100644 index 00000000000..7bc90353edf --- /dev/null +++ b/agama/engine/src/test/java/io/jans/agama/test/MathFlowTest.java @@ -0,0 +1,61 @@ +package io.jans.agama.test; + +import com.gargoylesoftware.htmlunit.html.HtmlPage; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.testng.annotations.Test; + +import static org.testng.Assert.*; + +public class MathFlowTest extends BaseTest { + + private static final String QNAME = "io.jans.agama.test.math"; + + @Test + public void runEmpty() { + HtmlPage page = launch(QNAME, Collections.singletonMap("numbers",Collections.emptyList())); + logger.info("Landed at {}", page.getUrl()); + assertServerError(page); + } + + @Test + public void runRandom() { + + int len = (int) (Math.random() * 10); + List list = new ArrayList<>(); + + for (int i = 0; i < len; i++) { + list.add(1 + (int) (Math.random() * 100)); + } + run(list); + + } + + @Test + public void runFixed1() { + run(Arrays.asList(30, 42, 70, 105)); + } + + @Test + public void runFixed2() { + run(Arrays.asList(6, 12, 22, 27)); + } + + @Test + public void runFixed3() { + run(Arrays.asList(1, 1, 1)); + } + + private void run(List list) { + + HtmlPage page = launch(QNAME, Collections.singletonMap("numbers", list)); + logger.info("Landed at {}", page.getUrl()); + validateFinishPage(page, true); + + } + +} diff --git a/agama/engine/src/test/java/io/jans/agama/test/SaySomething2FlowTest.java b/agama/engine/src/test/java/io/jans/agama/test/SaySomething2FlowTest.java new file mode 100644 index 00000000000..ca163472546 --- /dev/null +++ b/agama/engine/src/test/java/io/jans/agama/test/SaySomething2FlowTest.java @@ -0,0 +1,56 @@ +package io.jans.agama.test; + +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.testng.annotations.Parameters; +import org.testng.annotations.Test; + +import static org.testng.Assert.*; + +public class SaySomething2FlowTest extends BaseTest { + + private static final String QNAME = "org.gluu.flow1"; + + @Test + public void something() { + HtmlPage page = run("Rosamaria Montibeller"); + validateFinishPage(page, true); + } + + @Test + public void nothing() { + HtmlPage page = run(null); + validateFinishPage(page, false); + } + + private HtmlPage run(String text) { + + HtmlPage page = launch(QNAME, null); + + //click on the "Continue" button + HtmlSubmitInput button = page.getForms().get(0).getInputByValue("Continue"); + page = doClick(button); + + assertOK(page); + assertTextContained(page.getVisibleText(), "Gluu"); //see f1/*.ftl + + HtmlForm form = page.getForms().get(0); + if (text != null) { + //See f1/index.ftl + typeInInputWithName(form, "something", text); + } + + //click on the "Continue" button + button = form.getInputByValue("Continue"); + return doClick(button); + + } + +} diff --git a/agama/engine/src/test/java/io/jans/agama/test/SaySomething3FlowTest.java b/agama/engine/src/test/java/io/jans/agama/test/SaySomething3FlowTest.java new file mode 100644 index 00000000000..997665dd571 --- /dev/null +++ b/agama/engine/src/test/java/io/jans/agama/test/SaySomething3FlowTest.java @@ -0,0 +1,39 @@ +package io.jans.agama.test; + +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.testng.annotations.Parameters; +import org.testng.annotations.Test; + +import static org.testng.Assert.*; + +public class SaySomething3FlowTest extends BaseTest { + + private static final String QNAME = "org.gluu.flow3"; + + @Test + public void test() { + HtmlPage page = launch(QNAME, null); + + //click on the "Go" button + HtmlSubmitInput button = page.getForms().get(0).getInputByValue("Go"); + page = doClick(button); + + assertOK(page); + assertTextContained(page.getVisibleText(), "Agama"); //see me/myindex.ftl and f1/index2.ftl + + button = page.getForms().get(0).getInputByValue("Continue"); + page = doClick(button); + + validateFinishPage(page, false); + + } + +} \ No newline at end of file diff --git a/agama/engine/src/test/java/io/jans/agama/test/SaySomethingFlowTest.java b/agama/engine/src/test/java/io/jans/agama/test/SaySomethingFlowTest.java new file mode 100644 index 00000000000..9a57032b0e7 --- /dev/null +++ b/agama/engine/src/test/java/io/jans/agama/test/SaySomethingFlowTest.java @@ -0,0 +1,58 @@ +package io.jans.agama.test; + +import com.gargoylesoftware.htmlunit.html.HtmlForm; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput; + +import java.util.Arrays; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.testng.annotations.Parameters; +import org.testng.annotations.Test; + +import static org.testng.Assert.*; + +public class SaySomethingFlowTest extends BaseTest { + + private static final String QNAME = "org.gluu.flow2"; + + @Test + public void nothing() { + HtmlPage page = run(null, null); + validateFinishPage(page, false); + } + + @Test + public void NoOnesomething() { + HtmlPage page = run(null, "Jans over Gluu"); + validateFinishPage(page, true); + } + + @Test + public void someOnesomething() { + HtmlPage page = run("jgomer2001", "I like CE"); + validateFinishPage(page, true); + } + + private HtmlPage run(String name, String text) { + + boolean nameEmpty = name == null; + HtmlPage page = launch(QNAME, nameEmpty ? null : Collections.singletonMap("val", name)); + + if (!nameEmpty) assertTextContained(page.getVisibleText(), name); + + HtmlForm form = page.getForms().get(0); + + if (text != null) { + //See f1/index.ftl + typeInInputWithName(form, "something", text); + } + //click on the "Continue" button + HtmlSubmitInput button = form.getInputByValue("Continue"); + return doClick(button); + + } + +} \ No newline at end of file diff --git a/agama/engine/src/test/java/io/jans/agama/test/UidOnlyAuthTest.java b/agama/engine/src/test/java/io/jans/agama/test/UidOnlyAuthTest.java new file mode 100644 index 00000000000..d4d290e5c78 --- /dev/null +++ b/agama/engine/src/test/java/io/jans/agama/test/UidOnlyAuthTest.java @@ -0,0 +1,73 @@ +package io.jans.agama.test; + +import com.gargoylesoftware.htmlunit.Page; +import com.gargoylesoftware.htmlunit.html.HtmlPage; +import com.gargoylesoftware.htmlunit.html.HtmlSubmitInput; + +import java.util.Collections; + +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Parameters; +import org.testng.annotations.Test; + +import static org.testng.Assert.*; + +public class UidOnlyAuthTest extends BaseTest { + + private static final String QNAME = "io.jans.agama.test.auth.uidOnly"; + + @BeforeClass + public void enableJS() { + client.getOptions().setJavaScriptEnabled(true); + } + + @Test + public void randUid() { + + start("" + Math.random()); + HtmlPage page = (HtmlPage) currentPageAfter(2000); + + assertOK(page); + assertTrue(page.getUrl().toString().endsWith("error.htm")); + assertTextContained(page.getVisibleText().toLowerCase(), "failed", "authenticate"); + + } + + @Test(dependsOnMethods = "randUid", alwaysRun = true) + @Parameters("redirectUri") + public void adminUid(String redirectUri) { + + start("admin"); + Page page = currentPageAfter(2000); + + assertOK(page); + assertTrue(page.getUrl().toString().startsWith(redirectUri)); + + } + + private HtmlPage start(String uid) { + + HtmlPage page = launch(QNAME, Collections.singletonMap("uid", uid)); + //click on the "Continue" button + HtmlSubmitInput button = page.getForms().get(0).getInputByValue("Continue"); + return doClick(button); + + } + + private Page currentPageAfter(long wait) { + + try { + //wait for the auto-submitting javascript to execute (see finished.ftlh) and redirections to take place + Thread.sleep(wait); + Page p = client.getCurrentWindow().getEnclosedPage(); + + logger.debug("Landed at {}", p.getUrl()); + return p; + } catch (Exception e) { + fail(e.getMessage(), e); + } + return null; + + } + +} \ No newline at end of file diff --git a/agama/engine/src/test/resources/flows/io.jans.agama.test.auth.uidOnly b/agama/engine/src/test/resources/flows/io.jans.agama.test.auth.uidOnly new file mode 100644 index 00000000000..6397e9d13f3 --- /dev/null +++ b/agama/engine/src/test/resources/flows/io.jans.agama.test.auth.uidOnly @@ -0,0 +1,10 @@ +//This flow is based on the "hello world" flow found in Agama docs (quick-start quide) +Flow io.jans.agama.test.auth.uidOnly + Basepath "hello" + Inputs uid + +in = { name: uid } +RRF "index.ftlh" in + +Log "Done!" +Finish uid diff --git a/agama/engine/src/test/resources/flows/io.jans.agama.test.math b/agama/engine/src/test/resources/flows/io.jans.agama.test.math new file mode 100644 index 00000000000..f9ca23b221f --- /dev/null +++ b/agama/engine/src/test/resources/flows/io.jans.agama.test.math @@ -0,0 +1,52 @@ +//This flow is used to test some Java calls. It does not make use of idiomatic Agama. There is no UI either here +Flow io.jans.agama.test.math + Basepath "" + Inputs numbers //A non-empty list of positive integers + +// 1. Find the smallest +small = Call java.util.Collections#min numbers +Log "min element is" small + + +// 2. Concat them all in a string +strings = [ ] +Iterate over numbers using n + i = strings.length + strings[i] = Call java.lang.Integer#toString n + +cat = Call java.lang.String#join "" strings +Log "concatenation is" cat + + +// 3. Sumation (with Repeat) +s = 0 +Repeat numbers.length times max + i = idx[0] + s = Call java.lang.Math#addExact s numbers[i] + +Log "sumation is" s + + +// 4. Find if they are mutually relatively prime (no integer divides them all) +When numbers.length is 1 or small is 1 + Finish true + +divisor = 1 +small = Call java.lang.Math#decrementExact small + +Repeat small times max + divisor = Call java.lang.Math#incrementExact divisor + + //Try to divide the numbers by 2, 3, ... small+1 + k = Iterate over numbers using n + modulus = Call java.lang.Math#floorMod n divisor + Quit When modulus is 0 + + Quit When k is numbers.length + +When k is numbers.length + Log "% are relative primes" numbers +Otherwise + Log "All numbers can be divided by" divisor + +Finish true diff --git a/agama/engine/src/test/resources/flows/io.jans.agama.test.showConfig b/agama/engine/src/test/resources/flows/io.jans.agama.test.showConfig new file mode 100644 index 00000000000..a0ba0158b2f --- /dev/null +++ b/agama/engine/src/test/resources/flows/io.jans.agama.test.showConfig @@ -0,0 +1,8 @@ +Flow io.jans.agama.test.showConfig + Basepath "" + Timeout 15 seconds + Configs conf + +RRF "custom/printConfigs.ftlh" conf + +Finish false diff --git a/agama/engine/src/test/resources/flows/org.gluu.flow1 b/agama/engine/src/test/resources/flows/org.gluu.flow1 new file mode 100644 index 00000000000..206628a0085 --- /dev/null +++ b/agama/engine/src/test/resources/flows/org.gluu.flow1 @@ -0,0 +1,11 @@ +//This flow appeared originally in the demos of the authentication-trees project +Flow org.gluu.flow1 + Basepath "f1" + +data = RRF "index.ftl" + +data = Trigger org.gluu.flow2 data.secret[0] + +Log "@debug Subflow finished successfully?" data.success + +Finish data diff --git a/agama/engine/src/test/resources/flows/org.gluu.flow2 b/agama/engine/src/test/resources/flows/org.gluu.flow2 new file mode 100644 index 00000000000..e3bac09e4dd --- /dev/null +++ b/agama/engine/src/test/resources/flows/org.gluu.flow2 @@ -0,0 +1,14 @@ +//This flow appeared originally in the demos of the authentication-trees project +Flow org.gluu.flow2 + Basepath "f1" + Inputs val + +x = {value: val} +data = RRF "index2.ftl" x + +When data.something is "" + Log "There was a missing value" + ret = { success: false, error: "You forgot something!" } + Finish ret +Otherwise + Finish true diff --git a/agama/engine/src/test/resources/flows/org.gluu.flow3 b/agama/engine/src/test/resources/flows/org.gluu.flow3 new file mode 100644 index 00000000000..24d7e8e06c9 --- /dev/null +++ b/agama/engine/src/test/resources/flows/org.gluu.flow3 @@ -0,0 +1,7 @@ +Flow org.gluu.flow3 + Basepath "me" + +obj = Trigger org.gluu.flow1 + Override templates "f1/index.ftl" "myindex.ftlh" + +Finish obj diff --git a/agama/engine/src/test/resources/log4j2-test.xml b/agama/engine/src/test/resources/log4j2-test.xml new file mode 100644 index 00000000000..018859bed4c --- /dev/null +++ b/agama/engine/src/test/resources/log4j2-test.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/agama/engine/src/test/resources/templates/custom/printConfigs.ftlh b/agama/engine/src/test/resources/templates/custom/printConfigs.ftlh new file mode 100644 index 00000000000..abcf7512ebf --- /dev/null +++ b/agama/engine/src/test/resources/templates/custom/printConfigs.ftlh @@ -0,0 +1,14 @@ + + + + +<#-- rendering this page crashes if neither joke nor phrase are defined --> +

${joke} +

${phrase} + +

+ +
+ + + diff --git a/agama/engine/src/test/resources/templates/f1/index.ftl b/agama/engine/src/test/resources/templates/f1/index.ftl new file mode 100644 index 00000000000..4ada4213bd3 --- /dev/null +++ b/agama/engine/src/test/resources/templates/f1/index.ftl @@ -0,0 +1,14 @@ +<#ftl output_format="HTML"> + + + +

Hi I'm Flow 1!

+ +
+ + + +
+ + + diff --git a/agama/engine/src/test/resources/templates/f1/index2.ftl b/agama/engine/src/test/resources/templates/f1/index2.ftl new file mode 100644 index 00000000000..3befc0da296 --- /dev/null +++ b/agama/engine/src/test/resources/templates/f1/index2.ftl @@ -0,0 +1,15 @@ +<#ftl output_format="HTML"> + + + +

Hi I'm Flow 2!

+

${value!""}

+ +
+