diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index f33fe6fac22..97049410915 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -26,10 +26,6 @@ updates:
schedule:
interval: daily
- - package-ecosystem: pip
- directory: /demos/jans-tent
- schedule:
- interval: daily
- package-ecosystem: docker
directory: /docker-jans-all-in-one
diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml
index d398da52eca..6a0dfef7cb1 100644
--- a/.github/workflows/build-docker-image.yml
+++ b/.github/workflows/build-docker-image.yml
@@ -57,7 +57,7 @@ jobs:
egress-policy: audit
- name: Install Cosign
- uses: sigstore/cosign-installer@v3.5.0
+ uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
@@ -91,9 +91,9 @@ jobs:
if: steps.build_docker_image.outputs.build || github.event_name == 'tags'
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 python3 -m pip install --upgrade pip || echo "Failed to upgrade pip"
+ sudo pip3 install --ignore-installed setuptools --upgrade
+ sudo pip3 install --ignore-installed -r ./automation/requirements.txt
sudo apt-get update
#- uses: actions/delete-package-versions@v5
@@ -165,19 +165,19 @@ jobs:
fi
# UPDATE BUILD DATES INSIDE THE DOCKERFILE BEFORE BUILDING THE DEV IMAGES TRIGGERED BY JENKINS
- - name: Setup Python 3.7
+ - name: Setup Python 3.10
if: github.event_name == 'workflow_dispatch' && ${{ matrix.docker-images }} != 'loadtesting-jmeter'
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
with:
- python-version: 3.7
+ python-version: "3.10"
- name: Install Python dependencies
if: github.event_name == 'workflow_dispatch' && ${{ matrix.docker-images }} != 'loadtesting-jmeter'
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 python3 -m pip install --upgrade pip || echo "Failed to upgrade pip"
+ sudo pip3 install --ignore-installed setuptools --upgrade
+ sudo pip3 install --ignore-installed -r ./automation/requirements.txt
sudo apt-get update
sudo apt-get install jq
diff --git a/.github/workflows/build-docs.yml b/.github/workflows/build-docs.yml
index b1b771aa930..28e9ce016b9 100644
--- a/.github/workflows/build-docs.yml
+++ b/.github/workflows/build-docs.yml
@@ -209,7 +209,10 @@ jobs:
# END move generated chart from a previous step
# copy search from nightly to all other versions. This is to ensure that the search index is available for all versions
- for folder in v*/; do cp -r nightly/search "$folder"; done
+ for folder in v*/; do
+ cp -r nightly/search "$folder"
+ git add $folder/search && git update-index --refresh
+ done
# END copy search from nightly to all other versions
echo "Replacing release number markers with actual release number"
diff --git a/.github/workflows/build-packages.yml b/.github/workflows/build-packages.yml
index 6d5687ec882..258caa392d2 100644
--- a/.github/workflows/build-packages.yml
+++ b/.github/workflows/build-packages.yml
@@ -5,6 +5,8 @@ on:
tags:
- 'v**'
- 'nightly'
+permissions:
+ contents: read
jobs:
publish_binary_packages:
if: github.repository == 'JanssenProject/jans'
@@ -196,7 +198,7 @@ jobs:
run: |
sudo apt-get update
sudo apt-get install -y python3 build-essential ca-certificates dbus systemd iproute2 gpg python3-pip python3-dev libpq-dev gcc
- python3 -m pip install --upgrade pip
+ python3 -m pip install --upgrade pip || echo "Failed to upgrade pip"
pip3 install shiv wheel setuptools
echo "Building jans-linux-setup package"
sudo chown -R runner:docker /home/runner/work/jans/jans
@@ -356,8 +358,8 @@ jobs:
git_user_signingkey: true
git_commit_gpgsign: true
- - uses: actions/setup-python@v5
- - uses: PyO3/maturin-action@v1
+ - uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
+ - uses: PyO3/maturin-action@ea5bac0f1ccd0ab11c805e2b804bfcb65dac2eab # v1.45.0
with:
working-directory: ${{ github.workspace }}/jans-cedarling/bindings/cedarling_python
command: build
diff --git a/.github/workflows/build-test.yml b/.github/workflows/build-test.yml
index 0142d4bc5b4..845e8702a16 100644
--- a/.github/workflows/build-test.yml
+++ b/.github/workflows/build-test.yml
@@ -40,35 +40,50 @@ on:
concurrency:
group: run-once
cancel-in-progress: false
+permissions:
+ contents: read
jobs:
cleanup:
- if: github.event_name == 'push' && github.event.ref == 'refs/heads/main'
runs-on: ubuntu-20.04
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ permissions:
+ contents: read
+ packages: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1
with:
egress-policy: audit
- name: Get version ID for 0.0.0-nightly
- if: github.event_name == 'push' && github.ref == 'refs/heads/main'
id: get_version_id
run: |
- services=$(gh api -H "Accept: application/vnd.github+json" \
- /orgs/JanssenProject/packages?package_type=maven \
- | jq -r '.[].name')
- for service in "${services}"; do
- version_id=$(gh api -H "Accept: application/vnd.github+json" \
- /orgs/JanssenProject/packages/maven/io.jans.${service}/versions \
- | jq -r '.[] | select(.name == "0.0.0-nightly") | .id')
- echo "version_id=$version_id" >> $GITHUB_ENV
- gh api --method DELETE \
- -H "Accept: application/vnd.github+json" \
- -H "X-GitHub-Api-Version: 2022-11-28" \
- /orgs/JanssenProject/packages/maven/io.jans."${service}"/versions/"${version_id}"
+ page=1
+ services=""
+ while true; do
+ response=$(gh api -H "Accept: application/vnd.github+json" \
+ /orgs/JanssenProject/packages?package_type=maven\&per_page=100\&page=$page)
+ names=$(echo "$response" | jq -r '.[].name')
+ if [ -z "$names" ]; then
+ break
+ fi
+ services="$services $names"
+ page=$((page + 1))
done
-
+
+ services=$(echo "$services" | tr '\n' ' ' | sed 's/ *$//')
+ echo "Services: $services"
+ for service in $services; do
+ echo "Checking $service"
+ version_id=$(gh api -H "Accept: application/vnd.github+json" \
+ /orgs/JanssenProject/packages/maven/"${service}"/versions \
+ | jq -r '.[] | select(.name == "0.0.0-nightly") | .id')
+ echo "version_id=$version_id" >> $GITHUB_ENV
+ gh api --method DELETE \
+ -H "Accept: application/vnd.github+json" \
+ -H "X-GitHub-Api-Version: 2022-11-28" \
+ /orgs/JanssenProject/packages/maven/"${service}"/versions/"${version_id}" || echo "Failed to delete $service"
+ done
prep-matrix:
needs: cleanup
@@ -126,18 +141,18 @@ jobs:
with:
egress-policy: audit
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.inputs.branch }}
- name: Set up Java 17
- uses: actions/setup-java@v4
+ uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
java-version: '17'
distribution: 'adopt'
- name: Set up Maven
- uses: actions/setup-java@v4
+ uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
java-version: '17'
distribution: 'adopt'
@@ -159,7 +174,7 @@ jobs:
- name: Archive results
if: always()
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
with:
name: build-results
path: ${{ matrix.service }}/target
@@ -170,7 +185,9 @@ jobs:
run-tests:
if: github.event_name == 'push' || github.event_name == 'pull_request' || (github.event_name == 'workflow_dispatch' && github.event.inputs.project == 'jans-bom, jans-orm, jans-core, jans-lock/lock-server, agama, jans-auth-server, jans-link, jans-fido2, jans-scim, jans-keycloak-link, jans-config-api, jans-keycloak-integration, jans-casa')
- permissions: write-all
+ permissions:
+ contents: read
+ packages: write
needs: cleanup
runs-on: ubuntu-20.04
env:
@@ -198,18 +215,18 @@ jobs:
with:
egress-policy: audit
- name: Checkout code
- uses: actions/checkout@v4
+ uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
ref: ${{ github.event.inputs.branch }}
- name: Set up Java 17
- uses: actions/setup-java@v4
+ uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
java-version: '17'
distribution: 'adopt'
- name: Set up Maven
- uses: actions/setup-java@v4
+ uses: actions/setup-java@7a6d8a8234af8eb26422e24e3006232cccaa061b # v4.6.0
with:
java-version: '17'
distribution: 'adopt'
@@ -276,13 +293,13 @@ jobs:
ls /tmp/reports/
- name: Upload Test Results
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@6f51ac03b9356f520e9adb1b1b7802705f340c2b # v4.5.0
with:
name: ${{ matrix.persistence }}-test-results
path: /tmp/reports
- name: Publish Test Report ${{ matrix.persistence }}
- uses: starburstdata/action-testng-report@v1
+ uses: starburstdata/action-testng-report@f245422953fb97ec5075d07782a1b596124b7cc4 # v1.0.5
with:
report_paths: /tmp/reports/${{ matrix.persistence }}*.xml
github_token: ${{ github.token }}
diff --git a/.github/workflows/lint-flak8.yml b/.github/workflows/lint-flak8.yml
index 63dadc76b64..e8eff713621 100644
--- a/.github/workflows/lint-flak8.yml
+++ b/.github/workflows/lint-flak8.yml
@@ -4,14 +4,16 @@ on:
branches:
- main
paths:
- #TODO: add all python projects paths below "jans-pycloudlib", "jans-cli-tui", "jans-linux-setup"
- - 'demos/jans-tent/**'
+ - 'jans-pycloudlib/**'
+ - 'jans-cli-tui/**'
+ - 'jans-linux-setup/**'
pull_request:
branches:
- main
paths:
- #TODO: add all python projects paths below "jans-pycloudlib", "jans-cli-tui", "jans-linux-setup"
- - 'demos/jans-tent/**'
+ - 'jans-pycloudlib/**'
+ - 'jans-cli-tui/**'
+ - 'jans-linux-setup/**'
permissions:
contents: read
@@ -23,8 +25,11 @@ jobs:
#max-parallel: 1
fail-fast: false
matrix:
- #TODO: add all python projects paths below "jans-pycloudlib", "jans-cli-tui", "jans-linux-setup"
- python-projects: ["demos/jans-tent"]
+ python-projects: [
+ "jans-pycloudlib",
+ "jans-cli-tui",
+ "jans-linux-setup"
+ ]
steps:
- name: Harden Runner
uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1
diff --git a/.github/workflows/ops-docs.yml b/.github/workflows/ops-docs.yml
index 07c61013b3f..34311ad3e04 100644
--- a/.github/workflows/ops-docs.yml
+++ b/.github/workflows/ops-docs.yml
@@ -71,10 +71,10 @@ jobs:
with:
fetch-depth: 0
- - name: Set up Python 3.7
+ - name: Set up Python 3.10
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
with:
- python-version: 3.7
+ python-version: "3.10"
- name: Auto-merge inhouse doc prs
run: |
diff --git a/.github/workflows/ops-label-pr-issues.yml b/.github/workflows/ops-label-pr-issues.yml
index 73528021229..bf0b1cd5f83 100644
--- a/.github/workflows/ops-label-pr-issues.yml
+++ b/.github/workflows/ops-label-pr-issues.yml
@@ -31,17 +31,17 @@ jobs:
- name: check out code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- - name: Setup Python 3.7
+ - name: Set up Python 3.10
uses: actions/setup-python@0a5c61591373683505ea898e09a3ea4f39ef2b9c # v5.0.0
with:
- python-version: 3.7
+ python-version: "3.10"
- 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 python3 -m pip install --upgrade pip || echo "Failed to upgrade pip"
+ sudo pip3 install --ignore-installed setuptools --upgrade
+ sudo pip3 install --ignore-installed -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
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index ca0b9fcd917..d7df2a159cb 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -10,12 +10,17 @@ jobs:
strategy:
fail-fast: false
steps:
+ - name: Harden Runner
+ uses: step-security/harden-runner@0080882f6c36860b6ba35c610c98ce87d4e2f26f # v2.10.2
+ with:
+ egress-policy: audit
+
- name: Checkout
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
with:
fetch-depth: 0
- - uses: googleapis/release-please-action@v4
+ - uses: googleapis/release-please-action@7987652d64b4581673a76e33ad5e98e3dd56832f # v4.1.3
id: release-please
with:
release-type: simple
diff --git a/.github/workflows/sanitary-github-cache.yml b/.github/workflows/sanitary-github-cache.yml
index b2bfb70f57d..e1dd3fa9676 100644
--- a/.github/workflows/sanitary-github-cache.yml
+++ b/.github/workflows/sanitary-github-cache.yml
@@ -4,7 +4,8 @@ on:
types:
- closed
workflow_dispatch:
-
+permissions:
+ contents: read
jobs:
cleanup:
runs-on: ubuntu-latest
diff --git a/.github/workflows/sanitary-workflow-runs.yml b/.github/workflows/sanitary-workflow-runs.yml
index fd3137becc7..c8115cc62a8 100644
--- a/.github/workflows/sanitary-workflow-runs.yml
+++ b/.github/workflows/sanitary-workflow-runs.yml
@@ -3,6 +3,8 @@ on:
schedule:
- cron: '0 0 */2 * *'
workflow_dispatch:
+permissions:
+ contents: read
jobs:
del_runs:
runs-on: ubuntu-latest
diff --git a/.github/workflows/scan-sonar.yml b/.github/workflows/scan-sonar.yml
index 66284080304..69cac4cfc36 100644
--- a/.github/workflows/scan-sonar.yml
+++ b/.github/workflows/scan-sonar.yml
@@ -55,7 +55,8 @@ on:
- '!**.txt'
workflow_dispatch:
-
+permissions:
+ contents: read
jobs:
sonar-scan:
name: sonar scan
@@ -82,7 +83,9 @@ jobs:
jans-linux-setup
jans-cli-tui
jans-pycloudlib
-
+ permissions:
+ contents: read
+ pull-requests: read
steps:
- name: Harden Runner
uses: step-security/harden-runner@a4aa98b93cab29d9b1101a6143fb8bce00e2eac4 # v2.7.1
diff --git a/.github/workflows/test-cedarling.yml b/.github/workflows/test-cedarling.yml
index 6647eba00c0..896caa6c449 100644
--- a/.github/workflows/test-cedarling.yml
+++ b/.github/workflows/test-cedarling.yml
@@ -19,7 +19,7 @@ jobs:
egress-policy: audit
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- name: Install Rust
- uses: dtolnay/rust-toolchain@stable
+ uses: dtolnay/rust-toolchain@1ff72ee08e3cb84d84adba594e0a297990fc1ed3 # stable
- name: Run Tests
run: |
cd ./jans-cedarling
@@ -45,7 +45,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
- python3 -m pip install --upgrade pip
+ python3 -m pip install --upgrade pip || echo "Failed to upgrade pip"
python3 -m pip install tox
- name: Test with pytest
run: |
diff --git a/.github/workflows/test-jans-pycloudlib.yml b/.github/workflows/test-jans-pycloudlib.yml
index 3603b64f320..b673adb249f 100644
--- a/.github/workflows/test-jans-pycloudlib.yml
+++ b/.github/workflows/test-jans-pycloudlib.yml
@@ -41,7 +41,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
- python3 -m pip install --upgrade pip
+ python3 -m pip install --upgrade pip || echo "Failed to upgrade pip"
python3 -m pip install tox
- name: Test with pytest
run: |
diff --git a/README.md b/README.md
index 44b01972fad..5aca229dbed 100644
--- a/README.md
+++ b/README.md
@@ -59,7 +59,6 @@ commercial distribution of Janssen Project Components called
| **[Jans Lock](jans-lock)** | An enterprise authorization solution featuring the Cedarling, a stateless PDP and the Lock Server which centralizes audit logs and configuration. | ![Incubating](https://img.shields.io/badge/Incubating-%23f79307) |
| **[Jans Tarp](demos/jans-tarp)** | An OpenID Connect RP test website that runs as a browser plugin in Chrome or Firefox. | ![Incubating](https://img.shields.io/badge/Incubating-%23f79307) |
| **[Jans Chip](demos/jans-chip)** | Sample iOS and Android mobile applications that implement the full OAuth and FIDO security stack for app integrity, client constrained access tokens, and user presence. | ![Demo](https://img.shields.io/badge/Demo-%23368af7) |
-| **[Jans Tent](demos/jans-tent)** | A test Relying Party ("RP") built using Python and Flask. Enables you to send different requests by quickly modifying just one configuration file. | ![Demo](https://img.shields.io/badge/Demo-%23368af7) |
## Installation
diff --git a/demos/README.md b/demos/README.md
index d16501ccf25..66b93674826 100644
--- a/demos/README.md
+++ b/demos/README.md
@@ -4,6 +4,10 @@ This folder holds different demos for different applications with janssen author
## [Benchmarking](benchmarking)
Holds a docker load test image packaging for Janssen. This image can load test users to a janssen environment and can execute jmeter tests.
-## [Jans-tent](jans-tent)
-Reliable OpenID client to be used in auth testing.
+## [Janssen Chip](jans-chip)
+- A first party android mobile application that leverages dynamic client registration (DCR), DPoP access tokens.
+- Passkey authentication
+
+## [Janssen Tarp](jans-tarp)
+A Relying Party tool in form of a Browser Extension for convenient testing of authentication flows on a browser.
diff --git a/demos/jans-tent/.flaskenv b/demos/jans-tent/.flaskenv
deleted file mode 100644
index bc1b2cf6e71..00000000000
--- a/demos/jans-tent/.flaskenv
+++ /dev/null
@@ -1,2 +0,0 @@
-#.flaskenv
-FLASK_APP=clientapp
diff --git a/demos/jans-tent/.gitignore b/demos/jans-tent/.gitignore
deleted file mode 100644
index 6b3dc1fcd19..00000000000
--- a/demos/jans-tent/.gitignore
+++ /dev/null
@@ -1,146 +0,0 @@
-#jans-tent-specific
-client_info.json
-*.log.*
-
-# Byte-compiled / optimized / DLL files
-__pycache__/
-*.py[cod]
-*$py.class
-
-# C extensions
-*.so
-
-# Distribution / packaging
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-share/python-wheels/
-*.egg-info/
-.installed.cfg
-*.egg
-MANIFEST
-
-# PyInstaller
-# Usually these files are written by a python script from a template
-# before PyInstaller builds the exe, so as to inject date/other infos into it.
-*.manifest
-*.spec
-
-# Installer logs
-pip-log.txt
-pip-delete-this-directory.txt
-
-# Unit test / coverage reports
-htmlcov/
-.tox/
-.nox/
-.coverage
-.coverage.*
-.cache
-nosetests.xml
-coverage.xml
-*.cover
-*.py,cover
-.hypothesis/
-.pytest_cache/
-cover/
-
-# Translations
-*.mo
-*.pot
-
-# Django stuff:
-*.log
-local_settings.py
-db.sqlite3
-db.sqlite3-journal
-
-# Flask stuff:
-instance/
-.webassets-cache
-
-# Scrapy stuff:
-.scrapy
-
-# Sphinx documentation
-docs/_build/
-
-# PyBuilder
-.pybuilder/
-target/
-
-# Jupyter Notebook
-.ipynb_checkpoints
-
-# IPython
-profile_default/
-ipython_config.py
-
-# pyenv
-# For a library or package, you might want to ignore these files since the code is
-# intended to run in multiple environments; otherwise, check them in:
-# .python-version
-
-# pipenv
-# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
-# However, in case of collaboration, if having platform-specific dependencies or dependencies
-# having no cross-platform support, pipenv may install dependencies that don't work, or not
-# install all needed dependencies.
-#Pipfile.lock
-
-# PEP 582; used by e.g. github.com/David-OConnor/pyflow
-__pypackages__/
-
-# Celery stuff
-celerybeat-schedule
-celerybeat.pid
-
-# SageMath parsed files
-*.sage.py
-
-# Environments
-.env
-.venv
-env/
-venv/
-ENV/
-env.bak/
-venv.bak/
-
-# Spyder project settings
-.spyderproject
-.spyproject
-
-# Rope project settings
-.ropeproject
-
-# mkdocs documentation
-/site
-
-# mypy
-.mypy_cache/
-.dmypy.json
-dmypy.json
-
-# Pyre type checker
-.pyre/
-
-# pytype static type analyzer
-.pytype/
-
-# Cython debug symbols
-cython_debug/
-
-.vscode/
-.scannerwork
-
diff --git a/demos/jans-tent/LICENSE b/demos/jans-tent/LICENSE
deleted file mode 100644
index 6912a5f93c9..00000000000
--- a/demos/jans-tent/LICENSE
+++ /dev/null
@@ -1,201 +0,0 @@
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- APPENDIX: How to apply the Apache License to your work.
-
- To apply the Apache License to your work, attach the following
- boilerplate notice, with the fields enclosed by brackets "[]"
- replaced with your own identifying information. (Don't include
- the brackets!) The text should be enclosed in the appropriate
- comment syntax for the file format. We also recommend that a
- file or class name and description of purpose be included on the
- same "printed page" as the copyright notice for easier
- identification within third-party archives.
-
- Copyright 2023 Christian Eland
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
\ No newline at end of file
diff --git a/demos/jans-tent/README.md b/demos/jans-tent/README.md
deleted file mode 100644
index 3f6c6c1ff87..00000000000
--- a/demos/jans-tent/README.md
+++ /dev/null
@@ -1,144 +0,0 @@
-# Jans Tent
-
-To test an OpenID Provider ("OP"), you need a test Relying Party ("RP"). Jans
-Tent is easy to configure RP which enables you to send different requests by
-quickly modifying one file (`config.py`). It's a Python Flask application,
-so it's easy to hack for other testing requirements.
-
-By default, it uses `localhost` as the `redirect_uri`, so if you run it on your
-laptop, all you need to do is specify the OP hostname to run it. Tent uses
-dynamic client registration to obtain client credentials. But you can also use
-an existing client_id if you like.
-
-## Installation
-
-**Important**: Ensure you have `Python >= 3.11`
-
-**Mac Users**: We recommend using [pyenv - simple python version management](https://github.com/pyenv/pyenv) instead of Os x native python.
-
-1. Navigate to the project root folder `jans/demos/jans-tent`
-2. Create virtual environment
-```bash
-python3 -m venv venv
-````
-3. Activate the virtual virtual environment
-```bash
-source venv/bin/activate
-```
-4. Install dependencies
-```bash
-pip install -r requirements.txt
-```
-
-## Setup
-
-### 1. Edit configuration file `clientapp/config.py` according to your needs:
- * Set `ISSUER`, replace `op_hostname` (required)
- * Set any other desired configuration
-
-### 2. Generate test RP server self signed certs
-
-Generate `key.pem` and `cert.pem` at `jans-tent` project root folder (`jans/demos/jans-tent`). i.e:
-```bash
-openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -sha256 -days 365 -nodes
-```
-
-### 3. Import your OP TLS certificate
-
-(remember to be inside your virtual environment)
-
-Supply the hostname of the ISSUER after the `=`
-
-```bash
-export OP_HOSTNAME=
-```
-
-```bash
-echo | openssl s_client -servername $OP_HOSTNAME -connect $OP_HOSTNAME:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > op_web_cert.cer
-```
-
-```bash
-export CERT_PATH=$(python3 -m certifi)
-```
-
-```bash
-export SSL_CERT_FILE=${CERT_PATH}
-```
-
-```bash
-export REQUESTS_CA_BUNDLE=${CERT_PATH} && mv op_web_cert.cer $CERT_PATH
-```
-
-## Using the server
-
-### Start the server
-
-Please notice that your client will be automatically registered once the server
-starts. If your client was already registered, when you start the server again,
-it won't register. Remember to be inside your virtual environment!
-
-```bash
-python main.py
-```
-
-### Login!
-
-Navigate your browser to `https://localhost:9090` and click the link to start.
-
-## Manual client configuration
-
-In case your OP doesn't support dynamic registration, manually configure your
-client by creating a file caled `client_info.json` in the `jans-tent` folder
-with the following claims:
-
-```json
-{
- "op_metadata_url": "https://op_hostname/.well-known/openid-configuration",
- "client_id": "e4f2c3a9-0797-4c6c-9268-35c5546fb3e9",
- "client_secret": "a3e71cf1-b9b4-44c5-a9e6-4c7b5c660a5d"
-}
-```
-
-## Updating Tent to use a different OP
-
-If you want to test a different OP, do the following:
-
-1. Remove `op_web_cert` from the tent folder, and follow the procedure above
-to download and install a new OP TLS certificate
-2. Remove `client_info.json` from the tent folder
-3. Update the value of `ISSUER` in `./clientapp/config.py`
-4. Run `./register_new_client.py`
-
-## Other Tent endpoints
-
-### Auto-register endpoint
-
-Sending a `POST` request to Jans Tent `/register` endpoint containing a `JSON`
-with the OP/AS url and client url, like this:
-
-```json
-{
- "op_url": "https://OP_HOSTNAME",
- "client_url": "https://localhost:9090",
- "additional_params": {
- "scope": "openid mail profile"
- }
-}
-```
-Please notice that `additional_params` is not required by endpoint.
-
-The response will return the registered client id and client secret
-
-### Auto-config endpoint
-
-Sending a `POST` request to the Tent `/configuration` endpoint, containing the
-client id, client secret, and metadata endpoint will fetch data from OP metadata
-url and override the `config.py` settings during runtime.
-
-```json
-{
- "client_id": "e4f2c3a9-0797-4c6c-9268-35c5546fb3e9",
- "client_secret": "5c9e4775-0f1d-4a56-87c9-a629e1f88b9b",
- "op_metadata_url": "https://OP_HOSTNAME/.well-known/openid-configuration"
-}
-```
diff --git a/demos/jans-tent/behave.ini b/demos/jans-tent/behave.ini
deleted file mode 100644
index cbb1bc67a71..00000000000
--- a/demos/jans-tent/behave.ini
+++ /dev/null
@@ -1,3 +0,0 @@
-[behave]
-stderr_capture=False
-stdout_capture=False
diff --git a/demos/jans-tent/clientapp/__init__.py b/demos/jans-tent/clientapp/__init__.py
deleted file mode 100644
index a7429e815a5..00000000000
--- a/demos/jans-tent/clientapp/__init__.py
+++ /dev/null
@@ -1,251 +0,0 @@
-'''
-Project: Test Auth Client
-Author: Christian Hawk
-
-
-Licensed under the Apache License, Version 2.0 (the 'License');
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an 'AS IS' BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-'''
-import base64
-import urllib
-import json
-import os
-from urllib.parse import urlparse
-from authlib.integrations.flask_client import OAuth
-from flask import (Flask, jsonify, redirect, render_template, request, session,
- url_for)
-from . import config as cfg
-from .helpers.client_handler import ClientHandler
-from .helpers.cgf_checker import register_client_if_no_client_info
-from .utils.logger import setup_logger
-
-setup_logger()
-
-oauth = OAuth()
-
-
-def add_config_from_json():
- with open('client_info.json', 'r') as openfile:
- client_info = json.load(openfile)
- cfg.SERVER_META_URL = client_info['op_metadata_url']
- cfg.CLIENT_ID = client_info['client_id']
- cfg.CLIENT_SECRET = client_info['client_secret']
- cfg.END_SESSION_ENDPOINT = client_info['end_session_endpoint'] # separate later
-
-
-def get_preselected_provider():
- provider_id_string = cfg.PRE_SELECTED_PROVIDER_ID
- provider_object = '{ "provider" : "%s" }' % provider_id_string
- provider_object_bytes = provider_object.encode()
- base64url_bytes = base64.urlsafe_b64encode(provider_object_bytes)
- base64url_value = base64url_bytes.decode()
- # if base64url_value.endswith('='):
- # base64url_value_unpad = base64url_value.replace('=', '')
- # return base64url_value_unpad
- return base64url_value
-
-
-def get_provider_host():
- provider_host_string = cfg.PROVIDER_HOST_STRING
- provider_object = '{ "providerHost" : "%s" }' % provider_host_string
- provider_object_bytes = provider_object.encode()
- base64url_bytes = base64.urlsafe_b64encode(provider_object_bytes)
- base64url_value = base64url_bytes.decode()
- # if base64url_value.endswith('='):
- # base64url_value_unpad = base64url_value.replace('=', '')
- # return base64url_value_unpad
- return base64url_value
-
-
-def ssl_verify(ssl_verify=cfg.SSL_VERIFY):
- if ssl_verify is False:
- os.environ['CURL_CA_BUNDLE'] = ""
-
-
-class BaseClientErrors(Exception):
- status_code = 500
-
-
-def create_app():
- register_client_if_no_client_info()
- add_config_from_json()
- ssl_verify()
-
- app = Flask(__name__)
-
- app.secret_key = b'fasfafpj3rasdaasfglaksdgags331s'
- app.config['OP_CLIENT_ID'] = cfg.CLIENT_ID
- app.config['OP_CLIENT_SECRET'] = cfg.CLIENT_SECRET
- oauth.init_app(app)
- oauth.register(
- 'op',
- server_metadata_url=cfg.SERVER_META_URL,
- client_kwargs={
- 'scope': cfg.SCOPE
- },
- token_endpoint_auth_method=cfg.SERVER_TOKEN_AUTH_METHOD
- )
-
- @app.route('/')
- def index():
- user = session.get('user')
- id_token = session.get('id_token')
- return render_template("home.html", user=user, id_token=id_token)
-
- @app.route('/logout')
- def logout():
- app.logger.info('Called /logout')
- if 'id_token' in session.keys():
- app.logger.info('Cleaning session credentials')
- token_hint = session.get('id_token')
- session.pop('id_token')
- session.pop('user')
- parsed_redirect_uri = urllib.parse.urlparse(cfg.REDIRECT_URIS[0])
- post_logout_redirect_uri = '%s://%s' % (parsed_redirect_uri.scheme, parsed_redirect_uri.netloc)
- return redirect(
- '%s?post_logout_redirect_uri=%s&token_hint=%s' % (
- cfg.END_SESSION_ENDPOINT, post_logout_redirect_uri, token_hint
- )
- )
-
- app.logger.info('Not authorized to logout, redirecting to index')
- return redirect(url_for('index'))
-
- @app.route('/register', methods=['POST'])
- def register():
- app.logger.info('/register called')
- content = request.json
- app.logger.debug('data = %s' % content)
- status = 0
- data = ''
- if content is None:
- status = 400
- # message = 'No json data posted'
- elif 'op_url' and 'redirect_uris' not in content:
- status = 400
- # message = 'Not needed keys found in json'
- else:
- app.logger.info('Trying to register client %s on %s' %
- (content['redirect_uris'], content['op_url']))
- op_url = content['op_url']
- redirect_uris = content['redirect_uris']
-
- op_parsed_url = urlparse(op_url)
- client_parsed_redirect_uri = urlparse(redirect_uris[0])
-
- if op_parsed_url.scheme != 'https' or client_parsed_redirect_uri.scheme != 'https':
- status = 400
-
- elif (((
- op_parsed_url.path != '' or op_parsed_url.query != '') or client_parsed_redirect_uri.path == '') or client_parsed_redirect_uri.query != ''):
- status = 400
-
- else:
- additional_metadata = {}
- if 'additional_params' in content.keys():
- additional_metadata = content['additional_params']
- client_handler = ClientHandler(
- content['op_url'], content['redirect_uris'], additional_metadata
- )
- data = client_handler.get_client_dict()
- status = 200
- return jsonify(data), status
-
- @app.route('/protected-content', methods=['GET'])
- def protected_content():
- app.logger.debug('/protected-content - cookies = %s' % request.cookies)
- app.logger.debug('/protected-content - session = %s' % session)
- if 'user' in session:
- return session['user']
-
- return redirect(url_for('login'))
-
- @app.route('/login')
- def login():
- app.logger.info('/login requested')
- redirect_uri = cfg.REDIRECT_URIS[0]
- app.logger.debug('/login redirect_uri = %s' % redirect_uri)
- # response = oauth.op.authorize_redirect()
- query_args = {
- 'redirect_uri': redirect_uri,
- }
-
- if cfg.ACR_VALUES is not None:
- query_args['acr_values'] = cfg.ACR_VALUES
-
- # used for inbound-saml, uncomment and set config.py to use it
- # if cfg.PRE_SELECTED_PROVIDER is True:
- # query_args[
- # 'preselectedExternalProvider'] = get_preselected_provider()
-
- # used for gluu-passport, , uncomment and set config.py to use it
- # if cfg.PROVIDER_HOST_STRING is not None:
- # query_args["providerHost"] = get_provider_host()
-
- if cfg.ADDITIONAL_PARAMS is not None:
- query_args |= cfg.ADDITIONAL_PARAMS
-
- response = oauth.op.authorize_redirect(**query_args)
-
- app.logger.debug('/login authorize_redirect(redirect_uri) url = %s' %
- (response.location))
-
- return response
-
- @app.route('/oidc_callback')
- @app.route('/callback')
- def callback():
- try:
- if not request.args['code']:
- return {}, 400
-
- app.logger.info('/callback - received %s - %s' %
- (request.method, request.query_string))
- token = oauth.op.authorize_access_token()
- app.logger.debug('/callback - token = %s' % token)
- user = oauth.op.userinfo()
- app.logger.debug('/callback - user = %s' % user)
- session['user'] = user
- session['id_token'] = token['userinfo']
- app.logger.debug('/callback - cookies = %s' % request.cookies)
- app.logger.debug('/callback - session = %s' % session)
-
- return redirect('/')
-
- except Exception as error:
- app.logger.error(str(error))
- return {'error': str(error)}, 400
-
- @app.route("/configuration", methods=["POST"])
- def configuration():
- # Receives client configuration via API
- app.logger.info('/configuration called')
- content = request.json
- app.logger.debug("content = %s" % content)
- if content is not None:
- if 'provider_id' in content:
- cfg.PRE_SELECTED_PROVIDER_ID = content['provider_id']
- cfg.PRE_SELECTED_PROVIDER = True
- app.logger.debug('/configuration: provider_id = %s' %
- content['provider_id'])
-
- return jsonify({"provider_id": content['provider_id']}), 200
-
- if "client_id" in content and "client_secret" in content:
- # Setup client_id and client_secret
- oauth.op.client_id = content['client_id']
- oauth.op.client_secret = content['client_secret']
- return {}, 200
- else:
- return {}, 400
-
- return app
diff --git a/demos/jans-tent/clientapp/config.py b/demos/jans-tent/clientapp/config.py
deleted file mode 100644
index 04bcb8df3b9..00000000000
--- a/demos/jans-tent/clientapp/config.py
+++ /dev/null
@@ -1,36 +0,0 @@
-# REQUIRED
-# Replace op_hostname
-ISSUER = 'https://op_hostname'
-
-# Tent redirect uri
-REDIRECT_URIS = [
- 'https://localhost:9090/oidc_callback'
-]
-
-# OPTIONAL: Use at your own risk
-
-# Token authentication method can be
-# client_secret_basic
-# client_secret_post
-# none
-SERVER_TOKEN_AUTH_METHOD = 'client_secret_post'
-
-# ACR VALUES
-# Examples:
-# ACR_VALUES = "agama"
-# ACR_VALUES = 'simple_password_auth'
-ACR_VALUES = None
-
-# ADDITIONAL PARAMS TO CALL AUTHORIZE ENDPOINT, WITHOUT BASE64 ENCODING. USE DICT {'param': 'value'}
-# ADDITIONAL_PARAMS = {'paramOne': 'valueOne', 'paramTwo': 'valueTwo'}
-ADDITIONAL_PARAMS = None
-
-# SYSTEM SETTINGS
-# use with caution, unsecure requests, for development environments
-SSL_VERIFY = False
-
-# SCOPES
-# Only scope "openid" is required for a pairwise identifier from the OP.
-# OP can provision additional optional scopes as needed.
-# SCOPE = 'openid email profile'
-SCOPE = 'openid'
diff --git a/demos/jans-tent/clientapp/helpers/__init__.py b/demos/jans-tent/clientapp/helpers/__init__.py
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/demos/jans-tent/clientapp/helpers/cgf_checker.py b/demos/jans-tent/clientapp/helpers/cgf_checker.py
deleted file mode 100644
index e5ade597adf..00000000000
--- a/demos/jans-tent/clientapp/helpers/cgf_checker.py
+++ /dev/null
@@ -1,17 +0,0 @@
-from os.path import exists
-import logging
-from clientapp.utils.dcr_from_config import register
-
-logger = logging.getLogger(__name__)
-
-
-def configuration_exists() -> bool:
- return exists('client_info.json')
-
-
-def register_client_if_no_client_info() -> None:
- if configuration_exists() :
- logger.info('Found configuration file client_info.json, skipping auto-register')
- else:
- logger.info('Client configuration not found, trying to auto-register through DCR')
- register()
diff --git a/demos/jans-tent/clientapp/helpers/client_handler.py b/demos/jans-tent/clientapp/helpers/client_handler.py
deleted file mode 100644
index 7e5f8f12e2a..00000000000
--- a/demos/jans-tent/clientapp/helpers/client_handler.py
+++ /dev/null
@@ -1,117 +0,0 @@
-'''
-Project: Test Auth Client
-Author: Christian Hawk
-
-
-Licensed under the Apache License, Version 2.0 (the 'License');
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an 'AS IS' BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-'''
-import logging
-import json
-from httplib2 import RelativeURIError
-from typing import Optional, Dict, Any
-
-from oic.oauth2 import ASConfigurationResponse
-from oic.oic import Client
-from oic.utils.authn.client import CLIENT_AUTHN_METHOD
-from .custom_msg_factory import CustomMessageFactory
-
-
-logger = logging.getLogger(__name__)
-
-
-class ClientHandler:
- __redirect_uris = None
- __client_id = None
- __client_secret = None
- __metadata_url = None
- __op_url = None
- __additional_metadata = None
- __end_session_endpoint = None
- op_data = None
-
- def __init__(self, op_url: str, redirect_uris: list[str], additional_metadata: dict):
- """[initializes]
-
- :param op_url: [url from oidc provider starting with https]
- :type op_url: str
- :param redirect_uris: [url from client starting with https]
- :type redirect_uris: list
- :param additional_metadata: additional client metadata
- :type additional_metadata: dict
- """
- self.__additional_metadata = additional_metadata
- self.clientAdapter = Client(client_authn_method=CLIENT_AUTHN_METHOD, message_factory=CustomMessageFactory)
- self.__op_url = op_url
- self.__redirect_uris = redirect_uris
- self.__metadata_url = '%s/.well-known/openid-configuration' % op_url
- self.op_data = self.discover(op_url)
- self.reg_info = self.register_client(op_data=self.op_data, redirect_uris=redirect_uris)
- self.__end_session_endpoint = self.op_data['end_session_endpoint']
- self.__client_id = self.reg_info['client_id']
- self.__client_secret = self.reg_info['client_secret']
-
- def get_client_dict(self) -> dict:
- r = {
- 'op_metadata_url': self.__metadata_url,
- 'client_id': self.__client_id,
- 'client_secret': self.__client_secret,
- 'end_session_endpoint': self.__end_session_endpoint
- }
-
- return r
-
- def register_client(self, op_data: ASConfigurationResponse = op_data, redirect_uris: Optional[list[str]] = __redirect_uris) -> dict:
- """[register client and returns client information]
-
- :param op_data: [description]
- :type op_data: dict
- :param redirect_uris: [description]
- :type redirect_uris: list[str]
- :return: [client information including client-id and secret]
- :rtype: dict
- """
- logger.debug('called ClientHandler´s register_client method')
- registration_args = {'redirect_uris': redirect_uris,
- 'response_types': ['code'],
- 'grant_types': ['authorization_code'],
- 'application_type': 'web',
- 'client_name': 'Jans Tent',
- 'token_endpoint_auth_method': 'client_secret_post',
- **self.__additional_metadata
- }
- logger.info('calling register with registration_args: %s', json.dumps(registration_args, indent=2))
- reg_info = self.clientAdapter.register(op_data['registration_endpoint'], **registration_args)
- logger.info('register_client - reg_info = %s', json.dumps(reg_info.to_dict(), indent=2))
- return reg_info
-
- def discover(self, op_url: Optional[str] = __op_url) -> ASConfigurationResponse:
- """Discover op information on .well-known/open-id-configuration
- :param op_url: [description], defaults to __op_url
- :type op_url: str, optional
- :return: [data retrieved from OP url]
- :rtype: ASConfigurationResponse
- """
- logger.debug('called discover')
- try:
- op_data = self.clientAdapter.provider_config(op_url)
- return op_data
-
- except json.JSONDecodeError as err:
- logger.error('Error trying to decode JSON: %s' % err)
-
- except RelativeURIError as err:
- logger.error(err)
-
- except Exception as e:
- logging.error('An unexpected ocurred: %s' % e)
-
diff --git a/demos/jans-tent/clientapp/helpers/custom_msg_factory.py b/demos/jans-tent/clientapp/helpers/custom_msg_factory.py
deleted file mode 100644
index 11f0ae09a60..00000000000
--- a/demos/jans-tent/clientapp/helpers/custom_msg_factory.py
+++ /dev/null
@@ -1,59 +0,0 @@
-"""
-Custom message factory required by pyoic to add scope param
-Overrides RegistrationRequest, RegistrationResponse
-and use them to create CustomMessageFactory
-"""
-
-from oic.oic.message import OIDCMessageFactory, RegistrationRequest, RegistrationResponse, MessageTuple, OPTIONAL_LOGICAL
-from oic.oauth2.message import OPTIONAL_LIST_OF_STRINGS, REQUIRED_LIST_OF_STRINGS, SINGLE_OPTIONAL_STRING, SINGLE_OPTIONAL_INT
-
-
-class MyRegistrationRequest(RegistrationRequest):
- c_param = {
- "redirect_uris": REQUIRED_LIST_OF_STRINGS,
- "response_types": OPTIONAL_LIST_OF_STRINGS,
- "grant_types": OPTIONAL_LIST_OF_STRINGS,
- "application_type": SINGLE_OPTIONAL_STRING,
- "contacts": OPTIONAL_LIST_OF_STRINGS,
- "client_name": SINGLE_OPTIONAL_STRING,
- "logo_uri": SINGLE_OPTIONAL_STRING,
- "client_uri": SINGLE_OPTIONAL_STRING,
- "policy_uri": SINGLE_OPTIONAL_STRING,
- "tos_uri": SINGLE_OPTIONAL_STRING,
- "jwks": SINGLE_OPTIONAL_STRING,
- "jwks_uri": SINGLE_OPTIONAL_STRING,
- "sector_identifier_uri": SINGLE_OPTIONAL_STRING,
- "subject_type": SINGLE_OPTIONAL_STRING,
- "id_token_signed_response_alg": SINGLE_OPTIONAL_STRING,
- "id_token_encrypted_response_alg": SINGLE_OPTIONAL_STRING,
- "id_token_encrypted_response_enc": SINGLE_OPTIONAL_STRING,
- "userinfo_signed_response_alg": SINGLE_OPTIONAL_STRING,
- "userinfo_encrypted_response_alg": SINGLE_OPTIONAL_STRING,
- "userinfo_encrypted_response_enc": SINGLE_OPTIONAL_STRING,
- "request_object_signing_alg": SINGLE_OPTIONAL_STRING,
- "request_object_encryption_alg": SINGLE_OPTIONAL_STRING,
- "request_object_encryption_enc": SINGLE_OPTIONAL_STRING,
- "token_endpoint_auth_method": SINGLE_OPTIONAL_STRING,
- "token_endpoint_auth_signing_alg": SINGLE_OPTIONAL_STRING,
- "default_max_age": SINGLE_OPTIONAL_INT,
- "require_auth_time": OPTIONAL_LOGICAL,
- "default_acr_values": OPTIONAL_LIST_OF_STRINGS,
- "initiate_login_uri": SINGLE_OPTIONAL_STRING,
- "request_uris": OPTIONAL_LIST_OF_STRINGS,
- "post_logout_redirect_uris": OPTIONAL_LIST_OF_STRINGS,
- "frontchannel_logout_uri": SINGLE_OPTIONAL_STRING,
- "frontchannel_logout_session_required": OPTIONAL_LOGICAL,
- "backchannel_logout_uri": SINGLE_OPTIONAL_STRING,
- "backchannel_logout_session_required": OPTIONAL_LOGICAL,
- "scope": OPTIONAL_LIST_OF_STRINGS, # added
- }
- c_default = {"application_type": "web", "response_types": ["code"]}
- c_allowed_values = {
- "application_type": ["native", "web"],
- "subject_type": ["public", "pairwise"],
- }
-
-
-class CustomMessageFactory(OIDCMessageFactory):
- registration_endpoint = MessageTuple(MyRegistrationRequest, RegistrationResponse)
-
diff --git a/demos/jans-tent/clientapp/templates/home.html b/demos/jans-tent/clientapp/templates/home.html
deleted file mode 100644
index 021c6fcfaac..00000000000
--- a/demos/jans-tent/clientapp/templates/home.html
+++ /dev/null
@@ -1,21 +0,0 @@
-
-
Index Test
-
- Welcome to the test of your life
-
- {% if user %}
- Userinfo JSON payload
-
- {{ user|tojson }}
-
-
- id_token JSON payload
-
- {{ id_token|tojson }}
-
- logout
- {% else %}
-
- {% endif %}
-
-
\ No newline at end of file
diff --git a/demos/jans-tent/clientapp/utils/__init__.py b/demos/jans-tent/clientapp/utils/__init__.py
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/demos/jans-tent/clientapp/utils/dcr_from_config.py b/demos/jans-tent/clientapp/utils/dcr_from_config.py
deleted file mode 100644
index 7ab19246abf..00000000000
--- a/demos/jans-tent/clientapp/utils/dcr_from_config.py
+++ /dev/null
@@ -1,41 +0,0 @@
-import logging
-import urllib.parse
-
-from clientapp import config as cfg
-from clientapp.helpers.client_handler import ClientHandler
-import json
-from urllib import parse
-
-OP_URL = cfg.ISSUER
-REDIRECT_URIS = cfg.REDIRECT_URIS
-SCOPE = cfg.SCOPE
-parsed_redirect_uri = urllib.parse.urlparse(cfg.REDIRECT_URIS[0])
-POST_LOGOUT_REDIRECT_URI = '%s://%s' % (parsed_redirect_uri.scheme, parsed_redirect_uri.netloc)
-
-
-def setup_logging() -> None:
- logging.getLogger('oic')
- logging.getLogger('urllib3')
- logging.basicConfig(
- level=logging.DEBUG,
- handlers=[logging.StreamHandler(), logging.FileHandler('register_new_client.log')],
- format='[%(asctime)s] %(levelname)s %(name)s in %(module)s : %(message)s')
-
-
-def register() -> None:
- """
- Register client with information from config and write info to client_info.json
- :return: None
- """
- logger = logging.getLogger(__name__)
- scope_as_list = SCOPE.split(" ")
- additional_params = {
- 'scope': scope_as_list,
- 'post_logout_redirect_uris': [POST_LOGOUT_REDIRECT_URI]
- }
- client_handler = ClientHandler(OP_URL, REDIRECT_URIS, additional_params)
- json_client_info = json.dumps(client_handler.get_client_dict(), indent=4)
- with open('client_info.json', 'w') as outfile:
- logger.info('Writing registered client information to client_info.json')
- outfile.write(json_client_info)
-
diff --git a/demos/jans-tent/clientapp/utils/logger.py b/demos/jans-tent/clientapp/utils/logger.py
deleted file mode 100644
index acbcc2ca7bf..00000000000
--- a/demos/jans-tent/clientapp/utils/logger.py
+++ /dev/null
@@ -1,16 +0,0 @@
-import logging
-from logging.handlers import TimedRotatingFileHandler
-
-
-def setup_logger() -> None:
- formatter = logging.Formatter("[%(asctime)s] %(levelname)s %(name)s in %(module)s : %(message)s")
- log_file = "test-client.log"
- file_handler = TimedRotatingFileHandler(log_file, when='midnight')
- console_handler = logging.StreamHandler()
- console_handler.setFormatter(formatter)
- file_handler.setFormatter(formatter)
- logging.getLogger("oic")
- logging.getLogger("oauth")
- logging.getLogger("flask-oidc")
- logging.getLogger("urllib3")
- logging.basicConfig(level=logging.DEBUG, handlers=[file_handler, console_handler])
diff --git a/demos/jans-tent/docs/images/authorize_code_flow.png b/demos/jans-tent/docs/images/authorize_code_flow.png
deleted file mode 100644
index a0c26264989..00000000000
Binary files a/demos/jans-tent/docs/images/authorize_code_flow.png and /dev/null differ
diff --git a/demos/jans-tent/main.py b/demos/jans-tent/main.py
deleted file mode 100644
index ebd89f31fd6..00000000000
--- a/demos/jans-tent/main.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from clientapp import create_app
-
-if __name__ == '__main__':
- app = create_app()
- app.debug = True
- app.run(host='0.0.0.0', ssl_context=('cert.pem', 'key.pem'), port=9090, use_reloader=False)
diff --git a/demos/jans-tent/register_new_client.py b/demos/jans-tent/register_new_client.py
deleted file mode 100644
index 89061c42275..00000000000
--- a/demos/jans-tent/register_new_client.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# executes a new client auto-register from config.py
-import logging
-from clientapp.utils.dcr_from_config import register
-
-# add independent logging for CLI script
-logging.getLogger('oic')
-logging.getLogger('urllib3')
-logging.basicConfig(
- level=logging.DEBUG,
- handlers=[logging.StreamHandler(), logging.FileHandler('register_new_client.log')],
- format='[%(asctime)s] %(levelname)s %(name)s in %(module)s : %(message)s')
-register()
diff --git a/demos/jans-tent/requirements.txt b/demos/jans-tent/requirements.txt
deleted file mode 100644
index f6438fdbaae..00000000000
--- a/demos/jans-tent/requirements.txt
+++ /dev/null
@@ -1,119 +0,0 @@
-appnope==0.1.3
-astroid==2.12.5
-asttokens==2.0.8
-async-generator==1.10
-attrs==22.1.0
-Authlib==1.2.0
-autopep8==1.7.0
-backcall==0.2.0
-bandit==1.7.4
-behave==1.2.6
-certifi==2022.12.7
-cffi==1.15.1
-chardet==5.0.0
-charset-normalizer==2.1.1
-click==8.1.3
-coverage==6.4.4
-cryptography==42.0.0
-decorator==5.1.1
-defusedxml==0.7.1
-dill==0.3.5.1
-dodgy==0.2.1
-EasyProcess==1.1
-executing==1.0.0
-flake8==5.0.4
-Flask==2.2.2
-flask-oidc==1.4.0
-future==0.18.3
-gitdb==4.0.9
-GitPython==3.1.37
-h11==0.13.0
-httplib2==0.21.0
-idna==3.3
-importlib-metadata==4.12.0
-iniconfig==1.1.1
-install==1.3.5
-ipdb==0.13.9
-ipython==8.10.0
-ipython-genutils==0.2.0
-isort==5.10.1
-itsdangerous==2.0.0
-jedi==0.18.1
-Jinja2==3.1.2
-lazy-object-proxy==1.7.1
-Mako==1.2.4
-MarkupSafe==2.1.1
-matplotlib-inline==0.1.6
-mccabe==0.7.0
-more-itertools==8.14.0
-mypy==0.971
-mypy-extensions==0.4.3
-oauth2client==4.1.3
-oic==1.5.0
-outcome==1.2.0
-packaging==21.3
-parse==1.19.0
-parse-type==0.6.0
-parso==0.8.3
-pbr==5.10.0
-pep8==1.7.1
-pep8-naming==0.13.2
-pexpect==4.8.0
-pickleshare==0.7.5
-platformdirs==2.5.2
-pluggy==1.0.0
-poetry-semver==0.1.0
-prompt-toolkit==3.0.31
-prospector==0.12.2
-ptyprocess==0.7.0
-pure-eval==0.2.2
-py==1.11.0
-pyasn1==0.4.8
-pyasn1-modules==0.2.8
-pycodestyle==2.9.1
-pycparser==2.21
-pycryptodomex==3.17
-pydocstyle==6.1.1
-pyflakes==2.5.0
-Pygments==2.13.0
-pyjwkest==1.4.2
-pylama==8.4.1
-pylint==2.15.0
-pylint-celery==0.3
-pylint-common==0.2.5
-pylint-django==2.5.3
-pylint-flask==0.6
-pylint-plugin-utils==0.7
-pyOpenSSL==22.0.0
-pyparsing==3.0.9
-PySocks==1.7.1
-pytest==7.1.3
-python-dotenv==0.21.0
-PyVirtualDisplay==3.0
-PyYAML==6.0
-requests==2.28.1
-requirements-detector==1.0.3
-rsa==4.9
-selenium==4.4.3
-setoptconf==0.3.0
-six==1.16.0
-smmap==5.0.0
-sniffio==1.3.0
-snowballstemmer==2.2.0
-sortedcontainers==2.4.0
-stack-data==0.5.0
-stevedore==4.0.0
-toml==0.10.2
-tomli==2.0.1
-tomlkit==0.11.4
-traitlets==5.3.0
-trio==0.21.0
-trio-websocket==0.9.2
-typed-ast==1.5.4
-typing_extensions==4.3.0
-urllib3==1.26.12
-wcwidth==0.2.5
-Werkzeug==2.2.2
-wrapt==1.14.1
-wsproto==1.2.0
-zipp==3.8.1
diff --git a/demos/jans-tent/tests/behaver/features/environment.py b/demos/jans-tent/tests/behaver/features/environment.py
deleted file mode 100644
index 037be43286f..00000000000
--- a/demos/jans-tent/tests/behaver/features/environment.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from selenium import webdriver
-import os
-from pyvirtualdisplay import Display
-
-display = Display(visible=0, size=(1024, 768))
-
-
-def before_all(context):
- os.environ['CURL_CA_BUNDLE'] = ""
- display.start()
-
-
-def before_scenario(context, scenario):
- options = webdriver.FirefoxOptions()
- options.headless = True
- context.web = webdriver.Firefox()
-
- # context.web = webdriver.Firefox()
-
-
-def after_scenario(context, scenario):
- context.web.delete_all_cookies()
- context.web.close()
-
-
-def after_step(context, step):
- print()
-
-
-def after_all(context):
- pass
diff --git a/demos/jans-tent/tests/behaver/features/oidc_auth.feature b/demos/jans-tent/tests/behaver/features/oidc_auth.feature
deleted file mode 100644
index b95f5df3e61..00000000000
--- a/demos/jans-tent/tests/behaver/features/oidc_auth.feature
+++ /dev/null
@@ -1,40 +0,0 @@
-Feature: Allow authenticated users to access protected pages
-
- @authenticated
- Scenario: User is authenticated
- Given username is "johndoe"
- And user is authenticated
- And protected content link is https://chris.testingenv.org/protected-content
- When user clicks the protected content link
- Then user access the protected content link
-
- Scenario: User does not exist
- Given user does not exist
- And protected content link is https://chris.testingenv.org/protected-content
- When user clicks the protected content link
- Then user goes to external login page
-
- Scenario: User is not authenticated
- Given username is "johndoe"
- And protected content link is https://chris.testingenv.org/protected-content
- When user clicks the protected content link
- Then user goes to external login page
-
- # Scenario: Normal user try to access admin content
- # Given username is "johndoe"
- # And user role is "user"
- # And protected content link is https://chris.testingenv.org/admin/admin-protected-content
- # When user clicks the protected content link
- # Then user gets a 403 error
-
- # Scenario: Admin can access admin contents
- # Given username is "johndoe"
- # And user role is "admin"
- # And protected content link is https://chris.testingenv.org/admin/admin-protected-content
- # When user clicks the protected content link
- # Then user access the protected content link
-
-
-
-
-
diff --git a/demos/jans-tent/tests/behaver/features/passport_social_auth.feature b/demos/jans-tent/tests/behaver/features/passport_social_auth.feature
deleted file mode 100644
index 685576147f2..00000000000
--- a/demos/jans-tent/tests/behaver/features/passport_social_auth.feature
+++ /dev/null
@@ -1,26 +0,0 @@
-Feature: use passport social github to login
- """
- As an user,
- I want to use passport-social flow to authenticate
- So I can access protected-content
- """
-
- Background:
- Given auth method is passport-social
- And user is visiting "/"
-
- Scenario: User is authenticated
- Given username is "johndoe"
- And protected content link is https://localhost:5000/content/protected-user-content
- When user clicks the protected content link
- Then user access the protected content link
-
- Scenario: User is not authenticated
- Given user is not authenticated
- When user clicks the protected content link
- Then user goes to external login page
-
-
-
-
-
\ No newline at end of file
diff --git a/demos/jans-tent/tests/behaver/features/steps/allow.py b/demos/jans-tent/tests/behaver/features/steps/allow.py
deleted file mode 100644
index 97a6ddfdb4a..00000000000
--- a/demos/jans-tent/tests/behaver/features/steps/allow.py
+++ /dev/null
@@ -1,116 +0,0 @@
-from behave import when, then, given
-import requests
-import time
-from selenium.webdriver.common.by import By
-
-base_url = "https://chris.testingenv.org"
-
-
-def cookiesTransformer(sel_session_id, sel_other_cookies):
- ''' This transform cookies from selenium to requests '''
- s = requests.Session()
- s.cookies.set('session_id', sel_session_id)
- i = 0
- while i < len(sel_other_cookies):
- s.cookies.set(sel_other_cookies[i]['name'],
- sel_other_cookies[i]['value'],
- path=sel_other_cookies[i]['path'],
- domain=sel_other_cookies[i]['domain'],
- secure=sel_other_cookies[i]['secure'],
- rest={'httpOnly': sel_other_cookies[i]['httpOnly']})
- i = i + 1
-
- return s
-
-
-@given(u'username is "{username}"')
-def define_username(context, username):
- context.username = username
- context.password = "test123"
-
-
-@given(u'user is authenticated')
-def user_authenticates(context):
- context.web.get("https://chris.testingenv.org/login")
- time.sleep(3)
- context.web.set_window_size(625, 638)
- context.web.find_element(By.ID, "username").click()
- context.web.find_element(By.ID, "username").send_keys("johndoo")
- time.sleep(3)
- context.web.find_element(By.ID, "password").send_keys("test123")
- context.web.find_element(By.ID, "loginButton").click()
- time.sleep(3)
-
-
-@given(u'protected content link is {protected_content}')
-def define_protected_content_link(context, protected_content):
- context.protected_content = protected_content
-
-
-@when(u'user clicks the protected content link')
-def user_clicks_protected_content_link(context):
-
- context.web.get(base_url)
- time.sleep(2)
- context.web.find_element_by_xpath(
- '//a[@href="' + "https://chris.testingenv.org/protected-content" +
- '"]').click()
- context.has_clicked = True
- context.response = requests.get(context.protected_content)
-
-
-@then(u'user access the protected content link')
-def user_access_protected_content_link(context):
- # WE FETCH THE COOKIES FROM SELENIUM AND PASS THEM TO REQUESTS TO VALIDATE
- #sel_cookies = context.web.get_cookies()
- #sel_cookie = sel_cookies[0]
- # set cookie in requests
-
- # get session id from selenium
- #sel_session_id = context.web.session_id
- '''
- sess = requests.Session()
-
- sess.cookies.set('session_id',sel_session_id)
- sess.cookies.set(
- sel_cookie['name'],
- sel_cookie['value'],
- path = sel_cookie['path'],
- domain = sel_cookie['domain'],
- secure = sel_cookie['secure'],
- rest= {'httpOnly' : sel_cookie['httpOnly']}
- )
-
- new_sess = cookiesTransformer(sel_session_id,sel_cookies)
- '''
- new_sess = cookiesTransformer(context.web.session_id,
- context.web.get_cookies())
- res = new_sess.get(context.protected_content, verify=False)
-
- assert res.url == context.protected_content
-
-
-@given(u'user does not exist')
-def user_does_not_exist(context):
- pass
-
-
-@then(u'user goes to external login page')
-def user_directed_to_external_login_page(context):
- #context.web.get("https://chris.testingenv.org/login")
-
- time.sleep(1)
- external_login_url = 'https://chris.gluutwo.org/oxauth/login.htm'
- #import ipdb; ipdb.set_trace()
- assert (context.web.current_url == external_login_url)
- #new_sess = cookiesTransformer(context.web.session_id,context.web.get_cookies())
-
-
-@given(u'user role is "{role}"')
-def define_user_role(context, role):
- context.role = role
-
-
-@then(u'user gets a 403 error')
-def step_impl(context):
- raise NotImplementedError(u'STEP: Then user gets a 403 error')
diff --git a/demos/jans-tent/tests/unit_integration/helper.py b/demos/jans-tent/tests/unit_integration/helper.py
deleted file mode 100644
index b282f4b64ab..00000000000
--- a/demos/jans-tent/tests/unit_integration/helper.py
+++ /dev/null
@@ -1,189 +0,0 @@
-
-from unittest import TestCase
-from unittest.mock import MagicMock
-import clientapp
-from clientapp import create_app
-from clientapp.helpers.client_handler import ClientHandler
-from flask import Flask
-from typing import List
-import helper
-import os
-import builtins
-
-
-class FlaskBaseTestCase(TestCase):
- def setUp(self):
- self.stashed_add_config_from_json = clientapp.add_config_from_json
- clientapp.cfg.CLIENT_ID = 'any-client-id-stub'
- clientapp.cfg.CLIENT_SECRET = 'any-client-secret-stub'
- clientapp.cfg.SERVER_META_URL = 'https://ophostname.com/server/meta/url'
- clientapp.cfg.END_SESSION_ENDPOINT = 'https://ophostname.com/end_session_endpoint'
- clientapp.add_config_from_json = MagicMock(name='add_config_from_json')
- clientapp.add_config_from_json.return_value(None)
- self.stashed_discover = ClientHandler.discover
- self.stashed_register_client = ClientHandler.register_client
- self.stashed_open = builtins.open
- builtins.open = MagicMock(name='open')
- ClientHandler.discover = MagicMock(name='discover')
- ClientHandler.discover.return_value = helper.OP_DATA_DICT_RESPONSE
- ClientHandler.register_client = MagicMock(name='register_client')
- ClientHandler.register_client.return_value = helper.REGISTER_CLIENT_RESPONSE
- self.app = create_app()
- self.app.testing = True
- self.app_context = self.app.test_request_context(
- base_url="https://chris.testingenv.org")
- self.app_context.push()
- self.client = self.app.test_client()
-
- #self.oauth = OAuth(self.app)
- os.environ['AUTHLIB_INSECURE_TRANSPORT'] = "1"
-
- def tearDown(self) -> None:
- ClientHandler.discover = self.stashed_discover
- ClientHandler.register_client = self.stashed_register_client
- builtins.open = self.stashed_open
- clientapp.add_config_from_json = self.stashed_add_config_from_json
-
-
-# Helper functions
-def app_endpoints(app: Flask) -> List[str]:
- """ Return all enpoints in app """
- endpoints = []
- for item in app.url_map.iter_rules():
- endpoint = item.endpoint.replace("_", "-")
- endpoints.append(endpoint)
- return endpoints
-
-
-# Mocks
-OP_DATA_DICT_RESPONSE = {
- 'request_parameter_supported': True,
- 'token_revocation_endpoint': 'https://t1.techno24x7.com/oxauth/restv1/revoke',
- 'introspection_endpoint': 'https://t1.techno24x7.com/oxauth/restv1/introspection',
- 'claims_parameter_supported': False,
- 'issuer': 'https://t1.techno24x7.com',
- 'userinfo_encryption_enc_values_supported': ['RSA1_5', 'RSA-OAEP', 'A128KW', 'A256KW'],
- 'id_token_encryption_enc_values_supported': ['A128CBC+HS256', 'A256CBC+HS512', 'A128GCM', 'A256GCM'],
- 'authorization_endpoint': 'https://t1.techno24x7.com/oxauth/restv1/authorize',
- 'service_documentation': 'http://gluu.org/docs',
- 'id_generation_endpoint': 'https://t1.techno24x7.com/oxauth/restv1/id',
- 'claims_supported': ['street_address', 'country', 'zoneinfo', 'birthdate', 'role', 'gender', 'formatted',
- 'user_name', 'phone_mobile_number', 'preferred_username', 'locale', 'inum', 'updated_at',
- 'nickname', 'email', 'website', 'email_verified', 'profile', 'locality',
- 'phone_number_verified', 'given_name', 'middle_name', 'picture', 'name', 'phone_number',
- 'postal_code', 'region', 'family_name'],
- 'scope_to_claims_mapping': [{
- 'profile': ['name', 'family_name', 'given_name', 'middle_name', 'nickname', 'preferred_username', 'profile',
- 'picture', 'website', 'gender', 'birthdate', 'zoneinfo', 'locale', 'updated_at']
- }, {
- 'openid': []
- }, {
- 'https://t1.techno24x7.com/oxauth/restv1/uma/scopes/scim_access': []
- }, {
- 'permission': ['role']
- }, {
- 'super_gluu_ro_session': []
- }, {
- 'https://t1.techno24x7.com/oxauth/restv1/uma/scopes/passport_access': []
- }, {
- 'phone': ['phone_number_verified', 'phone_number']
- }, {
- 'revoke_session': []
- }, {
- 'address': ['formatted', 'postal_code', 'street_address', 'locality', 'country', 'region']
- }, {
- 'clientinfo': ['name', 'inum']
- }, {
- 'mobile_phone': ['phone_mobile_number']
- }, {
- 'email': ['email_verified', 'email']
- }, {
- 'user_name': ['user_name']
- }, {
- 'oxtrust-api-write': []
- }, {
- 'oxd': []
- }, {
- 'uma_protection': []
- }, {
- 'oxtrust-api-read': []
- }],
- 'op_policy_uri': 'http://ox.gluu.org/doku.php?id=oxauth:policy',
- 'token_endpoint_auth_methods_supported': ['client_secret_basic', 'client_secret_post', 'client_secret_jwt',
- 'private_key_jwt', 'tls_client_auth', 'self_signed_tls_client_auth'],
- 'tls_client_certificate_bound_access_tokens': True,
- 'response_modes_supported': ['query', 'form_post', 'fragment'],
- 'backchannel_logout_session_supported': True,
- 'token_endpoint': 'https://t1.techno24x7.com/oxauth/restv1/token',
- 'response_types_supported': ['code id_token', 'code', 'id_token', 'token', 'code token', 'code id_token token',
- 'id_token token'],
- 'request_uri_parameter_supported': True,
- 'backchannel_user_code_parameter_supported': False,
- 'grant_types_supported': ['implicit', 'refresh_token', 'client_credentials', 'authorization_code', 'password',
- 'urn:ietf:params:oauth:grant-type:uma-ticket'],
- 'ui_locales_supported': ['en', 'bg', 'de', 'es', 'fr', 'it', 'ru', 'tr'],
- 'userinfo_endpoint': 'https://t1.techno24x7.com/oxauth/restv1/userinfo',
- 'op_tos_uri': 'http://ox.gluu.org/doku.php?id=oxauth:tos',
- 'auth_level_mapping': {
- '-1': ['simple_password_auth'],
- '60': ['passport_saml'],
- '40': ['passport_social']
- },
- 'require_request_uri_registration': False,
- 'id_token_encryption_alg_values_supported': ['RSA1_5', 'RSA-OAEP', 'A128KW', 'A256KW'],
- 'frontchannel_logout_session_supported': True,
- 'claims_locales_supported': ['en'],
- 'clientinfo_endpoint': 'https://t1.techno24x7.com/oxauth/restv1/clientinfo',
- 'request_object_signing_alg_values_supported': ['none', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512',
- 'ES256', 'ES384', 'ES512'],
- 'request_object_encryption_alg_values_supported': ['RSA1_5', 'RSA-OAEP', 'A128KW', 'A256KW'],
- 'session_revocation_endpoint': 'https://t1.techno24x7.com/oxauth/restv1/revoke_session',
- 'check_session_iframe': 'https://t1.techno24x7.com/oxauth/opiframe.htm',
- 'scopes_supported': ['address', 'openid', 'clientinfo', 'user_name', 'profile',
- 'https://t1.techno24x7.com/oxauth/restv1/uma/scopes/scim_access', 'uma_protection',
- 'permission', 'revoke_session', 'oxtrust-api-write', 'oxtrust-api-read', 'phone',
- 'mobile_phone', 'oxd', 'super_gluu_ro_session', 'email',
- 'https://t1.techno24x7.com/oxauth/restv1/uma/scopes/passport_access'],
- 'backchannel_logout_supported': True,
- 'acr_values_supported': ['simple_password_auth', 'passport_saml', 'urn:oasis:names:tc:SAML:2.0:ac:classes:Password',
- 'urn:oasis:names:tc:SAML:2.0:ac:classes:InternetProtocol',
- 'urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport', 'passport_social'],
- 'request_object_encryption_enc_values_supported': ['A128CBC+HS256', 'A256CBC+HS512', 'A128GCM', 'A256GCM'],
- 'display_values_supported': ['page', 'popup'],
- 'userinfo_signing_alg_values_supported': ['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'ES256', 'ES384',
- 'ES512'],
- 'claim_types_supported': ['normal'],
- 'userinfo_encryption_alg_values_supported': ['RSA1_5', 'RSA-OAEP', 'A128KW', 'A256KW'],
- 'end_session_endpoint': 'https://t1.techno24x7.com/oxauth/restv1/end_session',
- 'revocation_endpoint': 'https://t1.techno24x7.com/oxauth/restv1/revoke',
- 'backchannel_authentication_endpoint': 'https://t1.techno24x7.com/oxauth/restv1/bc-authorize',
- 'token_endpoint_auth_signing_alg_values_supported': ['HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'ES256',
- 'ES384', 'ES512'],
- 'frontchannel_logout_supported': True,
- 'jwks_uri': 'https://t1.techno24x7.com/oxauth/restv1/jwks',
- 'subject_types_supported': ['public', 'pairwise'],
- 'id_token_signing_alg_values_supported': ['none', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'ES256',
- 'ES384', 'ES512'],
- 'registration_endpoint': 'https://t1.techno24x7.com/oxauth/restv1/register',
- 'id_token_token_binding_cnf_values_supported': ['tbh']
-}
-
-REGISTER_CLIENT_RESPONSE = {'allow_spontaneous_scopes': False, 'application_type': 'web', 'rpt_as_jwt': False,
- 'registration_client_uri': 'https://t1.techno24x7.com/jans-auth/restv1/register?client_id'
- '=079f3682-3d60-4bca-8ff7-bbc7dbc18cd7',
- 'run_introspection_script_before_jwt_creation': False,
- 'registration_access_token': '89c51fd6-34ec-497e-a4ae-85e21b7e725b',
- 'client_id': '079f3682-3d60-4bca-8ff7-bbc7dbc18cd7',
- 'token_endpoint_auth_method': 'client_secret_post',
- 'scope': 'online_access device_sso openid permission uma_protection offline_access',
- 'client_secret': '8f53c454-f6ee-4181-8581-9f1ee120b878', 'client_id_issued_at': 1680038429,
- 'backchannel_logout_session_required': False, 'client_name': 'Jans Tent',
- 'par_lifetime': 600, 'spontaneous_scopes': [], 'id_token_signed_response_alg': 'RS256',
- 'access_token_as_jwt': False, 'grant_types': ['authorization_code'],
- 'subject_type': 'pairwise', 'additional_token_endpoint_auth_methods': [],
- 'keep_client_authorization_after_expiration': False, 'require_par': False,
- 'redirect_uris': ['https://localhost:9090/oidc_callback'], 'additional_audience': [],
- 'frontchannel_logout_session_required': False, 'client_secret_expires_at': 0,
- 'access_token_signing_alg': 'RS256', 'response_types': ['code']}
-
-
diff --git a/demos/jans-tent/tests/unit_integration/test_callback_endpoint.py b/demos/jans-tent/tests/unit_integration/test_callback_endpoint.py
deleted file mode 100644
index 91ebb43edca..00000000000
--- a/demos/jans-tent/tests/unit_integration/test_callback_endpoint.py
+++ /dev/null
@@ -1,62 +0,0 @@
-import clientapp
-from flask import Flask, url_for
-from typing import List
-from helper import FlaskBaseTestCase
-
-
-def app_endpoints(app: Flask) -> List[str]:
- """ Return all enpoints in app """
-
- endpoints = []
- for item in app.url_map.iter_rules():
- endpoint = item.endpoint.replace("_", "-")
- endpoints.append(endpoint)
- return endpoints
-
-
-# class FlaskBaseTestCase(TestCase):
-# def setUp(self):
-# self.app = clientapp.create_app()
-# self.app.testing = True
-# self.app_context = self.app.test_request_context(
-# base_url="https://chris.testingenv.org")
-# self.app_context.push()
-# self.client = self.app.test_client()
-# #self.oauth = OAuth(self.app)
-# os.environ['AUTHLIB_INSECURE_TRANSPORT'] = "1"
-
-
-class TestCallbackEndpoint(FlaskBaseTestCase):
- def test_oidc_callback_endpoint_exist(self):
- endpoints = []
- for item in clientapp.create_app().url_map.iter_rules():
- endpoint = item.rule
- endpoints.append(endpoint)
-
- self.assertTrue('/oidc_callback' in endpoints,
- "enpoint /oidc_callback knão existe no app")
-
- def test_callback_endpoint_should_exist(self):
-
- self.assertTrue('callback' in app_endpoints(clientapp.create_app()),
- 'endpoint /callback does not exist in app')
-
- def test_endpoint_args_without_code_should_return_400(self):
- resp = self.client.get(url_for('callback'))
-
- self.assertEqual(resp.status_code, 400)
-
-
-'''
- def test_endpoint_should_return_status_code_302(self):
- # if there is
-
- self.assertEqual(
- self.client.get(url_for('callback')).status_code,
- 302,
- 'Callback endpoint is not returning 302 status_code'
- )
-
-
- #def test_endpoint_should_return_
-'''
diff --git a/demos/jans-tent/tests/unit_integration/test_cfg_checker.py b/demos/jans-tent/tests/unit_integration/test_cfg_checker.py
deleted file mode 100644
index e69de29bb2d..00000000000
diff --git a/demos/jans-tent/tests/unit_integration/test_client_register_endpoint.py b/demos/jans-tent/tests/unit_integration/test_client_register_endpoint.py
deleted file mode 100644
index 83aef096738..00000000000
--- a/demos/jans-tent/tests/unit_integration/test_client_register_endpoint.py
+++ /dev/null
@@ -1,145 +0,0 @@
-from helper import FlaskBaseTestCase
-import clientapp
-import helper
-from flask import url_for
-from clientapp.helpers.client_handler import ClientHandler
-from unittest.mock import MagicMock, patch
-
-
-class TestRegisterEndpoint(FlaskBaseTestCase):
-
- def test_if_app_has_register_endpoint(self):
- self.assertIn(
- 'register',
- helper.app_endpoints(clientapp.create_app())
- )
-
- def test_if_endpoint_accepts_post(self):
- methods = None
- for rule in self.app.url_map.iter_rules('register'):
- methods = rule.methods
- self.assertIn(
- 'POST',
- methods
- )
-
- # def test_init_should_call_discover_once(self):
- # ClientHandler.discover = MagicMock(name='discover')
- # ClientHandler.discover.return_value = helper.OP_DATA_DICT_RESPONSE
- # ClientHandler.discover.assert_called_once()
-
- def test_endpoint_should_return_valid_req(self):
- self.assertIn(
- self.client.post(url_for('register')).status_code,
- range(100, 511),
- '/register returned invalid requisition'
- )
-
- @patch('clientapp.helpers.client_handler.ClientHandler.__init__', MagicMock(return_value=None))
- def test_endpoint_should_init_client_handler(self):
- self.client.post(url_for('register'), json={
- 'op_url': 'https://test.com',
- 'redirect_uris': ['https://clienttoberegistered.com/oidc_callback']
- })
- ClientHandler.__init__.assert_called_once()
-
- @patch('clientapp.helpers.client_handler.ClientHandler.__init__', MagicMock(return_value=None))
- def test_endpoint_should_accept_2_params(self):
- first_value = 'https://op'
- second_value = ['https://client.com.br/oidc_callback']
-
- self.client.post(url_for('register'), json={
- 'op_url': first_value,
- 'redirect_uris': second_value
- })
- ClientHandler.__init__.assert_called_once_with(first_value, second_value, {})
-
- @patch('clientapp.helpers.client_handler.ClientHandler.__init__', MagicMock(return_value=None))
- def test_endpoint_should_accept_3_params(self):
- first_value = 'https://op'
- second_value = ['https://client.com.br/oidc_callback']
- third_value = {'scope': 'openid email profile'}
-
- self.client.post(url_for('register'), json={
- 'op_url': first_value,
- 'redirect_uris': second_value,
- 'additional_params': third_value
- })
-
- ClientHandler.__init__.assert_called_once_with(first_value, second_value, third_value)
-
- def test_endpoint_should_return_error_code_400_if_no_data_sent(self):
- self.assertEqual(
- self.client.post(url_for('register')).status_code,
- 400,
- 'status_code for empty request is NOT 400'
- )
-
- def test_should_return_400_error_if_no_needed_keys_provided(self):
- self.assertEqual(
- self.client.post(url_for('register'), json={
- 'other_key': 'othervalue',
- 'another_key': 'another_value'
- }).status_code,
- 400,
- 'not returning 400 code if no needed keys provided'
- )
-
- def test_should_return_400_if_values_are_not_valid_urls(self):
- self.assertEqual(
- self.client.post(url_for('register'), json={
- 'op_url': 'not_valid_url',
- 'redirect_uris': ['https://clienttoberegistered.com/oidc_callback']
- }).status_code,
- 400,
- 'not returning status 400 if values are not valid urls'
- )
-
- @patch('clientapp.helpers.client_handler.ClientHandler.get_client_dict', MagicMock(return_value=None))
- def test_valid_post_should_should_call_get_client_dict_once(self):
- op_url = 'https://op.com.br'
- self.client.post(url_for('register'), json={
- 'op_url': op_url,
- 'redirect_uris': ['https://clienttoberegistered.com/oidc_callback']
- })
- ClientHandler.get_client_dict.assert_called_once()
-
- def test_should_should_return_200_if_registered(self):
- op_url = 'https://op.com.br'
- test_client_id = '1234-5678-9ten11'
- test_client_secret = 'mysuperprotectedsecret'
- with patch.object(ClientHandler, 'get_client_dict', return_value={
- 'op_metadata_url': '%s/.well-known/open-id-configuration' % op_url,
- 'client_id': test_client_id,
- 'client_secret': test_client_secret
- }) as get_client_dict:
- response = self.client.post(url_for('register'), json={
- 'op_url': op_url,
- 'redirect_uris': ['https://clienttoberegistered.com/oidc_callback']
- })
- self.assertEqual(response.status_code, 200)
- get_client_dict.reset()
-
- def test_should_return_expected_keys(self):
- op_url = 'https://op.com.br'
- redirect_uris = ['https://client.com.br/oidc_calback']
- test_client_id = '1234-5678-9ten11'
- test_client_secret = 'mysuperprotectedsecret'
- additional_params = {'param1': 'value1'}
-
- expected_keys = {'op_metadata_url', 'client_id', 'client_secret'}
-
- with patch.object(ClientHandler, 'get_client_dict', return_value={
- 'op_metadata_url': '%s/.well-known/open-id-configuration' % op_url,
- 'client_id': test_client_id,
- 'client_secret': test_client_secret
- }) as get_client_dict:
- response = self.client.post(url_for('register'), json={
- 'op_url': op_url,
- 'redirect_uris': redirect_uris,
- 'additional_params': additional_params
- })
- print(response)
- assert expected_keys <= response.json.keys(), response.json
-
- get_client_dict.reset()
diff --git a/demos/jans-tent/tests/unit_integration/test_config.py b/demos/jans-tent/tests/unit_integration/test_config.py
deleted file mode 100644
index 419e3bd56a9..00000000000
--- a/demos/jans-tent/tests/unit_integration/test_config.py
+++ /dev/null
@@ -1,19 +0,0 @@
-import clientapp.config as cfg
-from unittest import TestCase
-
-
-class TestConfig(TestCase):
- def test_has_attribute_SSL_VERIFY(self):
- self.assertTrue(hasattr(cfg, 'SSL_VERIFY'),
- 'SSL_VERIFY attribute is missing in config.')
-
- def test_SSL_VERIFY_has_boolean_value(self):
- self.assertTrue('__bool__' in cfg.SSL_VERIFY.__dir__(),
- 'SSL_VERIFY is not boolean.')
-
- def test_has_attribute_SCOPE(self):
- self.assertTrue(hasattr(cfg, 'SCOPE'),
- 'SCOPE attribute is missing in config.')
-
- def test_SCOPE_default_should_be_openid(self):
- self.assertTrue(cfg.SCOPE == 'openid')
diff --git a/demos/jans-tent/tests/unit_integration/test_configuration_endpoint.py b/demos/jans-tent/tests/unit_integration/test_configuration_endpoint.py
deleted file mode 100644
index 1890b1d4cee..00000000000
--- a/demos/jans-tent/tests/unit_integration/test_configuration_endpoint.py
+++ /dev/null
@@ -1,107 +0,0 @@
-import clientapp
-from flask import Flask, url_for
-from typing import List
-import json
-from helper import FlaskBaseTestCase
-
-
-def app_endpoints(app: Flask) -> List[str]:
- """ Return all enpoints in app """
-
- endpoints = []
- for item in app.url_map.iter_rules():
- endpoint = item.endpoint.replace("_", "-")
- endpoints.append(endpoint)
- return endpoints
-
-
-def valid_client_configuration():
- return {
- "client_id": "my-client-id",
- "client_secret": "my-client-secret",
- "op_metadata_url": "https://op.com/.well-known/openidconfiguration"
- }
-
-
-class TestConfigurationEndpoint(FlaskBaseTestCase):
- def test_create_app_has_configuration(self):
- self.assertTrue(
- 'configuration' in app_endpoints(clientapp.create_app()),
- 'endpoint /configuration does not exist in app')
-
- def test_configuration_endpoint_should_return_valid_req(self):
- self.assertIn(
- self.client.post(url_for('configuration')).status_code,
- range(100, 511), '/configuration returned invalid requisition')
-
- def test_endpoint_should_return_200_if_valid_json(self):
- headers = {'Content-type': 'application/json'}
- data = {'provider_id': 'whatever'}
- json_data = json.dumps(data)
- response = self.client.post(url_for('configuration'),
- data=json_data,
- headers=headers)
- self.assertEqual(response.status_code, 200)
-
- def test_endpoint_should_return_posted_data_if_valid_json(self):
- headers = {'Content-type': 'application/json'}
- data = {'provider_id': 'whatever'}
- json_data = json.dumps(data)
- response = self.client.post(url_for('configuration'),
- data=json_data,
- headers=headers)
-
- self.assertEqual(json_data, json.dumps(response.json))
-
- def test_endpoint_should_setup_cfg_with_provider_id(self):
- headers = {'Content-type': 'application/json'}
- data = {'provider_id': 'whatever'}
- json_data = json.dumps(data)
- self.client.post(url_for('configuration'),
- data=json_data,
- headers=headers)
-
- self.assertEqual(clientapp.cfg.PRE_SELECTED_PROVIDER_ID, 'whatever')
-
- def test_endpoint_should_setup_cfg_with_pre_selected_provider_true(self):
- clientapp.cfg.PRE_SELECTED_PROVIDER = False
- headers = {'Content-type': 'application/json'}
- data = {'provider_id': 'whatever'}
- json_data = json.dumps(data)
- self.client.post(url_for('configuration'),
- data=json_data,
- headers=headers)
-
- self.assertTrue(clientapp.cfg.PRE_SELECTED_PROVIDER, )
-
- def test_endpoint_should_return_200_if_valid_client_config(self):
- headers = {'Content-type': 'application/json'}
- json_data = json.dumps(valid_client_configuration())
- response = self.client.post(
- url_for('configuration'), data=json_data, headers=headers)
- self.assertEqual(response.status_code, 200,
- 'endpoint is NOT returning 200 for valid client configuration')
-
- def test_endpoint_should_register_new_oauth_client_id(self):
- headers = {'Content-type': 'application/json'}
- client_id = "my-client-id"
- client_secret = "my-client-secret"
- op_metadata_url = "https://op.com/.well-known/openidconfiguration"
- json_data = json.dumps({
- "client_id": client_id,
- "client_secret": client_secret,
- "op_metadata_url": op_metadata_url
- })
- self.client.post(
- url_for('configuration'), data=json_data, headers=headers)
- self.assertTrue(clientapp.oauth.op.client_id == client_id,
- 'endpoint is NOT changing op.client_id')
-
- def test_endpoint_should_register_new_oauth_client_secret(self):
- headers = {'Content-type': 'application/json'}
- json_data = json.dumps(valid_client_configuration())
- client_secret = valid_client_configuration()['client_secret']
- self.client.post(
- url_for('configuration'), data=json_data, headers=headers)
- self.assertTrue(clientapp.oauth.op.client_secret == client_secret,
- '%s is is not %s' % (clientapp.oauth.op.client_secret, client_secret))
diff --git a/demos/jans-tent/tests/unit_integration/test_dcr_from_config.py b/demos/jans-tent/tests/unit_integration/test_dcr_from_config.py
deleted file mode 100644
index e02f909ea28..00000000000
--- a/demos/jans-tent/tests/unit_integration/test_dcr_from_config.py
+++ /dev/null
@@ -1,76 +0,0 @@
-from clientapp.utils import dcr_from_config
-from clientapp import config as cfg
-from unittest.mock import MagicMock, patch, mock_open
-from unittest import TestCase
-from clientapp.helpers.client_handler import ClientHandler
-import helper
-import json
-import builtins
-
-class TestDrcFromConfig(TestCase):
-
- def setUp(self) -> None:
- # stashing to restore on teardown
- self.stashed_discover = ClientHandler.discover
- self.stashed_register_client = ClientHandler.register_client
- self.stashed_open = builtins.open
- ClientHandler.discover = MagicMock(name='discover')
- ClientHandler.discover.return_value = helper.OP_DATA_DICT_RESPONSE
- ClientHandler.register_client = MagicMock(name='register_client')
- ClientHandler.register_client.return_value = helper.REGISTER_CLIENT_RESPONSE
- builtins.open = MagicMock(name='open')
-
- def tearDown(self) -> None:
- ClientHandler.discover = self.stashed_discover
- ClientHandler.register_client = self.stashed_register_client
- builtins.open = self.stashed_open
-
- def test_if_setup_logging_exists(self):
- assert hasattr(dcr_from_config, 'setup_logging')
-
- def test_if_static_variables_exists(self):
- assert hasattr(dcr_from_config, 'OP_URL')
- assert hasattr(dcr_from_config, 'REDIRECT_URIS')
-
- def test_if_static_variables_from_config(self):
- assert dcr_from_config.OP_URL == cfg.ISSUER
- assert dcr_from_config.REDIRECT_URIS == cfg.REDIRECT_URIS
-
- def test_register_should_be_calable(self):
- assert callable(dcr_from_config.register), 'not callable'
-
- @patch('clientapp.helpers.client_handler.ClientHandler.__init__', MagicMock(return_value=None))
- def test_register_should_call_ClientHandler(self):
- dcr_from_config.register()
- ClientHandler.__init__.assert_called_once()
-
- @patch('clientapp.helpers.client_handler.ClientHandler.__init__', MagicMock(return_value=None))
- def test_register_should_call_ClientHandler_with_params(self):
- dcr_from_config.register()
- ClientHandler.__init__.assert_called_once_with(
- cfg.ISSUER, cfg.REDIRECT_URIS, {
- 'scope': cfg.SCOPE.split(" "),
- "post_logout_redirect_uris": ['https://localhost:9090']
- }
- )
-
- def test_register_should_call_open(self):
- with patch('builtins.open', mock_open()) as open_mock:
- dcr_from_config.register()
-
- open_mock.assert_called_once()
-
- def test_register_should_call_open_with_correct_params(self):
- with patch('builtins.open', mock_open()) as open_mock:
- dcr_from_config.register()
- open_mock.assert_called_once_with('client_info.json', 'w')
-
- def test_register_should_call_write_with_client_info(self):
- client = ClientHandler(cfg.ISSUER, cfg.REDIRECT_URIS, {})
- expected_json_client_info = json.dumps(client.get_client_dict(), indent=4)
- with patch('builtins.open', mock_open()) as open_mock:
- dcr_from_config.register()
- open_mock_handler = open_mock()
- open_mock_handler.write.assert_called_once_with(expected_json_client_info)
-
-
diff --git a/demos/jans-tent/tests/unit_integration/test_dynamic_client_registration.py b/demos/jans-tent/tests/unit_integration/test_dynamic_client_registration.py
deleted file mode 100644
index 0f51b7cb596..00000000000
--- a/demos/jans-tent/tests/unit_integration/test_dynamic_client_registration.py
+++ /dev/null
@@ -1,277 +0,0 @@
-from unittest import TestCase
-from unittest.mock import MagicMock
-import inspect
-
-import clientapp.helpers.client_handler as client_handler
-from typing import Optional
-import helper
-from oic.oauth2 import ASConfigurationResponse
-
-
-ClientHandler = client_handler.ClientHandler
-
-# helper
-def get_class_instance(op_url='https://t1.techno24x7.com',
- client_url='https://mock.test.com',
- additional_metadata={}):
- client_handler_obj = ClientHandler(op_url, client_url, additional_metadata)
- return client_handler_obj
-
-
-class TestDynamicClientRegistration(TestCase):
-
- def setUp(self) -> None:
- self.register_client_stash = ClientHandler.register_client
- self.discover_stash = ClientHandler.discover
-
- @staticmethod
- def mock_methods():
- ClientHandler.register_client = MagicMock(name='register_client')
- ClientHandler.register_client.return_value = helper.REGISTER_CLIENT_RESPONSE
- ClientHandler.discover = MagicMock(name='discover')
- ClientHandler.discover.return_value = helper.OP_DATA_DICT_RESPONSE
-
- def restore_stashed_mocks(self):
- ClientHandler.discover = self.discover_stash
- ClientHandler.register_client = self.register_client_stash
-
- def test_if_json_exists(self):
- self.assertTrue(hasattr(client_handler, 'json'),
- 'json does not exists in client_handler')
-
- def test_if_json_is_from_json_package(self):
- self.assertTrue(client_handler.json.__package__ == 'json',
- 'json is not from json')
-
- # testing ClientHandler class
- def test_if_ClientHandler_is_class(self):
- self.assertTrue(inspect.isclass(ClientHandler))
-
- def test_if_register_client_exists(self):
- self.assertTrue(hasattr(ClientHandler, 'register_client'),
- 'register_client does not exists in ClientHandler')
-
- def test_if_register_client_is_callable(self):
- self.assertTrue(callable(ClientHandler.register_client),
- 'register_client is not callable')
-
- def test_if_register_client_receives_params(self):
- expected_args = ['self', 'op_data', 'redirect_uris']
- self.assertTrue(
- inspect.getfullargspec(
- ClientHandler.register_client).args == expected_args,
- 'register_client does not receive expected args')
-
- def test_if_register_client_params_are_expected_type(self):
- insp = inspect.getfullargspec(ClientHandler.register_client)
- self.assertTrue(
- insp.annotations['op_data'] == ASConfigurationResponse
- and insp.annotations['redirect_uris'] == Optional[list[str]],
- 'register_client is not receiving the right params')
-
- def test_if_class_has_initial_expected_attrs(self):
- initial_expected_attrs = [
- '_ClientHandler__client_id',
- '_ClientHandler__client_secret',
- '_ClientHandler__redirect_uris',
- '_ClientHandler__metadata_url',
- '_ClientHandler__additional_metadata',
- 'discover', # method
- 'register_client' # method
- ]
-
- self.assertTrue(
- all(attr in ClientHandler.__dict__.keys()
- for attr in initial_expected_attrs),
- 'ClientHandler does not have initial attrs')
-
- def test_if_discover_exists(self):
- self.assertTrue(hasattr(ClientHandler, 'discover'),
- 'discover does not exists in ClientHandler')
-
- def test_if_discover_is_callable(self):
- self.assertTrue(callable(ClientHandler.discover),
- 'discover is not callable')
-
- def test_if_discover_receives_params(self):
- expected_args = ['self', 'op_url']
- self.assertTrue(
- inspect.getfullargspec(
- ClientHandler.discover).args == expected_args,
- 'discover does not receive expected args')
-
- def test_if_discover_params_are_expected_type(self):
- insp = inspect.getfullargspec(ClientHandler.discover)
- self.assertTrue(
- insp.annotations['op_url'] == Optional[str],
- 'discover is not receiving the right params')
-
- def test_discover_should_return_valid_dict(self):
- """[Checks if returns main keys]
- """
-
- main_keys = {
- 'issuer', 'authorization_endpoint', 'token_endpoint',
- 'userinfo_endpoint', 'clientinfo_endpoint',
- 'session_revocation_endpoint', 'end_session_endpoint',
- 'revocation_endpoint', 'registration_endpoint'
- }
-
- self.mock_methods()
- op_data = ClientHandler.discover(ClientHandler,
- 'https://t1.techno24x7.com')
- self.assertTrue(main_keys <= set(op_data),
- 'discovery return data does not have main keys')
- self.restore_stashed_mocks()
-
- def test_if_get_client_dict_exists(self):
- self.assertTrue(hasattr(ClientHandler, 'get_client_dict'),
- 'get_client_dict does not exists in ClientHandler')
-
- def test_if_get_client_dict_is_callable(self):
- self.assertTrue(callable(ClientHandler.get_client_dict),
- 'get_client_dict is not callable')
-
- def test_if_get_client_dict_receives_params(self):
- expected_args = ['self']
- self.assertTrue(
- inspect.getfullargspec(
- ClientHandler.get_client_dict).args == expected_args,
- 'get_client_dict does not receive expected args')
-
- def test_client_id_should_return_something(self):
- self.assertIsNotNone(
- ClientHandler.get_client_dict(ClientHandler),
- 'get_client_dict returning NoneType. It has to return something!')
-
- def test_get_client_dict_should_return_a_dict(self):
- self.assertIsInstance(ClientHandler.get_client_dict(ClientHandler),
- dict, 'get_client_dict is not returning a dict')
-
- def test_class_init_should_set_op_url(self):
- self.mock_methods()
-
- op_url = 'https://t1.techno24x7.com'
-
- client_handler_obj = get_class_instance(op_url)
-
- self.restore_stashed_mocks()
-
- self.assertEqual(client_handler_obj.__dict__['_ClientHandler__op_url'],
- op_url)
-
- def test_class_init_should_set_redirect_uris(self):
- self.mock_methods()
-
- op_url = 'https://t1.techno24x7.com'
- redirect_uris = 'https://mock.test.com/oidc_callback'
- client_handler_obj = ClientHandler(op_url, redirect_uris, {})
-
- self.restore_stashed_mocks()
-
- self.assertEqual(
- client_handler_obj.__dict__['_ClientHandler__redirect_uris'],
- redirect_uris)
-
- def test_class_init_should_set_metadata_url(self):
- self.mock_methods()
-
- op_url = 'https://t1.techno24x7.com'
-
- client_handler_obj = get_class_instance(op_url)
-
- self.restore_stashed_mocks()
-
- expected_metadata_url = op_url + '/.well-known/openid-configuration'
-
- self.assertEqual(
- client_handler_obj.__dict__['_ClientHandler__metadata_url'],
- expected_metadata_url)
-
- def test_class_init_should_set_additional_params(self):
- self.mock_methods()
- expected_metadata = {'metakey1': 'meta value 1'}
- client_handler_obj = get_class_instance(additional_metadata=expected_metadata)
- self.restore_stashed_mocks()
-
- self.assertEqual(
- client_handler_obj.__dict__['_ClientHandler__additional_metadata'],
- expected_metadata
- )
-
- def test_class_init_should_have_docstring(self):
- self.assertTrue(ClientHandler.__init__.__doc__,
- 'ClientHandler.__init__ has doc')
-
- def test_if_get_client_dict_return_expected_keys(self):
- expected_keys = [
- 'op_metadata_url',
- 'client_id',
- 'client_secret',
- ]
-
- self.mock_methods()
-
- client_handler_obj = get_class_instance()
- client_dict = client_handler_obj.get_client_dict()
-
- self.restore_stashed_mocks()
-
- self.assertTrue(
- all(key in client_dict.keys() for key in expected_keys),
- 'there is no %s IN %s: get_client_dict is NOT returning expected keys'
- % (str(expected_keys), str(client_dict.keys())))
-
- def test_get_client_dict_values_cannot_be_none(self):
- self.mock_methods()
-
- op_url = 'https://t1.techno24x7.com'
- client_handler_obj = get_class_instance(op_url)
- client_dict = client_handler_obj.get_client_dict()
-
- self.restore_stashed_mocks()
-
- for key in client_dict.keys():
- self.assertIsNotNone(client_dict[key],
- 'get_client_dict[%s] cannot be None!' % key)
-
- def test_get_client_dict_should_return_url_metadata_value(self):
- self.mock_methods()
-
- client_handler_obj = get_class_instance()
-
- self.restore_stashed_mocks()
-
- self.assertEqual(
- client_handler_obj.get_client_dict()['op_metadata_url'],
- client_handler_obj._ClientHandler__metadata_url)
-
- def test_get_client_dict_should_return_client_id_value(self):
- self.mock_methods()
-
- client_handler_obj = get_class_instance()
-
- self.restore_stashed_mocks()
-
- self.assertEqual(
- client_handler_obj.get_client_dict()['client_id'],
- client_handler_obj._ClientHandler__client_id
- )
-
- def test_init_should_call_discover_once(self):
- self.mock_methods()
-
- get_class_instance()
-
- ClientHandler.discover.assert_called_once()
-
- self.restore_stashed_mocks()
-
- def test_init_should_call_register_client_once(self):
- self.mock_methods()
-
- get_class_instance()
- ClientHandler.register_client.assert_called_once()
-
- self.restore_stashed_mocks()
-
diff --git a/demos/jans-tent/tests/unit_integration/test_flask_factory.py b/demos/jans-tent/tests/unit_integration/test_flask_factory.py
deleted file mode 100644
index 0813c3b1ad1..00000000000
--- a/demos/jans-tent/tests/unit_integration/test_flask_factory.py
+++ /dev/null
@@ -1,93 +0,0 @@
-from unittest import TestCase
-from unittest.mock import MagicMock
-import clientapp
-from flask import Flask
-import os
-import builtins
-from clientapp.helpers.client_handler import ClientHandler
-import helper
-
-
-class TestFlaskApp(TestCase):
-
- def setUp(self) -> None:
- self.stashed_add_config_from_json = clientapp.add_config_from_json
- clientapp.cfg.CLIENT_ID = 'any-client-id-stub'
- clientapp.cfg.CLIENT_SECRET = 'any-client-secret-stub'
- clientapp.cfg.SERVER_META_URL = 'https://ophostname.com/server/meta/url'
- clientapp.add_config_from_json = MagicMock(name='add_config_from_json')
- clientapp.add_config_from_json.return_value(None)
- self.stashed_discover = ClientHandler.discover
- self.stashed_register_client = ClientHandler.register_client
- self.stashed_open = builtins.open
- builtins.open = MagicMock(name='open')
- ClientHandler.discover = MagicMock(name='discover')
- ClientHandler.discover.return_value = helper.OP_DATA_DICT_RESPONSE
- ClientHandler.register_client = MagicMock(name='register_client')
- ClientHandler.register_client.return_value = helper.REGISTER_CLIENT_RESPONSE
-
- def tearDown(self) -> None:
- ClientHandler.discover = self.stashed_discover
- ClientHandler.register_client = self.stashed_register_client
- builtins.open = self.stashed_open
- clientapp.add_config_from_json = self.stashed_add_config_from_json
-
- def test_create_app_should_exist(self):
- self.assertEqual(hasattr(clientapp, 'create_app'), True,
- 'app factory does not exists')
-
- def test_create_app_should_be_invokable(self):
- self.assertEqual(callable(clientapp.create_app), True,
- 'cannot invoke create_app from clientapp')
-
- def test_create_app_should_return_a_flask_app(self):
-
- self.assertIsInstance(clientapp.create_app(), Flask,
- 'create_app is not returning a Flask instance')
-
- def test_if_app_has_secret_key(self):
- self.assertTrue(hasattr(clientapp.create_app(), 'secret_key'), )
-
- def test_if_secret_key_not_none(self):
- self.assertIsNotNone(clientapp.create_app().secret_key,
- 'app secret key is unexpectedly None')
-
- def test_if_oauth_is_app_extension(self):
- self.assertTrue('authlib.integrations.flask_client' in
- clientapp.create_app().extensions)
-
- def test_if_settings_py_exists(self):
- self.assertTrue(os.path.exists('clientapp/config.py'),
- 'File clientapp/config.py does not exist')
-
- def test_if_op_client_id_exists_in_app_configuration(self):
- self.assertTrue('OP_CLIENT_ID' in clientapp.create_app().config,
- 'No OP_CLIENT_ID in app.config')
-
- def test_if_clientapp_has_cfg(self):
- self.assertTrue(hasattr(clientapp, 'cfg'))
-
- def test_if_cfg_is_module_from_configpy(self):
- self.assertTrue(
- os.path.relpath(clientapp.cfg.__file__) == 'clientapp/config.py')
-
- ...
-
- def test_if_OP_CLIENT_ID_is_equal_cfg_CLIENT_ID(self):
- self.assertEqual(clientapp.create_app().config['OP_CLIENT_ID'],
- clientapp.cfg.CLIENT_ID)
-
- def test_if_OP_CLIENT_SECRET_exists_in_app_configuration(self):
- self.assertTrue('OP_CLIENT_SECRET' in clientapp.create_app().config,
- 'No OP_CLIENT_SECRET in app.config')
-
- def test_if_OP_CLIENT_SECRET_is_equal_cfg_CLIENT_ID(self):
- self.assertEqual(clientapp.create_app().config['OP_CLIENT_SECRET'],
- clientapp.cfg.CLIENT_SECRET)
-
- def test_if_has_attr_ssl_verify(self):
- self.assertTrue(hasattr(clientapp, 'ssl_verify'),
- 'There is no ssl_verify in clientapp')
-
- def test_should_have_method_to_set_CA_CURL_CERT(self):
- self.assertTrue(clientapp.ssl_verify.__call__)
diff --git a/demos/jans-tent/tests/unit_integration/test_gluu_preselected_provider.py b/demos/jans-tent/tests/unit_integration/test_gluu_preselected_provider.py
deleted file mode 100644
index 4f7fc9dc13f..00000000000
--- a/demos/jans-tent/tests/unit_integration/test_gluu_preselected_provider.py
+++ /dev/null
@@ -1,46 +0,0 @@
-import clientapp
-from helper import FlaskBaseTestCase
-
-
-class TestPreselectedProvider(FlaskBaseTestCase):
-
- # """
- # We should be able to send Preselected passport provider to gluu OIDC as a authorization param
- # like this: preselectedExternalProvider=
- # Where is the Base64-encoded representation of a small JSON
- # content that looking like this:
- # { "provider" : }
- # """
- def setUp(self):
- clientapp.cfg.PRE_SELECTED_PROVIDER = True
- FlaskBaseTestCase.setUp(FlaskBaseTestCase)
-
- def test_config_should_have_preselected_provider_option(self):
- self.assertTrue(hasattr(clientapp.cfg, 'PRE_SELECTED_PROVIDER'),
- 'cfg doesnt have PRE_SELECTED_PROVIDER attribute')
-
- def test_config_pre_selected_provider_should_be_boolean(self):
- self.assertTrue(
- type(clientapp.cfg.PRE_SELECTED_PROVIDER) == bool,
- 'cfg.PRE_SELECTED_PROVIDER is not bool')
-
- def test_preselected_provider_id_should_exist_in_cfg(self):
- self.assertTrue(hasattr(clientapp.cfg, 'PRE_SELECTED_PROVIDER_ID'))
-
- def test_clientapp_should_have_get_preselected_provider(self):
- self.assertTrue(
- hasattr(clientapp, 'get_preselected_provider'),
- 'client app does not have get_preselected_provider attr')
-
- def test_get_preselected_provider_should_be_callable(self):
- self.assertTrue(callable(clientapp.get_preselected_provider),
- 'get_preselected_provider is not callable')
-
- def test_get_selected_provider_should_return_base64(self):
-
- clientapp.cfg.PRE_SELECTED_PROVIDER_ID = 'saml-emaillink'
- expected_response = "eyAicHJvdmlkZXIiIDogInNhbWwtZW1haWxsaW5rIiB9"
- self.assertEqual(clientapp.get_preselected_provider(),
- expected_response)
-
-
diff --git a/demos/jans-tent/tests/unit_integration/test_logout_endpoint.py b/demos/jans-tent/tests/unit_integration/test_logout_endpoint.py
deleted file mode 100644
index c0bb85bfc87..00000000000
--- a/demos/jans-tent/tests/unit_integration/test_logout_endpoint.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import clientapp
-from helper import FlaskBaseTestCase, app_endpoints
-from flask import url_for, session
-from urllib import parse
-from clientapp import config as cfg
-
-class TestLogoutEndpoint(FlaskBaseTestCase):
- def authenticated_session_mock(self):
- with self.client.session_transaction() as session:
- session['id_token'] = 'id_token_stub'
-
- def test_endpoint_exists(self):
- self.assertIn(
- 'logout',
- app_endpoints(clientapp.create_app())
- )
-
- def test_endpoint_should_require_authentication(self):
- ...
- def test_logout_endpoint_should_redirect_to_home_if_unauthenticated(self):
- # print(self.client.get(url_for('logout')).response)
- response = self.client.get(url_for('logout'))
- assert(response.status_code == 302)
- assert(response.location == url_for('index'))
-
-
- def test_logout_endpoint_should_clear_session(self):
- with self.client.session_transaction() as sess:
- sess['id_token'] = 'id_token_stub'
- sess['user'] = 'userinfo stub'
-
- with self.client:
- self.client.get(url_for('logout'))
- assert 'id_token' not in session
- assert 'user' not in session
-
- def test_endpoint_should_redirect_to_end_session_endpoint(self):
- with self.client.session_transaction() as session:
- session['id_token'] = 'id_token_stub'
- session['user'] = 'userinfo stub'
-
- response = self.client.get(url_for('logout'))
-
- parsed_location = parse.urlparse(response.location)
- assert parsed_location.scheme == 'https'
- assert parsed_location.netloc == 'ophostname.com'
- assert parsed_location.path == '/end_session_endpoint'
-
-
-
- def test_endpoint_should_redirect_to_end_session_endpoint_with_params(self):
- token_stub = 'id_token_stub'
- with self.client.session_transaction() as session:
- session['id_token'] = token_stub
- session['user'] = 'userinfo stub'
-
- parsed_redirect_uri = parse.urlparse(cfg.REDIRECT_URIS[0])
- post_logout_uri = '%s://%s' % (parsed_redirect_uri.scheme, parsed_redirect_uri.netloc)
-
- expected_query = 'post_logout_redirect_uri=%s&token_hint=%s' % (post_logout_uri, token_stub)
- response = self.client.get(url_for('logout'))
-
- parsed_location = parse.urlparse(response.location)
- assert parsed_location.query == expected_query
-
diff --git a/demos/jans-tent/tests/unit_integration/test_protected_content_endpoint.py b/demos/jans-tent/tests/unit_integration/test_protected_content_endpoint.py
deleted file mode 100644
index 68f7012c88e..00000000000
--- a/demos/jans-tent/tests/unit_integration/test_protected_content_endpoint.py
+++ /dev/null
@@ -1,68 +0,0 @@
-from clientapp import create_app, session
-from flask import Flask, url_for
-from typing import List
-from werkzeug import local
-from helper import FlaskBaseTestCase
-
-
-def app_endpoint(app: Flask) -> List[str]:
- """ Return all enpoints in app """
-
- endpoints = []
- for item in app.url_map.iter_rules():
- endpoint = item.endpoint.replace("_", "-")
- endpoints.append(endpoint)
- return endpoints
-
-
-class TestProtectedContentEndpoint(FlaskBaseTestCase):
- def test_app_should_contain_protected_content_route(self):
-
- endpoints = app_endpoint(create_app())
- self.assertIn('protected-content', endpoints,
- 'protected-content route not found in app endpoints')
-
- def test_app_protected_content_route_should_return_valid_requisition(self):
-
- self.client.get(url_for('protected_content'))
-
- self.assertIn(
- self.client.get(url_for('protected_content')).status_code,
- range(100, 511),
- 'protected content route returned invalid requisition')
-
- def test_should_return_if_session_exists_in_clientapp(self):
- import clientapp
- self.assertTrue(hasattr(clientapp, 'session'),
- "session is not an attribute of clientapp")
- del clientapp
-
- def test_should_check_if_session_is_LocalProxy_instance(self):
- self.assertIsInstance(session, local.LocalProxy)
-
- def test_protected_content_return_status_200_ir_session_profile_exists(
- self):
-
- with self.client.session_transaction() as sess:
- sess['user'] = 'foo'
-
- self.assertEqual(
- self.client.get(url_for('protected_content')).status_code, 200)
-
- def test_should_return_302_if_no_session_profile(self):
- self.assertEqual(
- self.client.get(url_for('protected_content')).status_code, 302)
-
- def test_protected_content_should_redirect_to_login_if_session_profile_doesnt_exist(
- self):
-
- response = self.client.get(url_for('protected_content'))
- self.assertTrue(response.location.endswith(url_for('login')),
- 'Protected page is not redirecting to login page')
-
- ''' TODO
- def test_should_return_if_user_logged_in_exists(self):
- self.assertTrue(
- hasattr(app,'user_logged_in')
- )
- '''
diff --git a/docs/janssen-server/developer/agama/quick-start-using-agama-lab.md b/docs/janssen-server/developer/agama/quick-start-using-agama-lab.md
index 57d0a9597f6..df56dce9e18 100644
--- a/docs/janssen-server/developer/agama/quick-start-using-agama-lab.md
+++ b/docs/janssen-server/developer/agama/quick-start-using-agama-lab.md
@@ -387,7 +387,7 @@ Server deployment
## Test
-1. [Setup](https://github.com/JanssenProject/jans/tree/main/demos/jans-tent) Janssen Tent
+1. [Setup](https://github.com/JanssenProject/jans/tree/v1.2.0/demos/jans-tent) Janssen Tent
2. Change the configuration as given below in `config.py`
```