diff --git a/.github/workflows/sonar.yaml b/.github/workflows/sonar.yaml index d0a30566..f5055259 100644 --- a/.github/workflows/sonar.yaml +++ b/.github/workflows/sonar.yaml @@ -26,7 +26,7 @@ jobs: - run: pytest --cov=yoti_python_sdk yoti_python_sdk/tests --cov-report=xml:coverage-reports/coverage-new.xml - - run: sed -i 's+.*+/home/travis/build/getyoti/yoti-python-sdk/yoti_python_sdk+g' coverage-reports/coverage-new.xml + - run: sed -i 's@'$GITHUB_WORKSPACE'@/github/workspace/@g' coverage-reports/coverage-new.xml - uses: sonarsource/sonarcloud-github-action@master env: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index ff260478..9506b63c 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -13,12 +13,12 @@ jobs: strategy: fail-fast: false matrix: - python-version: [2.7, 3.6, 3.7, 3.8, 3.9, "3.10-dev"] + python-version: [3.7, 3.8, 3.9, "3.10"] steps: - uses: actions/checkout@v2 - - uses: actions/setup-python@v2.1.4 + - uses: actions/setup-python@v2.3.1 with: python-version: ${{ matrix.python-version }} @@ -40,14 +40,16 @@ jobs: steps: - uses: actions/checkout@v2 - - uses: actions/setup-python@v2.1.4 + - uses: actions/setup-python@v2.3.1 + with: + python-version: 3.9 - - run: pip install -U setuptools==45 + - run: pip install --upgrade setuptools - run: pushd examples/aml && pip install -r requirements.txt && popd - - run: pushd examples/yoti_example_django && pip install -r requirements.txt && popd - + - run: pushd examples/yoti_example_django && pip install --upgrade pip && pip install -r requirements.txt && popd + - run: pushd examples/yoti_example_flask && pip install -r requirements.txt && popd - + - run: pushd examples/doc_scan && pip install -r requirements.txt && popd diff --git a/.gitignore b/.gitignore index 8a197872..022b5cb0 100644 --- a/.gitignore +++ b/.gitignore @@ -105,3 +105,4 @@ examples/yoti_example_django/*.pem examples/yoti_example_flask/*.pem .scannerwork +.venv/ diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6ed7baf9..c94a38e6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,13 @@ exclude: protobuf/ repos: - repo: https://github.com/ambv/black - rev: stable + rev: 22.3.0 hooks: - id: black - - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v1.2.3 + + - repo: https://github.com/PyCQA/flake8 + rev: 4.0.1 hooks: - id: flake8 args: - - --ignore=E501,W5 + - --ignore=E501,W5 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 180a9b1f..00000000 --- a/.travis.yml +++ /dev/null @@ -1,77 +0,0 @@ -language: python - -dist: xenial - -git: - depth: 3 - -jobs: - allow_failures: - - python: "3.8-dev" - include: - - &test - stage: Test - python: "2.7" - cache: pip - before_install: - - pip install -U setuptools - install: - - pip install -r requirements.txt - - pip install -e .[dev] - script: - - pytest -v - - <<: *test - python: "3.4" - - <<: *test - python: "3.5" - - <<: *test - python: "3.5-dev" - - <<: *test - python: "3.6" - - <<: *test - python: "3.6-dev" - - <<: *test - python: "3.7" - - <<: *test - python: "3.7-dev" - - <<: *test - python: "3.8-dev" - - <<: *test - python: "3.8" - - - stage: Check Examples - name: AML - python: "3.8" - script: - - cd ./examples/aml - - pip install -r requirements.txt - - name: Django - python: "3.8" - script: - - cd ./examples/yoti_example_django - - pip install -r requirements.txt - - name: Flask - python: "3.8" - script: - - cd ./examples/yoti_example_flask - - pip install -r requirements.txt - - name: Doc Scan - python: "3.8" - script: - - cd ./examples/doc_scan - - pip install -r requirements.txt - - - stage: Analyze - name: Sonarcloud - python: "3.8" - addons: - sonarcloud: - organization: "getyoti" - install: - - pip install -r requirements.txt - - pip install -e .[dev] - script: - - pytest --cov=yoti_python_sdk yoti_python_sdk/tests --cov-report=xml:coverage-reports/coverage-new.xml - - sed -i 's+.*+/home/travis/build/getyoti/yoti-python-sdk/yoti_python_sdk+g' coverage-reports/coverage-new.xml - - sonar-scanner - if: type = pull_request OR branch = master diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 86e2f477..51e32699 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,7 +7,7 @@ ## Adding Features Any features added must be fully tested and documented, with examples supplied in the pull request. -The feature must support the lowest Python version that the SDK supports (see [the travis file](.travis.yml) for all supported versions). The feature +The feature must support the lowest Python version that the SDK supports (see [the GitHub workflow tests file](./.github/workflows/tests.yaml) for all supported versions). The feature must not introduce any unnecessary dependencies (although introducing a new third party library is open for discussion if absolutely required). diff --git a/README.md b/README.md index 1014886c..d50a9736 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ Yoti also allows you to enable user details verification from your mobile app by ## Requirements -To see the versions of Python this SDK is compatible with, see the [.travis.yml](/.travis.yml) file. +To see the versions of Python this SDK is compatible with, see the [the GitHub workflow tests file](./.github/workflows/tests.yaml) file. ## Installing the SDK diff --git a/examples/aml/requirements.in b/examples/aml/requirements.in new file mode 100644 index 00000000..7e25054c --- /dev/null +++ b/examples/aml/requirements.in @@ -0,0 +1,2 @@ +yoti>=2.14.0 +python-dotenv>=0.7.1 diff --git a/examples/aml/requirements.txt b/examples/aml/requirements.txt index b6c39efd..33889910 100644 --- a/examples/aml/requirements.txt +++ b/examples/aml/requirements.txt @@ -1,2 +1,46 @@ -yoti>=2.13.0 -python-dotenv>=0.7.1 +# +# This file is autogenerated by pip-compile with python 3.10 +# To update, run: +# +# pip-compile --output-file=requirements.txt requirements.in +# +asn1==2.2.0 + # via yoti +certifi==2021.10.8 + # via requests +cffi==1.15.0 + # via cryptography +charset-normalizer==2.0.10 + # via requests +cryptography==36.0.1 + # via + # pyopenssl + # yoti +deprecated==1.2.10 + # via yoti +future==0.18.2 + # via yoti +idna==3.3 + # via requests +iso8601==0.1.13 + # via yoti +protobuf==3.19.3 + # via yoti +pycparser==2.21 + # via cffi +pyopenssl==21.0.0 + # via yoti +python-dotenv==0.19.2 + # via -r requirements.in +pytz==2020.4 + # via yoti +requests==2.27.1 + # via yoti +six==1.16.0 + # via pyopenssl +urllib3==1.26.8 + # via requests +wrapt==1.13.3 + # via deprecated +yoti==2.14.0 + # via -r requirements.in diff --git a/examples/doc_scan/app.py b/examples/doc_scan/app.py index 3de18b4d..d550b6a6 100644 --- a/examples/doc_scan/app.py +++ b/examples/doc_scan/app.py @@ -47,6 +47,7 @@ def create_session(): .with_preset_issuing_country("GBR") .with_success_url("{url}/success".format(url=YOTI_APP_BASE_URL)) .with_error_url("{url}/error".format(url=YOTI_APP_BASE_URL)) + .with_privacy_policy_url("{url}/privacy-policy".format(url=YOTI_APP_BASE_URL)) .build() ) @@ -173,5 +174,10 @@ def media(): ) +@app.route("/privacy-policy") +def privacy_policy(): + return render_template("privacy.html") + + if __name__ == "__main__": app.run() diff --git a/examples/doc_scan/requirements.in b/examples/doc_scan/requirements.in index 4bfbe87a..572ae2b7 100644 --- a/examples/doc_scan/requirements.in +++ b/examples/doc_scan/requirements.in @@ -1,5 +1,6 @@ flask>=1.1.2 python-dotenv>=0.13.0 -yoti>=2.13.0 +yoti>=2.14.0 filetype>=1.0.7 pyopenssl>=19.1.0 +six>=1.16.0 \ No newline at end of file diff --git a/examples/doc_scan/requirements.txt b/examples/doc_scan/requirements.txt index 38b136a4..f0dca95c 100644 --- a/examples/doc_scan/requirements.txt +++ b/examples/doc_scan/requirements.txt @@ -1,34 +1,69 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.10 # To update, run: # # pip-compile --output-file=requirements.txt requirements.in # -asn1==2.2.0 # via yoti -certifi==2020.4.5.1 # via requests -cffi==1.14.0 # via cryptography -chardet==3.0.4 # via requests -click==7.1.2 # via flask -cryptography==3.2 # via pyopenssl, yoti -deprecated==1.2.10 # via yoti -filetype==1.0.7 # via -r requirements.in -flask==1.1.2 # via -r requirements.in -future==0.18.2 # via yoti -idna==2.9 # via requests -iso8601==0.1.13 # via yoti -itsdangerous==1.1.0 # via flask -jinja2==2.11.2 # via flask -markupsafe==1.1.1 # via jinja2 -protobuf==3.11.3 # via yoti -pycparser==2.20 # via cffi -pyopenssl==19.1.0 # via -r requirements.in, yoti -python-dotenv==0.13.0 # via -r requirements.in -requests==2.23.0 # via yoti -six==1.14.0 # via cryptography, protobuf, pyopenssl -urllib3==1.25.9 # via requests -werkzeug==1.0.1 # via flask -wrapt==1.12.1 # via deprecated -yoti==2.13.0 # via -r requirements.in +asn1==2.2.0 + # via yoti +certifi==2020.4.5.1 + # via requests +cffi==1.14.0 + # via cryptography +chardet==3.0.4 + # via requests +click==7.1.2 + # via flask +cryptography==3.2 + # via + # pyopenssl + # yoti +deprecated==1.2.10 + # via yoti +filetype==1.0.7 + # via -r requirements.in +flask==1.1.2 + # via -r requirements.in +future==0.18.2 + # via yoti +idna==2.9 + # via requests +iso8601==0.1.13 + # via yoti +itsdangerous==1.1.0 + # via flask +jinja2==2.11.2 + # via flask +markupsafe==1.1.1 + # via jinja2 +protobuf==3.11.3 + # via yoti +pycparser==2.20 + # via cffi +pyopenssl==19.1.0 + # via + # -r requirements.in + # yoti +python-dotenv==0.13.0 + # via -r requirements.in +pytz==2020.4 + # via yoti +requests==2.23.0 + # via yoti +six==1.16.0 + # via + # -r requirements.in + # cryptography + # protobuf + # pyopenssl +urllib3==1.25.9 + # via requests +werkzeug==1.0.1 + # via flask +wrapt==1.12.1 + # via deprecated +yoti==2.14.0 + # via -r requirements.in # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/examples/doc_scan/templates/privacy.html b/examples/doc_scan/templates/privacy.html new file mode 100644 index 00000000..f3b4c54a --- /dev/null +++ b/examples/doc_scan/templates/privacy.html @@ -0,0 +1,10 @@ +{% include "layout/header.html" %} +
+
+
+

Privacy Policy

+

Demo privacy policy

+
+
+
+{% include "layout/footer.html" %} \ No newline at end of file diff --git a/examples/yoti_example_django/Dockerfile b/examples/yoti_example_django/Dockerfile index 50c2bf9b..0eed957f 100644 --- a/examples/yoti_example_django/Dockerfile +++ b/examples/yoti_example_django/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.7-slim +FROM python:3.9-slim ARG YOTI_SCENARIO_ID ARG YOTI_CLIENT_SDK_ID ARG YOTI_KEY_FILE_PATH diff --git a/examples/yoti_example_django/requirements.in b/examples/yoti_example_django/requirements.in index 6210c53b..884d3bd9 100644 --- a/examples/yoti_example_django/requirements.in +++ b/examples/yoti_example_django/requirements.in @@ -1,6 +1,8 @@ -django>=3.0.7 +django>=4.0.1 django-sslserver>=0.22.0 python-dotenv>=0.7.1 requests>=2.20.0 urllib3>=1.24.2 -yoti>=2.13.0 +yoti>=2.14.0 +six>=1.16.0 +cffi>=1.15.0 \ No newline at end of file diff --git a/examples/yoti_example_django/requirements.txt b/examples/yoti_example_django/requirements.txt index 743d3e65..d966b31c 100644 --- a/examples/yoti_example_django/requirements.txt +++ b/examples/yoti_example_django/requirements.txt @@ -1,5 +1,5 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.10 # To update, run: # # pip-compile --output-file=requirements.txt requirements.in @@ -8,10 +8,14 @@ asgiref==3.4.1 # via django asn1==2.2.0 # via yoti +backports.zoneinfo==0.2.1 + # via django certifi==2018.4.16 # via requests -cffi==1.14.0 - # via cryptography +cffi==1.15.0 + # via + # -r requirements.in + # cryptography chardet==3.0.4 # via requests cryptography==3.2 @@ -20,7 +24,7 @@ cryptography==3.2 # yoti deprecated==1.2.10 # via yoti -django==3.1.12 +django==4.0.1 # via # -r requirements.in # django-sslserver @@ -40,14 +44,15 @@ pyopenssl==18.0.0 # via yoti python-dotenv==0.8.2 # via -r requirements.in -pytz==2018.4 - # via django +pytz==2020.4 + # via yoti requests==2.21.0 # via # -r requirements.in # yoti -six==1.11.0 +six==1.16.0 # via + # -r requirements.in # cryptography # protobuf # pyopenssl @@ -59,7 +64,7 @@ urllib3==1.24.2 # requests wrapt==1.12.1 # via deprecated -yoti==2.13.0 +yoti==2.14.0 # via -r requirements.in # The following packages are considered to be unsafe in a requirements file: diff --git a/examples/yoti_example_django/yoti_example/urls.py b/examples/yoti_example_django/yoti_example/urls.py index f8063333..77688930 100644 --- a/examples/yoti_example_django/yoti_example/urls.py +++ b/examples/yoti_example_django/yoti_example/urls.py @@ -13,17 +13,17 @@ 1. Import the include() function: from django.conf.urls import url, include 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) """ -from django.conf.urls import url +from django.urls import re_path from django.contrib import admin from .views import IndexView, AuthView, DynamicShareView, SourceConstraintsView urlpatterns = [ - url(r"^$", IndexView.as_view(), name="index"), - url(r"^yoti/auth/$", AuthView.as_view(), name="auth"), - url(r"^admin/", admin.site.urls), - url(r"^dynamic-share/$", DynamicShareView.as_view(), name="dynamic-share"), - url( + re_path(r"^$", IndexView.as_view(), name="index"), + re_path(r"^yoti/auth/$", AuthView.as_view(), name="auth"), + re_path(r"^admin/", admin.site.urls), + re_path(r"^dynamic-share/$", DynamicShareView.as_view(), name="dynamic-share"), + re_path( r"^source-constraint/$", SourceConstraintsView.as_view(), name="source-constraints", diff --git a/examples/yoti_example_flask/Dockerfile b/examples/yoti_example_flask/Dockerfile index a590e43a..4ec911ef 100644 --- a/examples/yoti_example_flask/Dockerfile +++ b/examples/yoti_example_flask/Dockerfile @@ -1,4 +1,4 @@ -FROM python:3.6.3 +FROM python:3.9.12 ARG YOTI_SCENARIO_ID ARG YOTI_CLIENT_SDK_ID ARG YOTI_KEY_FILE_PATH diff --git a/examples/yoti_example_flask/README.md b/examples/yoti_example_flask/README.md index 2589ede5..b63600f1 100644 --- a/examples/yoti_example_flask/README.md +++ b/examples/yoti_example_flask/README.md @@ -2,5 +2,5 @@ 1. Rename the [.env.example](.env.example) file to `.env` and fill in the required configuration values 1. Install dependencies: `pip install -r requirements.txt` -1. Run `python app.py` +1. Run `python -m flask run` 1. Navigate to https://localhost:5000 \ No newline at end of file diff --git a/examples/yoti_example_flask/requirements.in b/examples/yoti_example_flask/requirements.in index 89c202b6..89d0b0ee 100644 --- a/examples/yoti_example_flask/requirements.in +++ b/examples/yoti_example_flask/requirements.in @@ -1,9 +1,11 @@ -cffi>=1.14.0 +click>=7 +cffi>=1.15.0 flask>=1.0.4 -jinja2>=2.8.1 +jinja2>=3.0.3 pyopenssl>=19.0.0 python-dotenv>=0.7.1 requests>=2.20.0 urllib3>=1.24.2 -yoti>=2.13.0 +yoti>=2.14.0 werkzeug>=1.0.1 +six==1.16.0 \ No newline at end of file diff --git a/examples/yoti_example_flask/requirements.txt b/examples/yoti_example_flask/requirements.txt index a5a293a8..81f2e9f6 100644 --- a/examples/yoti_example_flask/requirements.txt +++ b/examples/yoti_example_flask/requirements.txt @@ -1,33 +1,79 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.9 # To update, run: # # pip-compile --output-file=requirements.txt requirements.in # -asn1==2.2.0 # via yoti -certifi==2018.4.16 # via requests -cffi==1.14.0 # via -r requirements.in, cryptography -chardet==3.0.4 # via requests -click==6.7 # via flask -cryptography==3.2 # via pyopenssl, yoti -deprecated==1.2.10 # via yoti -flask==1.1.1 # via -r requirements.in -future==0.16.0 # via yoti -idna==2.7 # via requests -iso8601==0.1.13 # via yoti -itsdangerous==0.24 # via flask -jinja2==2.10.1 # via -r requirements.in, flask -markupsafe==1.0 # via jinja2 -protobuf==3.6.0 # via yoti -pycparser==2.18 # via cffi -pyopenssl==19.0.0 # via -r requirements.in, yoti -python-dotenv==0.8.2 # via -r requirements.in -requests==2.21.0 # via -r requirements.in, yoti -six==1.11.0 # via cryptography, protobuf, pyopenssl -urllib3==1.24.2 # via -r requirements.in, requests -werkzeug==1.0.1 # via -r requirements.in, flask -wrapt==1.12.1 # via deprecated -yoti==2.13.0 # via -r requirements.in +asn1==2.2.0 + # via yoti +certifi==2018.4.16 + # via requests +cffi==1.15.0 + # via + # -r requirements.in + # cryptography +chardet==3.0.4 + # via requests +click==8.1.2 + # via + # -r requirements.in + # flask +cryptography==3.2 + # via + # pyopenssl + # yoti +deprecated==1.2.10 + # via yoti +flask==1.1.1 + # via -r requirements.in +future==0.16.0 + # via yoti +idna==2.7 + # via requests +iso8601==0.1.13 + # via yoti +itsdangerous==0.24 + # via flask +jinja2==3.0.3 + # via + # -r requirements.in + # flask +markupsafe==2.0.1 + # via jinja2 +protobuf==3.6.0 + # via yoti +pycparser==2.18 + # via cffi +pyopenssl==19.0.0 + # via + # -r requirements.in + # yoti +python-dotenv==0.8.2 + # via -r requirements.in +pytz==2020.4 + # via yoti +requests==2.21.0 + # via + # -r requirements.in + # yoti +six==1.16.0 + # via + # -r requirements.in + # cryptography + # protobuf + # pyopenssl +urllib3==1.24.2 + # via + # -r requirements.in + # requests +werkzeug==1.0.1 + # via + # -r requirements.in + # flask +wrapt==1.12.1 + # via deprecated +yoti==2.14.0 + # via -r requirements.in # The following packages are considered to be unsafe in a requirements file: # setuptools diff --git a/requirements.in b/requirements.in index a229f183..e3096e0a 100644 --- a/requirements.in +++ b/requirements.in @@ -1,15 +1,16 @@ asn1==2.2.0 # asn1 2.3.0 introduces enum34 as a dependency, which causes problems on some envs cryptography==2.8.0 -cffi==1.14.3 +cffi==1.15.0 future==0.18.2 -itsdangerous==1.1.0 +itsdangerous==2.0.1 pbr==1.10.0 -protobuf==3.13.0 +protobuf==3.19.4 pyopenssl==19.1.0 PyYAML==5.2 # PyYAML 5.3 does not support Python 3.4 -pytz==2020.4 +pytz==2022.1 requests>=2.20.0 urllib3>=1.24.3 -deprecated==1.2.10 -wheel==0.33.6 -iso8601==0.1.13 +deprecated==1.2.13 +wheel==0.37.1 +iso8601==1.0.2 +six>=1.16.0 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index b8c0490b..ea88377b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,30 +1,57 @@ # -# This file is autogenerated by pip-compile +# This file is autogenerated by pip-compile with python 3.9 # To update, run: # # pip-compile --output-file=requirements.txt requirements.in # -asn1==2.2.0 # via -r requirements.in -certifi==2018.11.29 # via requests -cffi==1.14.3 # via -r requirements.in, cryptography -chardet==3.0.4 # via requests -cryptography==2.8 # via -r requirements.in, pyopenssl -deprecated==1.2.10 # via -r requirements.in -future==0.18.2 # via -r requirements.in -idna==2.7 # via requests -iso8601==0.1.13 # via -r requirements.in -itsdangerous==1.1.0 # via -r requirements.in -pbr==1.10.0 # via -r requirements.in -protobuf==3.13.0 # via -r requirements.in -pycparser==2.18 # via cffi -pyopenssl==19.1.0 # via -r requirements.in -pytz==2020.4 # via -r requirements.in -pyyaml==5.2 # via -r requirements.in -requests==2.21.0 # via -r requirements.in -six==1.10.0 # via cryptography, protobuf, pyopenssl -urllib3==1.24.3 # via -r requirements.in, requests -wheel==0.33.6 # via -r requirements.in -wrapt==1.11.2 # via deprecated - -# The following packages are considered to be unsafe in a requirements file: -# setuptools +asn1==2.2.0 + # via -r requirements.in +certifi==2018.11.29 + # via requests +cffi==1.15.0 + # via + # -r requirements.in + # cryptography +chardet==3.0.4 + # via requests +cryptography==2.8 + # via + # -r requirements.in + # pyopenssl +deprecated==1.2.13 + # via -r requirements.in +future==0.18.2 + # via -r requirements.in +idna==2.7 + # via requests +iso8601==1.0.2 + # via -r requirements.in +itsdangerous==2.0.1 + # via -r requirements.in +pbr==1.10.0 + # via -r requirements.in +protobuf==3.19.4 + # via -r requirements.in +pycparser==2.18 + # via cffi +pyopenssl==19.1.0 + # via -r requirements.in +pytz==2022.1 + # via -r requirements.in +pyyaml==5.2 + # via -r requirements.in +requests==2.21.0 + # via -r requirements.in +six==1.16.0 + # via + # -r requirements.in + # cryptography + # pyopenssl +urllib3==1.24.3 + # via + # -r requirements.in + # requests +wheel==0.37.1 + # via -r requirements.in +wrapt==1.11.2 + # via deprecated diff --git a/setup.py b/setup.py index 4f3033a6..7fba8798 100644 --- a/setup.py +++ b/setup.py @@ -18,15 +18,16 @@ author="Yoti", author_email="websdk@yoti.com", install_requires=[ - "deprecated==1.2.10", + "deprecated==1.2.13", "cryptography>=2.2.1", "protobuf>=3.1.0", "requests>=2.11.1", - "future>=0.11.0", + "future>=0.18.2", "asn1==2.2.0", "pyopenssl>=18.0.0", - "iso8601==0.1.13", - "pytz==2020.4", + "iso8601==1.0.2", + "wheel==0.37.1", + "pytz==2021.3", ], extras_require={ "examples": [ @@ -34,18 +35,20 @@ "Flask>=1.0.4", "python-dotenv>=0.7.1", "django-sslserver>=0.22.0", - "Werkzeug==1.0.1", + "Werkzeug==2.0.2", ], "dev": [ - "pre-commit==1.17.0", - "pytest>=4.6.11", + "pre-commit==2.16.0", + "pytest>=7.1.2", "pytest-cov>=2.7.1", "pylint==1.9.4", "pylint-exit>=1.1.0", "python-coveralls==2.9.3", "coverage==4.5.4", "mock==2.0.0", - "virtualenv==20.1.0", + "virtualenv==20.13.0", + "flake8==4.0.1", + "pip-tools==6.6.0", ], }, classifiers=[ @@ -54,13 +57,12 @@ "Operating System :: OS Independent", "Intended Audience :: Developers", "Programming Language :: Python", - "Programming Language :: Python :: 2", - "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.4", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", "Topic :: Software Development :: Libraries :: Python Modules", ], keywords="yoti sdk 2FA multifactor authentication verification identity login register verify 2Factor", diff --git a/sonar-project.properties b/sonar-project.properties index b8f41b51..f77622f8 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -2,7 +2,7 @@ sonar.host.url = https://sonarcloud.io sonar.organization = getyoti sonar.projectKey = getyoti:python sonar.projectName = Python SDK -sonar.projectVersion = 2.14.0 +sonar.projectVersion = 2.15.0 sonar.exclusions = yoti_python_sdk/tests/**,examples/**,yoti_python_sdk/protobuf/**/* sonar.python.pylint.reportPath = coverage.out diff --git a/yoti_python_sdk/__init__.py b/yoti_python_sdk/__init__.py index a33747d5..e2084235 100644 --- a/yoti_python_sdk/__init__.py +++ b/yoti_python_sdk/__init__.py @@ -1,8 +1,7 @@ # -*- coding: utf-8 -*- import os -from distutils.util import convert_path from os import environ - +from .version import __version__ from yoti_python_sdk.client import Client DEFAULTS = { @@ -17,11 +16,8 @@ directory_name = os.path.dirname(__file__) version_path = os.path.join(directory_name, "version.py") -ver_path = convert_path(version_path) -with open(ver_path) as ver_file: - exec(ver_file.read(), main_ns) +exec(open(version_path).read()) -__version__ = main_ns["__version__"] YOTI_API_URL = environ.get("YOTI_API_URL", DEFAULTS["YOTI_API_URL"]) YOTI_PROFILE_ENDPOINT = "/api/v1" diff --git a/yoti_python_sdk/doc_scan/constants.py b/yoti_python_sdk/doc_scan/constants.py index 6aa18d1d..279d4e5e 100644 --- a/yoti_python_sdk/doc_scan/constants.py +++ b/yoti_python_sdk/doc_scan/constants.py @@ -36,3 +36,5 @@ IGNORE = "IGNORE" PROOF_OF_ADDRESS = "PROOF_OF_ADDRESS" + +WATCHLIST_SCREENING_CHECK_TYPE = "WATCHLIST_SCREENING" diff --git a/yoti_python_sdk/doc_scan/session/create/check/__init__.py b/yoti_python_sdk/doc_scan/session/create/check/__init__.py index c0c38292..89d5689f 100644 --- a/yoti_python_sdk/doc_scan/session/create/check/__init__.py +++ b/yoti_python_sdk/doc_scan/session/create/check/__init__.py @@ -2,10 +2,13 @@ from .document_comparison import RequestedIDDocumentComparisonCheckBuilder from .face_match import RequestedFaceMatchCheckBuilder from .liveness import RequestedLivenessCheckBuilder +from .watchlist_screen import WatchlistScreeningCheckBuilder + __all__ = [ "RequestedDocumentAuthenticityCheckBuilder", "RequestedIDDocumentComparisonCheckBuilder", "RequestedFaceMatchCheckBuilder", "RequestedLivenessCheckBuilder", + "WatchlistScreeningCheckBuilder", ] diff --git a/yoti_python_sdk/doc_scan/session/create/check/watchlist_screen.py b/yoti_python_sdk/doc_scan/session/create/check/watchlist_screen.py new file mode 100644 index 00000000..d686c816 --- /dev/null +++ b/yoti_python_sdk/doc_scan/session/create/check/watchlist_screen.py @@ -0,0 +1,106 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals + +from yoti_python_sdk.doc_scan import constants +from yoti_python_sdk.utils import YotiSerializable, remove_null_values +from .requested_check import RequestedCheck + + +class WatchlistScreeningCheckConfig(YotiSerializable): + """ + The configuration applied when creating a Watchlist screening check. + """ + + def __init__(self, manual_check, categories): + """ + :param manual_check: the watchlist screening check manual_check eg. "NEVER" + :type type: str + :param categories: list of categories for watchlist screening check config + :type max_retries: list + """ + self.__categories = categories + self.__manual_check = manual_check + + @property + def manual_check(self): + """ + Watchlist screening check manual check value + + :return: str + """ + return self.__manual_check + + @property + def categories(self): + """ + Watchlist screening check categories + + :return: list + """ + return self.__categories + + def to_json(self): + return remove_null_values( + {"manual_check": self.manual_check, "categories": self.__categories} + ) + + +class WatchlistScreeningCheck(RequestedCheck): + """ + Requests creation of a Watchlist screening check + """ + + def __init__(self, config): + """ + :param config: the Watchlist screening check configuration + :type config: WatchlistScreeningCheckConfig + """ + self.__config = config + + @property + def type(self): + return constants.WATCHLIST_SCREENING_CHECK_TYPE + + @property + def config(self): + return self.__config + + +class WatchlistScreeningCheckBuilder(object): + """ + Builder to assist creation of :class:`WatchlistScreeningCheck` + """ + + def __init__(self): + self.__categories = None + self.__manual_check = None + + def with_categories(self, categories): + """ + Sets the WatchListScreeningCheck categories + + :return: the builder + :rtype: WatchlistScreeningCheckBuilder + """ + self.__categories = categories + + return self + + def with_manual_check(self, manual_check): + """ + Sets the WatchListScreeningCheck manual_check + + :param liveness_type: the manual_check + :type liveness_type: str + :return: the builder + :rtype: WatchlistScreeningCheckBuilder + """ + self.__manual_check = manual_check + + return self + + def build(self): + config = WatchlistScreeningCheckConfig( + self.__manual_check, self.__categories or [] + ) + return WatchlistScreeningCheck(config) diff --git a/yoti_python_sdk/doc_scan/session/create/filter/orthogonal_restrictions_filter.py b/yoti_python_sdk/doc_scan/session/create/filter/orthogonal_restrictions_filter.py index 6e0bcf4e..63d634ae 100644 --- a/yoti_python_sdk/doc_scan/session/create/filter/orthogonal_restrictions_filter.py +++ b/yoti_python_sdk/doc_scan/session/create/filter/orthogonal_restrictions_filter.py @@ -68,11 +68,14 @@ def to_json(self): class OrthogonalRestrictionsFilter(DocumentFilter): - def __init__(self, country_restriction, type_restriction): + def __init__( + self, country_restriction, type_restriction, allow_non_latin_documents=None + ): DocumentFilter.__init__(self, filter_type=ORTHOGONAL_RESTRICTIONS) self.__country_restriction = country_restriction self.__type_restriction = type_restriction + self.__allow_non_latin_documents = allow_non_latin_documents @property def country_restriction(self): @@ -94,10 +97,23 @@ def type_restriction(self): """ return self.__type_restriction + @property + def allow_non_latin_documents(self): + """ + Returns the flag for whether non-latin documents are allowed. + + :return: allow_non_latin_documents + :rtype: bool + """ + return self.__allow_non_latin_documents + def to_json(self): parent = DocumentFilter.to_json(self) parent["country_restriction"] = self.country_restriction parent["type_restriction"] = self.type_restriction + if self.__allow_non_latin_documents is not None: + parent["allow_non_latin_documents"] = self.__allow_non_latin_documents + return remove_null_values(parent) @@ -117,6 +133,7 @@ class OrthogonalRestrictionsFilterBuilder(object): def __init__(self): self.__country_restriction = None self.__type_restriction = None + self.__allow_non_latin_documents = None def with_whitelisted_country_codes(self, country_codes): """ @@ -170,6 +187,26 @@ def with_blacklisted_document_types(self, document_types): self.__type_restriction = TypeRestriction(INCLUSION_BLACKLIST, document_types) return self + def allow_non_latin_documents(self): + """ + Sets a True value for "allow non-latin documents" flag. + + :return: the builder + :rtype: OrthogonalRestrictionsFilterBuilder + """ + self.__allow_non_latin_documents = True + return self + + def disable_non_latin_documents(self): + """ + Sets a False value for "allow non-latin documents" flag. + + :return: the builder + :rtype: OrthogonalRestrictionsFilterBuilder + """ + self.__allow_non_latin_documents = False + return self + def build(self): """ Builds the orthogonal filter, using the supplied whitelisted/blacklisted values @@ -178,5 +215,7 @@ def build(self): :rtype: OrthogonalRestrictionsFilter """ return OrthogonalRestrictionsFilter( - self.__country_restriction, self.__type_restriction + self.__country_restriction, + self.__type_restriction, + self.__allow_non_latin_documents, ) diff --git a/yoti_python_sdk/doc_scan/session/create/notification_config.py b/yoti_python_sdk/doc_scan/session/create/notification_config.py index 476a38b0..d89a5ce0 100644 --- a/yoti_python_sdk/doc_scan/session/create/notification_config.py +++ b/yoti_python_sdk/doc_scan/session/create/notification_config.py @@ -16,7 +16,7 @@ class NotificationConfig(YotiSerializable): to poll for the state of the Session. """ - def __init__(self, auth_token, endpoint, topics=None): + def __init__(self, auth_token, endpoint, topics=None, auth_type=None): """ :param auth_token: the authorization token :type auth_token: str @@ -31,6 +31,7 @@ def __init__(self, auth_token, endpoint, topics=None): self.__auth_token = auth_token self.__endpoint = endpoint self.__topics = list(set(topics)) # Get unique values + self.__auth_type = auth_type @property def auth_token(self): @@ -62,9 +63,21 @@ def topics(self): """ return self.__topics + @property + def auth_type(self): + """ + The authentication type that the notification will use to + authenticate itself. + + :return: the endpoint + :rtype: str + """ + return self.__auth_type + def to_json(self): return remove_null_values( { + "auth_type": self.auth_type, "auth_token": self.auth_token, "endpoint": self.endpoint, "topics": self.topics, @@ -81,6 +94,7 @@ def __init__(self): self.__auth_token = None self.__endpoint = None self.__topics = [] + self.__auth_type = None def with_auth_token(self, token): """ @@ -154,6 +168,26 @@ def for_check_completion(self): """ return self.with_topic(CHECK_COMPLETION) + def with_basic_auth_type(self): + """ + Setup "BASIC" auth type for notifications. + + :return: the builder + :rtype: NotificationConfigBuilder + """ + self.__auth_type = "BASIC" + return self + + def with_bearer_auth_type(self): + """ + Setup "BEARER" auth type for notifications. + + :return: the builder + :rtype: NotificationConfigBuilder + """ + self.__auth_type = "BEARER" + return self + def build(self): """ Builds the :class:`NotificationConfig` using the supplied values @@ -161,4 +195,6 @@ def build(self): :return: the build notification config :rtype: NotificationConfig """ - return NotificationConfig(self.__auth_token, self.__endpoint, self.__topics) + return NotificationConfig( + self.__auth_token, self.__endpoint, self.__topics, self.__auth_type + ) diff --git a/yoti_python_sdk/doc_scan/session/create/sdk_config.py b/yoti_python_sdk/doc_scan/session/create/sdk_config.py index c76fe897..37cde6b5 100644 --- a/yoti_python_sdk/doc_scan/session/create/sdk_config.py +++ b/yoti_python_sdk/doc_scan/session/create/sdk_config.py @@ -21,6 +21,8 @@ def __init__( preset_issuing_country, success_url, error_url, + allow_handoff=None, + privacy_policy_url=None, ): """ :param allowed_capture_methods: the allowed capture methods @@ -39,6 +41,10 @@ def __init__( :type success_url: str :param error_url: the error url :type error_url: str + :param privacy_policy_url: the privacy policy url + :type privacy_policy_url: str + :param allow_handoff: boolean flag for allow_handoff + :type allow_handoff: bool """ self.__allowed_capture_methods = allowed_capture_methods self.__primary_colour = primary_colour @@ -48,6 +54,8 @@ def __init__( self.__preset_issuing_country = preset_issuing_country self.__success_url = success_url self.__error_url = error_url + self.__privacy_policy_url = privacy_policy_url + self.__allow_handoff = allow_handoff @property def allowed_capture_methods(self): @@ -121,6 +129,25 @@ def error_url(self): """ return self.__error_url + @property + def privacy_policy_url(self): + """ + The privacy policy URL + + :return: the privacy policy url + """ + return self.__privacy_policy_url + + @property + def allow_handoff(self): + """ + Flag to enable/disable relying business to handoff + support when creating a session. + + :return: the allow_handoff + """ + return self.__allow_handoff + def to_json(self): return remove_null_values( { @@ -132,6 +159,8 @@ def to_json(self): "preset_issuing_country": self.preset_issuing_country, "success_url": self.success_url, "error_url": self.error_url, + "privacy_policy_url": self.privacy_policy_url, + "allow_handoff": self.allow_handoff, } ) @@ -150,6 +179,8 @@ def __init__(self): self.__preset_issuing_country = None self.__success_url = None self.__error_url = None + self.__privacy_policy_url = None + self.__allow_handoff = None def with_allowed_capture_methods(self, allowed_capture_methods): """ @@ -265,6 +296,30 @@ def with_error_url(self, url): self.__error_url = url return self + def with_privacy_policy_url(self, url): + """ + Sets the privacy policy URL + + :param url: the privacy policy URL + :type url: str + :return: the builder + :rtype: SdkConfigBuilder + """ + self.__privacy_policy_url = url + return self + + def with_allow_handoff(self, flag): + """ + Sets the allow handoff flag + + :param flag: boolean value for flag + :type flag: bool + :return: the builder + :rtype: SdkConfigBuilder + """ + self.__allow_handoff = flag + return self + def build(self): return SdkConfig( self.__allowed_capture_methods, @@ -275,4 +330,6 @@ def build(self): self.__preset_issuing_country, self.__success_url, self.__error_url, + self.__allow_handoff, + self.__privacy_policy_url, ) diff --git a/yoti_python_sdk/doc_scan/session/create/session_spec.py b/yoti_python_sdk/doc_scan/session/create/session_spec.py index ef3bbce4..75a2b8be 100644 --- a/yoti_python_sdk/doc_scan/session/create/session_spec.py +++ b/yoti_python_sdk/doc_scan/session/create/session_spec.py @@ -21,6 +21,7 @@ def __init__( requested_tasks=None, required_documents=None, block_biometric_consent=None, + session_deadline=None, ): """ :param client_session_token_ttl: the client session token TTL @@ -41,6 +42,8 @@ def __init__( :type required_documents: list[RequiredDocument] or None :param block_biometric_consent: block the collection of biometric consent :type block_biometric_consent: bool + :param session_deadline: session deadline using a Zoned timestamp + "type session_deadline: str """ if requested_tasks is None: requested_tasks = [] @@ -58,6 +61,7 @@ def __init__( self.__requested_tasks = requested_tasks self.__required_documents = required_documents self.__block_biometric_consent = block_biometric_consent + self.__session_deadline = session_deadline @property def client_session_token_ttl(self): @@ -152,6 +156,16 @@ def block_biometric_consent(self): """ return self.__block_biometric_consent + @property + def session_deadline(self): + """ + Session deadline used by IDV + + :return: session deadline + :rtype: str + """ + return self.__session_deadline + def to_json(self): return remove_null_values( { @@ -164,6 +178,7 @@ def to_json(self): "sdk_config": self.sdk_config, "required_documents": self.required_documents, "block_biometric_consent": self.block_biometric_consent, + "session_deadline": self.session_deadline, } ) @@ -183,6 +198,7 @@ def __init__(self): self.__requested_tasks = [] self.__required_documents = [] self.__block_biometric_consent = None + self.__session_deadline = None def with_client_session_token_ttl(self, value): """ @@ -196,6 +212,19 @@ def with_client_session_token_ttl(self, value): self.__client_session_token_ttl = value return self + def with_session_deadline(self, value): + """ + Sets the deadline that the session needs to be completed by. + Can be used as an alternative to with_client_session_token_ttl. + + :param value: the session deadline + :type value: str + :return: the builder + :rtype: SessionSpecBuilder + """ + self.__session_deadline = value + return self + def with_resources_ttl(self, value): """ Sets the resources TTL (time-to-live) @@ -309,4 +338,5 @@ def build(self): self.__requested_tasks, self.__required_documents, self.__block_biometric_consent, + self.__session_deadline, ) diff --git a/yoti_python_sdk/doc_scan/session/create/subcheck/__init__.py b/yoti_python_sdk/doc_scan/session/create/subcheck/__init__.py new file mode 100644 index 00000000..2e248d33 --- /dev/null +++ b/yoti_python_sdk/doc_scan/session/create/subcheck/__init__.py @@ -0,0 +1,5 @@ +from .issuing_authority_sub_check import IssuingAuthoritySubCheckBuilder + +__all__ = [ + "IssuingAuthoritySubCheckBuilder", +] diff --git a/yoti_python_sdk/doc_scan/session/create/subcheck/issuing_authority_sub_check.py b/yoti_python_sdk/doc_scan/session/create/subcheck/issuing_authority_sub_check.py new file mode 100644 index 00000000..f39b426f --- /dev/null +++ b/yoti_python_sdk/doc_scan/session/create/subcheck/issuing_authority_sub_check.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- + +from yoti_python_sdk.doc_scan.session.create.filter.document_filter import ( + DocumentFilter, +) +from yoti_python_sdk.utils import YotiSerializable +from .sub_check import SubRequestedCheck + + +class IssuingAuthoritySubCheck(SubRequestedCheck): + """ + Requests creation of an Issuing Authority Sub Check. + """ + + def __init__(self, filter=None): + self._filter = filter + + @property + def requested(self): + return True + + @property + def filter(self): + return self._filter + + +class IssuingAuthoritySubCheckBuilder: + """ + Builder for Issuing Authority Sub Check. + """ + + def __init__(self): + self._filter = None + + def with_filter(self, filter): + if not issubclass(type(filter), DocumentFilter): + raise ValueError("invalid filter") + + self._filter = filter + + return self + + def build(self): + return IssuingAuthoritySubCheck(filter=self._filter) diff --git a/yoti_python_sdk/doc_scan/session/create/subcheck/sub_check.py b/yoti_python_sdk/doc_scan/session/create/subcheck/sub_check.py new file mode 100644 index 00000000..d4fa882d --- /dev/null +++ b/yoti_python_sdk/doc_scan/session/create/subcheck/sub_check.py @@ -0,0 +1,20 @@ +from abc import ABCMeta +from abc import abstractmethod + +from yoti_python_sdk.utils import YotiSerializable + + +class SubRequestedCheck(YotiSerializable): + """ + Requests creation of a SubCheck to be performed on a document + """ + + __metaclass__ = ABCMeta + + @property + @abstractmethod + def type(self): + raise NotImplementedError + + def to_json(self): + return remove_null_values({"type": self.type, "config": self.config}) diff --git a/yoti_python_sdk/doc_scan/session/retrieve/check_response.py b/yoti_python_sdk/doc_scan/session/retrieve/check_response.py index f1cb515d..f05c4cf5 100644 --- a/yoti_python_sdk/doc_scan/session/retrieve/check_response.py +++ b/yoti_python_sdk/doc_scan/session/retrieve/check_response.py @@ -184,3 +184,11 @@ class SupplementaryDocumentTextDataCheckResponse(CheckResponse): """ pass + + +class WatchlistScreeningCheckResponse(CheckResponse): + """ + Represents a watchlist screening check for a given session + """ + + pass diff --git a/yoti_python_sdk/doc_scan/session/retrieve/create_session_result.py b/yoti_python_sdk/doc_scan/session/retrieve/create_session_result.py index c64dd9da..2990f473 100644 --- a/yoti_python_sdk/doc_scan/session/retrieve/create_session_result.py +++ b/yoti_python_sdk/doc_scan/session/retrieve/create_session_result.py @@ -16,6 +16,7 @@ def __init__(self, data=None): data = dict() self.__client_session_token_ttl = data.get("client_session_token_ttl", None) + self.__session_deadline = data.get("session_deadline", None) self.__session_id = data.get("session_id", None) self.__client_session_token = data.get("client_session_token", None) @@ -49,3 +50,13 @@ def session_id(self): :rtype: str or None """ return self.__session_id + + @property + def session_deadline(self): + """ + The deadline that the session needs to be completed by. + + :return: the session deadline + :rtype: str or None + """ + return self.__session_deadline diff --git a/yoti_python_sdk/doc_scan/session/retrieve/get_session_result.py b/yoti_python_sdk/doc_scan/session/retrieve/get_session_result.py index f39eed98..1d995d04 100644 --- a/yoti_python_sdk/doc_scan/session/retrieve/get_session_result.py +++ b/yoti_python_sdk/doc_scan/session/retrieve/get_session_result.py @@ -16,6 +16,7 @@ LivenessCheckResponse, TextDataCheckResponse, SupplementaryDocumentTextDataCheckResponse, + WatchlistScreeningCheckResponse, ) from .resource_container import ResourceContainer @@ -80,6 +81,7 @@ def __parse_check(check): constants.ID_DOCUMENT_FACE_MATCH: FaceMatchCheckResponse, constants.ID_DOCUMENT_TEXT_DATA_CHECK: TextDataCheckResponse, constants.LIVENESS: LivenessCheckResponse, + constants.WATCHLIST_SCREENING_CHECK_TYPE: WatchlistScreeningCheckResponse, constants.ID_DOCUMENT_COMPARISON: IDDocumentComparisonCheckResponse, constants.SUPPLEMENTARY_DOCUMENT_TEXT_DATA_CHECK: SupplementaryDocumentTextDataCheckResponse, } diff --git a/yoti_python_sdk/doc_scan/support/supported_documents.py b/yoti_python_sdk/doc_scan/support/supported_documents.py index 55505b78..a35a21e1 100644 --- a/yoti_python_sdk/doc_scan/support/supported_documents.py +++ b/yoti_python_sdk/doc_scan/support/supported_documents.py @@ -4,11 +4,16 @@ def __init__(self, data=None): data = dict() self.__type = data.get("type", None) + self.__is_strictly_latin = data.get("is_strictly_latin", None) @property def type(self): return self.__type + @property + def is_strictly_latin(self): + return self.__is_strictly_latin + class SupportedCountry(object): def __init__(self, data=None): diff --git a/yoti_python_sdk/tests/doc_scan/session/create/check/test_wathclist_check.py b/yoti_python_sdk/tests/doc_scan/session/create/check/test_wathclist_check.py new file mode 100644 index 00000000..3bfec82b --- /dev/null +++ b/yoti_python_sdk/tests/doc_scan/session/create/check/test_wathclist_check.py @@ -0,0 +1,104 @@ +import json +import unittest + +from yoti_python_sdk.doc_scan import constants +from yoti_python_sdk.doc_scan.session.create.check import ( + WatchlistScreeningCheckBuilder, +) +from yoti_python_sdk.doc_scan.session.create.check.requested_check import RequestedCheck +from yoti_python_sdk.doc_scan.session.create.check.watchlist_screen import ( + WatchlistScreeningCheck, + WatchlistScreeningCheckConfig, +) +from yoti_python_sdk.utils import YotiEncoder +from yoti_python_sdk.doc_scan.constants import WATCHLIST_SCREENING_CHECK_TYPE + + +class WatchlistScreeningCheckTest(unittest.TestCase): + def test_should_build_correctly_with_manual_check(self): + dummy_manual_check = "DUMMY_VALUE" + + result = ( + WatchlistScreeningCheckBuilder() + .with_manual_check(dummy_manual_check) + .build() + ) + + assert isinstance(result, RequestedCheck) + assert isinstance(result, WatchlistScreeningCheck) + + assert result.type == constants.WATCHLIST_SCREENING_CHECK_TYPE + assert result.config.manual_check == dummy_manual_check + assert result.config.categories == [] + + def test_should_build_corretly_with_categories(self): + dummy_categories = ["FIRST", "SECOND"] + + result = ( + WatchlistScreeningCheckBuilder().with_categories(dummy_categories).build() + ) + + assert isinstance(result, RequestedCheck) + assert isinstance(result, WatchlistScreeningCheck) + + assert result.type == constants.WATCHLIST_SCREENING_CHECK_TYPE + assert result.config.categories == dummy_categories + assert result.config.manual_check is None + + def test_should_build_correctly_with_manual_check_and_categories(self): + dummy_manual_check = "DUMMY_VALUE" + dummy_categories = ["FIRST", "SECOND"] + + result = ( + WatchlistScreeningCheckBuilder() + .with_manual_check(dummy_manual_check) + .with_categories(dummy_categories) + .build() + ) + + assert isinstance(result, RequestedCheck) + assert isinstance(result, WatchlistScreeningCheck) + + assert result.type == constants.WATCHLIST_SCREENING_CHECK_TYPE + assert result.config.manual_check == dummy_manual_check + assert result.config.categories == dummy_categories + + def test_should_serialize_to_json_without_error(self): + another_dummy_manual_check = "DUMMY_VALUE" + another_dummy_categories = ["FIRST", "SECOND"] + + result = ( + WatchlistScreeningCheckBuilder() + .with_manual_check(another_dummy_manual_check) + .with_categories(another_dummy_categories) + .build() + ) + + s = json.dumps(result, cls=YotiEncoder) + assert s is not None and s != "" + + result = ( + WatchlistScreeningCheckBuilder() + .with_categories(another_dummy_categories) + .build() + ) + + s = json.dumps(result, cls=YotiEncoder) + assert s is not None and s != "" + + s = json.loads(s) + assert s.get("type") == WATCHLIST_SCREENING_CHECK_TYPE + assert s.get("config") == {"categories": another_dummy_categories} + + result = ( + WatchlistScreeningCheckBuilder() + .with_manual_check(another_dummy_manual_check) + .build() + ) + + s = json.dumps(result, cls=YotiEncoder) + assert s is not None and s != "" + + s = json.loads(s) + assert s.get("type") == WATCHLIST_SCREENING_CHECK_TYPE + assert s.get("config") == {"manual_check": "DUMMY_VALUE", "categories": []} diff --git a/yoti_python_sdk/tests/doc_scan/session/create/subcheck/__init__.py b/yoti_python_sdk/tests/doc_scan/session/create/subcheck/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/yoti_python_sdk/tests/doc_scan/session/create/subcheck/test_issuing_authority_sub_check.py b/yoti_python_sdk/tests/doc_scan/session/create/subcheck/test_issuing_authority_sub_check.py new file mode 100644 index 00000000..677dfc40 --- /dev/null +++ b/yoti_python_sdk/tests/doc_scan/session/create/subcheck/test_issuing_authority_sub_check.py @@ -0,0 +1,67 @@ +import unittest + +from yoti_python_sdk.doc_scan.session.create.filter.orthogonal_restrictions_filter import ( + OrthogonalRestrictionsFilterBuilder, +) +from yoti_python_sdk.doc_scan.session.create.subcheck import ( + IssuingAuthoritySubCheckBuilder, +) +from yoti_python_sdk.doc_scan.session.create.subcheck.issuing_authority_sub_check import ( + IssuingAuthoritySubCheck, +) + + +class IssuingAuthoritySubCheckTest(unittest.TestCase): + def test_should_build_correctly_without_additional_data(self): + issuing_authority_sub_check = IssuingAuthoritySubCheckBuilder().build() + + assert isinstance(issuing_authority_sub_check, IssuingAuthoritySubCheck) + + def test_should_build_correctly_with_filter(self): + filter = ( + OrthogonalRestrictionsFilterBuilder() + .with_whitelisted_country_codes(["GBR", "FRA"]) + .with_whitelisted_document_types(["PASSPORT", "STATE_ID"]) + .build() + ) + + issuing_authority_sub_check = ( + IssuingAuthoritySubCheckBuilder().with_filter(filter=filter).build() + ) + + assert isinstance(issuing_authority_sub_check, IssuingAuthoritySubCheck) + assert issuing_authority_sub_check.filter == filter + + def test_should_always_build_with_requested_as_boolean_true(self): + issuing_authority_sub_check = IssuingAuthoritySubCheckBuilder().build() + + assert issuing_authority_sub_check.requested is True + + def test_allow_non_latin_documents_set_to_true(self): + filter = ( + OrthogonalRestrictionsFilterBuilder().allow_non_latin_documents().build() + ) + + assert filter.allow_non_latin_documents is True + + def test_allow_non_latin_documents_set_to_false(self): + filter = ( + OrthogonalRestrictionsFilterBuilder().disable_non_latin_documents().build() + ) + + assert filter.allow_non_latin_documents is False + + def test_default_non_latin_documents(self): + filter = OrthogonalRestrictionsFilterBuilder().build() + + assert "allow_non_latin_documents" not in filter.to_json() + + def test_build_invalid_filter(self): + filter = "invalid" + + with self.assertRaises(ValueError): + IssuingAuthoritySubCheckBuilder().with_filter(filter=filter).build() + + +if __name__ == "__main__": + unittest.main() diff --git a/yoti_python_sdk/tests/doc_scan/session/create/test_notification_config.py b/yoti_python_sdk/tests/doc_scan/session/create/test_notification_config.py index f207ff14..1fa09b29 100644 --- a/yoti_python_sdk/tests/doc_scan/session/create/test_notification_config.py +++ b/yoti_python_sdk/tests/doc_scan/session/create/test_notification_config.py @@ -12,6 +12,7 @@ class NotificationConfigTest(unittest.TestCase): SOME_AUTH_TOKEN = "someAuthToken" SOME_ENDPOINT = "someEndpoint" SOME_TOPIC = "someTopic" + SOME_AUTH_TYPE = "someAuthType" def test_should_build_correctly(self): result = ( @@ -26,6 +27,7 @@ def test_should_build_correctly(self): assert result.auth_token is self.SOME_AUTH_TOKEN assert result.endpoint is self.SOME_ENDPOINT assert self.SOME_TOPIC in result.topics + assert result.auth_type is None def test_should_add_resource_update_topic(self): result = ( @@ -104,6 +106,30 @@ def test_should_store_unique_topics(self): assert len(result.topics) == 1 + def test_build_with_basic_auth_type(self): + result = ( + NotificationConfigBuilder() + .with_auth_token(self.SOME_AUTH_TOKEN) + .with_endpoint(self.SOME_ENDPOINT) + .with_topic(self.SOME_TOPIC) + .with_basic_auth_type() + .build() + ) + + assert result.auth_type is "BASIC" + + def test_build_with_bearer_auth_type(self): + result = ( + NotificationConfigBuilder() + .with_auth_token(self.SOME_AUTH_TOKEN) + .with_endpoint(self.SOME_ENDPOINT) + .with_topic(self.SOME_TOPIC) + .with_bearer_auth_type() + .build() + ) + + assert result.auth_type is "BEARER" + def test_should_serialize_to_json_without_error(self): result = ( NotificationConfigBuilder() diff --git a/yoti_python_sdk/tests/doc_scan/session/create/test_sdk_config.py b/yoti_python_sdk/tests/doc_scan/session/create/test_sdk_config.py index 6de7b4ca..d621a441 100644 --- a/yoti_python_sdk/tests/doc_scan/session/create/test_sdk_config.py +++ b/yoti_python_sdk/tests/doc_scan/session/create/test_sdk_config.py @@ -14,6 +14,8 @@ class SdkConfigTest(unittest.TestCase): SOME_PRESET_ISSUING_COUNTRY = "USA" SOME_SUCCESS_URL = "https://mysite.com/yoti/success" SOME_ERROR_URL = "https://mysite.com/yoti/error" + SOME_PRIVACY_POLICY_URL = "https://mysite.com/privacy" + SOME_ALLOW_HANDOFF = True def test_should_build_correctly(self): result = ( @@ -26,6 +28,8 @@ def test_should_build_correctly(self): .with_preset_issuing_country(self.SOME_PRESET_ISSUING_COUNTRY) .with_success_url(self.SOME_SUCCESS_URL) .with_error_url(self.SOME_ERROR_URL) + .with_privacy_policy_url(self.SOME_PRIVACY_POLICY_URL) + .with_allow_handoff(self.SOME_ALLOW_HANDOFF) .build() ) @@ -38,12 +42,24 @@ def test_should_build_correctly(self): assert result.preset_issuing_country is self.SOME_PRESET_ISSUING_COUNTRY assert result.success_url is self.SOME_SUCCESS_URL assert result.error_url is self.SOME_ERROR_URL + assert result.privacy_policy_url is self.SOME_PRIVACY_POLICY_URL + assert result.allow_handoff is True def test_should_allows_camera(self): result = SdkConfigBuilder().with_allows_camera().build() assert result.allowed_capture_methods == "CAMERA" + def test_not_passing_allow_handoff(self): + result = SdkConfigBuilder().with_allows_camera().build() + + assert result.allow_handoff is None + + def test_passing_allow_handoff_false_value(self): + result = SdkConfigBuilder().with_allow_handoff(False).build() + + assert result.allow_handoff is False + def test_should_serialize_to_json_without_error(self): result = ( SdkConfigBuilder() @@ -55,6 +71,7 @@ def test_should_serialize_to_json_without_error(self): .with_preset_issuing_country(self.SOME_PRESET_ISSUING_COUNTRY) .with_success_url(self.SOME_SUCCESS_URL) .with_error_url(self.SOME_ERROR_URL) + .with_privacy_policy_url(self.SOME_PRIVACY_POLICY_URL) .build() ) diff --git a/yoti_python_sdk/tests/doc_scan/session/create/test_session_spec.py b/yoti_python_sdk/tests/doc_scan/session/create/test_session_spec.py index 41f0a5e5..034c4b05 100644 --- a/yoti_python_sdk/tests/doc_scan/session/create/test_session_spec.py +++ b/yoti_python_sdk/tests/doc_scan/session/create/test_session_spec.py @@ -21,6 +21,7 @@ class SessionSpecTest(unittest.TestCase): SOME_CLIENT_SESSION_TOKEN_TTL = 300 SOME_RESOURCES_TTL = 100000 SOME_USER_TRACKING_ID = "someUserTrackingId" + SOME_SESSION_DEADLINE = "2021-09-03T11:40:54.727619+02:00" def test_should_build_correctly(self): sdk_config_mock = Mock(spec=SdkConfig) @@ -31,6 +32,7 @@ def test_should_build_correctly(self): result = ( SessionSpecBuilder() .with_client_session_token_ttl(self.SOME_CLIENT_SESSION_TOKEN_TTL) + .with_session_deadline(self.SOME_SESSION_DEADLINE) .with_resources_ttl(self.SOME_RESOURCES_TTL) .with_user_tracking_id(self.SOME_USER_TRACKING_ID) .with_notifications(notification_mock) @@ -49,6 +51,7 @@ def test_should_build_correctly(self): assert requested_check_mock in result.requested_checks assert len(result.requested_tasks) == 1 assert requested_task_mock in result.requested_tasks + assert result.session_deadline == self.SOME_SESSION_DEADLINE def test_should_serialize_to_json_without_error(self): sdk_config_mock = Mock(spec=SdkConfig) @@ -66,6 +69,7 @@ def test_should_serialize_to_json_without_error(self): result = ( SessionSpecBuilder() .with_client_session_token_ttl(self.SOME_CLIENT_SESSION_TOKEN_TTL) + .with_session_deadline(self.SOME_SESSION_DEADLINE) .with_resources_ttl(self.SOME_RESOURCES_TTL) .with_user_tracking_id(self.SOME_USER_TRACKING_ID) .with_notifications(notification_mock) diff --git a/yoti_python_sdk/tests/doc_scan/support/test_supported_documents.py b/yoti_python_sdk/tests/doc_scan/support/test_supported_documents.py index 762d509d..a2887318 100644 --- a/yoti_python_sdk/tests/doc_scan/support/test_supported_documents.py +++ b/yoti_python_sdk/tests/doc_scan/support/test_supported_documents.py @@ -18,6 +18,24 @@ def test_supported_document_should_not_throw_exception_on_missing_data(): assert result.type is None +def test_supported_document_created_with_is_strictly_latin_as_true(): + result = SupportedDocument({"is_strictly_latin": True}) + + assert result.is_strictly_latin is True + + +def test_supported_document_created_with_is_strictly_latin_as_false(): + result = SupportedDocument({"is_strictly_latin": False}) + + assert result.is_strictly_latin is False + + +def test_supported_document_created_without_is_strictly_latin(): + result = SupportedDocument({"type": "someSupportedDocument"}) + + assert result.is_strictly_latin is None + + def test_supported_country_should_parse_data(): data = { "code": "someCode", diff --git a/yoti_python_sdk/tests/dynamic_sharing_service/extension/test_third_party_attribute_extension.py b/yoti_python_sdk/tests/dynamic_sharing_service/extension/test_third_party_attribute_extension.py index 7c7a57ae..1bf054ba 100644 --- a/yoti_python_sdk/tests/dynamic_sharing_service/extension/test_third_party_attribute_extension.py +++ b/yoti_python_sdk/tests/dynamic_sharing_service/extension/test_third_party_attribute_extension.py @@ -117,9 +117,18 @@ def test_should_format_utc_expiry_dates_correctly(expiry_date, expected_value): @pytest.mark.parametrize( "expiry_date, tz_name", [ - (datetime(2030, 6, 6, 8, 0, 0, 0), "US/Eastern",), - (datetime(2030, 6, 6, 15, 0, 0, 0), "Europe/Moscow",), - (datetime(2030, 6, 6, 7, 0, 0, 0), "America/Jamaica",), + ( + datetime(2030, 6, 6, 8, 0, 0, 0), + "US/Eastern", + ), + ( + datetime(2030, 6, 6, 15, 0, 0, 0), + "Europe/Moscow", + ), + ( + datetime(2030, 6, 6, 7, 0, 0, 0), + "America/Jamaica", + ), (datetime(2030, 6, 6, 23, 0, 0, 0), "Etc/GMT-11"), (datetime(2030, 6, 6, 7, 0, 0, 0), "Etc/GMT+5"), # In order to conform with the POSIX style, those zones beginning diff --git a/yoti_python_sdk/tests/test_activity_details.py b/yoti_python_sdk/tests/test_activity_details.py index 1735b030..1fc349f1 100644 --- a/yoti_python_sdk/tests/test_activity_details.py +++ b/yoti_python_sdk/tests/test_activity_details.py @@ -85,7 +85,7 @@ def create_age_verified_field( ): activity_details.field = lambda: None activity_details.field.name = ( - "age_over:{0}".format(age) if over is True else "age_under:".format(age) + "age_over:{0}".format(age) if over is True else "age_under:{0}".format(age) ) activity_details.field.value = encoded_string_verified_value activity_details.field.content_type = Protobuf.CT_STRING diff --git a/yoti_python_sdk/tests/test_anchor.py b/yoti_python_sdk/tests/test_anchor.py index 0adf48aa..7305d6fa 100644 --- a/yoti_python_sdk/tests/test_anchor.py +++ b/yoti_python_sdk/tests/test_anchor.py @@ -1,13 +1,14 @@ # -*- coding: utf-8 -*- import logging +import pytz import time from datetime import datetime - -from yoti_python_sdk.protobuf.attribute_public_api import Attribute_pb2 +from datetime import timedelta import yoti_python_sdk from yoti_python_sdk import config from yoti_python_sdk.anchor import Anchor +from yoti_python_sdk.protobuf.attribute_public_api import Attribute_pb2 from yoti_python_sdk.tests import anchor_fixture_parser @@ -118,11 +119,10 @@ def test_processing_unknown_anchor_data(): (anchor.value, anchor.anchor_type, anchor.sub_type) for anchor in anchors ] - expected_timestamp = datetime(2019, 3, 5, 10, 45, 11, 840037) - actual_timestamp = anchors[0].signed_timestamp + expected_timestamp = datetime(2019, 3, 5, 10, 45, 11, 840037, tzinfo=pytz.utc) + actual_timestamp = anchors[0].signed_timestamp.astimezone(pytz.utc) assert expected_timestamp == actual_timestamp - assert "document-registration-server" in [ a.value for a in anchors[0].origin_server_certs.issuer ] diff --git a/yoti_python_sdk/version.py b/yoti_python_sdk/version.py index 18b49b72..f697b811 100644 --- a/yoti_python_sdk/version.py +++ b/yoti_python_sdk/version.py @@ -1,2 +1,2 @@ # -*- coding: utf-8 -*- -__version__ = "2.14.0" +__version__ = "2.15.0"