[
  {
    "path": ".codecov.yml",
    "content": "# Docs: https://docs.codecov.io/docs/codecovyml-reference\n\ncodecov:\n  require_ci_to_pass: yes\n\ncoverage:\n  precision: 1\n  round: down\n  status:\n    project:\n      default:\n        target: auto\n    patch: no\n    changes: no\n\ncomment:\n  layout: \"diff,files\"\n  require_changes: yes\n"
  },
  {
    "path": ".coveragerc",
    "content": "[run]\nsource = django_auth_adfs\n"
  },
  {
    "path": ".editorconfig",
    "content": "# http://editorconfig.org\n\nroot = true\n\n[*]\nindent_style = space\nindent_size = 4\ninsert_final_newline = true\ntrim_trailing_whitespace = true\nend_of_line = lf\ncharset = utf-8\n\n# Use 2 spaces for the HTML files\n[*.html]\nindent_size = 2\n\n# The JSON files contain newlines inconsistently\n[*.json]\nindent_size = 2\ninsert_final_newline = ignore\n\n[**/admin/js/vendor/**]\nindent_style = ignore\nindent_size = ignore\n\n# Minified JavaScript files shouldn't be changed\n[**.min.js]\nindent_style = ignore\ninsert_final_newline = ignore\n\n# Makefiles always use tabs for indentation\n[Makefile]\nindent_style = tab\n\n# Batch files use tabs for indentation\n[*.bat]\nindent_style = tab\n\n[*.txt]\ninsert_final_newline = false\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [jobec, jonasks, sondrelg]\n"
  },
  {
    "path": ".github/workflows/codecov.yml",
    "content": "name: coverage\n\non:\n  pull_request:\n  push:\n    branches:\n      - main\n\njobs:\n  codecov:\n    # ---------------------------------------------------\n    #    Documentation and examples can be found at\n    #      https://github.com/snok/install-poetry\n    # ---------------------------------------------------\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v3\n    - uses: actions/setup-python@v5\n      with:\n        python-version: \"3.10.5\"\n    - name: Install poetry\n      uses: snok/install-poetry@v1\n      with:\n        virtualenvs-in-project: true\n    - name: Load cached venv\n      id: cached-poetry-dependencies\n      uses: actions/cache@v3\n      with:\n        path: .venv\n        key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}-1\n    - name: Update poetry lock\n      run: poetry lock\n      if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'\n    - name: Install dependencies\n      run: poetry install\n      if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'\n    - name: Test with Django test\n      run: |\n        poetry run coverage run manage.py test -v 2\n        poetry run coverage xml\n    - name: Upload coverage\n      uses: codecov/codecov-action@v2\n      with:\n        file: ./coverage.xml\n        fail_ci_if_error: true\n"
  },
  {
    "path": ".github/workflows/publish_to_pypi.yml",
    "content": "name: Publish django-auth-adfs to PyPI 📦\n\non:\n  release:\n    types: [published]\n\njobs:\n  build-and-publish:\n    name: Build and publish\n    runs-on: ubuntu-latest\n    steps:\n    - uses: actions/checkout@v3\n    - name: Set up Python 3.9\n      uses: actions/setup-python@v5\n      with:\n        python-version: 3.9\n    - name: Install poetry\n      uses: snok/install-poetry@v1\n    - name: Build and publish\n      run: |\n        poetry config pypi-token.pypi ${{ secrets.pypi_password }}\n        poetry publish --build --no-interaction\n"
  },
  {
    "path": ".github/workflows/testing.yml",
    "content": "name: test\n\non:\n  pull_request:\n  push:\n    branches:\n      - main\n\njobs:\n  linting:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v3\n      - uses: actions/setup-python@v5\n        with:\n          python-version: \"3\"\n      - uses: actions/cache@v3\n        with:\n          path: ~/.cache/pip\n          key: ${{ runner.os }}-pip\n          restore-keys: |\n            ${{ runner.os }}-pip-\n            ${{ runner.os }}-\n      - run: python -m pip install flake8\n      - run: |\n          flake8 .\n\n  test:\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        python-version: [ \"3.9\", \"3.10\", \"3.11\", \"3.12\", \"3.13\"]\n        django-version: [ \"4.2\", \"5.0\", \"5.1\", \"5.2\"]\n        drf-version: [\"3.14\", \"3.15\", \"3.16\"]\n        exclude:\n          # Python 3.9 is incompatible with Django v5+\n          - django-version: 5.0\n            python-version: 3.9\n          - django-version: 5.1\n            python-version: 3.9\n          - django-version: 5.2\n            python-version: 3.9\n          # Django 4.2 is incompatible with Python 3.13+\n          - django-version: 4.2\n            python-version: 3.13\n          #Django 6 is incompatible with < Pyhton 3.12\n          -   django-version: 6.0\n              python-version: 3.9\n          -   django-version: 6.0\n              python-version: 3.10\n          -   django-version: 6.0\n              python-version: 3.11\n\n    steps:\n      - uses: actions/checkout@v3\n      - uses: actions/setup-python@v5\n        with:\n          python-version: ${{ matrix.python-version }}\n          allow-prereleases: true\n      - uses: snok/install-poetry@v1\n        with:\n          virtualenvs-in-project: true\n      - name: Load cached venv\n        id: cached-poetry-dependencies\n        uses: actions/cache@v3\n        with:\n          path: .venv\n          key: ${{ hashFiles('**/poetry.lock') }}-${{ matrix.python-version }}-0\n      - run: poetry env use ${{ matrix.python-version }} && poetry lock  && poetry install --no-root\n        if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'\n      - run: |\n          source .venv/bin/activate\n          pip install \"Django~=${{ matrix.django-version }}.0a1\"\n          pip install \"djangorestframework~=${{ matrix.drf-version }}.0\"\n      - name: Run tests\n        run: |\n          source .venv/bin/activate\n          poetry run coverage run manage.py test -v 2\n          poetry run coverage report -m\n"
  },
  {
    "path": ".gitignore",
    "content": "# based on:\n# https://github.com/github/gitignore/blob/master/Python.gitignore\n# https://www.jetbrains.com/pycharm/help/managing-projects-under-version-control.html\n\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nenv/\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\n*.egg-info/\n.installed.cfg\n*.egg\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*,cover\n.hypothesis/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\ndb.sqlite3\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# PyCharm\n# https://www.jetbrains.com/pycharm/help/managing-projects-under-version-control.html\n.idea/\n\n# Virtual env\n.venv/\n\n# Vagrant\n.vagrant/\n"
  },
  {
    "path": ".readthedocs.yaml",
    "content": "# Read the Docs configuration file\n# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details\n\n# Required\nversion: 2\n\n# Set the version of Python and other tools you might need\nbuild:\n  os: ubuntu-22.04\n  tools:\n    python: \"3.11\"\n\n# Build documentation in the docs/ directory with Sphinx\nsphinx:\n  configuration: docs/conf.py\n\n# We recommend specifying your dependencies to enable reproducible builds:\n# https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html\npython:\n  install:\n  - requirements: docs/requirements.txt\n"
  },
  {
    "path": "CONTRIBUTING.rst",
    "content": "============\nContributing\n============\n\nContributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.\n\nGet Started!\n------------\n\nTypes of Contributions\n----------------------\nYou can contribute in many ways:\n\nReport Bugs\n~~~~~~~~~~~\n\nReport bugs in the issue section of the repository on GitHub.\n\nIf you are reporting a bug, please include:\n\n* Detailed steps to reproduce the bug.\n* Any details about your local setup that might be helpful in troubleshooting.\n\nFix Bugs\n~~~~~~~~\n\nLook through the issues for bugs. Anything tagged with \"bug\" is open to whoever wants to implement it.\n\nImplement Features\n~~~~~~~~~~~~~~~~~~\n\nLook through the issues for features. Anything tagged with \"feature\" is open to whoever wants to implement it.\n\nWrite Documentation\n~~~~~~~~~~~~~~~~~~~\n\nWe could always use more documentation, whether as part of the docs or in docstrings in the code.\n\nSubmit Feedback\n~~~~~~~~~~~~~~~\n\nThe best way to send feedback is to file an issue on GitHub.\n\nIf you are proposing a feature:\n\n* Explain in detail how it would work.\n* Keep the scope as narrow as possible, to make it easier to implement.\n\nSet up your environment\n~~~~~~~~~~~~~~~~~~~~~~~\n1. Fork the upstream django-auth-adfs repository into a personal account.\n\n2. Install poetry running ``pip install poetry`` or ``curl -sSL https://raw.githubusercontent.com/python-poetry/poetry/master/get-poetry.py | python -``\n\n3. Configure poetry to create a virtual environment in your project folder: ``poetry config virtualenvs.in-project true``\n\n3. Install dependencies by running ``poetry install``\n\n4. Create a new branch for your changes\n\n5. Push the topic branch to your personal fork\n\n6. Create a pull request to the django-auth-adfs repository with a detailed explanation\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2016, Joris Beckers\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n  list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice, this\n  list of conditions and the following disclaimer in the documentation and/or\n  other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "MANIFEST.in",
    "content": "include CONTRIBUTING.rst\ninclude README.rst\n\nrecursive-include tests *\nrecursive-exclude * __pycache__\nrecursive-exclude * *.py[co]\n\nrecursive-include django_auth_adfs/templates *.html\nrecursive-include docs *.rst conf.py Makefile make.bat\nrecursive-include docs/_static *\nrecursive-include docs/_templates *\n"
  },
  {
    "path": "README.rst",
    "content": "ADFS Authentication for Django\n==============================\n\n.. image:: https://readthedocs.org/projects/django-auth-adfs/badge/?version=latest\n    :target: http://django-auth-adfs.readthedocs.io/en/latest/?badge=latest\n    :alt: Documentation Status\n.. image:: https://img.shields.io/pypi/v/django-auth-adfs.svg\n    :target: https://pypi.python.org/pypi/django-auth-adfs\n.. image:: https://img.shields.io/pypi/pyversions/django-auth-adfs.svg\n    :target: https://pypi.python.org/pypi/django-auth-adfs#downloads\n.. image:: https://img.shields.io/pypi/djversions/django-auth-adfs.svg\n    :target: https://pypi.python.org/pypi/django-auth-adfs\n.. image:: https://codecov.io/github/snok/django-auth-adfs/coverage.svg?branch=main\n    :target: https://codecov.io/github/snok/django-auth-adfs?branch=main\n\nA Django authentication backend for Microsoft ADFS and Azure AD\n\n* Free software: BSD License\n* Homepage: https://github.com/snok/django-auth-adfs\n* Documentation: http://django-auth-adfs.readthedocs.io/\n\nFeatures\n--------\n\n* Integrates Django with Active Directory on Windows 2012 R2, 2016 or Azure AD in the cloud.\n* Provides seamless single sign on (SSO) for your Django project on intranet environments.\n* Auto creates users and adds them to Django groups based on info received from ADFS.\n* Django Rest Framework (DRF) integration: Authenticate against your API with an ADFS access token.\n\nInstallation\n------------\n\nPython package::\n\n    pip install django-auth-adfs\n\nIn your project's ``settings.py`` add these settings.\n\n.. code-block:: python\n\n    AUTHENTICATION_BACKENDS = (\n        ...\n        'django_auth_adfs.backend.AdfsAuthCodeBackend',\n        ...\n    )\n\n    INSTALLED_APPS = (\n        ...\n        # Needed for the ADFS redirect URI to function\n        'django_auth_adfs',\n        ...\n\n    # checkout the documentation for more settings\n    AUTH_ADFS = {\n        \"SERVER\": \"adfs.yourcompany.com\",\n        \"CLIENT_ID\": \"your-configured-client-id\",\n        \"RELYING_PARTY_ID\": \"your-adfs-RPT-name\",\n        # Make sure to read the documentation about the AUDIENCE setting\n        # when you configured the identifier as a URL!\n        \"AUDIENCE\": \"microsoft:identityserver:your-RelyingPartyTrust-identifier\",\n        \"CA_BUNDLE\": \"/path/to/ca-bundle.pem\",\n        \"CLAIM_MAPPING\": {\"first_name\": \"given_name\",\n                          \"last_name\": \"family_name\",\n                          \"email\": \"email\"},\n    }\n\n    # Configure django to redirect users to the right URL for login\n    LOGIN_URL = \"django_auth_adfs:login\"\n    LOGIN_REDIRECT_URL = \"/\"\n\n    ########################\n    # OPTIONAL SETTINGS\n    ########################\n\n    MIDDLEWARE = (\n        ...\n        # With this you can force a user to login without using\n        # the LoginRequiredMixin on every view class\n        #\n        # You can specify URLs for which login is not enforced by\n        # specifying them in the LOGIN_EXEMPT_URLS setting.\n        'django_auth_adfs.middleware.LoginRequiredMiddleware',\n    )\n\nIn your project's ``urls.py`` add these paths:\n\n.. code-block:: python\n\n    urlpatterns = [\n        ...\n        path('oauth2/', include('django_auth_adfs.urls')),\n    ]\n\nThis will add these paths to Django:\n\n* ``/oauth2/login`` where users are redirected to, to initiate the login with ADFS.\n* ``/oauth2/login_no_sso`` where users are redirected to, to initiate the login with ADFS but forcing a login screen.\n* ``/oauth2/callback`` where ADFS redirects back to after login. So make sure you set the redirect URI on ADFS to this.\n* ``/oauth2/logout`` which logs out the user from both Django and ADFS.\n\nBelow is sample Django template code to use these paths depending if\nyou'd like to use GET or POST requests. Logging out was deprecated in\n`Django 4.1 <https://docs.djangoproject.com/en/5.1/releases/4.1/#features-deprecated-in-4-1>`_.\n\n- Using GET requests:\n\n    .. code-block:: html\n\n        <a href=\"{% url 'django_auth_adfs:logout' %}\">Logout</a>\n        <a href=\"{% url 'django_auth_adfs:login' %}\">Login</a>\n        <a href=\"{% url 'django_auth_adfs:login-no-sso' %}\">Login (no SSO)</a>\n\n- Using POST requests:\n\n    .. code-block:: html+django\n\n        <form method=\"post\" action=\"{% url 'django_auth_adfs:logout' %}\">\n            {% csrf_token %}\n            <button type=\"submit\">Logout</button>\n        </form>\n        <form method=\"post\" action=\"{% url 'django_auth_adfs:login' %}\">\n            {% csrf_token %}\n            <input type=\"hidden\" name=\"next\" value=\"{{ next }}\">\n            <button type=\"submit\">Login</button>\n        </form>\n        <form method=\"post\" action=\"{% url 'django_auth_adfs:login-no-sso' %}\">\n            {% csrf_token %}\n            <input type=\"hidden\" name=\"next\" value=\"{{ next }}\">\n            <button type=\"submit\">Login (no SSO)</button>\n        </form>\n\nContributing\n------------\nContributions to the code are more then welcome.\nFor more details have a look at the ``CONTRIBUTING.rst`` file.\n"
  },
  {
    "path": "Vagrantfile",
    "content": "dir = File.expand_path(\"..\", __FILE__)\n\nVagrant.configure(\"2\") do |config|\n  config.vagrant.plugins = \"vagrant-reload\"\n\n  config.vm.define \"adfs\", autostart: false do |adfs|\n    adfs.vm.hostname = \"adfs\"\n    adfs.vm.box = \"StefanScherer/windows_2019\"\n\n    adfs.vm.provider \"virtualbox\" do |v|\n      v.memory = 2048\n      v.gui = true\n      v.customize [\"modifyvm\", :id, \"--clipboard\", \"bidirectional\"]\n    end\n\n    # If you change this IP, also change the DNS server for the \"web\" VM.\n    adfs.vm.network \"private_network\", ip: \"10.10.10.2\"\n\n    # Some winrm hacking\n    # It prevents the connection with the VM from dropping\n    # after promoting it to a domain controller\n    adfs.winrm.timeout = 180\n    adfs.winrm.retry_limit = 20\n    adfs.winrm.retry_delay = 10\n    adfs.winrm.transport = :plaintext\n    adfs.winrm.basic_auth_only = true\n\n    # Setup the domain controller\n    adfs.vm.provision \"shell\", privileged: false, path: File.join(dir, 'vagrant', '01-setup-domain.ps1')\n    adfs.vm.provision :reload\n    adfs.vm.provision \"shell\", privileged: false, path: File.join(dir, 'vagrant', '02-setup-vagrant-user.ps1')\n    # Setup ADFS\n    adfs.vm.provision \"shell\", privileged: false, path: File.join(dir, 'vagrant', '03-setup-adfs.ps1')\n    adfs.vm.provision :reload\n    # Configure ADFS for use with the example project\n    adfs.vm.provision \"shell\", privileged: false, path: File.join(dir, 'vagrant', '04-example-adfs-config.ps1')\n  end\n\n  config.vm.define \"web\" do |web|\n    web.vm.hostname = \"web\"\n    web.vm.box = \"debian/buster64\"\n\n    # If you change this IP, you also have to change it in the file 03-example-adfs-config.ps1\n    web.vm.network \"private_network\", ip: \"10.10.10.10\"\n    web.vm.network \"forwarded_port\", guest: 8000, host: 8000\n\n    # Install all needed tools and migrate the 2 example django projects\n    web.vm.provision \"shell\", privileged: true, inline: <<-SHELL\n      set -x\n      apt-get update\n      apt-get install -y python3-pip\n      # Install django-auth-adfs in editable mode\n      pip3 install -e /vagrant\n      # Install DRF to demo the API integration\n      pip3 install djangorestframework django-filter\n      # run migrate command for both example projects\n      python3 /vagrant/demo/adfs/manage.py makemigrations polls\n      python3 /vagrant/demo/adfs/manage.py migrate\n      python3 /vagrant/demo/formsbased/manage.py makemigrations polls\n      python3 /vagrant/demo/formsbased/manage.py migrate\n      # Set fixed hosts entry to ADFS server\n      echo \"10.10.10.2 adfs.example.com\" >> /etc/hosts\n    SHELL\n  end\nend\n"
  },
  {
    "path": "demo/adfs/manage.py",
    "content": "#!/usr/bin/env python\nimport os\nimport sys\n\nif __name__ == \"__main__\":\n    os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"mysite.settings\")\n    try:\n        from django.core.management import execute_from_command_line\n    except ImportError as exc:\n        raise ImportError(\n            \"Couldn't import Django. Are you sure it's installed and \"\n            \"available on your PYTHONPATH environment variable? Did you \"\n            \"forget to activate a virtual environment?\"\n        ) from exc\n    execute_from_command_line(sys.argv)\n"
  },
  {
    "path": "demo/adfs/mysite/__init__.py",
    "content": ""
  },
  {
    "path": "demo/adfs/mysite/settings.py",
    "content": "\"\"\"\nDjango settings for mysite project.\n\nGenerated by 'django-admin startproject' using Django 2.0.5.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/2.0/topics/settings/\n\nFor the full list of settings and their values, see\nhttps://docs.djangoproject.com/en/2.0/ref/settings/\n\"\"\"\n\nimport os\n\n# Build paths inside the project like this: os.path.join(BASE_DIR, ...)\nBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))\n\n\n# Quick-start development settings - unsuitable for production\n# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/\n\n# SECURITY WARNING: keep the secret key used in production secret!\nSECRET_KEY = '72zx9=7byz54=z-@oyuv^h)nse=qljty65zj$nj*$z42j353sa'\n\n# SECURITY WARNING: don't run with debug turned on in production!\nDEBUG = True\n\nALLOWED_HOSTS = ['*']\n\n\n# Application definition\n\nINSTALLED_APPS = [\n    'django_auth_adfs',\n    'polls',\n    'django.contrib.admin',\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n    'rest_framework',\n]\n\nMIDDLEWARE = [\n    'django.middleware.security.SecurityMiddleware',\n    'django.contrib.sessions.middleware.SessionMiddleware',\n    'django.middleware.common.CommonMiddleware',\n    'django.middleware.csrf.CsrfViewMiddleware',\n    'django.contrib.auth.middleware.AuthenticationMiddleware',\n    'django.contrib.messages.middleware.MessageMiddleware',\n    'django.middleware.clickjacking.XFrameOptionsMiddleware',\n]\n\nROOT_URLCONF = 'mysite.urls'\n\nTEMPLATES = [\n    {\n        'BACKEND': 'django.template.backends.django.DjangoTemplates',\n        'DIRS': [os.path.join(BASE_DIR, 'templates')],\n        'APP_DIRS': True,\n        'OPTIONS': {\n            'context_processors': [\n                'django.template.context_processors.debug',\n                'django.template.context_processors.request',\n                'django.contrib.auth.context_processors.auth',\n                'django.contrib.messages.context_processors.messages',\n            ],\n        },\n    },\n]\n\nWSGI_APPLICATION = 'mysite.wsgi.application'\n\n\n# Database\n# https://docs.djangoproject.com/en/2.0/ref/settings/#databases\n\nDATABASES = {\n    'default': {\n        'ENGINE': 'django.db.backends.sqlite3',\n        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),\n    }\n}\n\n\n# Password validation\n# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators\n\nAUTH_PASSWORD_VALIDATORS = [\n    {\n        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',\n    },\n]\n\nAUTHENTICATION_BACKENDS = (\n    'django_auth_adfs.backend.AdfsAuthCodeBackend',\n    'django_auth_adfs.backend.AdfsAccessTokenBackend',\n)\n\nAUTH_ADFS = {\n    \"SERVER\": \"adfs.example.com\",\n    \"CLIENT_ID\": \"487d8ff7-80a8-4f62-b926-c2852ab06e94\",\n    \"RELYING_PARTY_ID\": \"web.example.com\",\n    # Make sure to read the documentation about the AUDIENCE setting\n    # when you configured the identifier as a URL!\n    \"AUDIENCE\": \"microsoft:identityserver:web.example.com\",\n    \"CA_BUNDLE\": False,  # <<<-- !!! DON'T DO THIS IN A PRODUCTION SETUP !!!\n    \"CLAIM_MAPPING\": {\"first_name\": \"given_name\",\n                      \"last_name\": \"family_name\",\n                      \"email\":          \"email\"},\n    #                   ^^ = Model field   ^^ = Claim\n    \"GROUP_TO_FLAG_MAPPING\": {\"is_superuser\":   \"django_admins\"},\n    #                            ^^ = Model field    ^^ = Group\n    # \"BOOLEAN_CLAIM_MAPPING\": {\"is_staff\": \"is_staff\"},\n    \"CONFIG_RELOAD_INTERVAL\": 0.1,\n}\n\n# Internationalization\n# https://docs.djangoproject.com/en/2.0/topics/i18n/\n\nLANGUAGE_CODE = 'en-us'\n\nTIME_ZONE = 'UTC'\n\nUSE_I18N = True\n\nUSE_L10N = True\n\nUSE_TZ = True\n\n\n# Static files (CSS, JavaScript, Images)\n# https://docs.djangoproject.com/en/2.0/howto/static-files/\n\nSTATIC_URL = '/static/'\nSTATIC_ROOT = os.path.join(BASE_DIR, \"static\")\n\nLOGIN_URL = \"django_auth_adfs:login\"\nLOGIN_REDIRECT_URL = \"/\"\n\nLOGGING = {\n    'version': 1,\n    'disable_existing_loggers': False,\n    'formatters': {\n        'verbose': {\n            'format': '%(levelname)s %(asctime)s %(name)s %(message)s'\n        },\n    },\n    'handlers': {\n        'console': {\n            'class': 'logging.StreamHandler',\n            'formatter': 'verbose'\n        },\n    },\n    'loggers': {\n        'django_auth_adfs': {\n            'handlers': ['console'],\n            'level': 'DEBUG',\n        },\n    },\n}\nREST_FRAMEWORK = {\n    'DEFAULT_PERMISSION_CLASSES': (\n        'rest_framework.permissions.IsAuthenticatedOrReadOnly',\n    ),\n    'DEFAULT_AUTHENTICATION_CLASSES': (\n        'django_auth_adfs.rest_framework.AdfsAccessTokenAuthentication',\n        'rest_framework.authentication.SessionAuthentication',\n    )\n}\n"
  },
  {
    "path": "demo/adfs/mysite/urls.py",
    "content": "\"\"\"mysite URL Configuration\n\nThe `urlpatterns` list routes URLs to views. For more information please see:\n    https://docs.djangoproject.com/en/2.0/topics/http/urls/\nExamples:\nFunction views\n    1. Add an import:  from my_app import views\n    2. Add a URL to urlpatterns:  path('', views.home, name='home')\nClass-based views\n    1. Add an import:  from other_app.views import Home\n    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')\nIncluding another URLconf\n    1. Import the include() function: from django.urls import include, path\n    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))\n\"\"\"\nfrom django.conf import settings\nfrom django.conf.urls.static import static\nfrom django.contrib import admin\nfrom django.contrib.auth.decorators import login_required\nfrom django.urls import include, path\nfrom django.views.generic.base import TemplateView\n\nadmin.site.login = login_required(admin.site.login)\n\nurlpatterns = [\n    path('', TemplateView.as_view(template_name='home.html'), name='home'),\n    path('polls/', include('polls.urls')),\n    path('api/', include('polls.api.urls')),\n\n    path('admin/', admin.site.urls, name='admin'),\n\n    # The default rest framework urls shouldn't be included\n    # If we include them, we'll end up with the DRF login page,\n    # instead of being redirected to the ADFS login page.\n    #\n    # path('api-auth/', include('rest_framework.urls')),\n    #\n    path('oauth2/', include('django_auth_adfs.urls')),\n    # This overrides the DRF login page\n    path('oauth2/', include('django_auth_adfs.drf_urls')),\n] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)\n"
  },
  {
    "path": "demo/adfs/mysite/wsgi.py",
    "content": "\"\"\"\nWSGI config for mysite project.\n\nIt exposes the WSGI callable as a module-level variable named ``application``.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/\n\"\"\"\n\nimport os\n\nfrom django.core.wsgi import get_wsgi_application\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"mysite.settings\")\n\napplication = get_wsgi_application()\n"
  },
  {
    "path": "demo/adfs/polls/__init__.py",
    "content": ""
  },
  {
    "path": "demo/adfs/polls/admin.py",
    "content": "from django.contrib import admin\n\nfrom .models import Choice, Question\n\n\nclass ChoiceInline(admin.TabularInline):\n    model = Choice\n    extra = 3\n\n\nclass QuestionAdmin(admin.ModelAdmin):\n    fieldsets = [\n        (None,               {'fields': ['question_text']}),\n        ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),\n    ]\n    inlines = [ChoiceInline]\n    list_display = ('question_text', 'pub_date', 'was_published_recently')\n    list_filter = ['pub_date']\n    search_fields = ['question_text']\n\n\nadmin.site.register(Question, QuestionAdmin)\n"
  },
  {
    "path": "demo/adfs/polls/api/__init__.py",
    "content": ""
  },
  {
    "path": "demo/adfs/polls/api/filters.py",
    "content": "import django_filters\nfrom ..models import Choice, Question\n\n\nclass QuestionFilter(django_filters.FilterSet):\n    class Meta:\n        model = Question\n        fields = ['question_text', 'pub_date']\n\n\nclass ChoiceFilter(django_filters.FilterSet):\n    class Meta:\n        model = Choice\n        fields = ['question', 'choice_text', 'votes']\n"
  },
  {
    "path": "demo/adfs/polls/api/serializers.py",
    "content": "from ..models import Choice, Question\nimport rest_framework.serializers as serializers\n\n\nclass QuestionSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = Question\n        fields = ['id', 'question_text', 'pub_date']\n\n\nclass ChoiceSerializer(serializers.ModelSerializer):\n    votes = serializers.IntegerField(read_only=True)\n\n    class Meta:\n        model = Choice\n        fields = ['id', 'question', 'choice_text', 'votes']\n"
  },
  {
    "path": "demo/adfs/polls/api/urls.py",
    "content": "from rest_framework import routers\n\nfrom . import views\n\n\nrouter = routers.DefaultRouter()\n\nrouter.register(r'questions', views.QuestionViewSet)\nrouter.register(r'choices', views.ChoiceViewSet)\n\napp_name = 'polls-api'\nurlpatterns = router.urls\n"
  },
  {
    "path": "demo/adfs/polls/api/views.py",
    "content": "from rest_framework.viewsets import ModelViewSet\nfrom rest_framework.decorators import action\nfrom rest_framework.permissions import IsAuthenticated\nfrom rest_framework.response import Response\n\nfrom ..models import Question, Choice\nfrom .serializers import QuestionSerializer, ChoiceSerializer\nfrom .filters import QuestionFilter, ChoiceFilter\n\n\nclass QuestionViewSet(ModelViewSet):\n    queryset = Question.objects.all()\n    serializer_class = QuestionSerializer\n    filter_class = QuestionFilter\n\n\nclass ChoiceViewSet(ModelViewSet):\n    queryset = Choice.objects.all()\n    serializer_class = ChoiceSerializer\n    filter_class = ChoiceFilter\n\n    @action(methods=[\"post\"], detail=True, permission_classes=[IsAuthenticated])\n    def vote(self, request, pk=None):\n        \"\"\"\n        post:\n        A description of the post method on the custom action.\n        \"\"\"\n        choice = self.get_object()\n        choice.vote()\n        serializer = self.get_serializer(choice)\n        return Response(serializer.data)\n"
  },
  {
    "path": "demo/adfs/polls/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass PollsConfig(AppConfig):\n    name = 'polls'\n"
  },
  {
    "path": "demo/adfs/polls/migrations/0001_initial.py",
    "content": "# Generated by Django 2.2.6 on 2019-11-01 17:08\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='Question',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('question_text', models.CharField(max_length=200)),\n                ('pub_date', models.DateTimeField(auto_now_add=True, verbose_name='date published')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='Choice',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('choice_text', models.CharField(max_length=200)),\n                ('votes', models.IntegerField(default=0)),\n                ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='polls.Question')),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "demo/adfs/polls/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "demo/adfs/polls/models.py",
    "content": "import datetime\n\nfrom django.db import models\nfrom django.utils import timezone\n\n\nclass Question(models.Model):\n    question_text = models.CharField(max_length=200)\n    pub_date = models.DateTimeField('date published', auto_now_add=True)\n\n    def __str__(self):\n        return self.question_text\n\n    def was_published_recently(self):\n        now = timezone.now()\n        return now - datetime.timedelta(days=1) <= self.pub_date <= now\n    was_published_recently.admin_order_field = 'pub_date'\n    was_published_recently.boolean = True\n    was_published_recently.short_description = 'Published recently?'\n\n\nclass Choice(models.Model):\n    question = models.ForeignKey(Question, on_delete=models.CASCADE)\n    choice_text = models.CharField(max_length=200)\n    votes = models.IntegerField(default=0)\n\n    def __str__(self):\n        return self.choice_text\n\n    def vote(self):\n        assert not self._state.adding, \"You can't vote on an unsaved choice\"\n        self.refresh_from_db()\n        self.votes += 1\n        self.full_clean()\n        self.save()\n"
  },
  {
    "path": "demo/adfs/polls/templates/admin/base_site.html",
    "content": "{% extends \"admin/base.html\" %}\n\n{% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}\n\n{% block branding %}\n<h1 id=\"site-name\"><a href=\"{% url 'admin:index' %}\">Polls Administration</a></h1>\n{% endblock %}\n\n{% block nav-global %}{% endblock %}\n"
  },
  {
    "path": "demo/adfs/polls/templates/polls/detail.html",
    "content": "{% extends \"base.html\" %}\n{% block content %}\n<h3>{{ question.question_text }}</h3>\n<div class=\"row\">\n  <div class=\"col\">\n    <ul class=\"list-group\" style=\"max-width: 400px\">\n    {% for choice in question.choice_set.all %}\n        <li class=\"list-group-item d-flex justify-content-between align-items-center\">{{ choice.choice_text }} <span class=\"badge badge-primary badge-pill\">{{ choice.votes }}</span></li>\n    {% endfor %}\n    </ul>\n  </div>\n</div>\n<div class=\"row mt-2\">\n  <div class=\"col\">\n    <a class=\"btn btn-primary\" role=\"button\" href=\"{% url 'polls:vote' question.id %}\">Vote</a>\n  </div>\n</div>\n{% endblock %}\n"
  },
  {
    "path": "demo/adfs/polls/templates/polls/index.html",
    "content": "{% extends \"base.html\" %}\n{% block content %}\n  <h3>Available polls</h3>\n  {% if latest_question_list %}\n    <div class=\"list-group\">\n    {% for question in latest_question_list %}\n        <a class=\"list-group-item list-group-item-action\" href=\"{% url 'polls:detail' question.id %}\">{{ question.question_text }}</a>\n    {% endfor %}\n    </div>\n  {% else %}\n    <p>No polls are available.</p>\n  {% endif %}\n{% endblock %}\n"
  },
  {
    "path": "demo/adfs/polls/templates/polls/vote.html",
    "content": "{% extends \"base.html\" %}\n{% block content %}\n<h3>{{ question.question_text }}</h3>\n\n{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}\n\n\n<form action=\"{% url 'polls:vote' question.id %}\" method=\"post\">\n{% csrf_token %}\n{% for choice in question.choice_set.all %}\n    <input type=\"radio\" name=\"choice\" id=\"choice{{ forloop.counter }}\" value=\"{{ choice.id }}\" />\n    <label for=\"choice{{ forloop.counter }}\">{{ choice.choice_text }}</label><br />\n{% endfor %}\n<input type=\"submit\" value=\"Vote\" class=\"btn btn-primary\" />\n</form>\n{% endblock %}\n"
  },
  {
    "path": "demo/adfs/polls/urls.py",
    "content": "from django.urls import path\n\nfrom . import views\n\napp_name = 'polls'\nurlpatterns = [\n    path('', views.IndexView.as_view(), name='index'),\n    path('<int:pk>/', views.DetailView.as_view(), name='detail'),\n    path('<int:pk>/vote/', views.VoteView.as_view(), name='vote'),\n    # path('<int:pk>/savevote/', views.savevote, name='savevote'),\n]\n"
  },
  {
    "path": "demo/adfs/polls/views.py",
    "content": "from django.contrib.auth.mixins import LoginRequiredMixin\nfrom django.http import HttpResponseRedirect\nfrom django.shortcuts import get_object_or_404, render\nfrom django.urls import reverse\nfrom django.utils import timezone\nfrom django.views import generic\n\nfrom .models import Choice, Question\n\n\nclass IndexView(generic.ListView):\n    template_name = 'polls/index.html'\n    context_object_name = 'latest_question_list'\n\n    def get_queryset(self):\n        \"\"\"\n        Return the last five published questions (not including those set to be\n        published in the future).\n        \"\"\"\n        return Question.objects.filter(\n            pub_date__lte=timezone.now()\n        ).order_by('-pub_date')[:5]\n\n\nclass DetailView(generic.DetailView):\n    model = Question\n    template_name = 'polls/detail.html'\n\n    def get_queryset(self):\n        \"\"\"\n        Excludes any questions that aren't published yet.\n        \"\"\"\n        return Question.objects.filter(pub_date__lte=timezone.now())\n\n\nclass VoteView(LoginRequiredMixin, generic.DetailView):\n    model = Question\n    template_name = 'polls/vote.html'\n\n    def get_queryset(self):\n        \"\"\"\n        Excludes any questions that aren't published yet.\n        \"\"\"\n        return Question.objects.filter(pub_date__lte=timezone.now())\n\n    def post(self, request, pk, *args, **kwargs):\n        question = get_object_or_404(Question, pk=pk)\n        try:\n            selected_choice = question.choice_set.get(pk=request.POST['choice'])\n        except (KeyError, Choice.DoesNotExist):\n            # Redisplay the question voting form.\n            return render(request, 'polls/vote.html', {\n                'question': question,\n                'error_message': \"You didn't select a choice.\",\n            })\n        else:\n            selected_choice.vote()\n            # Always return an HttpResponseRedirect after successfully dealing\n            # with POST data. This prevents data from being posted twice if a\n            # user hits the Back button.\n            return HttpResponseRedirect(reverse('polls:detail', args=(question.id,)))\n"
  },
  {
    "path": "demo/adfs/templates/base.html",
    "content": "{% load static %}\n<!doctype html>\n<html lang=\"en\">\n  <head>\n    <!-- Required meta tags -->\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n\n    <title>{% block title %}Polls App{% endblock %}</title>\n\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"{% static 'bootstrap.min.css' %}\" />\n</head>\n<body>\n<div class=\"container\">\n  <nav class=\"navbar navbar-expand-sm navbar-dark bg-dark\">\n    <a class=\"navbar-brand\" href=\"/\">Polls</a>\n    <div class=\"collapse navbar-collapse\" id=\"navbarTogglerDemo03\">\n      <ul class=\"navbar-nav mr-auto\">\n        <li class=\"nav-item active\">\n          <a class=\"nav-link\" href=\"{% url 'polls:index' %}\">Polls</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"{% url 'admin:index' %}\">Admin</a>\n        </li>\n        {% if user.is_authenticated %}\n        <li>\n          <a class=\"nav-link\" href=\"{% url 'django_auth_adfs:logout' %}\">Logout</a>\n        </li>\n        {% else %}\n        <li>\n          <a class=\"nav-link\" href=\"{% url 'django_auth_adfs:login' %}\">Login</a>\n        </li>\n        <li>\n          <a class=\"nav-link\" href=\"{% url 'django_auth_adfs:login-no-sso' %}\">Login (no SSO)</a>\n        </li>\n        {% endif %}\n      </ul>\n    </div>\n  </nav>\n  <div class=\"row mt-1\">\n    <div class=\"col-sm\">\n      {% block content %}{% endblock %}\n    </div>\n  </div>\n  <div class=\"row mt-2\">\n    <div class=\"col-sm\">\n      <div class=\"card bg-secondary text-white fixed-bottom\">\n        <div class=\"card-header\">\n          Current User Info\n        </div>\n        <div class=\"card-body\">\n          <pre class=\"mb-0 pr-5 card-text text-white float-left\">\nid         = {{ user.id }}\nusername   = {{ user.username }}\nfirst_name = {{ user.first_name }}\nlast_name  = {{ user.last_name }}\nemail      = {{ user.email }}\n</pre>\n          <pre class=\"mb-0 pr-5 card-text text-white float-left\">\nis_authenticated = {{ user.is_authenticated }}\nis_staff         = {{ user.is_staff }}\nis_active        = {{ user.is_active }}\nis_superuser     = {{ user.is_superuser }}\n</pre>\n          <pre class=\"mb-0 card-text text-white float-left\">\nlast_login  = {{ user.last_login }}\ndate_joined = {{ user.date_joined }}\n</pre>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n</body>\n</html>\n\n\n\n"
  },
  {
    "path": "demo/adfs/templates/home.html",
    "content": "{% extends 'base.html' %}\n\n{% block title %}Home{% endblock %}\n\n{% block content %}\n  <div class=\"jumbotron jumbotron-fluid\">\n  <div class=\"container\">\n    <h1 class=\"display-4\">Welcome to the polls app</h1>\n    <p class=\"lead\">Use the menu above to navigate</p>\n  </div>\n</div>\n{% endblock %}\n"
  },
  {
    "path": "demo/formsbased/manage.py",
    "content": "#!/usr/bin/env python\nimport os\nimport sys\n\nif __name__ == \"__main__\":\n    os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"mysite.settings\")\n    try:\n        from django.core.management import execute_from_command_line\n    except ImportError as exc:\n        raise ImportError(\n            \"Couldn't import Django. Are you sure it's installed and \"\n            \"available on your PYTHONPATH environment variable? Did you \"\n            \"forget to activate a virtual environment?\"\n        ) from exc\n    execute_from_command_line(sys.argv)\n"
  },
  {
    "path": "demo/formsbased/mysite/__init__.py",
    "content": ""
  },
  {
    "path": "demo/formsbased/mysite/settings.py",
    "content": "\"\"\"\nDjango settings for mysite project.\n\nGenerated by 'django-admin startproject' using Django 2.0.5.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/2.0/topics/settings/\n\nFor the full list of settings and their values, see\nhttps://docs.djangoproject.com/en/2.0/ref/settings/\n\"\"\"\n\nimport os\n\n# Build paths inside the project like this: os.path.join(BASE_DIR, ...)\nBASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))\n\n\n# Quick-start development settings - unsuitable for production\n# See https://docs.djangoproject.com/en/2.0/howto/deployment/checklist/\n\n# SECURITY WARNING: keep the secret key used in production secret!\nSECRET_KEY = '72zx9=7byz54=z-@oyuv^h)nse=qljty65zj$nj*$z42j353sa'\n\n# SECURITY WARNING: don't run with debug turned on in production!\nDEBUG = True\n\nALLOWED_HOSTS = ['*']\n\n\n# Application definition\n\nINSTALLED_APPS = [\n    'polls',\n    'django.contrib.admin',\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n    'rest_framework',\n]\n\nMIDDLEWARE = [\n    'django.middleware.security.SecurityMiddleware',\n    'django.contrib.sessions.middleware.SessionMiddleware',\n    'django.middleware.common.CommonMiddleware',\n    'django.middleware.csrf.CsrfViewMiddleware',\n    'django.contrib.auth.middleware.AuthenticationMiddleware',\n    'django.contrib.messages.middleware.MessageMiddleware',\n    'django.middleware.clickjacking.XFrameOptionsMiddleware',\n]\n\nROOT_URLCONF = 'mysite.urls'\n\nTEMPLATES = [\n    {\n        'BACKEND': 'django.template.backends.django.DjangoTemplates',\n        'DIRS': [os.path.join(BASE_DIR, 'templates')],\n        'APP_DIRS': True,\n        'OPTIONS': {\n            'context_processors': [\n                'django.template.context_processors.debug',\n                'django.template.context_processors.request',\n                'django.contrib.auth.context_processors.auth',\n                'django.contrib.messages.context_processors.messages',\n            ],\n        },\n    },\n]\n\nWSGI_APPLICATION = 'mysite.wsgi.application'\n\n\n# Database\n# https://docs.djangoproject.com/en/2.0/ref/settings/#databases\n\nDATABASES = {\n    'default': {\n        'ENGINE': 'django.db.backends.sqlite3',\n        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),\n    }\n}\n\n\n# Password validation\n# https://docs.djangoproject.com/en/2.0/ref/settings/#auth-password-validators\n\nAUTH_PASSWORD_VALIDATORS = [\n    {\n        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',\n    },\n    {\n        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',\n    },\n]\n\n\n# Internationalization\n# https://docs.djangoproject.com/en/2.0/topics/i18n/\n\nLANGUAGE_CODE = 'en-us'\n\nTIME_ZONE = 'UTC'\n\nUSE_I18N = True\n\nUSE_L10N = True\n\nUSE_TZ = True\n\n\n# Static files (CSS, JavaScript, Images)\n# https://docs.djangoproject.com/en/2.0/howto/static-files/\n\nSTATIC_URL = '/static/'\nSTATIC_ROOT = os.path.join(BASE_DIR, \"static\")\n\nLOGIN_REDIRECT_URL = \"home\"\n\nREST_FRAMEWORK = {\n    'DEFAULT_PERMISSION_CLASSES': (\n        'rest_framework.permissions.IsAuthenticatedOrReadOnly',\n    ),\n    'DEFAULT_AUTHENTICATION_CLASSES': (\n        'rest_framework.authentication.SessionAuthentication',\n    )\n}\n"
  },
  {
    "path": "demo/formsbased/mysite/urls.py",
    "content": "\"\"\"mysite URL Configuration\n\nThe `urlpatterns` list routes URLs to views. For more information please see:\n    https://docs.djangoproject.com/en/2.0/topics/http/urls/\nExamples:\nFunction views\n    1. Add an import:  from my_app import views\n    2. Add a URL to urlpatterns:  path('', views.home, name='home')\nClass-based views\n    1. Add an import:  from other_app.views import Home\n    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')\nIncluding another URLconf\n    1. Import the include() function: from django.urls import include, path\n    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))\n\"\"\"\nfrom django.conf import settings\nfrom django.conf.urls.static import static\nfrom django.contrib import admin\nfrom django.contrib.auth import views as auth_views\nfrom django.urls import include, path\nfrom django.views.generic.base import TemplateView\n\n\nurlpatterns = [\n    path('', TemplateView.as_view(template_name='home.html'), name='home'),\n    path('polls/', include('polls.urls')),\n    path('api/', include('polls.api.urls')),\n    path('api-auth/', include('rest_framework.urls')),\n    path('admin/', admin.site.urls, name='admin'),\n    path('accounts/login/', auth_views.LoginView.as_view(), name='login'),\n    path('accounts/logout/', auth_views.LogoutView.as_view(), name='logout'),\n] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)\n"
  },
  {
    "path": "demo/formsbased/mysite/wsgi.py",
    "content": "\"\"\"\nWSGI config for mysite project.\n\nIt exposes the WSGI callable as a module-level variable named ``application``.\n\nFor more information on this file, see\nhttps://docs.djangoproject.com/en/2.0/howto/deployment/wsgi/\n\"\"\"\n\nimport os\n\nfrom django.core.wsgi import get_wsgi_application\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"mysite.settings\")\n\napplication = get_wsgi_application()\n"
  },
  {
    "path": "demo/formsbased/polls/__init__.py",
    "content": ""
  },
  {
    "path": "demo/formsbased/polls/admin.py",
    "content": "from django.contrib import admin\n\nfrom .models import Choice, Question\n\n\nclass ChoiceInline(admin.TabularInline):\n    model = Choice\n    extra = 3\n\n\nclass QuestionAdmin(admin.ModelAdmin):\n    fieldsets = [\n        (None,               {'fields': ['question_text']}),\n        ('Date information', {'fields': ['pub_date'], 'classes': ['collapse']}),\n    ]\n    inlines = [ChoiceInline]\n    list_display = ('question_text', 'pub_date', 'was_published_recently')\n    list_filter = ['pub_date']\n    search_fields = ['question_text']\n\n\nadmin.site.register(Question, QuestionAdmin)\n"
  },
  {
    "path": "demo/formsbased/polls/api/__init__.py",
    "content": ""
  },
  {
    "path": "demo/formsbased/polls/api/filters.py",
    "content": "import django_filters\nfrom ..models import Choice, Question\n\n\nclass QuestionFilter(django_filters.FilterSet):\n    class Meta:\n        model = Question\n        fields = ['question_text', 'pub_date']\n\n\nclass ChoiceFilter(django_filters.FilterSet):\n    class Meta:\n        model = Choice\n        fields = ['question', 'choice_text', 'votes']\n"
  },
  {
    "path": "demo/formsbased/polls/api/serializers.py",
    "content": "from ..models import Choice, Question\nimport rest_framework.serializers as serializers\n\n\nclass QuestionSerializer(serializers.ModelSerializer):\n    class Meta:\n        model = Question\n        fields = ['id', 'question_text', 'pub_date']\n\n\nclass ChoiceSerializer(serializers.ModelSerializer):\n    votes = serializers.IntegerField(read_only=True)\n\n    class Meta:\n        model = Choice\n        fields = ['id', 'question', 'choice_text', 'votes']\n"
  },
  {
    "path": "demo/formsbased/polls/api/urls.py",
    "content": "from rest_framework import routers\n\nfrom . import views\n\n\nrouter = routers.DefaultRouter()\n\nrouter.register(r'questions', views.QuestionViewSet)\nrouter.register(r'choices', views.ChoiceViewSet)\n\napp_name = 'polls-api'\nurlpatterns = router.urls\n"
  },
  {
    "path": "demo/formsbased/polls/api/views.py",
    "content": "from rest_framework.viewsets import ModelViewSet\nfrom rest_framework.decorators import action\nfrom rest_framework.permissions import IsAuthenticated\nfrom rest_framework.response import Response\n\nfrom ..models import Question, Choice\nfrom .serializers import QuestionSerializer, ChoiceSerializer\nfrom .filters import QuestionFilter, ChoiceFilter\n\n\nclass QuestionViewSet(ModelViewSet):\n    queryset = Question.objects.all()\n    serializer_class = QuestionSerializer\n    filter_class = QuestionFilter\n\n\nclass ChoiceViewSet(ModelViewSet):\n    queryset = Choice.objects.all()\n    serializer_class = ChoiceSerializer\n    filter_class = ChoiceFilter\n\n    @action(methods=[\"post\"], detail=True, permission_classes=[IsAuthenticated])\n    def vote(self, request, pk=None):\n        \"\"\"\n        post:\n        A description of the post method on the custom action.\n        \"\"\"\n        choice = self.get_object()\n        choice.vote()\n        serializer = self.get_serializer(choice)\n        return Response(serializer.data)\n"
  },
  {
    "path": "demo/formsbased/polls/apps.py",
    "content": "from django.apps import AppConfig\n\n\nclass PollsConfig(AppConfig):\n    name = 'polls'\n"
  },
  {
    "path": "demo/formsbased/polls/migrations/0001_initial.py",
    "content": "# Generated by Django 2.2.6 on 2019-11-01 17:08\n\nfrom django.db import migrations, models\nimport django.db.models.deletion\n\n\nclass Migration(migrations.Migration):\n\n    initial = True\n\n    dependencies = [\n    ]\n\n    operations = [\n        migrations.CreateModel(\n            name='Question',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('question_text', models.CharField(max_length=200)),\n                ('pub_date', models.DateTimeField(auto_now_add=True, verbose_name='date published')),\n            ],\n        ),\n        migrations.CreateModel(\n            name='Choice',\n            fields=[\n                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),\n                ('choice_text', models.CharField(max_length=200)),\n                ('votes', models.IntegerField(default=0)),\n                ('question', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='polls.Question')),\n            ],\n        ),\n    ]\n"
  },
  {
    "path": "demo/formsbased/polls/migrations/__init__.py",
    "content": ""
  },
  {
    "path": "demo/formsbased/polls/models.py",
    "content": "import datetime\n\nfrom django.db import models\nfrom django.utils import timezone\n\n\nclass Question(models.Model):\n    question_text = models.CharField(max_length=200)\n    pub_date = models.DateTimeField('date published', auto_now_add=True)\n\n    def __str__(self):\n        return self.question_text\n\n    def was_published_recently(self):\n        now = timezone.now()\n        return now - datetime.timedelta(days=1) <= self.pub_date <= now\n    was_published_recently.admin_order_field = 'pub_date'\n    was_published_recently.boolean = True\n    was_published_recently.short_description = 'Published recently?'\n\n\nclass Choice(models.Model):\n    question = models.ForeignKey(Question, on_delete=models.CASCADE)\n    choice_text = models.CharField(max_length=200)\n    votes = models.IntegerField(default=0)\n\n    def __str__(self):\n        return self.choice_text\n\n    def vote(self):\n        assert not self._state.adding, \"You can't vote on an unsaved choice\"\n        self.refresh_from_db()\n        self.votes += 1\n        self.full_clean()\n        self.save()\n"
  },
  {
    "path": "demo/formsbased/polls/templates/admin/base_site.html",
    "content": "{% extends \"admin/base.html\" %}\n\n{% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}\n\n{% block branding %}\n<h1 id=\"site-name\"><a href=\"{% url 'admin:index' %}\">Polls Administration</a></h1>\n{% endblock %}\n\n{% block nav-global %}{% endblock %}\n"
  },
  {
    "path": "demo/formsbased/polls/templates/polls/detail.html",
    "content": "{% extends \"base.html\" %}\n{% block content %}\n<h3>{{ question.question_text }}</h3>\n<div class=\"row\">\n  <div class=\"col\">\n    <ul class=\"list-group\" style=\"max-width: 400px\">\n    {% for choice in question.choice_set.all %}\n        <li class=\"list-group-item d-flex justify-content-between align-items-center\">{{ choice.choice_text }} <span class=\"badge badge-primary badge-pill\">{{ choice.votes }}</span></li>\n    {% endfor %}\n    </ul>\n  </div>\n</div>\n<div class=\"row mt-2\">\n  <div class=\"col\">\n    <a class=\"btn btn-primary\" role=\"button\" href=\"{% url 'polls:vote' question.id %}\">Vote</a>\n  </div>\n</div>\n{% endblock %}\n"
  },
  {
    "path": "demo/formsbased/polls/templates/polls/index.html",
    "content": "{% extends \"base.html\" %}\n{% block content %}\n  <h3>Available polls</h3>\n  {% if latest_question_list %}\n    <div class=\"list-group\">\n    {% for question in latest_question_list %}\n        <a class=\"list-group-item list-group-item-action\" href=\"{% url 'polls:detail' question.id %}\">{{ question.question_text }}</a>\n    {% endfor %}\n    </div>\n  {% else %}\n    <p>No polls are available.</p>\n  {% endif %}\n{% endblock %}\n"
  },
  {
    "path": "demo/formsbased/polls/templates/polls/vote.html",
    "content": "{% extends \"base.html\" %}\n{% block content %}\n<h3>{{ question.question_text }}</h3>\n\n{% if error_message %}<p><strong>{{ error_message }}</strong></p>{% endif %}\n\n\n<form action=\"{% url 'polls:vote' question.id %}\" method=\"post\">\n{% csrf_token %}\n{% for choice in question.choice_set.all %}\n    <input type=\"radio\" name=\"choice\" id=\"choice{{ forloop.counter }}\" value=\"{{ choice.id }}\" />\n    <label for=\"choice{{ forloop.counter }}\">{{ choice.choice_text }}</label><br />\n{% endfor %}\n<input type=\"submit\" value=\"Vote\" class=\"btn btn-primary\" />\n</form>\n{% endblock %}\n"
  },
  {
    "path": "demo/formsbased/polls/urls.py",
    "content": "from django.urls import path\n\nfrom . import views\n\napp_name = 'polls'\nurlpatterns = [\n    path('', views.IndexView.as_view(), name='index'),\n    path('<int:pk>/', views.DetailView.as_view(), name='detail'),\n    path('<int:pk>/vote/', views.VoteView.as_view(), name='vote'),\n    # path('<int:pk>/savevote/', views.savevote, name='savevote'),\n]\n"
  },
  {
    "path": "demo/formsbased/polls/views.py",
    "content": "from django.contrib.auth.mixins import LoginRequiredMixin\nfrom django.http import HttpResponseRedirect\nfrom django.shortcuts import get_object_or_404, render\nfrom django.urls import reverse\nfrom django.utils import timezone\nfrom django.views import generic\n\nfrom .models import Choice, Question\n\n\nclass IndexView(generic.ListView):\n    template_name = 'polls/index.html'\n    context_object_name = 'latest_question_list'\n\n    def get_queryset(self):\n        \"\"\"\n        Return the last five published questions (not including those set to be\n        published in the future).\n        \"\"\"\n        return Question.objects.filter(\n            pub_date__lte=timezone.now()\n        ).order_by('-pub_date')[:5]\n\n\nclass DetailView(generic.DetailView):\n    model = Question\n    template_name = 'polls/detail.html'\n\n    def get_queryset(self):\n        \"\"\"\n        Excludes any questions that aren't published yet.\n        \"\"\"\n        return Question.objects.filter(pub_date__lte=timezone.now())\n\n\nclass VoteView(LoginRequiredMixin, generic.DetailView):\n    model = Question\n    template_name = 'polls/vote.html'\n\n    def get_queryset(self):\n        \"\"\"\n        Excludes any questions that aren't published yet.\n        \"\"\"\n        return Question.objects.filter(pub_date__lte=timezone.now())\n\n    def post(self, request, pk, *args, **kwargs):\n        question = get_object_or_404(Question, pk=pk)\n        try:\n            selected_choice = question.choice_set.get(pk=request.POST['choice'])\n        except (KeyError, Choice.DoesNotExist):\n            # Redisplay the question voting form.\n            return render(request, 'polls/vote.html', {\n                'question': question,\n                'error_message': \"You didn't select a choice.\",\n            })\n        else:\n            selected_choice.vote()\n            # Always return an HttpResponseRedirect after successfully dealing\n            # with POST data. This prevents data from being posted twice if a\n            # user hits the Back button.\n            return HttpResponseRedirect(reverse('polls:detail', args=(question.id,)))\n"
  },
  {
    "path": "demo/formsbased/templates/base.html",
    "content": "{% load static %}\n<!doctype html>\n<html lang=\"en\">\n  <head>\n    <!-- Required meta tags -->\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, shrink-to-fit=no\">\n\n    <title>{% block title %}Polls App{% endblock %}</title>\n\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"{% static 'bootstrap.min.css' %}\" />\n</head>\n<body>\n<div class=\"container\">\n  <nav class=\"navbar navbar-expand-sm navbar-dark bg-dark\">\n    <a class=\"navbar-brand\" href=\"/\">Polls</a>\n    <div class=\"collapse navbar-collapse\" id=\"navbarTogglerDemo03\">\n      <ul class=\"navbar-nav mr-auto\">\n        <li class=\"nav-item active\">\n          <a class=\"nav-link\" href=\"{% url 'polls:index' %}\">Polls</a>\n        </li>\n        <li class=\"nav-item\">\n          <a class=\"nav-link\" href=\"{% url 'admin:index' %}\">Admin</a>\n        </li>\n        <li>\n        {% if user.is_authenticated %}\n          <a class=\"nav-link\" href=\"{% url 'logout' %}\">Logout</a>\n        {% else %}\n          <a class=\"nav-link\" href=\"{% url 'login' %}\">Login</a>\n        {% endif %}\n        </li>\n      </ul>\n    </div>\n  </nav>\n  <div class=\"row mt-1\">\n    <div class=\"col-sm\">\n      {% block content %}{% endblock %}\n    </div>\n  </div>\n  <div class=\"row mt-2\">\n    <div class=\"col-sm\">\n      <div class=\"card bg-secondary text-white fixed-bottom\">\n        <div class=\"card-header\">\n          Current User Info\n        </div>\n        <div class=\"card-body\">\n          <pre class=\"mb-0 pr-5 card-text text-white float-left\">\nid         = {{ user.id }}\nusername   = {{ user.username }}\nfirst_name = {{ user.first_name }}\nlast_name  = {{ user.last_name }}\nemail      = {{ user.email }}\n</pre>\n          <pre class=\"mb-0 pr-5 card-text text-white float-left\">\nis_authenticated = {{ user.is_authenticated }}\nis_staff         = {{ user.is_staff }}\nis_active        = {{ user.is_active }}\nis_superuser     = {{ user.is_superuser }}\n</pre>\n          <pre class=\"mb-0 card-text text-white float-left\">\nlast_login  = {{ user.last_login }}\ndate_joined = {{ user.date_joined }}\n</pre>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n</body>\n</html>\n\n\n\n"
  },
  {
    "path": "demo/formsbased/templates/home.html",
    "content": "{% extends 'base.html' %}\n\n{% block title %}Home{% endblock %}\n\n{% block content %}\n  <div class=\"jumbotron jumbotron-fluid\">\n  <div class=\"container\">\n    <h1 class=\"display-4\">Welcome to the polls app</h1>\n    <p class=\"lead\">Use the menu above to navigate</p>\n  </div>\n</div>\n{% endblock %}\n"
  },
  {
    "path": "demo/formsbased/templates/registration/logged_out.html",
    "content": "{% extends 'base.html' %}\n\n{% block title %}See you!{% endblock %}\n\n{% block content %}\n  <h2>Logged out</h2>\n  <p>You have been successfully logged out.</p>\n  <p><a href=\"{% url 'login' %}\">Log in</a> again.</p>\n{% endblock %}\n"
  },
  {
    "path": "demo/formsbased/templates/registration/login.html",
    "content": "{% extends \"base.html\" %}\n\n{% block content %}\n<h3>Login</h3>\n{% if form.errors %}\n<p>Your username and password didn't match. Please try again.</p>\n{% endif %}\n\n{% if next %}\n    {% if user.is_authenticated %}\n    <p>Your account doesn't have access to this page. To proceed,\n    please login with an account that has access.</p>\n    {% else %}\n    <p>Please login to see this page.</p>\n    {% endif %}\n{% endif %}\n<form style=\"width: 300px\" method=\"post\" action=\"{% url 'login' %}\">\n{% csrf_token %}\n  <div class=\"form-group row\">\n    <div class=\"col-sm-12\">\n      <input type=\"text\" name=\"username\" class=\"form-control\" id=\"inputUsername3\" placeholder=\"Username\">\n    </div>\n  </div>\n  <div class=\"form-group row\">\n    <div class=\"col-sm-12\">\n      <input type=\"password\" name=\"password\" class=\"form-control\" id=\"inputPassword3\" placeholder=\"Password\">\n    </div>\n  </div>\n  <div class=\"form-group row\">\n    <div class=\"col-sm-12\">\n      <button type=\"submit\" class=\"btn btn-primary\">Login</button>\n    </div>\n  </div>\n  <input type=\"hidden\" name=\"next\" value=\"{{ next }}\" />\n</form>\n{% endblock %}\n"
  },
  {
    "path": "django_auth_adfs/__init__.py",
    "content": "\"\"\"\nDon't put imports or code here\nThis file is imported by setup.py\nAdding imports here will break setup.py\n\"\"\"\n\n__version__ = '1.16.0'\n"
  },
  {
    "path": "django_auth_adfs/backend.py",
    "content": "import logging\n\nimport jwt\nfrom django.contrib.auth import get_user_model\nfrom django.contrib.auth.backends import ModelBackend\nfrom django.contrib.auth.models import Group\nfrom django.core.exceptions import (ImproperlyConfigured, ObjectDoesNotExist,\n                                    PermissionDenied)\n\nfrom django_auth_adfs import signals\nfrom django_auth_adfs.config import provider_config, settings\nfrom django_auth_adfs.exceptions import MFARequired\n\nlogger = logging.getLogger(\"django_auth_adfs\")\n\n\nclass AdfsBaseBackend(ModelBackend):\n\n    def _ms_request(self, action, url, data=None, **kwargs):\n        \"\"\"\n        Make a Microsoft Entra/GraphQL request\n\n\n        Args:\n            action (callable): The callable for making a request.\n            url (str): The URL the request should be sent to.\n            data (dict): Optional dictionary of data to be sent in the request.\n\n        Returns:\n            response: The response from the server. If it's not a 200, a\n                      PermissionDenied is raised.\n        \"\"\"\n        response = action(url, data=data, timeout=settings.TIMEOUT, **kwargs)\n        # 200 = valid token received\n        # 400 = 'something' is wrong in our request\n        if response.status_code == 400:\n            if response.json().get(\"error_description\", \"\").startswith(\"AADSTS50076\"):\n                raise MFARequired\n            logger.error(\"ADFS server returned an error: %s\", response.json()[\"error_description\"])\n            raise PermissionDenied\n\n        if response.status_code != 200:\n            logger.error(\"Unexpected ADFS response: %s\", response.content.decode())\n            raise PermissionDenied\n        return response\n\n    def exchange_auth_code(self, authorization_code, request):\n        logger.debug(\"Received authorization code: %s\", authorization_code)\n        data = {\n            'grant_type': 'authorization_code',\n            'client_id': settings.CLIENT_ID,\n            'redirect_uri': provider_config.redirect_uri(request),\n            'code': authorization_code,\n        }\n        if settings.CLIENT_SECRET:\n            data['client_secret'] = settings.CLIENT_SECRET\n\n        logger.debug(\"Getting access token at: %s\", provider_config.token_endpoint)\n        response = self._ms_request(provider_config.session.post, provider_config.token_endpoint, data)\n        adfs_response = response.json()\n        return adfs_response\n\n    def get_obo_access_token(self, access_token):\n        \"\"\"\n        Gets an On Behalf Of (OBO) access token, which is required to make queries against MS Graph\n\n        Args:\n            access_token (str): Original authorization access token from the user\n\n        Returns:\n            obo_access_token (str): OBO access token that can be used with the MS Graph API\n        \"\"\"\n        logger.debug(\"Getting OBO access token: %s\", provider_config.token_endpoint)\n        data = {\n            \"grant_type\": \"urn:ietf:params:oauth:grant-type:jwt-bearer\",\n            \"client_id\": settings.CLIENT_ID,\n            \"client_secret\": settings.CLIENT_SECRET,\n            \"assertion\": access_token,\n            \"requested_token_use\": \"on_behalf_of\",\n        }\n        if provider_config.token_endpoint.endswith(\"/v2.0/token\"):\n            data[\"scope\"] = 'GroupMember.Read.All'\n        else:\n            data[\"resource\"] = 'https://graph.microsoft.com'\n\n        response = self._ms_request(provider_config.session.get, provider_config.token_endpoint, data)\n        obo_access_token = response.json()[\"access_token\"]\n        logger.debug(\"Received OBO access token: %s\", obo_access_token)\n        return obo_access_token\n\n    def get_group_memberships_from_ms_graph_params(self):\n        \"\"\"\n        Return the parameters to be used in the querystring\n        when fetching the user's group memberships.\n\n        Possible keys to be used:\n            - $count\n            - $expand\n            - $filter\n            - $orderby\n            - $search\n            - $select\n            - $top\n\n        Docs:\n            https://learn.microsoft.com/en-us/graph/api/group-list-transitivememberof?view=graph-rest-1.0&tabs=python#http-request\n        \"\"\"\n        return {}\n\n    def get_group_memberships_from_ms_graph(self, obo_access_token):\n        \"\"\"\n        Looks up a users group membership from the MS Graph API\n\n        Args:\n            obo_access_token (str): Access token obtained from the OBO authorization endpoint\n\n        Returns:\n            claim_groups (list): List of the users group memberships\n        \"\"\"\n        graph_url = \"https://{}/v1.0/me/transitiveMemberOf/microsoft.graph.group\".format(\n            provider_config.msgraph_endpoint\n        )\n        headers = {\"Authorization\": \"Bearer {}\".format(obo_access_token)}\n        response = self._ms_request(\n            action=provider_config.session.get,\n            url=graph_url,\n            data=self.get_group_memberships_from_ms_graph_params(),\n            headers=headers,\n        )\n        claim_groups = []\n        for group_data in response.json()[\"value\"]:\n            if group_data[\"displayName\"] is None:\n                logger.error(\n                    \"The application does not have the required permission to read user groups from \"\n                    \"MS Graph (GroupMember.Read.All)\"\n                )\n                raise PermissionDenied\n\n            claim_groups.append(group_data[\"displayName\"])\n        return claim_groups\n\n    def validate_access_token(self, access_token):\n        for idx, key in enumerate(provider_config.signing_keys):\n            try:\n                # Explicitly define the verification option.\n                # The list below is the default the jwt module uses.\n                # Explicit is better then implicit and it protects against\n                # changes in the defaults the jwt module uses.\n                options = {\n                    'verify_signature': True,\n                    'verify_exp': True,\n                    'verify_nbf': True,\n                    'verify_iat': True,\n                    'verify_aud': True,\n                    'verify_iss': True,\n                    'require_exp': False,\n                    'require_iat': False,\n                    'require_nbf': False\n                }\n                # Validate token and return claims\n                return jwt.decode(\n                    access_token,\n                    key=key,\n                    algorithms=['RS256', 'RS384', 'RS512'],\n                    audience=settings.AUDIENCE,\n                    issuer=provider_config.issuer,\n                    options=options,\n                    leeway=settings.JWT_LEEWAY\n                )\n            except jwt.ExpiredSignatureError as error:\n                logger.info(\"Signature has expired: %s\", error)\n                raise PermissionDenied\n            except jwt.DecodeError as error:\n                # If it's not the last certificate in the list, skip to the next one\n                if idx < len(provider_config.signing_keys) - 1:\n                    continue\n                else:\n                    logger.info('Error decoding signature: %s', error)\n                    raise PermissionDenied\n            except jwt.InvalidTokenError as error:\n                logger.info(str(error))\n                raise PermissionDenied\n\n    def process_access_token(self, access_token, adfs_response=None):\n        if not access_token:\n            raise PermissionDenied\n\n        logger.debug(\"Received access token: %s\", access_token)\n        claims = self.validate_access_token(access_token)\n        if (\n            settings.BLOCK_GUEST_USERS\n            and claims.get('tid')\n            != settings.TENANT_ID\n        ):\n            logger.info('Guest user denied')\n            raise PermissionDenied\n        if not claims:\n            raise PermissionDenied\n\n        groups = self.process_user_groups(claims, access_token)\n        user = self.create_user(claims)\n        self.update_user_attributes(user, claims)\n        self.update_user_groups(user, groups)\n        self.update_user_flags(user, claims, groups)\n\n        signals.post_authenticate.send(\n            sender=self,\n            user=user,\n            claims=claims,\n            adfs_response=adfs_response\n        )\n\n        user.full_clean()\n        user.save()\n        return user\n\n    def process_user_groups(self, claims, access_token):\n        \"\"\"\n        Checks the user groups are in the claim or pulls them from MS Graph if\n        applicable\n\n        Args:\n            claims (dict): claims from the access token\n            access_token (str): Used to make an OBO authentication request if\n            groups must be obtained from Microsoft Graph\n\n        Returns:\n            groups (list): Groups the user is a member of, taken from the access token or MS Graph\n        \"\"\"\n        groups = []\n        if settings.GROUPS_CLAIM is None:\n            logger.debug(\"No group claim has been configured\")\n            return groups\n\n        if settings.GROUPS_CLAIM in claims:\n            groups = claims[settings.GROUPS_CLAIM]\n            if not isinstance(groups, list):\n                groups = [groups, ]\n        elif (\n            settings.TENANT_ID != \"adfs\"\n            and \"_claim_names\" in claims\n            and settings.GROUPS_CLAIM in claims[\"_claim_names\"]\n        ):\n            obo_access_token = self.get_obo_access_token(access_token)\n            groups = self.get_group_memberships_from_ms_graph(obo_access_token)\n        else:\n            logger.debug(\"The configured groups claim %s was not found in the access token\",\n                         settings.GROUPS_CLAIM)\n\n        return groups\n\n    def create_user(self, claims):\n        \"\"\"\n        Create the user if it doesn't exist yet\n\n        Args:\n            claims (dict): claims from the access token\n\n        Returns:\n            django.contrib.auth.models.User: A Django user\n        \"\"\"\n        # Create the user\n        username_claim = settings.USERNAME_CLAIM\n        guest_username_claim = settings.GUEST_USERNAME_CLAIM\n        usermodel = get_user_model()\n\n        iss = claims.get('iss')\n        idp = claims.get('idp', iss)\n        if (\n            guest_username_claim\n            and not claims.get(username_claim)\n            and not settings.BLOCK_GUEST_USERS\n            and (claims.get('tid') != settings.TENANT_ID or iss != idp)\n        ):\n            username_claim = guest_username_claim\n\n        if not claims.get(username_claim):\n            logger.error(\"User claim's doesn't have the claim '%s' in his claims: %s\" %\n                         (username_claim, claims))\n            raise PermissionDenied\n\n        userdata = {usermodel.USERNAME_FIELD: claims[username_claim]}\n\n        try:\n            user = usermodel.objects.get(**userdata)\n        except usermodel.DoesNotExist:\n            if settings.CREATE_NEW_USERS:\n                user = usermodel.objects.create(**userdata)\n                logger.debug(\"User '%s' has been created.\", claims[username_claim])\n            else:\n                logger.debug(\"User '%s' doesn't exist and creating users is disabled.\", claims[username_claim])\n                raise PermissionDenied\n        if not user.password:\n            user.set_unusable_password()\n        return user\n\n    # https://github.com/snok/django-auth-adfs/issues/241\n    def update_user_attributes(self, user, claims, claim_mapping=None):\n        \"\"\"\n        Updates user attributes based on the CLAIM_MAPPING setting.\n\n        Recursively updates related fields if CLAIM_MAPPING settings has\n        nested dictionaries.\n\n        Args:\n            user (django.contrib.auth.models.User): User model instance\n            claims (dict): claims from the access token\n        \"\"\"\n        if claim_mapping is None:\n            claim_mapping = settings.CLAIM_MAPPING\n        required_fields = [field.name for field in user._meta.get_fields() if getattr(field, 'blank', True) is False]\n\n        for field, claim in claim_mapping.items():\n            if hasattr(user, field) or user._meta.fields_map.get(field):\n                if not isinstance(claim, dict):\n                    if claim in claims:\n                        setattr(user, field, claims[claim])\n                        logger.debug(\"Attribute '%s' for instance '%s' was set to '%s'.\", field, user, claims[claim])\n                    else:\n                        if field in required_fields:\n                            msg = \"Claim not found in access token: '{}'. Check ADFS claims mapping.\"\n                            raise ImproperlyConfigured(msg.format(claim))\n                        else:\n                            logger.warning(\"Claim '%s' for field '%s' was not found in \"\n                                           \"the access token for instance '%s'. \"\n                                           \"Field is not required and will be left empty\", claim, field, user)\n                else:\n                    try:\n                        self.update_user_attributes(getattr(user, field), claims, claim_mapping=claim)\n                    except ObjectDoesNotExist:\n                        logger.warning(\"Object for field '{}' does not exist for: '{}'.\".format(field, user))\n\n            else:\n                msg = \"Model '{}' has no field named '{}'. Check ADFS claims mapping.\"\n                raise ImproperlyConfigured(msg.format(user._meta.model_name, field))\n\n    def update_user_groups(self, user, claim_groups):\n        \"\"\"\n        Updates user group memberships based on the GROUPS_CLAIM setting.\n\n        Args:\n            user (django.contrib.auth.models.User): User model instance\n            claim_groups (list): User groups from the access token / MS Graph\n        \"\"\"\n        if settings.GROUPS_CLAIM is not None:\n            # Update the user's group memberships\n            user_group_names = user.groups.all().values_list(\"name\", flat=True)\n\n            if sorted(claim_groups) != sorted(user_group_names):\n                # Get the list of already existing groups in one SQL query\n                existing_claimed_groups = Group.objects.filter(name__in=claim_groups)\n\n                if settings.MIRROR_GROUPS:\n                    existing_claimed_group_names = (\n                        group.name for group in existing_claimed_groups\n                    )\n                    # One SQL query by created group.\n                    # bulk_create could have been used here but we want to send signals.\n                    new_claimed_groups = [\n                        Group.objects.get_or_create(name=name)[0]\n                        for name in claim_groups if name not in existing_claimed_group_names\n                    ]\n                    # Associate the users to all claimed groups\n                    user.groups.set(tuple(existing_claimed_groups) + tuple(new_claimed_groups))\n                else:\n                    # Associate the user to only existing claimed groups\n                    user.groups.set(existing_claimed_groups)\n\n    def update_user_flags(self, user, claims, claim_groups):\n        \"\"\"\n        Updates user boolean attributes based on the BOOLEAN_CLAIM_MAPPING setting.\n\n        Args:\n            user (django.contrib.auth.models.User): User model instance\n            claims (dict): Claims from the access token\n            claim_groups (list): User groups from the access token / MS Graph\n        \"\"\"\n        if settings.GROUPS_CLAIM is not None:\n            for flag, group in settings.GROUP_TO_FLAG_MAPPING.items():\n                if hasattr(user, flag):\n                    if not isinstance(group, list):\n                        group = [group]\n\n                    if any(group_list_item in claim_groups for group_list_item in group):\n                        value = True\n                    else:\n                        value = False\n                    setattr(user, flag, value)\n                    logger.debug(\"Attribute '%s' for user '%s' was set to '%s'.\", flag, user, value)\n                else:\n                    msg = \"User model has no field named '{}'. Check ADFS boolean claims mapping.\"\n                    raise ImproperlyConfigured(msg.format(flag))\n\n        for field, claim in settings.BOOLEAN_CLAIM_MAPPING.items():\n            if hasattr(user, field):\n                bool_val = False\n                if claim in claims and str(claims[claim]).lower() in ['y', 'yes', 't', 'true', 'on', '1']:\n                    bool_val = True\n                setattr(user, field, bool_val)\n                logger.debug(\"Attribute '%s' for user '%s' was set to '%s'.\", field, user, bool_val)\n            else:\n                msg = \"User model has no field named '{}'. Check ADFS boolean claims mapping.\"\n                raise ImproperlyConfigured(msg.format(field))\n\n\nclass AdfsAuthCodeBackend(AdfsBaseBackend):\n    \"\"\"\n    Authentication backend to allow authenticating users against a\n    Microsoft ADFS server with an authorization code.\n    \"\"\"\n\n    def authenticate(self, request=None, authorization_code=None, **kwargs):\n        # If there's no token or code, we pass control to the next authentication backend\n        if authorization_code is None or authorization_code == '':\n            logger.debug(\"Authentication backend was called but no authorization code was received\")\n            return\n\n        # If loaded data is too old, reload it again\n        provider_config.load_config()\n\n        adfs_response = self.exchange_auth_code(authorization_code, request)\n        access_token = adfs_response[\"access_token\"]\n        user = self.process_access_token(access_token, adfs_response)\n        return user\n\n\nclass AdfsAccessTokenBackend(AdfsBaseBackend):\n    \"\"\"\n    Authentication backend to allow authenticating users against a\n    Microsoft ADFS server with an access token retrieved by the client.\n    \"\"\"\n\n    def authenticate(self, request=None, access_token=None, **kwargs):\n        # If loaded data is too old, reload it again\n        provider_config.load_config()\n\n        # If there's no token or code, we pass control to the next authentication backend\n        if access_token is None or access_token == '':\n            logger.debug(\"Authentication backend was called but no access token was received\")\n            return\n\n        access_token = access_token.decode()\n        user = self.process_access_token(access_token)\n        return user\n\n\nclass AdfsBackend(AdfsAuthCodeBackend):\n    \"\"\" Backwards compatible class name \"\"\"\n    pass\n"
  },
  {
    "path": "django_auth_adfs/config.py",
    "content": "import base64\nimport logging\nimport warnings\nfrom datetime import datetime, timedelta\nfrom xml.etree import ElementTree\n\nimport requests\nimport requests.adapters\nfrom urllib3.util.retry import Retry\nfrom cryptography.hazmat.backends.openssl.backend import backend\nfrom cryptography.x509 import load_der_x509_certificate\nfrom django.conf import settings as django_settings\nfrom django.contrib.auth import REDIRECT_FIELD_NAME\nfrom django.contrib.auth import get_user_model\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.http import QueryDict\nfrom django.shortcuts import render\nfrom django.utils.module_loading import import_string\n\ntry:\n    from django.urls import reverse\nexcept ImportError:  # Django < 1.10\n    from django.core.urlresolvers import reverse\n\nlogger = logging.getLogger(\"django_auth_adfs\")\n\nAZURE_AD_SERVER = \"login.microsoftonline.com\"\nDEFAULT_SETTINGS_CLASS = 'django_auth_adfs.config.Settings'\n\n\nclass ConfigLoadError(Exception):\n    pass\n\n\ndef _get_settings_class():\n    \"\"\"\n    Get the AUTH_ADFS setting from the Django settings.\n    \"\"\"\n    if not hasattr(django_settings, \"AUTH_ADFS\"):\n        msg = \"The configuration directive 'AUTH_ADFS' was not found in your Django settings\"\n        raise ImproperlyConfigured(msg)\n    cls = django_settings.AUTH_ADFS.get('SETTINGS_CLASS', DEFAULT_SETTINGS_CLASS)\n    return import_string(cls)\n\n\nclass Settings(object):\n    \"\"\"\n    Settings implementation reading from the Django settings.\n    \"\"\"\n\n    def __init__(self):\n        # Set defaults\n        self.AUDIENCE = None  # Required\n        self.BLOCK_GUEST_USERS = False\n        self.BOOLEAN_CLAIM_MAPPING = {}\n        self.CA_BUNDLE = True\n        self.CLAIM_MAPPING = {}\n        self.CLIENT_ID = None  # Required\n        self.CLIENT_SECRET = None\n        self.CONFIG_RELOAD_INTERVAL = 24  # hours\n        self.CREATE_NEW_USERS = True\n        self.DISABLE_SSO = False\n        self.GROUP_TO_FLAG_MAPPING = {}\n        self.GROUPS_CLAIM = \"group\"\n        self.LOGIN_EXEMPT_URLS = []\n        self.MIRROR_GROUPS = False\n        self.RELYING_PARTY_ID = None  # Required\n        self.RETRIES = 3\n        self.SERVER = None  # Required\n        self.TENANT_ID = None  # Required\n        self.TIMEOUT = 5\n        self.USERNAME_CLAIM = \"winaccountname\"\n        self.GUEST_USERNAME_CLAIM = None\n        self.JWT_LEEWAY = 0\n        self.CUSTOM_FAILED_RESPONSE_VIEW = lambda request, error_message, status: render(\n            request, 'django_auth_adfs/login_failed.html', {'error_message': error_message}, status=status\n        )\n        self.PROXIES = None\n\n        self.VERSION = 'v1.0'\n        self.SCOPES = []\n\n        required_settings = [\n            \"AUDIENCE\",\n            \"CLIENT_ID\",\n            \"RELYING_PARTY_ID\",\n            \"USERNAME_CLAIM\",\n        ]\n\n        deprecated_settings = {\n            \"AUTHORIZE_PATH\": \"This setting is automatically loaded from ADFS.\",\n            \"ISSUER\": \"This setting is automatically loaded from ADFS.\",\n            \"LOGIN_REDIRECT_URL\": \"Instead use the standard Django settings with the same name.\",\n            \"REDIR_URI\": \"This setting is automatically determined based on the URL configuration of Django.\",\n            \"SIGNING_CERT\": \"The token signing certificates are automatically loaded from ADFS.\",\n            \"TOKEN_PATH\": \"This setting is automatically loaded from ADFS.\",\n        }\n\n        if not hasattr(django_settings, \"AUTH_ADFS\"):\n            msg = \"The configuration directive 'AUTH_ADFS' was not found in your Django settings\"\n            raise ImproperlyConfigured(msg)\n        _settings = django_settings.AUTH_ADFS\n        # Settings class is loaded by now. Delete this setting\n        if \"SETTINGS_CLASS\" in _settings:\n            del _settings[\"SETTINGS_CLASS\"]\n\n        # Handle deprecated settings\n        for setting, message in deprecated_settings.items():\n            if setting in _settings:\n                warnings.warn(\"Setting {} is deprecated and it's value was ignored. {}\".format(setting, message),\n                              DeprecationWarning)\n                del _settings[setting]\n\n        if \"CERT_MAX_AGE\" in _settings:\n            _settings[\"CONFIG_RELOAD_INTERVAL\"] = _settings[\"CERT_MAX_AGE\"]\n            warnings.warn('Setting CERT_MAX_AGE has been renamed to CONFIG_RELOAD_INTERVAL. The value was copied.',\n                          DeprecationWarning)\n            del _settings[\"CERT_MAX_AGE\"]\n\n        if \"GROUP_CLAIM\" in _settings:\n            _settings[\"GROUPS_CLAIM\"] = _settings[\"GROUP_CLAIM\"]\n            warnings.warn('Setting GROUP_CLAIM has been renamed to GROUPS_CLAIM. The value was copied.',\n                          DeprecationWarning)\n            del _settings[\"GROUP_CLAIM\"]\n\n        if \"RESOURCE\" in _settings:\n            _settings[\"RELYING_PARTY_ID\"] = _settings[\"RESOURCE\"]\n            del _settings[\"RESOURCE\"]\n        if \"TENANT_ID\" in _settings:\n            # If a tenant ID was set, switch to Azure AD mode\n            if \"SERVER\" in _settings:\n                raise ImproperlyConfigured(\"The SERVER cannot be set when TENANT_ID is set.\")\n            self.SERVER = AZURE_AD_SERVER\n            self.USERNAME_CLAIM = \"upn\"\n            self.GROUPS_CLAIM = \"groups\"\n            self.CLAIM_MAPPING = {\"first_name\": \"given_name\",\n                                  \"last_name\": \"family_name\",\n                                  \"email\": \"email\"}\n        elif \"VERSION\" in _settings:\n            raise ImproperlyConfigured(\"The VERSION cannot be set when TENANT_ID is not set.\")\n\n        if self.VERSION == \"v2.0\" and not self.SCOPES and self.RELYING_PARTY_ID:\n            warnings.warn('Use `SCOPES` for AzureAD instead of RELYING_PARTY_ID', DeprecationWarning)\n        if not isinstance(self.SCOPES, list):\n            raise ImproperlyConfigured(\"Scopes must be a list\")\n        # Overwrite defaults with user settings\n        for setting, value in _settings.items():\n            if hasattr(self, setting):\n                setattr(self, setting, value)\n            else:\n                msg = \"'{0}' is not a valid configuration directive for django_auth_adfs.\"\n                raise ImproperlyConfigured(msg.format(setting))\n\n        if self.SERVER != AZURE_AD_SERVER and self.BLOCK_GUEST_USERS:\n            raise ImproperlyConfigured(\"You can only set BLOCK_GUEST_USERS when self.TENANT_ID is set\")\n\n        if self.TENANT_ID is None:\n            # For on premises ADFS, the tenant ID is set to adfs\n            # On AzureAD the adfs part in the URL happens to be replace by the tenant ID.\n            self.TENANT_ID = \"adfs\"\n\n        for setting in required_settings:\n            if not getattr(self, setting):\n                msg = \"django_auth_adfs setting '{0}' has not been set\".format(setting)\n                raise ImproperlyConfigured(msg)\n\n        # Setup dynamic settings\n        if not callable(self.CUSTOM_FAILED_RESPONSE_VIEW):\n            self.CUSTOM_FAILED_RESPONSE_VIEW = import_string(self.CUSTOM_FAILED_RESPONSE_VIEW)\n\n        # Validate setting conflicts\n        usermodel = get_user_model()\n        if usermodel.USERNAME_FIELD in self.CLAIM_MAPPING:\n            raise ImproperlyConfigured(\"You cannot set the username field of the user model from \"\n                                       \"the CLAIM_MAPPING setting. Instead use the USERNAME_CLAIM setting.\")\n\n\nclass ProviderConfig(object):\n    def __init__(self):\n        self._config_timestamp = None\n        self._mode = None\n\n        self.authorization_endpoint = None\n        self.signing_keys = None\n        self.token_endpoint = None\n        self.end_session_endpoint = None\n        self.issuer = None\n        self.msgraph_endpoint = None\n\n        allowed_methods = frozenset([\n            'HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE', 'POST'\n        ])\n\n        retry = Retry(\n            total=settings.RETRIES,\n            read=settings.RETRIES,\n            connect=settings.RETRIES,\n            backoff_factor=0.3,\n            allowed_methods=allowed_methods\n        )\n        self.session = requests.Session()\n        adapter = requests.adapters.HTTPAdapter(max_retries=retry)\n        self.session.mount('https://', adapter)\n        self.session.verify = settings.CA_BUNDLE\n        if hasattr(settings, \"PROXIES\"):\n            self.session.proxies = settings.PROXIES\n\n    def load_config(self):\n        # If loaded data is too old, reload it again\n        refresh_time = datetime.now() - timedelta(hours=settings.CONFIG_RELOAD_INTERVAL)\n        if self._config_timestamp is None or self._config_timestamp < refresh_time:\n            logger.debug(\"Loading ID Provider configuration.\")\n            try:\n                loaded = self._load_openid_config()\n                self._mode = \"openid_connect\"\n            except ConfigLoadError:\n                loaded = self._load_federation_metadata()\n                self._mode = \"oauth2\"\n\n            if not loaded:\n                if self._config_timestamp is None:\n                    msg = \"Could not load any data from ADFS server. \" \\\n                          \"Authentication against ADFS not be possible. \" \\\n                          \"Verify your settings and the connection with the ADFS server.\"\n                    logger.critical(msg)\n                    raise RuntimeError(msg)\n                else:\n                    # We got data from the previous time. Log a message, but don't abort.\n                    logger.warning(\"Could not load any data from ADFS server. Keeping previous configurations\")\n            self._config_timestamp = datetime.now()\n\n            logger.info(\"Loaded settings from ADFS server.\")\n            logger.info(\"operating mode:         %s\", self._mode)\n            logger.info(\"authorization endpoint: %s\", self.authorization_endpoint)\n            logger.info(\"token endpoint:         %s\", self.token_endpoint)\n            logger.info(\"end session endpoint:   %s\", self.end_session_endpoint)\n            logger.info(\"issuer:                 %s\", self.issuer)\n            logger.info(\"msgraph endpoint:       %s\", self.msgraph_endpoint)\n\n    def _load_openid_config(self):\n        if settings.VERSION != 'v1.0':\n            config_url = \"https://{}/{}/{}/.well-known/openid-configuration?appid={}\".format(\n                settings.SERVER, settings.TENANT_ID, settings.VERSION, settings.CLIENT_ID\n            )\n        else:\n            config_url = \"https://{}/{}/.well-known/openid-configuration?appid={}\".format(\n                settings.SERVER, settings.TENANT_ID, settings.CLIENT_ID\n            )\n\n        try:\n            logger.info(\"Trying to get OpenID Connect config from %s\", config_url)\n            response = self.session.get(config_url, timeout=settings.TIMEOUT)\n            response.raise_for_status()\n            openid_cfg = response.json()\n\n            response = self.session.get(openid_cfg[\"jwks_uri\"], timeout=settings.TIMEOUT)\n            response.raise_for_status()\n            signing_certificates = [x[\"x5c\"][0] for x in response.json()[\"keys\"] if x.get(\"use\", \"sig\") == \"sig\"]\n            #                               ^^^\n            # https://tools.ietf.org/html/draft-ietf-jose-json-web-key-41#section-4.7\n            # The PKIX certificate containing the key value MUST be the first certificate\n        except requests.HTTPError:\n            raise ConfigLoadError\n\n        self._load_keys(signing_certificates)\n        try:\n            self.authorization_endpoint = openid_cfg[\"authorization_endpoint\"]\n            self.token_endpoint = openid_cfg[\"token_endpoint\"]\n            self.end_session_endpoint = openid_cfg[\"end_session_endpoint\"]\n            if settings.TENANT_ID != 'adfs':\n                self.issuer = openid_cfg[\"issuer\"]\n                self.msgraph_endpoint = openid_cfg[\"msgraph_host\"]\n            else:\n                self.issuer = openid_cfg[\"access_token_issuer\"]\n                self.msgraph_endpoint = \"graph.microsoft.com\"\n        except KeyError:\n            raise ConfigLoadError\n        return True\n\n    def _load_federation_metadata(self):\n        server_url = \"https://{}\".format(settings.SERVER)\n        base_url = \"{}/{}\".format(server_url, settings.TENANT_ID)\n        if settings.TENANT_ID == \"adfs\":\n            adfs_config_url = server_url + \"/FederationMetadata/2007-06/FederationMetadata.xml\"\n        else:\n            adfs_config_url = base_url + \"/FederationMetadata/2007-06/FederationMetadata.xml\"\n\n        try:\n            logger.info(\"Trying to get ADFS Metadata file %s\", adfs_config_url)\n            response = self.session.get(adfs_config_url, timeout=settings.TIMEOUT)\n            response.raise_for_status()\n        except requests.HTTPError:\n            raise ConfigLoadError\n\n        # Extract token signing certificates\n        xml_tree = ElementTree.fromstring(response.content)\n        cert_nodes = xml_tree.findall(\n            \"./{urn:oasis:names:tc:SAML:2.0:metadata}RoleDescriptor\"\n            \"[@{http://www.w3.org/2001/XMLSchema-instance}type='fed:SecurityTokenServiceType']\"\n            \"/{urn:oasis:names:tc:SAML:2.0:metadata}KeyDescriptor[@use='signing']\"\n            \"/{http://www.w3.org/2000/09/xmldsig#}KeyInfo\"\n            \"/{http://www.w3.org/2000/09/xmldsig#}X509Data\"\n            \"/{http://www.w3.org/2000/09/xmldsig#}X509Certificate\")\n        signing_certificates = [node.text for node in cert_nodes]\n\n        self._load_keys(signing_certificates)\n        self.issuer = xml_tree.get(\"entityID\")\n        self.authorization_endpoint = base_url + \"/oauth2/authorize\"\n        self.token_endpoint = base_url + \"/oauth2/token\"\n        self.end_session_endpoint = base_url + \"/ls/?wa=wsignout1.0\"\n        self.msgraph_endpoint = \"graph.microsoft.com\"\n        return True\n\n    def _load_keys(self, certificates):\n        new_keys = []\n        for cert in certificates:\n            logger.debug(\"Loading public key from certificate: %s\", cert)\n            cert_obj = load_der_x509_certificate(base64.b64decode(cert), backend)\n            new_keys.append(cert_obj.public_key())\n        self.signing_keys = new_keys\n\n    def redirect_uri(self, request):\n        self.load_config()\n        return request.build_absolute_uri(reverse(\"django_auth_adfs:callback\"))\n\n    def build_authorization_endpoint(self, request, disable_sso=None, force_mfa=False):\n        \"\"\"\n        This function returns the ADFS authorization URL.\n\n        Args:\n            request(django.http.request.HttpRequest): A django Request object\n            disable_sso(bool): Whether to disable single sign-on and force the ADFS server to show a login prompt.\n            force_mfa(bool): If MFA should be forced\n\n        Returns:\n            str: The redirect URI\n\n        \"\"\"\n        self.load_config()\n        if request.method == 'POST':\n            redirect_to = request.POST.get(REDIRECT_FIELD_NAME, None)\n        else:\n            redirect_to = request.GET.get(REDIRECT_FIELD_NAME, None)\n        if not redirect_to:\n            redirect_to = django_settings.LOGIN_REDIRECT_URL\n        redirect_to = base64.urlsafe_b64encode(redirect_to.encode()).decode()\n        query = QueryDict(mutable=True)\n        query.update({\n            \"response_type\": \"code\",\n            \"client_id\": settings.CLIENT_ID,\n            \"resource\": settings.RELYING_PARTY_ID,\n            \"redirect_uri\": self.redirect_uri(request),\n            \"state\": redirect_to,\n        })\n        if self._mode == \"openid_connect\":\n            if settings.VERSION == 'v2.0':\n                if settings.SCOPES:\n                    query['scope'] = \" \".join(settings.SCOPES)\n                else:\n                    query[\"scope\"] = f\"openid api://{settings.RELYING_PARTY_ID}/.default\"\n                query.pop(\"resource\")\n            else:\n                query[\"scope\"] = \"openid\"\n            if (disable_sso is None and settings.DISABLE_SSO) or disable_sso is True:\n                query[\"prompt\"] = \"login\"\n            if force_mfa:\n                query[\"amr_values\"] = \"ngcmfa\"\n\n        return \"{0}?{1}\".format(self.authorization_endpoint, query.urlencode())\n\n    def build_end_session_endpoint(self):\n        \"\"\"\n        This function returns the ADFS end session URL to log a user out.\n\n        Returns:\n            str: The redirect URI\n\n        \"\"\"\n        self.load_config()\n        return self.end_session_endpoint\n\n\nsettings_cls = _get_settings_class()\nsettings = settings_cls()\nprovider_config = ProviderConfig()\n"
  },
  {
    "path": "django_auth_adfs/drf-urls.py",
    "content": "# flake8: noqa\nimport warnings\nfrom .drf_urls import *\n\nwarnings.warn(\n    \"drf-urls.py is not a valid module name and will be \"\n    \"removed in a future version, use drf_urls.py instead\",\n    PendingDeprecationWarning\n)\n"
  },
  {
    "path": "django_auth_adfs/drf_urls.py",
    "content": "\"\"\"\nThese URL patterns are used to override the default Django Rest Framework login page.\n\nIt's a bit of a hack, but DRF doesn't support overriding the login URL.\n\"\"\"\nfrom django.urls import re_path\n\nfrom django_auth_adfs import views\n\napp_name = \"rest_framework\"\n\nurlpatterns = [\n    re_path(r'^login$', views.OAuth2LoginView.as_view(), name='login'),\n    re_path(r'^logout$', views.OAuth2LogoutView.as_view(), name='logout'),\n]\n"
  },
  {
    "path": "django_auth_adfs/exceptions.py",
    "content": "class MFARequired(Exception):\n    \"\"\"\n    Exception to indicate that a MFA auth is required.\n    \"\"\"\n    pass\n"
  },
  {
    "path": "django_auth_adfs/middleware.py",
    "content": "\"\"\"\nBased on https://djangosnippets.org/snippets/1179/\n\"\"\"\nfrom re import compile\n\nfrom django.conf import settings as django_settings\nfrom django.contrib.auth.views import redirect_to_login\nfrom django.urls import reverse\n\nfrom django_auth_adfs.exceptions import MFARequired\nfrom django_auth_adfs.config import settings\n\nLOGIN_EXEMPT_URLS = [\n    compile(django_settings.LOGIN_URL.lstrip('/')),\n    compile(reverse(\"django_auth_adfs:login\").lstrip('/')),\n    compile(reverse(\"django_auth_adfs:logout\").lstrip('/')),\n    compile(reverse(\"django_auth_adfs:callback\").lstrip('/')),\n]\nif hasattr(settings, 'LOGIN_EXEMPT_URLS'):\n    LOGIN_EXEMPT_URLS += [compile(expr) for expr in settings.LOGIN_EXEMPT_URLS]\n\n\nclass LoginRequiredMiddleware:\n    \"\"\"\n    Middleware that requires a user to be authenticated to view any page other\n    than LOGIN_URL. Exemptions to this requirement can optionally be specified\n    in settings via a list of regular expressions in LOGIN_EXEMPT_URLS (which\n    you can copy from your urls.py).\n\n    Requires authentication middleware and template context processors to be\n    loaded. You'll get an error if they aren't.\n    \"\"\"\n    def __init__(self, get_response):\n        self.get_response = get_response\n\n    def __call__(self, request):\n        assert hasattr(request, 'user'), \"The Login Required middleware requires \" \\\n                                         \"authentication middleware to be installed. \" \\\n                                         \"Edit your MIDDLEWARE setting to insert \" \\\n                                         \"'django.contrib.auth.middleware.AuthenticationMiddleware'. \" \\\n                                         \"If that doesn't work, ensure your TEMPLATE_CONTEXT_PROCESSORS \" \\\n                                         \"setting includes 'django.core.context_processors.auth'.\"\n        if not request.user.is_authenticated:\n            path = request.path_info.lstrip('/')\n            if not any(m.match(path) for m in LOGIN_EXEMPT_URLS):\n                try:\n                    return redirect_to_login(request.get_full_path())\n                except MFARequired:\n                    return redirect_to_login('django_auth_adfs:login-force-mfa')\n\n        return self.get_response(request)\n"
  },
  {
    "path": "django_auth_adfs/rest_framework.py",
    "content": "from __future__ import absolute_import\n\nfrom django.contrib.auth import authenticate\nfrom rest_framework import exceptions\nfrom rest_framework.authentication import (\n    BaseAuthentication, get_authorization_header\n)\n\nfrom django_auth_adfs.exceptions import MFARequired\n\n\nclass AdfsAccessTokenAuthentication(BaseAuthentication):\n    \"\"\"\n    ADFS access Token authentication\n    \"\"\"\n    www_authenticate_realm = 'api'\n\n    def authenticate(self, request):\n        \"\"\"\n        Returns a `User` if a correct access token has been supplied\n        in the Authorization header.  Otherwise returns `None`.\n        \"\"\"\n        auth = get_authorization_header(request).split()\n\n        if not auth or auth[0].lower() != b'bearer':\n            return None\n\n        if len(auth) == 1:\n            msg = 'Invalid authorization header. No credentials provided.'\n            raise exceptions.AuthenticationFailed(msg)\n        elif len(auth) > 2:\n            msg = 'Invalid authorization header. Access token should not contain spaces.'\n            raise exceptions.AuthenticationFailed(msg)\n\n        # Authenticate the user\n        # The AdfsAuthCodeBackend authentication backend will notice the \"access_token\" parameter\n        # and skip the request for an access token using the authorization code\n        try:\n            user = authenticate(access_token=auth[1])\n        except MFARequired as e:\n            raise exceptions.AuthenticationFailed('MFA auth is required.') from e\n\n        if user is None:\n            raise exceptions.AuthenticationFailed('Invalid access token.')\n\n        if not user.is_active:\n            raise exceptions.AuthenticationFailed('User inactive or deleted.')\n\n        return user, auth[1]\n\n    def authenticate_header(self, request):\n        return 'Bearer realm=\"%s\" token_type=\"JWT\"' % self.www_authenticate_realm\n"
  },
  {
    "path": "django_auth_adfs/signals.py",
    "content": "from django.dispatch import Signal\n\n# Arguments sent with the signal:\n# * user\n# * claims\n# * adfs_response\npost_authenticate = Signal()\n"
  },
  {
    "path": "django_auth_adfs/templates/django_auth_adfs/login_failed.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Login Failure</title>\n  <style>\n    #message {\n      position: fixed;\n      top: 50%;\n      left: 50%;\n      width: 30em;\n      height: 3em;\n      margin-top: -9em;\n      margin-left: -15em;\n      border: 1px solid #ccc;\n      background-color: #f3f3f3;\n      font-family: Lato, proxima-nova, Helvetica Neue, Arial, sans-serif;\n      text-align: center;\n      display: flex;\n      justify-content: center;\n      align-content: center;\n      flex-direction: column;\n    }\n  </style>\n</head>\n<body>\n<div id=\"message\">\n  <strong>{{ error_message }}</strong>\n</div>\n</body>\n</html>\n"
  },
  {
    "path": "django_auth_adfs/urls.py",
    "content": "from django.urls import re_path\n\nfrom django_auth_adfs import views\n\napp_name = \"django_auth_adfs\"\n\nurlpatterns = [\n    re_path(r'^callback$', views.OAuth2CallbackView.as_view(), name='callback'),\n    re_path(r'^login$', views.OAuth2LoginView.as_view(), name='login'),\n    re_path(r'^login_no_sso$', views.OAuth2LoginNoSSOView.as_view(), name='login-no-sso'),\n    re_path(r'^login_force_mfa$', views.OAuth2LoginForceMFA.as_view(), name='login-force-mfa'),\n    re_path(r'^logout$', views.OAuth2LogoutView.as_view(), name='logout'),\n]\n"
  },
  {
    "path": "django_auth_adfs/views.py",
    "content": "import base64\nimport logging\n\nfrom django.conf import settings as django_settings\nfrom django.contrib.auth import authenticate, login, logout\nfrom django.shortcuts import redirect\ntry:\n    from django.utils.http import url_has_allowed_host_and_scheme\nexcept ImportError:\n    # Django <3.0\n    from django.utils.http import is_safe_url as url_has_allowed_host_and_scheme\nfrom django.views.generic import View\n\nfrom django_auth_adfs.config import provider_config, settings\nfrom django_auth_adfs.exceptions import MFARequired\n\nlogger = logging.getLogger(\"django_auth_adfs\")\n\n\nclass OAuth2CallbackView(View):\n    def get(self, request):\n        \"\"\"\n        Handles the redirect from ADFS to our site.\n        We try to process the passed authorization code and login the user.\n\n        Args:\n            request (django.http.request.HttpRequest): A Django Request object\n        \"\"\"\n        code = request.GET.get(\"code\")\n        if not code:\n            # Return an error message\n            return settings.CUSTOM_FAILED_RESPONSE_VIEW(\n                request,\n                error_message=\"No authorization code was provided.\",\n                status=400\n            )\n\n        redirect_to = request.GET.get(\"state\")\n        try:\n            user = authenticate(request=request, authorization_code=code)\n        except MFARequired:\n            return redirect(provider_config.build_authorization_endpoint(request, force_mfa=True))\n\n        if user:\n            if user.is_active:\n                login(request, user)\n                # Redirect to the \"after login\" page.\n                # Because we got redirected from ADFS, we can't know where the\n                # user came from.\n                if redirect_to:\n                    redirect_to = base64.urlsafe_b64decode(redirect_to.encode()).decode()\n                else:\n                    redirect_to = django_settings.LOGIN_REDIRECT_URL\n                url_is_safe = url_has_allowed_host_and_scheme(\n                    url=redirect_to,\n                    allowed_hosts=[request.get_host()],\n                    require_https=request.is_secure(),\n                )\n                redirect_to = redirect_to if url_is_safe else '/'\n                return redirect(redirect_to)\n            else:\n                # Return a 'disabled account' error message\n                return settings.CUSTOM_FAILED_RESPONSE_VIEW(\n                    request,\n                    error_message=\"Your account is disabled.\",\n                    status=403\n                )\n        else:\n            # Return an 'invalid login' error message\n            return settings.CUSTOM_FAILED_RESPONSE_VIEW(\n                request,\n                error_message=\"Login failed.\",\n                status=401\n            )\n\n\nclass OAuth2LoginView(View):\n    def get(self, request):\n        \"\"\"\n        Initiates the OAuth2 flow and redirect the user agent to ADFS\n\n        Args:\n            request (django.http.request.HttpRequest): A Django Request object\n        \"\"\"\n        return redirect(provider_config.build_authorization_endpoint(request))\n\n    def post(self, request):\n        \"\"\"\n        Initiates the OAuth2 flow and redirect the user agent to ADFS\n\n        Args:\n            request (django.http.request.HttpRequest): A Django Request object\n        \"\"\"\n        return redirect(provider_config.build_authorization_endpoint(request))\n\n\nclass OAuth2LoginNoSSOView(View):\n    def get(self, request):\n        \"\"\"\n        Initiates the OAuth2 flow and redirect the user agent to ADFS\n\n        Args:\n            request (django.http.request.HttpRequest): A Django Request object\n        \"\"\"\n        return redirect(provider_config.build_authorization_endpoint(request, disable_sso=True))\n\n    def post(self, request):\n        \"\"\"\n        Initiates the OAuth2 flow and redirect the user agent to ADFS\n\n        Args:\n            request (django.http.request.HttpRequest): A Django Request object\n        \"\"\"\n        return redirect(provider_config.build_authorization_endpoint(request, disable_sso=True))\n\n\nclass OAuth2LoginForceMFA(View):\n    def get(self, request):\n        \"\"\"\n        Initiates the OAuth2 flow and redirect the user agent to ADFS\n\n        Args:\n            request (django.http.request.HttpRequest): A Django Request object\n        \"\"\"\n        return redirect(provider_config.build_authorization_endpoint(request, force_mfa=True))\n\n    def post(self, request):\n        \"\"\"\n        Initiates the OAuth2 flow and redirect the user agent to ADFS\n\n        Args:\n            request (django.http.request.HttpRequest): A Django Request object\n        \"\"\"\n        return redirect(provider_config.build_authorization_endpoint(request, force_mfa=True))\n\n\nclass OAuth2LogoutView(View):\n    def get(self, request):\n        \"\"\"\n        Logs out the user from both Django and ADFS\n\n        Args:\n            request (django.http.request.HttpRequest): A Django Request object\n        \"\"\"\n        logout(request)\n        return redirect(provider_config.build_end_session_endpoint())\n\n    def post(self, request):\n        \"\"\"\n        Logs out the user from both Django and ADFS\n\n        Args:\n            request (django.http.request.HttpRequest): A Django Request object\n        \"\"\"\n        logout(request)\n        return redirect(provider_config.build_end_session_endpoint())\n"
  },
  {
    "path": "docs/Makefile",
    "content": "# Makefile for Sphinx documentation\n#\n\n# You can set these variables from the command line.\nSPHINXOPTS    =\nSPHINXBUILD   = sphinx-build\nPAPER         =\nBUILDDIR      = _build\n\n# User-friendly check for sphinx-build\nifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)\n$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)\nendif\n\n# Internal variables.\nPAPEROPT_a4     = -D latex_paper_size=a4\nPAPEROPT_letter = -D latex_paper_size=letter\nALLSPHINXOPTS   = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n# the i18n builder cannot share the environment and doctrees with the others\nI18NSPHINXOPTS  = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .\n\n.PHONY: help\nhelp:\n\t@echo \"Please use \\`make <target>' where <target> is one of\"\n\t@echo \"  html       to make standalone HTML files\"\n\t@echo \"  dirhtml    to make HTML files named index.html in directories\"\n\t@echo \"  singlehtml to make a single large HTML file\"\n\t@echo \"  pickle     to make pickle files\"\n\t@echo \"  json       to make JSON files\"\n\t@echo \"  htmlhelp   to make HTML files and a HTML help project\"\n\t@echo \"  qthelp     to make HTML files and a qthelp project\"\n\t@echo \"  applehelp  to make an Apple Help Book\"\n\t@echo \"  devhelp    to make HTML files and a Devhelp project\"\n\t@echo \"  epub       to make an epub\"\n\t@echo \"  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\"\n\t@echo \"  latexpdf   to make LaTeX files and run them through pdflatex\"\n\t@echo \"  latexpdfja to make LaTeX files and run them through platex/dvipdfmx\"\n\t@echo \"  text       to make text files\"\n\t@echo \"  man        to make manual pages\"\n\t@echo \"  texinfo    to make Texinfo files\"\n\t@echo \"  info       to make Texinfo files and run them through makeinfo\"\n\t@echo \"  gettext    to make PO message catalogs\"\n\t@echo \"  changes    to make an overview of all changed/added/deprecated items\"\n\t@echo \"  xml        to make Docutils-native XML files\"\n\t@echo \"  pseudoxml  to make pseudoxml-XML files for display purposes\"\n\t@echo \"  linkcheck  to check all external links for integrity\"\n\t@echo \"  doctest    to run all doctests embedded in the documentation (if enabled)\"\n\t@echo \"  coverage   to run coverage check of the documentation (if enabled)\"\n\n.PHONY: clean\nclean:\n\trm -rf $(BUILDDIR)/*\n\n.PHONY: html\nhtml:\n\t$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/html.\"\n\n.PHONY: dirhtml\ndirhtml:\n\t$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml\n\t@echo\n\t@echo \"Build finished. The HTML pages are in $(BUILDDIR)/dirhtml.\"\n\n.PHONY: singlehtml\nsinglehtml:\n\t$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml\n\t@echo\n\t@echo \"Build finished. The HTML page is in $(BUILDDIR)/singlehtml.\"\n\n.PHONY: pickle\npickle:\n\t$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle\n\t@echo\n\t@echo \"Build finished; now you can process the pickle files.\"\n\n.PHONY: json\njson:\n\t$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json\n\t@echo\n\t@echo \"Build finished; now you can process the JSON files.\"\n\n.PHONY: htmlhelp\nhtmlhelp:\n\t$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp\n\t@echo\n\t@echo \"Build finished; now you can run HTML Help Workshop with the\" \\\n\t      \".hhp project file in $(BUILDDIR)/htmlhelp.\"\n\n.PHONY: qthelp\nqthelp:\n\t$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp\n\t@echo\n\t@echo \"Build finished; now you can run \"qcollectiongenerator\" with the\" \\\n\t      \".qhcp project file in $(BUILDDIR)/qthelp, like this:\"\n\t@echo \"# qcollectiongenerator $(BUILDDIR)/qthelp/django-auth-adfs.qhcp\"\n\t@echo \"To view the help file:\"\n\t@echo \"# assistant -collectionFile $(BUILDDIR)/qthelp/django-auth-adfs.qhc\"\n\n.PHONY: applehelp\napplehelp:\n\t$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp\n\t@echo\n\t@echo \"Build finished. The help book is in $(BUILDDIR)/applehelp.\"\n\t@echo \"N.B. You won't be able to view it unless you put it in\" \\\n\t      \"~/Library/Documentation/Help or install it in your application\" \\\n\t      \"bundle.\"\n\n.PHONY: devhelp\ndevhelp:\n\t$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp\n\t@echo\n\t@echo \"Build finished.\"\n\t@echo \"To view the help file:\"\n\t@echo \"# mkdir -p $$HOME/.local/share/devhelp/django-auth-adfs\"\n\t@echo \"# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-auth-adfs\"\n\t@echo \"# devhelp\"\n\n.PHONY: epub\nepub:\n\t$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub\n\t@echo\n\t@echo \"Build finished. The epub file is in $(BUILDDIR)/epub.\"\n\n.PHONY: latex\nlatex:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo\n\t@echo \"Build finished; the LaTeX files are in $(BUILDDIR)/latex.\"\n\t@echo \"Run \\`make' in that directory to run these through (pdf)latex\" \\\n\t      \"(use \\`make latexpdf' here to do that automatically).\"\n\n.PHONY: latexpdf\nlatexpdf:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through pdflatex...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\n.PHONY: latexpdfja\nlatexpdfja:\n\t$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex\n\t@echo \"Running LaTeX files through platex and dvipdfmx...\"\n\t$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja\n\t@echo \"pdflatex finished; the PDF files are in $(BUILDDIR)/latex.\"\n\n.PHONY: text\ntext:\n\t$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text\n\t@echo\n\t@echo \"Build finished. The text files are in $(BUILDDIR)/text.\"\n\n.PHONY: man\nman:\n\t$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man\n\t@echo\n\t@echo \"Build finished. The manual pages are in $(BUILDDIR)/man.\"\n\n.PHONY: texinfo\ntexinfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo\n\t@echo \"Build finished. The Texinfo files are in $(BUILDDIR)/texinfo.\"\n\t@echo \"Run \\`make' in that directory to run these through makeinfo\" \\\n\t      \"(use \\`make info' here to do that automatically).\"\n\n.PHONY: info\ninfo:\n\t$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo\n\t@echo \"Running Texinfo files through makeinfo...\"\n\tmake -C $(BUILDDIR)/texinfo info\n\t@echo \"makeinfo finished; the Info files are in $(BUILDDIR)/texinfo.\"\n\n.PHONY: gettext\ngettext:\n\t$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale\n\t@echo\n\t@echo \"Build finished. The message catalogs are in $(BUILDDIR)/locale.\"\n\n.PHONY: changes\nchanges:\n\t$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes\n\t@echo\n\t@echo \"The overview file is in $(BUILDDIR)/changes.\"\n\n.PHONY: linkcheck\nlinkcheck:\n\t$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck\n\t@echo\n\t@echo \"Link check complete; look for any errors in the above output \" \\\n\t      \"or in $(BUILDDIR)/linkcheck/output.txt.\"\n\n.PHONY: doctest\ndoctest:\n\t$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest\n\t@echo \"Testing of doctests in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/doctest/output.txt.\"\n\n.PHONY: coverage\ncoverage:\n\t$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage\n\t@echo \"Testing of coverage in the sources finished, look at the \" \\\n\t      \"results in $(BUILDDIR)/coverage/python.txt.\"\n\n.PHONY: xml\nxml:\n\t$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml\n\t@echo\n\t@echo \"Build finished. The XML files are in $(BUILDDIR)/xml.\"\n\n.PHONY: pseudoxml\npseudoxml:\n\t$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml\n\t@echo\n\t@echo \"Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml.\"\n"
  },
  {
    "path": "docs/_templates/.gitkeep",
    "content": ""
  },
  {
    "path": "docs/adfs_3.0_config_guide.rst",
    "content": "Windows 2012 R2 - ADFS 3.0\n==========================\n\nGetting this module to work is sometimes not so straight forward. If your not familiar with JWT tokens or ADFS itself,\nit might take some tries to get all settings right.\n\nThis guide tries to give a basic overview of how to configure ADFS and how to determine the settings for\ndjango-auth-adfs. Installing and configuring the basics of ADFS is not explained here.\n\n* **ADFS server:** https://adfs.example.com\n* **Web server:** http://web.example.com:8000\n\nStep 1 - Configuring a Relying Party Trust\n------------------------------------------\n\nFrom the AD FS Management screen, go to **AD FS ➜ Trust Relationships ➜ Relying Party Trusts** and\nclick **Add Relying Party Trust...**\n\n.. image:: _static/2012/01_add_relying_party.png\n   :scale: 50 %\n\n------------\n\nClick **Start**\n\n.. image:: _static/2012/02_add_relying_party_wizard_page1.png\n   :scale: 50 %\n\n------------\n\nSelect **Enter data about the relying party manually** and click **Next**\n\n.. image:: _static/2012/03_add_relying_party_wizard_page2.png\n   :scale: 50 %\n\n------------\n\nEnter a display name for the relying party and click **Next**.\n\n.. image:: _static/2012/04_add_relying_party_wizard_page3.png\n   :scale: 50 %\n\n------------\n\nSelect **AD FS profile** and click **Next**\n\n.. image:: _static/2012/05_add_relying_party_wizard_page4.png\n   :scale: 50 %\n\n------------\n\nLeave everything empty click **Next**\n\n.. image:: _static/2012/06_add_relying_party_wizard_page5.png\n   :scale: 50 %\n\n------------\n\nWe don't need WS-Federation or SAML support so leave everything empty and click **Next**\n\n.. image:: _static/2012/07_add_relying_party_wizard_page6.png\n   :scale: 50 %\n\n------------\n\nEnter a relying party trust identifier and click **add**. The identifier can be anything but beware, there's a\ndifference between entering a URL and something else. For more details see the example section of\n:ref:`the AUDIENCE setting <audience_setting>`.\n\n.. note::\n    This is the value for the :ref:`audience_setting` and the :ref:`relying_party_id_setting` settings.\n\n.. image:: _static/2012/08_relying_party_id.png\n   :scale: 50 %\n\n------------\n\nSelect **I do not want to configure...** and click **Next**.\n\n.. image:: _static/2012/09_add_relying_party_wizard_page8.png\n   :scale: 50 %\n\n------------\n\nSelect **Permit all users to access the relying party** and click **Next**.\n\n.. image:: _static/2012/10_add_relying_party_wizard_page9.png\n   :scale: 50 %\n\n------------\n\nReview the settings and click **Next** to create the relying party.\n\n.. image:: _static/2012/11_add_relying_party_wizard_review.png\n   :scale: 50 %\n\n------------\n\nCheck **Open the Edit Claim Rules dialog...** and click **Close**\n\n.. image:: _static/2012/12_add_relying_party_wizard_page11.png\n   :scale: 50 %\n\n\nStep 2 - Configuring Claims\n---------------------------\n\nIf you selected **Open the Edit Claim Rules dialog...** while adding a relying party, this screen will open\nautomatically. Else you can open it by right clicking the relying party in the list and select **Edit Claim Rules...**\n\nOn the **Issuance Transform Rules** tab, click the **Add Rule** button\n\n.. image:: _static/2012/13_configure_claims_page1.png\n   :scale: 50 %\n\n------------\n\nSelect **Send LDAP Attributes as Claims** and click **Next**\n\n.. image:: _static/2012/14_configure_claims_page2.png\n   :scale: 50 %\n\n------------\n\nGive the rule a name and select **Active Directory** as the attribute store. Then configure the below claims.\n\n+----------------------------------+----------------------+\n| LDAP Attribute                   | Outgoing Claim Type  |\n+==================================+======================+\n| E-Mail-Addresses                 | E-Mail Address       |\n+----------------------------------+----------------------+\n| Given-Name                       | Given Name           |\n+----------------------------------+----------------------+\n| Surname                          | Surname              |\n+----------------------------------+----------------------+\n| Token-Groups - Unqualified Names | Group                |\n+----------------------------------+----------------------+\n| SAM-Account-Name                 | Windows Account Name |\n+----------------------------------+----------------------+\n\n.. image:: _static/2012/15_configure_claims_page3.png\n    :scale: 50 %\n\nClick **OK** to save the settings\n\n.. note::\n    The **Outgoing Claim Type** is what will be visible in the JWT Access Token. The first 3 claims will go into the\n    :ref:`claim_mapping_setting` setting. The 4th is the :ref:`groups_claim_setting` setting. The 5th is the\n    :ref:`username_claim_setting` setting.\n\n    You cannot just copy the outgoing claim type value from this screen and use it in the settings. The name of the\n    claim as it is in the JWT token is the short name which you can find in the AD FS Management screen underneath\n    **AD FS ➜ Service ➜ Claim Descriptions**\n\n------------\n\nYou should now see the rule added. Click **OK** to save the settings.\n\n.. image:: _static/2012/16_configure_claims_page4.png\n   :scale: 50 %\n\nStep 3 - Add an ADFS client\n---------------------------\n\nWhile the previous steps could be done via the GUI, the next step must be performed via PowerShell.\n\nPick a value for the following fields.\n\n+-------------+----------------------------------------------+\n| Name        | Example value                                |\n+=============+==============================================+\n| Name        | Django Application OAuth2 Client             |\n+-------------+----------------------------------------------+\n| ClientId    | 487d8ff7-80a8-4f62-b926-c2852ab06e94         |\n+-------------+----------------------------------------------+\n| RedirectUri | http://web.example.com/oauth2/callback       |\n+-------------+----------------------------------------------+\n\nNow execute the following command from a powershell console.\n\n.. code-block:: ps1con\n\n    PS C:\\Users\\Administrator> Add-ADFSClient -Name \"Django Application OAuth2 Client\" `\n                                              -ClientId \"487d8ff7-80a8-4f62-b926-c2852ab06e94\" `\n                                              -RedirectUri \"http://web.example.com/oauth2/callback\"\n\nThe **ClientId** value will be the :ref:`client_id_setting` setting and the **RedirectUri** value is based on where you\nadded the ```django_auth_adfs`` in your ``urls.py`` file.\n\nStep 4 - Determine configuration settings\n-----------------------------------------\n\nOnce everything is configured, you can use the below PowerShell commands to determine the value for the settings of this\npackage. The ``<<<<<<`` in the output indicate which settings should match this value.\n\n.. code-block:: ps1con\n\n    PS C:\\Users\\Administrator> Get-AdfsClient -Name \"Django Application OAuth2 Client\"\n\n    RedirectUri : {http://web.example.com:8000/oauth2/callback}\n    Name        : Django Application OAuth2 Client\n    Description :\n    ClientId    : 487d8ff7-80a8-4f62-b926-c2852ab06e94      <<< CLIENT_ID <<<\n    BuiltIn     : False\n    Enabled     : True\n    ClientType  : Public\n\n    PS C:\\Users\\Administrator> Get-AdfsProperties | select HostName | Format-List\n\n    HostName : adfs.example.com      <<< SERVER <<<\n\n    PS C:\\Users\\Administrator> Get-AdfsRelyingPartyTrust -Name \"Django Application\" | Select Identifier | Format-List\n\n    Identifier : {web.example.com}      <<< RELYING_PARTY_ID and AUDIENCE <<<\n\nIf you followed this guide, you should end up with a configuration like this.\n\n.. code-block:: python\n\n    AUTH_ADFS = {\n        \"SERVER\": \"adfs.example.com\",\n        \"CLIENT_ID\": \"487d8ff7-80a8-4f62-b926-c2852ab06e94 \",\n        \"RELYING_PARTY_ID\": \"web.example.com\",\n        \"AUDIENCE\": \"microsoft:identityserver:web.example.com\",\n        \"CLAIM_MAPPING\": {\"first_name\": \"given_name\",\n                          \"last_name\": \"family_name\",\n                          \"email\": \"email\"},\n        \"USERNAME_CLAIM\": \"winaccountname\",\n        \"GROUP_CLAIM\": \"group\"\n    }\n\nEnabling SSO for other browsers\n-------------------------------\n\nBy default, ADFS only supports seamless single sign-on for Internet Explorer.\nIn other browsers, users will always be prompted for their username and password.\n\nTo enable SSO also for other browsers like Chrome and Firefox, execute the following PowerShell command:\n\n.. code-block:: powershell\n\n    [System.Collections.ArrayList]$UserAgents = Get-AdfsProperties | select -ExpandProperty WIASupportedUserAgents\n    $UserAgents.Add(\"Mozilla/5.0\")\n    Set-ADFSProperties -WIASupportedUserAgents $UserAgents\n\nAfter that, restart the ADFS service on every server in the ADFS farm.\n\nFor firefox, you'll also have to change it's ``network.automatic-ntlm-auth.trusted-uris`` setting\nto include the URI of your ADFS server.\n"
  },
  {
    "path": "docs/adfs_4.0_config_guide.rst",
    "content": "Windows 2016 - ADFS 4.0\n=======================\n\nGetting this module to work is sometimes not so straight forward. If your not familiar with JWT tokens or ADFS itself,\nit might take some tries to get all settings right.\n\nThis guide tries to give a basic overview of how to configure ADFS and how to determine the settings for\ndjango-auth-adfs. Installing and configuring the basics of ADFS is not explained here.\n\n* **ADFS server:** https://adfs.example.com\n* **Web server:** http://web.example.com:8000\n\nStep 1 - Configuring an Application Group\n-----------------------------------------\n\nFrom the AD FS Management screen, go to **AD FS ➜ Application Groups** and\nclick **Add Application Group...**\n\n.. image:: _static/2016/01_add_app_group.png\n   :scale: 50 %\n\n------------\n\nFill in a **name** for the application group, select **Web browser accessing a web application** and click **Next**.\n\n.. image:: _static/2016/02_add_app_group_wizard_page1.png\n   :scale: 50 %\n\n------------\n\nMake note of the **Client Identifier** value. This will be the value for the :ref:`client_id_setting` setting.\n\nThe **Redirect URI** value must match with the domain where your Django application is located and the patch where you\nmapped the ``django_auth_adfs`` urls in your ``urls.py`` file. If you follow the installation steps from this\ndocumentation, this should be something like ``https://your.domain.com/oauth2/callback``.\n\n.. image:: _static/2016/03_add_native_app.png\n   :scale: 50 %\n\n------------\n\nSelect **Permit everyone** and click **Next**.\n\n.. image:: _static/2016/04_native_app_access_policy.png\n   :scale: 50 %\n\n------------\n\nReview the settings and click **Next**\n\n* The **Client ID** is the value for the :ref:`client_id_setting` setting.\n* The **Relying Party ID** is the value for the :ref:`relying_party_id_setting` and :ref:`audience_setting` setting.\n\nWhile they both are the same in this screenshot, they can be changed independently from one another afterwards.\n\n.. image:: _static/2016/05_review_settings.png\n   :scale: 50 %\n\n------------\n\nClose the wizard by clicking **Close**. Our django application is now registered in ADFS.\n\n.. image:: _static/2016/06_wizard_end.png\n   :scale: 50 %\n\nStep 2 - Configuring Claims\n---------------------------\n\nOpen the **properties** for the application group we just created.\nSelect the **Web application** entry and click **Edit**\n\n.. image:: _static/2016/07_app_group_settings.png\n   :scale: 50 %\n\n------------\n\nOn the **Issuance Transform Rules** tab, click the **Add Rule** button\n\n.. image:: _static/2016/08_add_claim_rules.png\n   :scale: 50 %\n\n------------\n\nSelect **Send LDAP Attributes as Claims** and click **Next**\n\n.. image:: _static/2016/08_add_ldap_attributes_part1.png\n   :scale: 50 %\n\n------------\n\nGive the rule a name and select **Active Directory** as the attribute store. Then configure the below claims.\n\n+----------------------------------+----------------------+\n| LDAP Attribute                   | Outgoing Claim Type  |\n+==================================+======================+\n| E-Mail-Addresses                 | E-Mail Address       |\n+----------------------------------+----------------------+\n| Given-Name                       | Given Name           |\n+----------------------------------+----------------------+\n| Surname                          | Surname              |\n+----------------------------------+----------------------+\n| Token-Groups - Unqualified Names | Group                |\n+----------------------------------+----------------------+\n| SAM-Account-Name                 | Windows Account Name |\n+----------------------------------+----------------------+\n\n.. image:: _static/2016/08_add_ldap_attributes_part2.png\n    :scale: 50 %\n\nClick **Finish** to save the settings\n\n.. note::\n    The **Outgoing Claim Type** is what will be visible in the JWT Access Token. The first 3 claims will go into the\n    :ref:`claim_mapping_setting` setting. The 4th is the :ref:`groups_claim_setting` setting. The 5th is the\n    :ref:`username_claim_setting` setting.\n\n    You cannot just copy the outgoing claim type value from this screen and use it in the settings. The name of the\n    claim as it is in the JWT token is the short name which you can find in the AD FS Management screen underneath\n    **AD FS ➜ Service ➜ Claim Descriptions**\n\n------------\n\nYou should now see the rule added. Click **OK** a couple of times to save the settings.\n\nStep 3 - Determine configuration settings\n-----------------------------------------\n\nOnce everything is configured, you can use the below PowerShell commands to determine the value for the settings of this\npackage. The ``<<<<<<`` in the output indicate which settings should match this value.\n\n.. code-block:: ps1con\n\n    PS C:\\Users\\Administrator> Get-AdfsNativeClientApplication\n\n    Name                       : Django Application - Native application\n    Identifier                 : 487d8ff7-80a8-4f62-b926-c2852ab06e94      <<< CLIENT_ID <<<\n    ApplicationGroupIdentifier : Django Application\n    Description                :\n    Enabled                    : True\n    RedirectUri                : {http://web.example.com:8000/oauth2/callback}\n    LogoutUri                  :\n\n    PS C:\\Users\\Administrator> Get-AdfsProperties | select HostName | Format-List\n\n    HostName : adfs.example.com      <<< SERVER <<<\n\n    PS C:\\Users\\Administrator> Get-AdfsWebApiApplication | select Identifier | Format-List\n\n    Identifier             : {web.example.com}      <<< RELYING_PARTY_ID and AUDIENCE <<<\n\nIf you followed this guide, you should end up with a configuration like this.\n\n.. code-block:: python\n\n    AUTH_ADFS = {\n        \"SERVER\": \"adfs.example.com\",\n        \"CLIENT_ID\": \"487d8ff7-80a8-4f62-b926-c2852ab06e94\",\n        \"RELYING_PARTY_ID\": \"web.example.com\",\n        \"AUDIENCE\": \"microsoft:identityserver:web.example.com\",\n        \"CLAIM_MAPPING\": {\"first_name\": \"given_name\",\n                          \"last_name\": \"family_name\",\n                          \"email\": \"email\"},\n        \"USERNAME_CLAIM\": \"winaccountname\",\n        \"GROUP_CLAIM\": \"group\"\n    }\n\nEnabling SSO for other browsers\n-------------------------------\n\nBy default, ADFS only supports seamless single sign-on for Internet Explorer.\nIn other browsers, users will always be prompted for their username and password.\n\nTo enable SSO also for other browsers like Chrome and Firefox, execute the following PowerShell command:\n\n.. code-block:: powershell\n\n    [System.Collections.ArrayList]$UserAgents = Get-AdfsProperties | select -ExpandProperty WIASupportedUserAgents\n    $UserAgents.Add(\"Mozilla/5.0\")\n    Set-ADFSProperties -WIASupportedUserAgents $UserAgents\n\nAfter that, restart the ADFS service on every server in the ADFS farm.\n\nFor firefox, you'll also have to change it's ``network.automatic-ntlm-auth.trusted-uris`` setting\nto include the URI of your ADFS server.\n"
  },
  {
    "path": "docs/azure_ad_config_guide.rst",
    "content": "Azure AD\n========\n\nGetting this module to work is sometimes not so straightforward. If you're not familiar with JWT tokens or Azure AD\nitself, it might take some tries to get all the settings right.\n\nThis guide tries to give a basic overview of how to configure Azure AD and how to determine the settings for\ndjango-auth-adfs. Installing and configuring the basics of Azure AD is not explained here.\n\n\nStep 1 - Register a backend application\n---------------------------------------\n\nAfter signing in to `Azure <https://portal.azure.com>`_. Open the **Azure Active Directory** dashboard. \n\n.. image:: _static/AzureAD/01-azure_active_directory.png\n   :scale: 50 %\n\n------------\n\n\nNote down your **Tenant_ID** as you will need it later.\n\n\n.. image:: _static/AzureAD/02-azure_dashboard.png\n    :scale: 50 %\n\n------------\n\n\nNavigate to **App Registrations**, then click **New registration** in the upper left hand corner.\n\n\n.. image:: _static/AzureAD/03-new_registrations.png\n    :scale: 50 %\n\n------------\n\n\nHere you register your application. \n\n1. The display name of your application. \n2. What type of accounts can access your application.\n3. Here you need to add allowed redirect URIs. The Redirect URI value must match with the domain where your Django application is located(*eg. http://localhost:8000/oauth2/callback*).\n\n\n.. image:: _static/AzureAD/04-app_registrations_specs.png\n    :scale: 50 %\n\n------------\n\n\nWhen done registering, you will be redirected to your applications overview. Here you need to note down your **Client_ID**. This is how your Django project finds the right Azure application.  \n\n\n.. image:: _static/AzureAD/05-application_overview.png\n    :scale: 50 %\n\n------------\n\n\nNext we need to generate a **Client_Secret**. Your application will use this to prove its identity when requesting a token. \n\n\n.. image:: _static/AzureAD/06-add_Secret.png\n    :scale: 50 %\n\n------------\n\n\nGive it a short (display) name. This is only used by you, to help keep track of in case you make more client secrets.\n\n\n.. image:: _static/AzureAD/07-add_Secret_name.png\n    :scale: 50 %\n\n------------\n\n\nCopy your secret (value). It will be become hidden after a short time, so be sure to note this quickly.\n\n\n.. image:: _static/AzureAD/08-copy_Secret.png\n    :scale: 50 %\n\n------------\n\n\n    \nStep 2 - Configuring settings.py\n--------------------------------\nWe need to update the ``settings.py`` to accommodate our registered Azure AD application. \n\nReplace your AUTH_ADFS with this.\n\n.. code-block:: python\n\n    # Client secret is not public information. Should store it as an environment variable.\n    \n    client_id = 'Your client id here'\n    client_secret = 'Your client secret here'\n    tenant_id = 'Your tenant id here'\n    \n\n    AUTH_ADFS = {\n        'AUDIENCE': [f'api://{client_id}', client_id],\n        'CLIENT_ID': client_id,\n        'CLIENT_SECRET': client_secret,\n        'CLAIM_MAPPING': {\n            'first_name': 'given_name',\n            'last_name': 'family_name',\n            'email': 'email'\n        },\n        'GROUPS_CLAIM': 'roles',\n        'MIRROR_GROUPS': True,\n        'USERNAME_CLAIM': 'upn',\n        'TENANT_ID': tenant_id,\n        'RELYING_PARTY_ID': client_id\n    }\n\n\n\nAdd this to your INSTALLED_APPS.\n\n.. code-block:: python\n\n    INSTALLED_APPS = [\n        ...\n        'django_auth_adfs',\n        ...\n    ]\n\n\n\nAdd this to your AUTHENTICATION_BACKENDS.\n\n.. code-block:: python\n\n    AUTHENTICATION_BACKENDS = [\n        ...\n        'django_auth_adfs.backend.AdfsAccessTokenBackend',\n        'django_auth_adfs.backend.AdfsAuthCodeBackend'\n        ...\n    ]\n\n\n\nAdd this path to your project's ``urls.py`` file.\n\n.. code-block:: python\n\n    urlpatterns = [\n        ...\n        path('oauth2/', include('django_auth_adfs.urls')),\n        ...\n    ] \n    \nStep 3 - Register and configure an Azure AD frontend application\n----------------------------------------------------------------\nJust like we did with our backend application in step 1, we have to register a new app for our frontend. In this example we are authenticating a Django Rest Framework token through a single page application(SPA). The redirect URI value must match with the domain where your frontend application is located(eg. http://localhost:3000).\n\n\n\n\n.. image:: _static/AzureAD/09_register_frontend_app.PNG\n    :scale: 50 %\n\n------------\n\nCopy your frontend's client ID, you will need later\n\n\n\n.. image:: _static/AzureAD/10_copy-frontend-client_id.png\n    :scale: 50 %\n\n------------\n\nNow we need to add a scope of permissions to our API. \nNavigate back to app registrations and click on your backend application.\nGo to **Expose an API** in the sidebar and press **add a scope**.\n\n\n.. image:: _static/AzureAD/11-navigate_to_expose_an_api.PNG\n    :scale: 50 %\n\n------------\n\nIf you have not created an Application ID URI, it will be autogenerated for you. Select it and press **save and continue**.\n\n\n.. image:: _static/AzureAD/13_set_app_id.PNG\n    :scale: 50 %\n\n------------\n\nThen we will create the actual scope. Call it \"read\", and just fill in all the required fields with \"read\" (maybe write an actual description).\n\n\n\n.. image:: _static/AzureAD/14_add_a_scope.PNG\n    :scale: 50 %\n\n------------\n\nNow we are going to add our frontend application as a trusted app for our backend. Press **add a client application**\n\n\n.. image:: _static/AzureAD/15_add_authorized_app_1.png\n    :scale: 50 %\n\n------------\n\nHere you need to paste in your frontend application (client) id.\n\n\n.. image:: _static/AzureAD/16_add_authorized_app_2.PNG\n    :scale: 50 %\n\n------------\n\nNow navigate back to app registrations. Click on your **frontend** application and navigate to API permissions. Press **add a permission**.\n\n\n.. image:: _static/AzureAD/17_navigate_to_api_permissions.PNG\n    :scale: 50 %\n\n------------\n\nThen we have to press **My API's** and then select the backend application. (This could be different if you don't have owner rights of the backend application.)\n\n\n.. image:: _static/AzureAD/18_add_permission.PNG\n    :scale: 50 %\n\n------------\n\nHere we can give our frontend the permission scope we created earlier. Press **Delegated permissions** (should be default) and select the permission you created and press **add permission**\n\n\n\n.. image:: _static/AzureAD/19_add-permission-2.PNG\n    :scale: 50 %\n\n------------\n\nFinally, sometimes the plugin will need to obtain the user groups claim from MS Graph (for example when the user has too many groups to fit in the access token), to ensure the plugin can do this successfully add the GroupMember.Read.All permission.\n\n\n.. image:: _static/AzureAD/20_add-permission-3.png\n    :scale: 50 %\n"
  },
  {
    "path": "docs/conf.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n#\n# django_auth_adfs documentation build configuration file, created by\n# sphinx-quickstart on Fri Jan 29 11:23:45 2016.\n#\n# This file is execfile()d with the current directory set to its\n# containing dir.\n#\n# Note that not all possible configuration values are present in this\n# autogenerated file.\n#\n# All configuration values have a default; values that are commented out\n# serve to show the default.\n\nimport sys\nimport os\nimport sphinx_rtd_theme\n\n# If extensions (or modules to document with autodoc) are in another directory,\n# add these directories to sys.path here. If the directory is relative to the\n# documentation root, use os.path.abspath to make it absolute, like shown here.\nsys.path.insert(0, os.path.abspath('../'))\nfrom django_auth_adfs import __version__\n\n# -- General configuration ------------------------------------------------\n\n# If your documentation needs a minimal Sphinx version, state it here.\n#needs_sphinx = '1.0'\n\n# Prevent non local immage warnings from showing\nsuppress_warnings = ['image.nonlocal_uri']\n\n# Add any Sphinx extension module names here, as strings. They can be\n# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom\n# ones.\nextensions = [\n    'sphinx.ext.autodoc',\n    'sphinx.ext.napoleon'\n]\n\n# Add any paths that contain templates here, relative to this directory.\ntemplates_path = ['_templates']\n\n# The suffix(es) of source filenames.\n# You can specify multiple suffix as a list of string:\n# source_suffix = ['.rst', '.md']\nsource_suffix = '.rst'\n\n# The encoding of source files.\n#source_encoding = 'utf-8-sig'\n\n# The master toctree document.\nmaster_doc = 'index'\n\n# General information about the project.\nproject = 'django_auth_adfs'\nimport datetime\ncopyright = str(datetime.date.today().year) + ', Joris Beckers'\nauthor = 'Joris Beckers'\n\n# The version info for the project you're documenting, acts as replacement for\n# |version| and |release|, also used in various other places throughout the\n# built documents.\n#\n# The short X.Y version.\nversion = __version__\n# The full version, including alpha/beta/rc tags.\nrelease = __version__\n\n# The language for content autogenerated by Sphinx. Refer to documentation\n# for a list of supported languages.\n#\n# This is also used if you do content translation via gettext catalogs.\n# Usually you set \"language\" from the command line for these cases.\nlanguage = None\n\n# There are two options for replacing |today|: either, you set today to some\n# non-false value, then it is used:\n#today = ''\n# Else, today_fmt is used as the format for a strftime call.\n#today_fmt = '%B %d, %Y'\n\n# List of patterns, relative to source directory, that match files and\n# directories to ignore when looking for source files.\nexclude_patterns = ['_build']\n\n# The reST default role (used for this markup: `text`) to use for all\n# documents.\n#default_role = None\n\n# If true, '()' will be appended to :func: etc. cross-reference text.\n#add_function_parentheses = True\n\n# If true, the current module name will be prepended to all description\n# unit titles (such as .. function::).\n#add_module_names = True\n\n# If true, sectionauthor and moduleauthor directives will be shown in the\n# output. They are ignored by default.\n#show_authors = False\n\n# The name of the Pygments (syntax highlighting) style to use.\npygments_style = 'sphinx'\n\n# A list of ignored prefixes for module index sorting.\n#modindex_common_prefix = []\n\n# If true, keep warnings as \"system message\" paragraphs in the built documents.\n#keep_warnings = False\n\n# If true, `todo` and `todoList` produce output, else they produce nothing.\ntodo_include_todos = False\n\n\n# -- Options for HTML output ----------------------------------------------\n\n# The theme to use for HTML and HTML Help pages.  See the documentation for\n# a list of builtin themes.\nhtml_theme = 'sphinx_rtd_theme'\n\n# Theme options are theme-specific and customize the look and feel of a theme\n# further.  For a list of options available for each theme, see the\n# documentation.\n#html_theme_options = {}\n\n# Add any paths that contain custom themes here, relative to this directory.\nhtml_theme_path = [sphinx_rtd_theme.get_html_theme_path()]\n\n# The name for this set of Sphinx documents.  If None, it defaults to\n# \"<project> v<release> documentation\".\n#html_title = None\n\n# A shorter title for the navigation bar.  Default is the same as html_title.\n#html_short_title = None\n\n# The name of an image file (relative to this directory) to place at the top\n# of the sidebar.\n#html_logo = None\n\n# The name of an image file (within the static path) to use as favicon of the\n# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32\n# pixels large.\n#html_favicon = None\n\n# Add any paths that contain custom static files (such as style sheets) here,\n# relative to this directory. They are copied after the builtin static files,\n# so a file named \"default.css\" will overwrite the builtin \"default.css\".\nhtml_static_path = ['_static']\n\n# Add any extra paths that contain custom files (such as robots.txt or\n# .htaccess) here, relative to this directory. These files are copied\n# directly to the root of the documentation.\n#html_extra_path = []\n\n# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,\n# using the given strftime format.\n#html_last_updated_fmt = '%b %d, %Y'\n\n# If true, SmartyPants will be used to convert quotes and dashes to\n# typographically correct entities.\n#html_use_smartypants = True\n\n# Custom sidebar templates, maps document names to template names.\n#html_sidebars = {}\n\n# Additional templates that should be rendered to pages, maps page names to\n# template names.\n#html_additional_pages = {}\n\n# If false, no module index is generated.\n#html_domain_indices = True\n\n# If false, no index is generated.\n#html_use_index = True\n\n# If true, the index is split into individual pages for each letter.\n#html_split_index = False\n\n# If true, links to the reST sources are added to the pages.\n#html_show_sourcelink = True\n\n# If true, \"Created using Sphinx\" is shown in the HTML footer. Default is True.\n#html_show_sphinx = True\n\n# If true, \"(C) Copyright ...\" is shown in the HTML footer. Default is True.\n#html_show_copyright = True\n\n# If true, an OpenSearch description file will be output, and all pages will\n# contain a <link> tag referring to it.  The value of this option must be the\n# base URL from which the finished HTML is served.\n#html_use_opensearch = ''\n\n# This is the file name suffix for HTML files (e.g. \".xhtml\").\n#html_file_suffix = None\n\n# Language to be used for generating the HTML full-text search index.\n# Sphinx supports the following languages:\n#   'da', 'de', 'en', 'es', 'fi', 'fr', 'h', 'it', 'ja'\n#   'nl', 'no', 'pt', 'ro', 'r', 'sv', 'tr'\n#html_search_language = 'en'\n\n# A dictionary with options for the search language support, empty by default.\n# Now only 'ja' uses this config value\n#html_search_options = {'type': 'default'}\n\n# The name of a javascript file (relative to the configuration directory) that\n# implements a search results scorer. If empty, the default will be used.\n#html_search_scorer = 'scorer.js'\n\n# Output file base name for HTML help builder.\nhtmlhelp_basename = 'django_auth_adfsdoc'\n\n# -- Options for LaTeX output ---------------------------------------------\n\nlatex_elements = {\n# The paper size ('letterpaper' or 'a4paper').\n#'papersize': 'letterpaper',\n\n# The font size ('10pt', '11pt' or '12pt').\n#'pointsize': '10pt',\n\n# Additional stuff for the LaTeX preamble.\n#'preamble': '',\n\n# Latex figure (float) alignment\n#'figure_align': 'htbp',\n}\n\n# Grouping the document tree into LaTeX files. List of tuples\n# (source start file, target name, title,\n#  author, documentclass [howto, manual, or own class]).\nlatex_documents = [\n    (master_doc, 'django_auth_adfs.tex', 'django_auth_adfs Documentation',\n     'Joris Beckers', 'manual'),\n]\n\n# The name of an image file (relative to this directory) to place at the top of\n# the title page.\n#latex_logo = None\n\n# For \"manual\" documents, if this is true, then toplevel headings are parts,\n# not chapters.\n#latex_use_parts = False\n\n# If true, show page references after internal links.\n#latex_show_pagerefs = False\n\n# If true, show URL addresses after external links.\n#latex_show_urls = False\n\n# Documents to append as an appendix to all manuals.\n#latex_appendices = []\n\n# If false, no module index is generated.\n#latex_domain_indices = True\n\n\n# -- Options for manual page output ---------------------------------------\n\n# One entry per manual page. List of tuples\n# (source start file, name, description, authors, manual section).\nman_pages = [\n    (master_doc, 'django_auth_adfs', 'django_auth_adfs Documentation',\n     [author], 1)\n]\n\n# If true, show URL addresses after external links.\n#man_show_urls = False\n\n\n# -- Options for Texinfo output -------------------------------------------\n\n# Grouping the document tree into Texinfo files. List of tuples\n# (source start file, target name, title, author,\n#  dir menu entry, description, category)\ntexinfo_documents = [\n    (master_doc, 'django_auth_adfs', 'django_auth_adfs Documentation',\n     author, 'django_auth_adfs', 'One line description of project.',\n     'Miscellaneous'),\n]\n\n# Documents to append as an appendix to all manuals.\n#texinfo_appendices = []\n\n# If false, no module index is generated.\n#texinfo_domain_indices = True\n\n# How to display URL addresses: 'footnote', 'no', or 'inline'.\n#texinfo_show_urls = 'footnote'\n\n# If true, do not generate a @detailmenu in the \"Top\" node's menu.\n#texinfo_no_detailmenu = False\n"
  },
  {
    "path": "docs/config_guides.rst",
    "content": "ADFS Config Guides\n==================\n\n.. toctree::\n   :maxdepth: 3\n\n   adfs_3.0_config_guide\n   adfs_4.0_config_guide\n   azure_ad_config_guide\n"
  },
  {
    "path": "docs/contributing.rst",
    "content": ".. include:: ../CONTRIBUTING.rst\n"
  },
  {
    "path": "docs/demo.rst",
    "content": "Demo\n====\nA ``Vagrantfile`` and example project are available to show what's needed to convert a Django project from form based\nauthentication to ADFS authentication.\n\nPrerequisites\n-------------\n* A hypervisor like `virtualbox <https://www.virtualbox.org/>`__.\n* A working `vagrant <https://www.vagrantup.com/>`__ installation. On Debian 11 (bullseye) if you use the `stock vagrant package <https://packages.debian.org/bullseye/vagrant>`__ you need to install these plugins::\n\n    vagrant plugin install winrm\n    vagrant plugin install winrm-fs\n    vagrant plugin install winrm-elevated\n    vagrant plugin install vagrant-reload\n\n* The github repository should be cloned/downloaded in some directory.\n\nThis guide assumes you're using VirtualBox, but another hypervisor should also work.\nIf you choose to use another one, make sure there's a windows server 2019 vagrant box available for it.\n\nComponents\n----------\nThe demo consists of 2 parts:\n\n* A web server VM.\n* A windows server 2019 VM.\n\nThe webserver will run Django and is reachable at ``http://web.example.com:8000``. The windows server will run a\ndomain controller and ADFS service.\n\nStarting the environment\n------------------------\nWeb server\n~~~~~~~~~~\nFirst we get the web server up and running.\n\n#. Navigate to the directory where you cloned/downloaded the github repository.\n#. Bring up the web server by running the command::\n\n    vagrant up web\n\n#. Wait as the vagrant box is downloaded and the needed software installed.\n#. Next, SSH into the web server::\n\n    vagrant ssh web\n\n#. Once connected, start the Django project::\n\n    cd /vagrant/demo/adfs\n    python3 manage.py runserver 0.0.0.0:8000\n\nyou should now be able to browse the demo project by opening the page `http://localhost:8000 <http://localhost:8000>`__\nin a browser. Pages requiring authentication wont work, because the ADFS server is not there yet.\n\n.. note::\n\n    There are 2 versions of the web example. One is a forms based authentication example, the other depends on ADFS.\n    If you want to run the forms based example, change the path above to ``/vagrant/demo/formsbased``\n\nADFS server\n~~~~~~~~~~~\nThe next vagrant box to start is the ADFS server. The scripts used for provisioning the ADFS server can be found in the\nfolder ``/vagrant`` inside the repository.\n\n#. Navigate to the directory where you cloned/downloaded the github repository.\n#. Bring up the ADFS server by running the command::\n\n    vagrant up adfs\n\n#. Wait as the vagrant box is downloaded and the needed software installed. **For this windows box, it takes a couple\n   of coffees before it's done.**\n#. Next, open window showing the login screen of the windows server. The login credentials are::\n\n    username: vagrant\n    password: vagrant\n\n#. Once logged in, install a browser like Chrome of Firefox.\n#. Next, in that browser on the windows server, verify you can open the page\n   `http://web.example.com:8000 <http://web.example.com:8000>`__\n\nIn the AD FS management console, you can check how the example project is configured. The config is in the\n**Application Groups** folder.\n\n.. note::\n\n    You wont be able to test the demo project from outside the windows machine because port 443 is not forwarded and\n    name resolution of adfs.example.com won't work. You can workaround this by forwarding that port 443 from the guest\n    to port 443 on your host and manually adding the right IP addresses in you hosts file.\n\n.. note::\n\n    Because windows server virtual boxes are rather rare on the vagrant cloud (they need to be rebuild every 180 days),\n    it might be that the box specified in the ``Vagrantfile`` doesn't work anymore. If you replace it by another one\n    that's just a vanilla windows server, it should work.\n\nUsing the demo\n--------------\nOnce everything is up and running, you can click around in the very basic poll app that the demo is.\n\n* The bottom of the page shows details about the logged in user.\n* There are 2 users already created in the Active Directory domain. Both having the default password ``Password123``\n\n    * ``bob@example.com`` which is a Django super user because he's a member of active directory group ``django_admins``.\n    * ``alice@example.com`` which is a regular Django user.\n\n* By default, only the page to vote on a poll requires you to be logged in.\n* There are no questions by default. Create some in the admin section with user ``bob``.\n* Compare the files in ``/vagrant/demo/formsbased`` to those in ``/vagrant/demo/adfs`` to see what was changed\n  to enable ADFS authentication in a demo project.\n"
  },
  {
    "path": "docs/faq.rst",
    "content": "Frequently Asked Questions\n==========================\n\nWhy am I always redirected to ``/accounts/profile/`` after login?\n-----------------------------------------------------------------\nThis is default Django behaviour. You can change it by setting the Django setting named\n`LOGIN_REDIRECT_URL <https://docs.djangoproject.com/en/dev/ref/settings/#login-redirect-url>`_.\n\nHow do I store additional info about a user?\n--------------------------------------------\n``django_auth_adfs`` can only store information in existing fields of the user model.\nIf you want to store extra info, you'll have to extend the default user model with extra fields and adjust\nthe :ref:`claim_mapping_setting` setting accordingly.\n\n`You can read about how to extend the user model here <https://simpleisbetterthancomplex.com/tutorial/2016/07/22/how-to-extend-django-user-model.html#abstractuser>`_\n\nI'm receiving an ``SSLError: CERTIFICATE_VERIFY_FAILED`` error.\n---------------------------------------------------------------\ndouble check your ``CA_BUNDLE`` setting. Most likely your ADFS server is using a certificate signed by an\nenterprise root CA. you'll need to put it's certificate in a file and set ``CA_BUNDLE`` to it's path.\n\nI'm receiving an ``KeyError: 'upn'`` error when authenticating against Azure AD.\n--------------------------------------------------------------------------------\nIn some circumstances, Azure AD does not send the ``upn`` claim used to determine the username. It's observed to happen\nwith guest users who's **source** in the users overview of Azure AD is ``Microsoft Account`` instead of\n``Azure Active Directory``.\n\nIn such cases, try setting the :ref:`username_claim_setting` to ``email`` instead of the default ``upn``. Or create a\nnew user in your Azure AD directory.\n\nWhy am I prompted for a username and password in Chrome/Firefox?\n----------------------------------------------------------------\nBy default, ADFS only triggers seamless single sign-on for Internet Explorer or Edge.\n\nHave a look at the ADFS configuration guides for details about how to got this working\nfor other browsers also.\n\nWhy is a user added and removed from the same group on every login?\n-------------------------------------------------------------------\nThis can be caused by having a case insensitive database, such as a ``MySQL`` database with default settings.\nYou can read more about `collation settings <https://docs.djangoproject.com/en/3.0/ref/databases/#collation-settings>`_\nin the official documentation.\n\nThe redirect_uri starts with HTTP, while my site is HTTPS only.\n---------------------------------------------------------------\nWhen you run Django behind a TLS terminating webserver or load balancer, then Django doesn't know the client arrived\nover a HTTPS connection. It will only see the plain HTTP traffic. Therefor, the link it generates and sends to ADFS\nas the ``redirect_uri`` query parameter, will start with HTTP, instead of HTTPS.\n\nTo tell Django to generate HTTPS links, you need to set it's ``SECURE_PROXY_SSL_HEADER`` setting and inject the correct\nHTTP header and value on your web server.\n\nFor more info, have a look at `Django's docs <https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header>`_.\n\nI cannot get it working!\n------------------------\nMake sure you follow the instructions in the troubleshooting guide.\nIt will enable debugging and can quickly tell you what is wrong.\n\nAlso, walk through the :ref:`settings` once, you might find one\nthat needs to be adjusted to match your situation.\n"
  },
  {
    "path": "docs/index.rst",
    "content": "ADFS Authentication for Django\n==============================\n\n.. image:: https://readthedocs.org/projects/django-auth-adfs/badge/?version=latest\n    :target: http://django-auth-adfs.readthedocs.io/en/latest/?badge=latest\n    :alt: Documentation Status\n.. image:: https://img.shields.io/pypi/v/django-auth-adfs.svg\n    :target: https://pypi.python.org/pypi/django-auth-adfs\n.. image:: https://img.shields.io/pypi/pyversions/django-auth-adfs.svg\n    :target: https://pypi.python.org/pypi/django-auth-adfs#downloads\n.. image:: https://img.shields.io/pypi/djversions/django-auth-adfs.svg\n    :target: https://pypi.python.org/pypi/django-auth-adfs\n.. image:: https://codecov.io/github/snok/django-auth-adfs/coverage.svg?branch=main\n    :target: https://codecov.io/github/snok/django-auth-adfs?branch=main\n\nA Django authentication backend for Microsoft ADFS and Azure AD\n\n* Free software: BSD License\n* Homepage: https://github.com/snok/django-auth-adfs\n* Documentation: http://django-auth-adfs.readthedocs.io/\n\nFeatures\n--------\n\n* Integrates Django with Active Directory on Windows 2012 R2, 2016 or Azure AD in the cloud.\n* Provides seamless single sign on (SSO) for your Django project on intranet environments.\n* Auto creates users and adds them to Django groups based on info received from ADFS.\n* Django Rest Framework (DRF) integration: Authenticate against your API with an ADFS access token.\n\nContents\n--------\n\n.. toctree::\n    :maxdepth: 3\n\n    install\n    oauth2_explained\n    settings_ref\n    config_guides\n    middleware\n    signals\n    rest_framework\n    demo\n    troubleshooting\n    faq\n    contributing\n"
  },
  {
    "path": "docs/install.rst",
    "content": ".. _install:\n\nInstallation\n============\n\nRequirements\n------------\n\n* Python 3.9 and above\n* Django 4.2 and above\n\nYou will also need the following:\n\n* A properly configured Microsoft Windows server 2012 R2 or 2016 with the **AD FS** role installed\n  or an Azure Active Directory setup.\n* A root CA bundle containing the root CA that signed the webserver certificate of your ADFS server if signed by an\n  enterprise CA.\n\n.. note::\n    When using Azure AD, beware of the following limitations:\n\n    * Users have no email address unless you assigned an Office 365 license to that user.\n    * Groups are listed with their GUID in the groups claim. Meaning you have to create your groups in Django using\n      these GUIDs, instead of their name.\n    * Usernames are in the form of an email address, hence users created in Django follow this format.\n    * You cannot send any custom claims, only those predefined by Azure AD.\n\nPackage installation\n--------------------\n\nPython package::\n\n    pip install django-auth-adfs\n\nSetting up django\n-----------------\n\nIn your project's ``settings.py`` add these settings.\n\n.. code-block:: python\n\n    AUTHENTICATION_BACKENDS = (\n        ...\n        'django_auth_adfs.backend.AdfsAuthCodeBackend',\n        ...\n    )\n\n    INSTALLED_APPS = (\n        ...\n        # Needed for the ADFS redirect URI to function\n        'django_auth_adfs',\n        ...\n\n    # checkout the documentation for more settings\n    AUTH_ADFS = {\n        \"SERVER\": \"adfs.yourcompany.com\",\n        \"CLIENT_ID\": \"your-configured-client-id\",\n        \"RELYING_PARTY_ID\": \"your-adfs-RPT-name\",\n        # Make sure to read the documentation about the AUDIENCE setting\n        # when you configured the identifier as a URL!\n        \"AUDIENCE\": \"microsoft:identityserver:your-RelyingPartyTrust-identifier\",\n        \"CA_BUNDLE\": \"/path/to/ca-bundle.pem\",\n        \"CLAIM_MAPPING\": {\"first_name\": \"given_name\",\n                          \"last_name\": \"family_name\",\n                          \"email\": \"email\"},\n    }\n\n    # Configure django to redirect users to the right URL for login\n    LOGIN_URL = \"django_auth_adfs:login\"\n    LOGIN_REDIRECT_URL = \"/\"\n\n    ########################\n    # OPTIONAL SETTINGS\n    ########################\n\n    MIDDLEWARE = (\n        ...\n        # With this you can force a user to login without using\n        # the LoginRequiredMixin on every view class\n        #\n        # You can specify URLs for which login is not enforced by\n        # specifying them in the LOGIN_EXEMPT_URLS setting.\n        'django_auth_adfs.middleware.LoginRequiredMiddleware',\n    )\n\n    # You can point login failures to a custom Django function based view for customization of the UI\n    CUSTOM_FAILED_RESPONSE_VIEW = 'dot.path.to.custom.views.login_failed'\n\nIn your project's ``urls.py`` add these paths:\n\n.. code-block:: python\n\n    urlpatterns = [\n        ...\n        path('oauth2/', include('django_auth_adfs.urls')),\n    ]\n\nThis will add these paths to Django:\n\n* ``/oauth2/login`` where users are redirected to, to initiate the login with ADFS.\n* ``/oauth2/login_no_sso`` where users are redirected to, to initiate the login with ADFS but forcing a login screen.\n* ``/oauth2/callback`` where ADFS redirects back to after login. So make sure you set the redirect URI on ADFS to this.\n* ``/oauth2/logout`` which logs out the user from both Django and ADFS.\n\nBelow is sample Django template code to use these paths depending if\nyou'd like to use GET or POST requests. Logging out was deprecated in\n`Django 4.1 <https://docs.djangoproject.com/en/5.1/releases/4.1/#features-deprecated-in-4-1>`_.\n\n- Using GET requests:\n\n    .. code-block:: html\n\n        <a href=\"{% url 'django_auth_adfs:logout' %}\">Logout</a>\n        <a href=\"{% url 'django_auth_adfs:login' %}\">Login</a>\n        <a href=\"{% url 'django_auth_adfs:login-no-sso' %}\">Login (no SSO)</a>\n\n- Using POST requests:\n\n    .. code-block:: html+django\n\n        <form method=\"post\" action=\"{% url 'django_auth_adfs:logout' %}\">\n            {% csrf_token %}\n            <button type=\"submit\">Logout</button>\n        </form>\n        <form method=\"post\" action=\"{% url 'django_auth_adfs:login' %}\">\n            {% csrf_token %}\n            <input type=\"hidden\" name=\"next\" value=\"{{ next }}\">\n            <button type=\"submit\">Login</button>\n        </form>\n        <form method=\"post\" action=\"{% url 'django_auth_adfs:login-no-sso' %}\">\n            {% csrf_token %}\n            <input type=\"hidden\" name=\"next\" value=\"{{ next }}\">\n            <button type=\"submit\">Login (no SSO)</button>\n        </form>\n"
  },
  {
    "path": "docs/make.bat",
    "content": "@ECHO OFF\n\nREM Command file for Sphinx documentation\n\nif \"%SPHINXBUILD%\" == \"\" (\n\tset SPHINXBUILD=sphinx-build\n)\nset BUILDDIR=_build\nset ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .\nset I18NSPHINXOPTS=%SPHINXOPTS% .\nif NOT \"%PAPER%\" == \"\" (\n\tset ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%\n\tset I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%\n)\n\nif \"%1\" == \"\" goto help\n\nif \"%1\" == \"help\" (\n\t:help\n\techo.Please use `make ^<target^>` where ^<target^> is one of\n\techo.  html       to make standalone HTML files\n\techo.  dirhtml    to make HTML files named index.html in directories\n\techo.  singlehtml to make a single large HTML file\n\techo.  pickle     to make pickle files\n\techo.  json       to make JSON files\n\techo.  htmlhelp   to make HTML files and a HTML help project\n\techo.  qthelp     to make HTML files and a qthelp project\n\techo.  devhelp    to make HTML files and a Devhelp project\n\techo.  epub       to make an epub\n\techo.  latex      to make LaTeX files, you can set PAPER=a4 or PAPER=letter\n\techo.  text       to make text files\n\techo.  man        to make manual pages\n\techo.  texinfo    to make Texinfo files\n\techo.  gettext    to make PO message catalogs\n\techo.  changes    to make an overview over all changed/added/deprecated items\n\techo.  xml        to make Docutils-native XML files\n\techo.  pseudoxml  to make pseudoxml-XML files for display purposes\n\techo.  linkcheck  to check all external links for integrity\n\techo.  doctest    to run all doctests embedded in the documentation if enabled\n\techo.  coverage   to run coverage check of the documentation if enabled\n\tgoto end\n)\n\nif \"%1\" == \"clean\" (\n\tfor /d %%i in (%BUILDDIR%\\*) do rmdir /q /s %%i\n\tdel /q /s %BUILDDIR%\\*\n\tgoto end\n)\n\n\nREM Check if sphinx-build is available and fallback to Python version if any\n%SPHINXBUILD% 1>NUL 2>NUL\nif errorlevel 9009 goto sphinx_python\ngoto sphinx_ok\n\n:sphinx_python\n\nset SPHINXBUILD=python -m sphinx.__init__\n%SPHINXBUILD% 2> nul\nif errorlevel 9009 (\n\techo.\n\techo.The 'sphinx-build' command was not found. Make sure you have Sphinx\n\techo.installed, then set the SPHINXBUILD environment variable to point\n\techo.to the full path of the 'sphinx-build' executable. Alternatively you\n\techo.may add the Sphinx directory to PATH.\n\techo.\n\techo.If you don't have Sphinx installed, grab it from\n\techo.http://sphinx-doc.org/\n\texit /b 1\n)\n\n:sphinx_ok\n\n\nif \"%1\" == \"html\" (\n\t%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/html.\n\tgoto end\n)\n\nif \"%1\" == \"dirhtml\" (\n\t%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.\n\tgoto end\n)\n\nif \"%1\" == \"singlehtml\" (\n\t%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.\n\tgoto end\n)\n\nif \"%1\" == \"pickle\" (\n\t%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the pickle files.\n\tgoto end\n)\n\nif \"%1\" == \"json\" (\n\t%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can process the JSON files.\n\tgoto end\n)\n\nif \"%1\" == \"htmlhelp\" (\n\t%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run HTML Help Workshop with the ^\n.hhp project file in %BUILDDIR%/htmlhelp.\n\tgoto end\n)\n\nif \"%1\" == \"qthelp\" (\n\t%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; now you can run \"qcollectiongenerator\" with the ^\n.qhcp project file in %BUILDDIR%/qthelp, like this:\n\techo.^> qcollectiongenerator %BUILDDIR%\\qthelp\\django-auth-adfs.qhcp\n\techo.To view the help file:\n\techo.^> assistant -collectionFile %BUILDDIR%\\qthelp\\django-auth-adfs.ghc\n\tgoto end\n)\n\nif \"%1\" == \"devhelp\" (\n\t%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished.\n\tgoto end\n)\n\nif \"%1\" == \"epub\" (\n\t%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The epub file is in %BUILDDIR%/epub.\n\tgoto end\n)\n\nif \"%1\" == \"latex\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished; the LaTeX files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"latexpdf\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tcd %BUILDDIR%/latex\n\tmake all-pdf\n\tcd %~dp0\n\techo.\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"latexpdfja\" (\n\t%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex\n\tcd %BUILDDIR%/latex\n\tmake all-pdf-ja\n\tcd %~dp0\n\techo.\n\techo.Build finished; the PDF files are in %BUILDDIR%/latex.\n\tgoto end\n)\n\nif \"%1\" == \"text\" (\n\t%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The text files are in %BUILDDIR%/text.\n\tgoto end\n)\n\nif \"%1\" == \"man\" (\n\t%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The manual pages are in %BUILDDIR%/man.\n\tgoto end\n)\n\nif \"%1\" == \"texinfo\" (\n\t%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.\n\tgoto end\n)\n\nif \"%1\" == \"gettext\" (\n\t%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The message catalogs are in %BUILDDIR%/locale.\n\tgoto end\n)\n\nif \"%1\" == \"changes\" (\n\t%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.The overview file is in %BUILDDIR%/changes.\n\tgoto end\n)\n\nif \"%1\" == \"linkcheck\" (\n\t%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Link check complete; look for any errors in the above output ^\nor in %BUILDDIR%/linkcheck/output.txt.\n\tgoto end\n)\n\nif \"%1\" == \"doctest\" (\n\t%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Testing of doctests in the sources finished, look at the ^\nresults in %BUILDDIR%/doctest/output.txt.\n\tgoto end\n)\n\nif \"%1\" == \"coverage\" (\n\t%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Testing of coverage in the sources finished, look at the ^\nresults in %BUILDDIR%/coverage/python.txt.\n\tgoto end\n)\n\nif \"%1\" == \"xml\" (\n\t%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The XML files are in %BUILDDIR%/xml.\n\tgoto end\n)\n\nif \"%1\" == \"pseudoxml\" (\n\t%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml\n\tif errorlevel 1 exit /b 1\n\techo.\n\techo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.\n\tgoto end\n)\n\n:end\n"
  },
  {
    "path": "docs/middleware.rst",
    "content": "Login Middleware\n================\n\n**django-auth-adfs** ships with a middleware class named ``LoginRequiredMiddleware``.\nYou can use it to force an unauthenticated user to login and be redirected to the URL specified in in Django's\n``LOGIN_URL`` setting without having to add code to every view.\n\nBy default it's disabled for the page defined in the ``LOGIN_URL`` setting and the redirect page for ADFS.\nBut by setting the ``LOGIN_EXEMPT_URLS`` setting, you can exclude other pages from authentication.\nHave a look at the :ref:`settings` for more information.\n\nTo enable the middleware, add it to ``MIDDLEWARE`` in ``settings.py`` (or ``MIDDLEWARE_CLASSES`` if using Django <1.10.\nmake sure to add it after any other session or authentication middleware to be sure all other methods of identifying\nthe user are tried first.\n\nIn your ``settings.py`` file, add the following:\n\n.. code-block:: python\n\n    MIDDLEWARE = (\n        ...\n        'django_auth_adfs.middleware.LoginRequiredMiddleware',\n    )\n\n    AUTH_ADFS = {\n        ...\n        \"LOGIN_EXEMPT_URLS\": [\"api/\", \"public/\"],\n        ...\n    }\n"
  },
  {
    "path": "docs/oauth2_explained.rst",
    "content": "OAuth2 and ADFS explained\n=========================\n\nThis chapter tries to explain how ADFS implements the OAuth2 and OpenID Connect standard and\nhow we can use this in Django.\n\nOAuth2 vs. OpenID Connect\n-------------------------\n\nWhat's `OAuth2 <https://tools.ietf.org/html/rfc6749>`__?\n\n    The OAuth 2.0 authorization framework enables a third-party\n    application to obtain limited access to an HTTP service, either on\n    behalf of a resource owner by orchestrating an approval interaction\n    between the resource owner and the HTTP service, or by allowing the\n    third-party application to obtain access on its own behalf.\n\nWhat's important is that it's only an **authorization** framework. It only\ntells you what the user is allowed to do but it doesn't tell you who the user is.\nAt its core, there's nothing in the protocol that gives you info about the user.\n\nTo solve this, there's the `OpenID Connect <https://openid.net/specs/openid-connect-core-1_0.html>`__\nframework.\n\n    OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 [RFC6749]\n    protocol. It enables Clients to verify the identity of the End-User based on the\n    authentication performed by an Authorization Server, as well as to obtain basic\n    profile information about the End-User in an interoperable and REST-like manner.\n\nSo, where the OAuth2 protocol lacks any user identifiable info, OpenID Connect does\ngive you info about who the user is. The access token returned by OpenID Connect is\na signed JWT token (JSON Web Token) containing claims about the user.\n\n``django-auth-adfs`` uses this access token to validate the issuer of the token by verifying the\nsignature and also uses it to keep the Django users database up to date and at the same time\nauthenticate users.\n\nDepending on the version of ADFS, there's support for different pieces of these protocol.\nThe table below tries to list the support in various ADFS versions:\n\n==================================  ============  =========  ========\nProtocol                            ADFS 2012 R2  ADFS 2016  Azure AD\n==================================  ============  =========  ========\nOAuth2                              Yes           Yes        Yes\nOpenID Connect                      No**          Yes        Yes\n==================================  ============  =========  ========\n\n** ADFS 2012 doesn't implement OpenID Connect, but it does return the access token\nas a JWT token, just like OpenID Connect would.\n\nOpenID Connect / OAuth2 Flow support:\n\n==================================  ============  =========  ========\nVersion                             ADFS 2012 R2  ADFS 2016  Azure AD\n==================================  ============  =========  ========\n**Authorization code grant**        Yes           Yes        Yes\nImplicit grant                      no            Yes        Yes\nResource owner password credential  no            Yes        Yes\nClient credential grant             no            Yes        Yes\n==================================  ============  =========  ========\n\nReferences:\n\n* https://blogs.msdn.microsoft.com/nicold/2018/03/23/oauth-2-0-protocol-support-level-for-adfs-2012r2-vs-adfs-2016/\n\nThe **Authorization Code Grant** is what ``django-auth-adfs`` uses.\n\nOAuth2 and Django\n-----------------\n\nLet's step through the process of how ``django-auth-adfs`` uses OAuth2 to authenticate\nand authorize users.\n\n.. note::\n\n    In all the graphs below, remember that the access token is what contains the info\n    about our user in the form of a signed JWT token.\n\nThe OAuth2 `RFC 6749 <https://tools.ietf.org/html/rfc6749#section-4.1>`__ specifies\nthe `Authorization Code Grant <https://tools.ietf.org/html/rfc6749#section-4.1>`__ flow as follows:\n\n.. code-block::\n    text\n\n     +----------+\n     | Resource |\n     |   Owner  |\n     |          |\n     +----------+\n          ^\n          |\n         (B)\n     +----|-----+          Client Identifier      +---------------+\n     |         -+----(A)-- & Redirection URI ---->|               |\n     |  User-   |                                 | Authorization |\n     |  Agent  -+----(B)-- User authenticates --->|     Server    |\n     |          |                                 |               |\n     |         -+----(C)-- Authorization Code ---<|               |\n     +-|----|---+                                 +---------------+\n       |    |                                         ^      v\n      (A)  (C)                                        |      |\n       |    |                                         |      |\n       ^    v                                         |      |\n     +---------+                                      |      |\n     |         |>---(D)-- Authorization Code ---------'      |\n     |  Client |          & Redirection URI                  |\n     |         |                                             |\n     |         |<---(E)----- Access Token -------------------'\n     +---------+       (w/ Optional Refresh Token)\n\n   Note: The lines illustrating steps (A), (B), and (C) are broken into\n   two parts as they pass through the user-agent.\n\n   The flow illustrated includes the following steps:\n\n   (A)  The client initiates the flow by directing the resource owner's\n        user-agent to the authorization endpoint.  The client includes\n        its client identifier, requested scope, local state, and a\n        redirection URI to which the authorization server will send the\n        user-agent back once access is granted (or denied).\n\n   (B)  The authorization server authenticates the resource owner (via\n        the user-agent) and establishes whether the resource owner\n        grants or denies the client's access request.\n\n   (C)  Assuming the resource owner grants access, the authorization\n        server redirects the user-agent back to the client using the\n        redirection URI provided earlier (in the request or during\n        client registration).  The redirection URI includes an\n        authorization code and any local state provided by the client\n        earlier.\n\n   (D)  The client requests an access token from the authorization\n        server's token endpoint by including the authorization code\n        received in the previous step.  When making the request, the\n        client authenticates with the authorization server.  The client\n        includes the redirection URI used to obtain the authorization\n        code for verification.\n\n   (E)  The authorization server authenticates the client, validates the\n        authorization code, and ensures that the redirection URI\n        received matches the URI used to redirect the client in\n        step (C).  If valid, the authorization server responds back with\n        an access token and, optionally, a refresh token.\n\nOne thing missing in the graph from the RFC is the ``Resource Server``.\nLet's add it to make things complete:\n\n.. code-block::\n    text\n\n     +----------+\n     | Resource |\n     |   Owner  |\n     |          |\n     +----------+\n          ^\n          |\n         (B)\n     +----|-----+          Client Identifier      +---------------+\n     |         -+----(A)-- & Redirection URI ---->|               |\n     |  User-   |                                 | Authorization |\n     |  Agent  -+----(B)-- User authenticates --->|     Server    |\n     |          |                                 |               |\n     |         -+----(C)-- Authorization Code ---<|               |\n     +-|----|---+                                 +---------------+\n       |    |                                         ^      v\n      (A)  (C)                                        |      |\n       |    |                                         |      |\n       ^    v                                         |      |\n     +---------+                                      |      |\n     |         |>---(D)-- Authorization Code ---------'      |\n     |  Client |          & Redirection URI                  |\n     |         |                                             |\n     |         |<---(E)----- Access Token -------------------'\n     +---------+       (w/ Optional Refresh Token)\n         |  ^\n         |  |\n        (F) Access Token\n         | (G)\n         v  |\n     +-----------------+\n     |                 |\n     | Resource Server |\n     |                 |\n     +-----------------+\n\n   Extra steps\n\n   (F)  The client makes a protected resource request to the resource\n        server by presenting the access token.\n   (G)  The resource server validates the access token, and if valid,\n        serves the request.\n\nAlright, now that we have the entire flow, let's translate the roles to our components\nand use a bit more comprehensible terms:\n\n.. code-block::\n    text\n\n     +----------+\n     |          |\n     |   User   |\n     |          |\n     +----------+\n          ^\n          |\n         (B)               Resource\n     +----|-----+          & Client Identifier    +---------------+\n     |         -+----(A)-- & Redirection URI ---->|               |\n     | Web      |                                 |      ADFS     |\n     | Browser -+----(B)-- User authenticates --->|     Server    |\n     |          |                                 |               |\n     |         -+----(C)-- Authorization Code ---<|               |\n     +-|---|----+                                 +---------------+\n       |   |  ^                                       ^      v\n      (A) (C)(G)                                      |      |\n       |   |  |                                       |      |\n       ^   v  |                                       |      |\n     +--------|+                                      |      |\n     |         |>---(D)-- Authorization Code ---------'      |\n     |  Django |          & Redirection URI                  |\n     |  Login  |                                             |\n     |         |<---(E)----- Access Token -------------------'\n     +---------+       (w/ Optional Refresh Token)\n       |    ^\n       |    |\n      (F) Access Token\n       |   (G) Session ID\n       v    |\n     +-------------------------------+\n     |                               |\n     | Django Authentication Backend |\n     |                               |\n     +-------------------------------+\n\nThe following things changed:\n\n* A ``resource`` parameter was added to step **A**. This is an ADFS specific thing used to identify which application.\n* Step **G** was extended up to the web browser. Resembling the session cookie sent back to the web browser.\n* ``Resource Owner`` ➜ ``User``\n* ``User-Agent`` ➜ ``Web Browser``\n* ``Authorization Serve`` ➜ ``ADFS Server``\n* ``Client`` ➜ ``Django Login``\n* ``Resource Server`` ➜ ``Django Authentication Backend``\n\nNotice how 2 roles were replaced by \"pieces\" of Django. Django effectively takes up\n2 roles here.\n\nIf you were to split Django in 2 parts, it's login pages and the authentication backends,\nthen the **login pages** would map to the ``Client`` role. It wants to get a session for the\nuser and give it a session cookie.\n\nThe **authentication backend** maps to the ``Resource Server`` role,\nauthenticating/authorizing the user and creating the session.\nThe session you can think of as being the protected resource.\n\nOnce the session is created, OAuth2 isn't used anymore. Django uses its sessions to\nauthenticate and authorize the user on subsequent requests.\n\nOn the ADFS side, you need to configure both the ``Client`` role part of Django\n(called a Native Application in ADFS 4.0), as well as the ``Resource Server`` part\n(called a Web Application in ADFS 4.0).\n\nRest Framework and OAuth2\n-------------------------\n\nWhen activating Django Rest Framework integration to protect an API, the roles shift once more.\n\nThe example assumes a situation where you use a script or some other application to make requests\nto your API. In that case, the OAuth2 flow also changes from the ``Authorization Code Grant`` flow\nto the ``Resource Owner Password Credentials Grant`` flow.\n\n.. note::\n\n    If you would call the API from a Single Page Application (SPA), you'll most likely be using the\n    ``Implicit Grant`` flow. We won't explain this flow here, but the principle is sort of the same.\n\nHere's the RFC explanation again:\n\n.. code-block::\n    text\n\n     +----------+\n     | Resource |\n     |  Owner   |\n     |          |\n     +----------+\n          v\n          |    Resource Owner\n         (A) Password Credentials\n          |\n          v\n     +---------+                                  +---------------+\n     |         |>--(B)---- Resource Owner ------->|               |\n     |         |         Password Credentials     | Authorization |\n     | Client  |                                  |     Server    |\n     |         |<--(C)---- Access Token ---------<|               |\n     |         |    (w/ Optional Refresh Token)   |               |\n     +---------+                                  +---------------+\n\n   The flow illustrated includes the following steps:\n\n   (A)  The resource owner provides the client with its username and\n        password.\n\n   (B)  The client requests an access token from the authorization\n        server's token endpoint by including the credentials received\n        from the resource owner.  When making the request, the client\n        authenticates with the authorization server.\n\n   (C)  The authorization server authenticates the client and validates\n        the resource owner credentials, and if valid, issues an access\n        token.\n\nAgain, let's add the ``Resource Server`` role to the picture:\n\n.. code-block::\n    text\n\n     +----------+\n     | Resource |\n     |  Owner   |\n     |          |\n     +----------+\n          v\n          |    Resource Owner\n         (A) Password Credentials\n          |\n          v\n     +---------+                                  +---------------+\n     |         |>--(B)---- Resource Owner ------->|               |\n     |         |         Password Credentials     | Authorization |\n     | Client  |                                  |     Server    |\n     |         |<--(C)---- Access Token ---------<|               |\n     |         |    (w/ Optional Refresh Token)   |               |\n     +---------+                                  +---------------+\n        |   ^\n        |   |\n       (D) Access Token\n        |  (E)\n        v   |\n     +-----------------+\n     |                 |\n     | Resource Server |\n     |                 |\n     +-----------------+\n\n   Extra steps\n\n   (D)  The client makes a protected resource request to the resource\n        server by presenting the access token.\n   (E)  The resource server validates the access token, and if valid,\n        serves the request.\n\n\nAnd let's map it to our components:\n\n.. code-block::\n    text\n\n     +----------+\n     |          |\n     | User     |\n     |          |\n     +----------+\n          v\n          |    Resource Owner\n         (A) Password Credentials\n          |\n          v\n     +-------------+                                  +---------------+\n     |             |>--(B)---- Resource Owner ------->|               |\n     |             |         Password Credentials     |      ADFS     |\n     | Application |                                  |     Server    |\n     |             |<--(C)---- Access Token ---------<|               |\n     |             |    (w/ Optional Refresh Token)   |               |\n     +-------------+                                  +---------------+\n        |   ^\n        |   |\n       (D) Access Token\n        |  (E)\n        v   |\n     +-----------------------+\n     |                       |\n     | Django Rest Framework |\n     |          API          |\n     |                       |\n     +-----------------------+\n\nLet's go over the changes again:\n\n* ``Resource Owner`` ➜ ``User``\n* ``Client`` ➜ ``Application``\n* ``Resource Server`` ➜ ``Django Rest Framework API``\n\nIn this case, a user inputs his username and password into an application/script.\nThe application fetches an access token on behalf of the user and uses it to\nmake calls to you API.\n\nADFS and OAuth2 lingo compared\n------------------------------\n\nPotayto, potahto...\n\nOAuth2 and ADFS don't keep the same name for components. Below is an overview of what OAuth2\nrole maps to which configuration part on ADFS.\n\n+-----------------------+----------------------+----------------------+----------------------+\n| OAuth2                | Azure AD             | ADFS 2016            | ADFS 2012            |\n+=======================+======================+======================+======================+\n| Resource Owner        | User                 | User                 | User                 |\n+-----------------------+----------------------+----------------------+----------------------+\n| Authorization Server  | ADFS server          | ADFS server          | ADFS server          |\n+-----------------------+----------------------+----------------------+----------------------+\n| Client                | Native Application   | * Native Application | Client               |\n|                       |                      | * Server Application |                      |\n+-----------------------+----------------------+----------------------+----------------------+\n| Resource Server       | Web app / API        | Web API              | Relying Party        |\n+-----------------------+----------------------+----------------------+----------------------+\n\n.. note::\n\n    For ADFS 2016, we assumed you use **application group** configuration instead of the\n    \"old-fashion\" Relying Party Trust config.\n\n    For ADFS 2012, the client part is not visible from the GUI and can only be configured\n    via PowerShell commands.\n"
  },
  {
    "path": "docs/requirements.txt",
    "content": "sphinx_rtd_theme\n"
  },
  {
    "path": "docs/rest_framework.rst",
    "content": "Rest Framework integration\n==========================\n\nSetup\n-----\n\nWhen using Django Rest Framework, you can also use this package to authenticate\nyour REST API clients. For this you need to do some extra configuration.\n\nYou also need to install ``djangorestframework`` (or add it to your\nproject dependencies)::\n\n    pip install djangorestframework\n\nThe default ``AdfsBackend`` backend expects an ``authorization_code``. The backend\nwill take care of obtaining an ``access_code`` from the Adfs server.\n\nWith the Django Rest Framework integration the client application needs to acquire\nthe access token by itself. See for an example: :ref:`request-access-token`. To\nauthenticate against the API you need to enable the ``AdfsAccessTokenBackend``.\n\nSteps to enable the Django Rest Framework integration are as following:\n\nAdd an extra authentication class to Django Rest Framework in ``settings.py``:\n\n.. code-block:: python\n\n    REST_FRAMEWORK = {\n        'DEFAULT_AUTHENTICATION_CLASSES': (\n            'django_auth_adfs.rest_framework.AdfsAccessTokenAuthentication',\n            'rest_framework.authentication.SessionAuthentication',\n        )\n    }\n\nEnable the ``AdfsAccessTokenBackend`` authentication backend in ``settings.py``:\n\n.. code-block:: python\n\n    AUTHENTICATION_BACKENDS = (\n        ...\n        'django_auth_adfs.backend.AdfsAccessTokenBackend',\n        ...\n    )\n\nPrevent your API from triggering a login redirect:\n\n.. code-block:: python\n\n    AUTH_ADFS = {\n        'LOGIN_EXEMPT_URLS': [\n            '^api',  # Assuming you API is available at /api\n        ],\n    }\n\n(Optional) Override the standard Django Rest Framework login pages in your main ``urls.py``:\n\n.. code-block:: python\n\n    urlpatterns = [\n        ...\n        # The default rest framework urls shouldn't be included\n        # If we include them, we'll end up with the DRF login page,\n        # instead of being redirected to the ADFS login page.\n        #\n        # path('api-auth/', include('rest_framework.urls')),\n        #\n        # This overrides the DRF login page\n        path('oauth2/', include('django_auth_adfs.drf_urls')),\n        ...\n    ]\n\n.. _request-access-token:\n\nRequesting an access token\n--------------------------\n\nWhen everything is configured, you can request an access token in your client (script) and\naccess the api like this:\n\n.. note::\n\n    This example is written for ADFS on windows server 2016 but with some changes in the\n    URLs should also work for Azure AD.\n\n.. code-block:: python\n\n    import getpass\n    import requests\n    from pprint import pprint\n\n    # Ask for password\n    user = getpass.getuser()\n    password = getpass.getpass(\"Password for \"+user+\": \")\n    user = user + \"@example.com\"\n\n    # Get an access token\n    payload = {\n        \"grant_type\": \"password\",\n        \"resource\": \"your-relying-party-id\",\n        \"client_id\": \"your-configured-client-id\",\n        \"username\": user,\n        \"password\": password,\n    }\n    response = requests.post(\n        \"https://adfs.example.com/adfs/oauth2/token\",\n        data=payload,\n        verify=False\n    )\n    response.raise_for_status()\n    response_data = response.json()\n    access_token = response_data['access_token']\n\n    # Make a request towards this API\n    headers = {\n        'Accept': 'application/json',\n        'Authorization': 'Bearer ' + access_token,\n    }\n    response = requests.get(\n        'https://web.example.com/api/questions',\n        headers=headers,\n        verify=False\n    )\n    pprint(response.json())\n\n\n.. note::\n\n    The following example is written for ADFS on windows server 2012 R2 and needs\n    the ``requests-ntlm`` module.\n\n    This example is here only for legacy reasons. If possible it's advised to\n    upgrade to 2016. Support for 2012 R2 is about to end.\n\n.. code-block:: python\n\n    import getpass\n    import re\n    import requests\n    from requests_ntlm import HttpNtlmAuth\n    from pprint import pprint\n\n    # Ask for password\n    user = getpass.getuser()\n    password = getpass.getpass(\"Password for \"+user+\": \")\n    user = \"EXAMPLE\\\\\" + user\n\n    # Get a authorization code\n    headers = {\"User-Agent\": \"Mozilla/5.0\"}\n    params = {\n        \"response_type\": \"code\",\n        \"resource\": \"your-relying-party-id\",\n        \"client_id\": \"your-configured-client-id\",\n        \"redirect_uri\": \"https://djangoapp.example.com/oauth2/callback\"\n    }\n    response = requests.get(\n        \"https://adfs.example.com/adfs/oauth2/authorize/wia\",\n        auth=HttpNtlmAuth(user, password),\n        headers=headers,\n        allow_redirects=False,\n        params=params,\n    )\n    response.raise_for_status()\n    code = re.search('code=(.*)', response.headers['location']).group(1)\n\n    # Get an access token\n    data = {\n        'grant_type': 'authorization_code',\n        'client_id': 'your-configured-client-id',\n        'redirect_uri': 'https://djangoapp.example.com/oauth2/callback',\n        'code': code,\n    }\n    response = requests.post(\n        \"https://adfs.example.com/adfs/oauth2/token\",\n        data,\n    )\n    response.raise_for_status()\n    response_data = response.json()\n    access_token = response_data['access_token']\n\n    # Make a request towards this API\n    headers = {\n        'Accept': 'application/json',\n        'Authorization': 'Bearer %s' % access_token,\n    }\n    response = requests.get(\n        'https://djangoapp.example.com/v1/pets?name=rudolf',\n        headers=headers\n    )\n    pprint(response.json())\n"
  },
  {
    "path": "docs/settings_ref.rst",
    "content": ".. _settings:\n\nSettings Reference\n==================\n\n.. _audience_setting:\n\nAUDIENCE\n--------\n* **Default**:\n* **Type**: ``string`` or ``list``\n\n**Required**\n\nSet this to the value of the ``aud`` claim your ADFS server sends back in the JWT token.\n\nYou can lookup this value by executing the powershell command ``Get-AdfsRelyingPartyTrust`` on the ADFS server\nand taking the ``Identifier`` value. But beware, it doesn't match exactly if it's not a URL.\n\nExamples\n\n+--------------------------------------------------+------------------------------------------------------------+\n| Relying Party Trust identifier                   | ``aud`` claim value                                        |\n+==================================================+============================================================+\n| your-RelyingPartyTrust-identifier                | microsoft:identityserver:your-RelyingPartyTrust-identifier |\n+--------------------------------------------------+------------------------------------------------------------+\n| https://adfs.yourcompany.com/adfs/services/trust | https://adfs.yourcompany.com/adfs/services/trust           |\n+--------------------------------------------------+------------------------------------------------------------+\n\n\n.. _block_guest_users_setting:\n\nBLOCK_GUEST_USERS\n-----------------\n* **Default**: ``False``\n* **Type**: ``boolean``\n\nWhether guest users of your Azure AD is allowed to log into the site. This is validated by matching\nthe ``http://schemas.microsoft.com/identity/claims/tenantid``-key in the claims towards the configured tenant.\n\n\n.. _boolean_claim_mapping_setting:\n\nBOOLEAN_CLAIM_MAPPING\n---------------------\n* **Default**: ``None``\n* **Type**: ``dictionary``\n\nA dictionary of claim/field mappings that is used to set boolean fields on the user account in Django.\n\nThe **key** represents user model field (e.g. ``first_name``)\nand the **value** represents the claim short name (e.g. ``given_name``).\n\nIf the value is any of ``y, yes, t, true, on, 1``, the field will be set to ``True``. All other values, or the absence of\nthe claim, will result in a value of ``False``\n\nexample\n\n.. code-block:: python\n\n    AUTH_ADFS = {\n        \"BOOLEAN_CLAIM_MAPPING\": {\"is_staff\": \"user_is_staff\",\n                                  \"is_superuser\": \"user_is_superuser\"},\n    }\n\n.. NOTE::\n   You can find the short name for the claims you configure in the ADFS management console underneath\n   **ADFS** ➜ **Service** ➜ **Claim Descriptions**\n\nCA_BUNDLE\n---------\n* **Default**: ``True``\n* **Type**: ``boolean`` or ``string``\n\nThe value of this setting is passed to the call to the ``Requests`` package when fetching the access token from ADFS.\nIt allows you to control the webserver certificate verification of the ADFS server.\n\n``True`` to use the default CA bundle of the ``requests`` package.\n\n``/path/to/ca-bundle.pem`` allows you to specify a path to a CA bundle file. If your ADFS server uses a certificate\nsigned by an enterprise root CA, you will need to specify the path to it's certificate here.\n\n``False`` disables the certificate check.\n\nHave a look at the `Requests documentation\n<http://docs.python-requests.org/en/master/user/advanced/#ssl-cert-verification>`_ for more details.\n\n.. warning::\n    Do not set this value to ``False`` in a production setup. Because we load certain settings from the ADFS server,\n    this might lead to a security issue. DNS hijacking for example might cause an attacker to inject his own\n    access token signing certificate.\n\n.. _claim_mapping_setting:\n\nCLAIM_MAPPING\n-------------\n* **Default**: ``None``\n* **Type**: ``dictionary``\n\nA dictionary of claim/field mappings that will be used to populate the user account in Django.\nThe user's details will be set according to this setting upon each login.\n\nThe **key** represents the user model field (e.g. ``first_name``)\nand the **value** represents the claim short name (e.g. ``given_name``).\n\nexample\n\n.. code-block:: python\n\n    AUTH_ADFS = {\n        \"CLAIM_MAPPING\": {\"first_name\": \"given_name\",\n                          \"last_name\": \"family_name\",\n                          \"email\": \"email\"},\n    }\n\nThe dictionary can also map extra details to the Django user account using an\n`Extension of the User model <https://docs.djangoproject.com/en/stable/topics/auth/customizing/#extending-the-existing-user-model>`_\nSet a dictionary as value in the CLAIM_MAPPING setting with as key the name User model.\nYou will need to make sure the related field exists before the user authenticates.\nThis can be done by creating a receiver on the\n`post_save <https://docs.djangoproject.com/en/4.0/ref/signals/#post-save>`_ signal that\ncreates the related instance when the ``User`` instance is created.\n\nexample\n\n.. code-block:: python\n\n    'CLAIM_MAPPING': {'first_name': 'given_name',\n                      'last_name': 'family_name',\n                      'email': 'upn',\n                      'userprofile': {\n                          'employee_id': 'employeeid'\n                      }}\n\n.. NOTE::\n   You can find the short name for the claims you configure in the ADFS management console underneath\n   **ADFS** ➜ **Service** ➜ **Claim Descriptions**\n\n.. _client_id_setting:\n\nCLIENT_ID\n---------\n* **Default**:\n* **Type**: ``dictionary``\n\n**Required**\n\nSet this to the value you configured on your ADFS server as ``ClientId`` when executing the ``Add-AdfsClient`` command.\n\nYou can lookup this value by executing the powershell command ``Get-AdfsClient`` on the ADFS server\nand taking the ``ClientId`` value.\n\nCLIENT_SECRET\n-------------\n* **Default**: ``None``\n* **Type**: ``string``\n\nA Client secret is generated by ADFS server when executing the ``Add-AdfsClient`` command with the\n``-GenerateClientSecret`` parameter.\n\nYou can lookup this value by executing the powershell command ``Get-AdfsClient`` on the ADFS server\nand taking the ``ClientSecret`` value.\n\nCONFIG_RELOAD_INTERVAL\n----------------------\n* **Default**: ``24``\n* **Unit**: hours\n* **Type**: ``integer``\n\nWhen starting Django, some settings are retrieved from the ADFS metadata file or the OpenID Connect configuration on the\nADFS server. Based on this information, certain configuration for this module is calculated.\n\nThis setting determines the interval after which the configuration is reloaded. This allows to automatically follow the\ntoken signing certificate rollover on ADFS.\n\n.. _create_new_users_setting:\n\nCREATE_NEW_USERS\n----------------\n* **Default**: ``True``\n* **Type**: ``boolean``\n\nDetermines whether users are created automatically if they do not exist.\n\nIf set to ``False``, then you need to create your users before they can log in.\n\nDISABLE_SSO\n-----------\n* **Default**: ``False``\n* **Type**: ``boolean``\n\n\nSetting this to ``True`` will globally disable the seamless single sign-on capability of ADFS.\nForcing ADFS to prompt users for a username and password, instead of automatically logging them in\nwith their current user. This allows users to use a different account then the one they are logged\nin with on their workstation.\n\nYou can also selectively enable this setting by using ``<a href=\"{% url 'django_auth_adfs:login-no-sso' %}\">...</a>``\nin a template instead of the regular ``<a href=\"{% url 'django_auth_adfs:login' %}\">...</a>``\n\n.. attention::\n\n    This does not work with ADFS 3.0 on windows 2012 because this setting requires OpenID Connect\n    which is not supported on ADFS 3.0\n\n\nJWT_LEEWAY\n-----------\n* **Default**: ``0``\n* **Type**: ``str``\n\nAllows you to set a leeway of the JWT token. See the official\n`PyJWT <https://pyjwt.readthedocs.io/en/stable/usage.html>`__ docs for more information.\n\n\nCUSTOM_FAILED_RESPONSE_VIEW\n--------------------------------\n* **Default**: ``lambda``\n* **Type**: ``str`` or ``callable``\n\nAllows you to set a custom django function view to handle login failures. Can be a dot path to your\nDjango function based view function or a callable.\n\nCallable must have the following method signature accepting ``error_message`` and ``status`` arguments:\n\n.. code-block:: python\n\n    def failed_response(request, error_message, status):\n        # Return an error message\n        return render(request, 'myapp/login_failed.html', {\n            'error_message': error_message,\n        }, status=status)\n\n\nGROUP_CLAIM\n-----------\nAlias of ``GROUPS_CLAIM``\n\n.. _groups_claim_setting:\n\nGROUPS_CLAIM\n------------\n* **Default**: ``group`` for ADFS or ``groups`` for Azure AD\n* **Type**: ``string``\n\nName of the claim in the JWT access token from ADFS that contains the groups the user is member of.\nIf an entry in this claim matches a group configured in Django, the user will join it automatically.\n\nIf using Azure AD and there are too many groups to fit in the JWT access token, the application will\nmake a request to the Microsoft GraphQL API to find the groups. If you have many groups but only\nneed a specific few, you can customize the request by overriding\n``AdfsBaseBackend.get_group_memberships_from_ms_graph_params`` and specifying the\n`OData query parameters <https://learn.microsoft.com/en-us/graph/api/group-list-transitivememberof?view=graph-rest-1.0&tabs=python#http-request>`_.\n\nSet this setting to ``None`` to disable automatic group handling. The group memberships of the user\nwill not be touched.\n\n.. IMPORTANT::\n   If not set to ``None``, a user's group membership in Django will be reset to math this claim's value.\n   If there's no value in the access token, the user will be removed from all groups.\n\n.. NOTE::\n   You can find the short name for the claims you configure in the ADFS management console underneath\n   **ADFS** ➜ **Service** ➜ **Claim Descriptions**\n\nGROUP_TO_FLAG_MAPPING\n---------------------\n* **Default**: ``None``\n* **Type**: ``dictionary``\n\nThis settings allows you to set flags on a user based on his group membership in Active Directory.\n\nFor example, if a user is a member of the group ``Django Staff``, you can automatically set the ``is_staff``\nfield of the user to ``True``.\n\nThe **key** represents the boolean user model field (e.g. ``is_staff``)\nand the **value**, which can either be a single String or an array of Strings, represents the group(s) name (e.g. ``Django Staff``).\n\nexample\n\n.. code-block:: python\n\n    AUTH_ADFS = {\n        \"GROUP_TO_FLAG_MAPPING\": {\"is_staff\": [\"Django Staff\", \"Other Django Staff\"],\n                                  \"is_superuser\": \"Django Admins\"},\n    }\n\n.. NOTE::\n   The group doesn't need to exist in Django for this to work. This will work as long as it's in the groups claim\n   in the access token.\n\nGUEST_USERNAME_CLAIM\n--------------------\n* **Default**: ``None``\n* **Type**: ``string``\n\nWhen these criteria are met:\n\n1. A ``guest_username_claim`` is configured\n2. Token claims do not have the configured ``settings.USERNAME_CLAIM`` in it\n3. The ``settings.BLOCK_GUEST_USERS`` is set to ``False``\n4. The claims ``tid`` does not match ``settings.TENANT_ID`` or claims ``idp`` does not match ``iss``.\n\nThen, the ``GUEST_USERNAME_CLAIM`` can be used to populate a username, when the ``USERNAME_CLAIM`` cannot be found in\nthe claims.\n\nThis can be useful when you want to use ``upn`` as a username claim for your own users,\nbut some guest users (such as normal outlook users) don't have that claim.\n\n\nLOGIN_EXEMPT_URLS\n-----------------\n* **Default**: ``None``\n* **Type**: ``list``\n\nWhen you activate the ``LoginRequiredMiddleware`` middleware, by default every page will redirect\nan unauthenticated user to the page configured in the Django setting ``LOGIN_URL``.\n\nIf you have pages that should not trigger this redirect, add them to this setting as a list value.\n\nEvery item it the list is interpreted as a regular expression.\n\nexample\n\n.. code-block:: python\n\n    AUTH_ADFS = {\n        'LOGIN_EXEMPT_URLS': [\n            '^$',\n            '^api'\n        ],\n    }\n\n.. _mirror_group_setting:\n\nMIRROR_GROUPS\n-------------\n* **Default**: ``False``\n* **Type**: ``boolean``\n\n\nThis parameter will create groups from ADFS in the Django database if they do not exist already.\n\n``True`` will create groups.\n\n``False`` will not create any extra groups.\n\n.. IMPORTANT::\n    This parameter only has effect if GROUP_CLAIM is set to something other then ``None``.\n\n.. _relying_party_id_setting:\n\nRELYING_PARTY_ID\n----------------\n* **Default**:\n* **Type**: ``string``\n\n**Required**\n\nSet this to the ``Relying party trust identifier`` value of the ``Relying Party Trust`` (2012) or ``Web application``\n(2016) you configured in ADFS.\n\nYou can lookup this value by executing the powershell command ``Get-AdfsRelyingPartyTrust`` (2012) or\n``Get-AdfsWebApiApplication`` (2016) on the ADFS server and taking the ``Identifier`` value.\n\nRESOURCE\n--------\nAlias for ``RELYING_PARTY_ID``\n\n.. _retries_setting:\n\nRETRIES\n-------\n* **Default**: ``3``\n* **Type**: ``integer``\n\nThe number of time a request to the ADFS server is retried. It allows, in combination with :ref:`timeout_setting`\nto fine tune the behaviour of the connection to ADFS.\n\n\nSCOPES\n------\n* **Default**: ``[]``\n* **Type**: ``list``\n\n**Only used when you have v2 AzureAD config**\n\n\n\nSERVER\n------\n* **Default**:\n* **Type**: ``string``\n\n**Required** when your identity provider is an on premises ADFS server.\n\nOnly one of ``SERVER`` or ``TENANT_ID`` can be set.\n\nThe FQDN of the ADFS server you want users to authenticate against.\n\nSETTINGS_CLASS\n--------------\n* **Default**: ``django_auth_adfs.config.Settings``\n* **Type**: ``string``\n\nBy default, django-auth-adfs reads the configuration from the Django setting\n``AUTH_ADFS``. You can provide the configuration in a custom implementation\nand point to it by using the ``SETTINGS_CLASS`` setting:\n\n.. code-block:: python\n\n    # in myapp.adfs.config\n\n    class CustomSettings:\n\n        SERVER = 'bar'\n        AUDIENCE = 'foo'\n        ...\n\n\n    # in settings.py\n\n    AUTH_ADFS = {\n        'SETTINGS_CLASS': 'myapp.adfs.config.CustomSettings',\n        # other settings are not needed\n    }\n\nThe value must be an importable dotted Python path, and the imported object\nmust be callable with no arguments to initialize.\n\nUse cases are storing configuration in database so an administrator can edit\nthe configuration in an admin interface.\n\n.. _tenant_id_setting:\n\nTENANT_ID\n---------\n* **Default**:\n* **Type**: ``string``\n\n**Required** when your identity provider is an Azure AD instance.\n\nOnly one of ``TENANT_ID`` or ``SERVER`` can be set.\n\nThe FQDN of the ADFS server you want users to authenticate against.\n\n.. _timeout_setting:\n\nTIMEOUT\n-------\n* **Default**: ``5``\n* **Unit**: seconds\n* **Type**: ``integer``\n\nThe timeout in seconds for every request made to the ADFS server. It's passed on as the ``timeout`` parameter\nto the underlying calls to the `requests <http://docs.python-requests.org/en/master/user/quickstart/#timeouts>`__\nlibrary.\n\nIt allows, in combination with :ref:`retries_setting` to fine tune the behaviour of the connection to ADFS.\n\n.. _username_claim_setting:\n\nUSERNAME_CLAIM\n--------------\n* **Default**: ``winaccountname`` for ADFS or ``upn`` for Azure AD.\n* **Type**: ``string``\n\nName of the claim sent in the JWT token from ADFS that contains the username.\nIf the user doesn't exist yet, this field will be used as it's username.\n\nThe value of the claim must be a unique value. No 2 users should ever have the same value.\n\n.. warning::\n   You shouldn't need to set this value for ADFS or Azure AD unless you use custom user models.\n   Because ``winaccountname`` maps to the ``sAMAccountName`` on Active Directory, which is guaranteed\n   to be unique. The same for Azure AD where ``upn`` maps to the ``UserPrincipleName``, which is unique\n   on Azure AD.\n\n.. NOTE::\n   You can find the short name for the claims you configure in the ADFS management console underneath\n   **ADFS** ➜ **Service** ➜ **Claim Descriptions**\n\n\n.. _version_setting:\n\nVERSION\n--------------\n* **Default**: ``v1.0``\n* **Type**: ``string``\n\nVersion of the Azure Active Directory endpoint version. By default it is set to ``v1.0``. At the time of writing this documentation, it can also be set to ``v2.0``. For new projects, ``v2.0`` is recommended. ``v1.0`` is kept as a default for backwards compatibility.\n\nPROXIES\n-------\n* **Default**: ``None``\n* **Type**: ``dict``\n\nAn optional proxy for all communication with the server. Example: ``{'http': '10.0.0.1', 'https': '10.0.0.2'}``\nSee the `requests documentation <https://requests.readthedocs.io/en/v3.0.0/api/#requests.Session.proxies>`__ for more information.\n"
  },
  {
    "path": "docs/signals.rst",
    "content": "Django Signals\n================\n\n**django-auth-adfs** uses Django `Signals <https://docs.djangoproject.com/en/stable/topics/signals/>` to allow the\napplication to listen for and execute custom logic at certain points in the authentication process. Currently, the\nfollowing signals are supported:\n\n* ``post_authenticate``: sent after a user has been authenticated through any subclass of ``AdfsBaseBackend``. The\n  signal is sent after all other processing is done, e.g. mapping claims and groups and creating the user in Django (if\n  :ref:`the CREATE_NEW_USERS setting <create_new_users_setting>` is enabled). In addition to the sender, the signal\n  includes the user object, the claims dictionary, and the ADFS response as arguments for the signal handler:\n\n  * ``sender`` (``AdfsBaseBackend``): the backend instance from which the signal was triggered.\n  * ``user`` (Django user class): the user object that was authenticated.\n  * ``claims`` (``dict``): the decoded access token JWT, which contains all claims sent from the identity provider.\n  * ``adfs_response`` (``dict|None``): used in the ``AdfsAuthCodeBackend`` to provide the full response received from\n    the server when exchanging an authorization code for an access token.\n\nTo use a signal in your application:\n\n.. code-block:: python\n\n    from django.dispatch import receiver\n    from django_auth_adfs.signals import post_authenticate\n\n\n    @receiver(post_authenticate)\n    def handle_post_authenticate(sender, user, claims, adfs_response=None, **kwargs):\n        user.do_post_auth_steps(claims, adfs_response)\n\n\n"
  },
  {
    "path": "docs/troubleshooting.rst",
    "content": "Troubleshooting\n===============\n\nTurn on Django debug logging\n----------------------------\nIf you run into any problems, set the logging level in Django to DEBUG.\nYou can do this by adding the configuration below to your ``settings.py``\n\nYou can see this logging in your console, or in you web server log if you're using something\nlike Apache with mod_wsgi.\n\nMore details about logging in Django can be found in\n`the official Django documentation <https://docs.djangoproject.com/en/dev/topics/logging/>`_\n\n.. code-block:: python\n\n    LOGGING = {\n        'version': 1,\n        'disable_existing_loggers': False,\n        'formatters': {\n            'verbose': {\n                'format': '%(levelname)s %(asctime)s %(name)s %(message)s'\n            },\n        },\n        'handlers': {\n            'console': {\n                'class': 'logging.StreamHandler',\n                'formatter': 'verbose'\n            },\n        },\n        'loggers': {\n            'django_auth_adfs': {\n                'handlers': ['console'],\n                'level': 'DEBUG',\n            },\n        },\n    }\n\nRun Django with warnings enabled\n--------------------------------\nStart the python interpreter that runs you Django with the ``-Wd`` parameter. This will show warnings that are otherwise\nsuppressed.\n\n.. code-block:: bash\n\n    python -Wd manage.py runserver\n\nHave a look at the demo project\n-------------------------------\nThere's an simple demo project available in the ``/demo`` folder and in the **demo** chapter of the documentation.\n\nIf you compare the files in the ``adfs`` folder with those in the ``formsbased`` folder, you'll see what needs to be\nchanged in a standard Django project to enable ADFS authentication.\n\nBesides that, there are a couple of PowerShell scripts available that are used while provisioning the ADFS server for\nthe demo. you can find them in the ``/vagrant`` folder in this repository. They might be useful to figure out what is\nwrong with the configuration of your ADFS server.\n\n**Note that they are only meant for getting a demo running. By no means are they meant to configure your ADFS server.**\n"
  },
  {
    "path": "manage.py",
    "content": "#!/usr/bin/env python\nimport os\nimport sys\n\nif __name__ == \"__main__\":\n    os.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"tests.settings\")\n\n    from django.core.management import execute_from_command_line\n\n    execute_from_command_line(sys.argv)\n"
  },
  {
    "path": "pyproject.toml",
    "content": "[tool.poetry]\nname = 'django-auth-adfs'\nversion = \"1.16.0\"  # Remember to also change __init__.py version\ndescription = 'A Django authentication backend for Microsoft ADFS and AzureAD'\nauthors = ['Joris Beckers <joris.beckers@gmail.com>']\nmaintainers = ['Jonas Krüger Svensson <jonas-ks@hotmail.com>', 'Sondre Lillebø Gundersen <sondrelg@live.no>']\nlicense = 'BSD-1-Clause'\nhomepage = 'https://github.com/snok/django-auth-adfs'\nrepository = 'https://github.com/snok/django-auth-adfs'\ndocumentation = 'https://django-auth-adfs.readthedocs.io/en/latest'\nkeywords = ['django', 'authentication', 'adfs', 'azure', 'ad', 'oauth2']\nreadme = 'README.rst'\nclassifiers = [\n    'Environment :: Web Environment',\n    'Framework :: Django :: 4.2',\n    'Framework :: Django :: 5.0',\n    'Framework :: Django :: 5.1',\n    'Framework :: Django :: 5.2',\n    'Framework :: Django :: 6.0',\n    'Intended Audience :: Developers',\n    'Intended Audience :: End Users/Desktop',\n    'Operating System :: OS Independent',\n    'License :: OSI Approved :: BSD License',\n    'Programming Language :: Python',\n    'Programming Language :: Python :: 3',\n    'Programming Language :: Python :: 3.9',\n    'Programming Language :: Python :: 3.10',\n    'Programming Language :: Python :: 3.11',\n    'Programming Language :: Python :: 3.12',\n    'Programming Language :: Python :: 3.13',\n    'Topic :: Internet :: WWW/HTTP',\n    'Topic :: Internet :: WWW/HTTP :: Dynamic Content',\n    'Topic :: Internet :: WWW/HTTP :: WSGI',\n    'Topic :: Software Development :: Libraries :: Application Frameworks',\n    'Topic :: Software Development :: Libraries :: Python Modules',\n    'Development Status :: 5 - Production/Stable',\n]\n\n[tool.poetry.dependencies]\npython = '^3.9'\ndjango = [\n    { version = '^4.2', python = '>=3.9 <3.10' },\n    { version = '^4.2 || ^5 || ^6', python = '>=3.10' },\n]\nrequests = '*'\nurllib3 = '*'\ncryptography = '*'\nPyJWT = \"*\"\n\n[tool.poetry.group.dev.dependencies]\nresponses = '*'\nmock = '*'\ncoverage = '*'\ndjangorestframework = '*'\ndjango-filter = \"*\"\n\n[build-system]\nrequires = [\"poetry-core>=1.0.0\"]\nbuild-backend = \"poetry.core.masonry.api\"\n"
  },
  {
    "path": "setup.cfg",
    "content": "[flake8]\nmax-line-length = 120\nexclude =\n    docs/*\n    .venv\n"
  },
  {
    "path": "tests/__init__.py",
    "content": ""
  },
  {
    "path": "tests/custom_config.py",
    "content": "class Settings(object):\n    RETRIES = 1\n    CA_BUNDLE = False\n\n    def __init__(self):\n        self.SERVER = 'custom-server'\n"
  },
  {
    "path": "tests/mock_files/FederationMetadata.xml",
    "content": "<EntityDescriptor ID=\"_7bfbd44d-3197-434f-b1de-388d1ead1308\" entityID=\"http://adfs.example.com/adfs/services/trust\"\n                  xmlns=\"urn:oasis:names:tc:SAML:2.0:metadata\">\n    <ds:Signature xmlns:ds=\"http://www.w3.org/2000/09/xmldsig#\">\n        <ds:SignedInfo>\n            <ds:CanonicalizationMethod Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n            <ds:SignatureMethod Algorithm=\"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256\"/>\n            <ds:Reference URI=\"#_7bfbd44d-3197-434f-b1de-388d1ead1308\">\n                <ds:Transforms>\n                    <ds:Transform Algorithm=\"http://www.w3.org/2000/09/xmldsig#enveloped-signature\"/>\n                    <ds:Transform Algorithm=\"http://www.w3.org/2001/10/xml-exc-c14n#\"/>\n                </ds:Transforms>\n                <ds:DigestMethod Algorithm=\"http://www.w3.org/2001/04/xmlenc#sha256\"/>\n                <ds:DigestValue>--</ds:DigestValue>\n            </ds:Reference>\n        </ds:SignedInfo>\n        <ds:SignatureValue>--</ds:SignatureValue>\n        <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n            <X509Data>\n                <X509Certificate>--</X509Certificate>\n            </X509Data>\n        </KeyInfo>\n    </ds:Signature>\n    <RoleDescriptor xsi:type=\"fed:ApplicationServiceType\"\n                    protocolSupportEnumeration=\"http://docs.oasis-open.org/ws-sx/ws-trust/200512 http://schemas.xmlsoap.org/ws/2005/02/trust http://docs.oasis-open.org/wsfed/federation/200706\"\n                    ServiceDisplayName=\"Example Corp\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n                    xmlns:fed=\"http://docs.oasis-open.org/wsfed/federation/200706\">\n        <KeyDescriptor use=\"encryption\">\n            <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n                <X509Data>\n                    <X509Certificate>--</X509Certificate>\n                </X509Data>\n            </KeyInfo>\n        </KeyDescriptor>\n        <fed:ClaimTypesRequested>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>E-Mail Address</auth:DisplayName>\n                <auth:Description>The e-mail address of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Given Name</auth:DisplayName>\n                <auth:Description>The given name of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Name</auth:DisplayName>\n                <auth:Description>The unique name of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>UPN</auth:DisplayName>\n                <auth:Description>The user principal name (UPN) of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/claims/CommonName\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Common Name</auth:DisplayName>\n                <auth:Description>The common name of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/claims/EmailAddress\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>AD FS 1.x E-Mail Address</auth:DisplayName>\n                <auth:Description>The e-mail address of the user when interoperating with AD FS 1.1 or AD FS 1.0\n                </auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/claims/Group\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Group</auth:DisplayName>\n                <auth:Description>A group that the user is a member of</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/claims/UPN\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>AD FS 1.x UPN</auth:DisplayName>\n                <auth:Description>The UPN of the user when interoperating with AD FS 1.1 or AD FS 1.0</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/role\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Role</auth:DisplayName>\n                <auth:Description>A role that the user has</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Surname</auth:DisplayName>\n                <auth:Description>The surname of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>PPID</auth:DisplayName>\n                <auth:Description>The private identifier of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Name ID</auth:DisplayName>\n                <auth:Description>The SAML name identifier of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Authentication time stamp</auth:DisplayName>\n                <auth:Description>Used to display the time and date that the user was authenticated</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Authentication method</auth:DisplayName>\n                <auth:Description>The method used to authenticate the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/denyonlysid\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Deny only group SID</auth:DisplayName>\n                <auth:Description>The deny-only group SID of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/denyonlyprimarysid\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Deny only primary SID</auth:DisplayName>\n                <auth:Description>The deny-only primary SID of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/denyonlyprimarygroupsid\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Deny only primary group SID</auth:DisplayName>\n                <auth:Description>The deny-only primary group SID of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Group SID</auth:DisplayName>\n                <auth:Description>The group SID of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/primarygroupsid\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Primary group SID</auth:DisplayName>\n                <auth:Description>The primary group SID of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Primary SID</auth:DisplayName>\n                <auth:Description>The primary SID of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Windows account name</auth:DisplayName>\n                <auth:Description>The domain account name of the user in the form of domain\\user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/isregistereduser\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Is Registered User</auth:DisplayName>\n                <auth:Description>User is registered to use this device</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/identifier\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Device Identifier</auth:DisplayName>\n                <auth:Description>Identifier of the device</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/registrationid\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Device Registration Identifier</auth:DisplayName>\n                <auth:Description>Identifier for Device Registration</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/displayname\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Device Registration DisplayName</auth:DisplayName>\n                <auth:Description>Display name of Device Registration</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/ostype\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Device OS type</auth:DisplayName>\n                <auth:Description>OS type of the device</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/osversion\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Device OS Version</auth:DisplayName>\n                <auth:Description>OS version of the device</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/ismanaged\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Is Managed Device</auth:DisplayName>\n                <auth:Description>Device is managed by a management service</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-forwarded-client-ip\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Forwarded Client IP</auth:DisplayName>\n                <auth:Description>IP address of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Client Application</auth:DisplayName>\n                <auth:Description>Type of the Client Application</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-user-agent\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Client User Agent</auth:DisplayName>\n                <auth:Description>Device type the client is using to access the application</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-ip\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Client IP</auth:DisplayName>\n                <auth:Description>IP address of the client</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Endpoint Path</auth:DisplayName>\n                <auth:Description>Absolute Endpoint path which can be used to determine active versus passive clients\n                </auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-proxy\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Proxy</auth:DisplayName>\n                <auth:Description>DNS name of the federation server proxy that passed the request</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/relyingpartytrustid\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Application Identifier</auth:DisplayName>\n                <auth:Description>Identifier for the Relying Party</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/applicationpolicy\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Application policies</auth:DisplayName>\n                <auth:Description>Application policies of the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType\n                Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/authoritykeyidentifier\"\n                Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Authority Key Identifier</auth:DisplayName>\n                <auth:Description>The Authority Key Identifier extension of the certificate that signed an issued\n                    certificate\n                </auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/basicconstraints\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Basic Constraint</auth:DisplayName>\n                <auth:Description>One of the basic constraints of the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/eku\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Enhanced Key Usage</auth:DisplayName>\n                <auth:Description>Describes one of the enhanced key usages of the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/issuer\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Issuer</auth:DisplayName>\n                <auth:Description>The name of the certificate authority that issued the X.509 certificate\n                </auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/issuername\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Issuer Name</auth:DisplayName>\n                <auth:Description>The distinguished name of the certificate issuer</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/keyusage\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Key Usage</auth:DisplayName>\n                <auth:Description>One of the key usages of the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/notafter\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Not After</auth:DisplayName>\n                <auth:Description>Date in local time after which a certificate is no longer valid</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/notbefore\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Not Before</auth:DisplayName>\n                <auth:Description>The date in local time on which a certificate becomes valid</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/certificatepolicy\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Certificate Policies</auth:DisplayName>\n                <auth:Description>The policies under which the certificate has been issued</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/rsa\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Public Key</auth:DisplayName>\n                <auth:Description>Public Key of the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/rawdata\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Certificate Raw Data</auth:DisplayName>\n                <auth:Description>The raw data of the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/san\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Subject Alternative Name</auth:DisplayName>\n                <auth:Description>One of the alternative names of the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/serialnumber\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Serial Number</auth:DisplayName>\n                <auth:Description>The serial number of a certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/signaturealgorithm\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Signature Algorithm</auth:DisplayName>\n                <auth:Description>The algorithm used to create the signature of a certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/subject\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Subject</auth:DisplayName>\n                <auth:Description>The subject from the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/subjectkeyidentifier\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Subject Key Identifier</auth:DisplayName>\n                <auth:Description>Describes the subject key identifier of the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/subjectname\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Subject Name</auth:DisplayName>\n                <auth:Description>The subject distinguished name from a certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType\n                Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/certificatetemplateinformation\"\n                Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>V2 Template Name</auth:DisplayName>\n                <auth:Description>The name of the version 2 certificate template used when issuing or renewing a\n                    certificate. The extension is Microsoft specific.\n                </auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType\n                Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/certificatetemplatename\"\n                Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>V1 Template Name</auth:DisplayName>\n                <auth:Description>The name of the version 1 certificate template used when issuing or renewing a\n                    certificate. The extension is Microsoft specific.\n                </auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/thumbprint\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Thumbprint</auth:DisplayName>\n                <auth:Description>Thumbprint of the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/x509version\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>X.509 Version</auth:DisplayName>\n                <auth:Description>The X.509 format version of a certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Inside Corporate Network</auth:DisplayName>\n                <auth:Description>Used to indicate if a request originated inside corporate network</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2012/01/passwordexpirationtime\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Password Expiration Time</auth:DisplayName>\n                <auth:Description>Used to display the time when the password expires</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2012/01/passwordexpirationdays\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Password Expiration Days</auth:DisplayName>\n                <auth:Description>Used to display the number of days to password expiry</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2012/01/passwordchangeurl\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Update Password URL</auth:DisplayName>\n                <auth:Description>Used to display the web address of update password service</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/claims/authnmethodsreferences\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Authentication Methods References</auth:DisplayName>\n                <auth:Description>Used to indicate all authentication methods used to authenticate the user\n                </auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/client-request-id\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Client Request ID</auth:DisplayName>\n                <auth:Description>Identifier for a user session</auth:Description>\n            </auth:ClaimType>\n        </fed:ClaimTypesRequested>\n        <fed:TargetScopes>\n            <EndpointReference xmlns=\"http://www.w3.org/2005/08/addressing\">\n                <Address>https://adfs.example.com/adfs/services/trust/2005/issuedtokenmixedasymmetricbasic256</Address>\n            </EndpointReference>\n            <EndpointReference xmlns=\"http://www.w3.org/2005/08/addressing\">\n                <Address>https://adfs.example.com/adfs/services/trust/2005/issuedtokenmixedsymmetricbasic256</Address>\n            </EndpointReference>\n            <EndpointReference xmlns=\"http://www.w3.org/2005/08/addressing\">\n                <Address>https://adfs.example.com/adfs/services/trust/13/issuedtokenmixedasymmetricbasic256</Address>\n            </EndpointReference>\n            <EndpointReference xmlns=\"http://www.w3.org/2005/08/addressing\">\n                <Address>https://adfs.example.com/adfs/services/trust/13/issuedtokenmixedsymmetricbasic256</Address>\n            </EndpointReference>\n            <EndpointReference xmlns=\"http://www.w3.org/2005/08/addressing\">\n                <Address>https://adfs.example.com/adfs/ls/</Address>\n            </EndpointReference>\n            <EndpointReference xmlns=\"http://www.w3.org/2005/08/addressing\">\n                <Address>http://adfs.example.com/adfs/services/trustttt</Address>\n            </EndpointReference>\n        </fed:TargetScopes>\n        <fed:ApplicationServiceEndpoint>\n            <EndpointReference xmlns=\"http://www.w3.org/2005/08/addressing\">\n                <Address>https://adfs.example.com/adfs/services/trust/2005/issuedtokenmixedasymmetricbasic256</Address>\n            </EndpointReference>\n        </fed:ApplicationServiceEndpoint>\n        <fed:PassiveRequestorEndpoint>\n            <EndpointReference xmlns=\"http://www.w3.org/2005/08/addressing\">\n                <Address>https://adfs.example.com/adfs/ls/</Address>\n            </EndpointReference>\n        </fed:PassiveRequestorEndpoint>\n    </RoleDescriptor>\n    <RoleDescriptor xsi:type=\"fed:SecurityTokenServiceType\"\n                    protocolSupportEnumeration=\"http://docs.oasis-open.org/ws-sx/ws-trust/200512 http://schemas.xmlsoap.org/ws/2005/02/trust http://docs.oasis-open.org/wsfed/federation/200706\"\n                    ServiceDisplayName=\"Example Corp\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n                    xmlns:fed=\"http://docs.oasis-open.org/wsfed/federation/200706\">\n        <KeyDescriptor use=\"signing\">\n            <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n                <X509Data>\n                    <X509Certificate>REPLACE_WITH_CERT_A</X509Certificate>\n                </X509Data>\n            </KeyInfo>\n        </KeyDescriptor>\n        <KeyDescriptor use=\"signing\">\n            <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n                <X509Data>\n                    <X509Certificate>REPLACE_WITH_CERT_B</X509Certificate>\n                </X509Data>\n            </KeyInfo>\n        </KeyDescriptor>\n        <fed:TokenTypesOffered>\n            <fed:TokenType Uri=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n            <fed:TokenType Uri=\"urn:oasis:names:tc:SAML:1.0:assertion\"/>\n        </fed:TokenTypesOffered>\n        <fed:ClaimTypesOffered>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>E-Mail Address</auth:DisplayName>\n                <auth:Description>The e-mail address of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Given Name</auth:DisplayName>\n                <auth:Description>The given name of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Name</auth:DisplayName>\n                <auth:Description>The unique name of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>UPN</auth:DisplayName>\n                <auth:Description>The user principal name (UPN) of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/claims/CommonName\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Common Name</auth:DisplayName>\n                <auth:Description>The common name of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/claims/EmailAddress\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>AD FS 1.x E-Mail Address</auth:DisplayName>\n                <auth:Description>The e-mail address of the user when interoperating with AD FS 1.1 or AD FS 1.0\n                </auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/claims/Group\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Group</auth:DisplayName>\n                <auth:Description>A group that the user is a member of</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/claims/UPN\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>AD FS 1.x UPN</auth:DisplayName>\n                <auth:Description>The UPN of the user when interoperating with AD FS 1.1 or AD FS 1.0</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/role\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Role</auth:DisplayName>\n                <auth:Description>A role that the user has</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Surname</auth:DisplayName>\n                <auth:Description>The surname of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>PPID</auth:DisplayName>\n                <auth:Description>The private identifier of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Name ID</auth:DisplayName>\n                <auth:Description>The SAML name identifier of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Authentication time stamp</auth:DisplayName>\n                <auth:Description>Used to display the time and date that the user was authenticated</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Authentication method</auth:DisplayName>\n                <auth:Description>The method used to authenticate the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/denyonlysid\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Deny only group SID</auth:DisplayName>\n                <auth:Description>The deny-only group SID of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/denyonlyprimarysid\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Deny only primary SID</auth:DisplayName>\n                <auth:Description>The deny-only primary SID of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/denyonlyprimarygroupsid\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Deny only primary group SID</auth:DisplayName>\n                <auth:Description>The deny-only primary group SID of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Group SID</auth:DisplayName>\n                <auth:Description>The group SID of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/primarygroupsid\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Primary group SID</auth:DisplayName>\n                <auth:Description>The primary group SID of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Primary SID</auth:DisplayName>\n                <auth:Description>The primary SID of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Windows account name</auth:DisplayName>\n                <auth:Description>The domain account name of the user in the form of domain\\user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/isregistereduser\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Is Registered User</auth:DisplayName>\n                <auth:Description>User is registered to use this device</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/identifier\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Device Identifier</auth:DisplayName>\n                <auth:Description>Identifier of the device</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/registrationid\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Device Registration Identifier</auth:DisplayName>\n                <auth:Description>Identifier for Device Registration</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/displayname\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Device Registration DisplayName</auth:DisplayName>\n                <auth:Description>Display name of Device Registration</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/ostype\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Device OS type</auth:DisplayName>\n                <auth:Description>OS type of the device</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/osversion\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Device OS Version</auth:DisplayName>\n                <auth:Description>OS version of the device</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/ismanaged\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Is Managed Device</auth:DisplayName>\n                <auth:Description>Device is managed by a management service</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-forwarded-client-ip\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Forwarded Client IP</auth:DisplayName>\n                <auth:Description>IP address of the user</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Client Application</auth:DisplayName>\n                <auth:Description>Type of the Client Application</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-user-agent\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Client User Agent</auth:DisplayName>\n                <auth:Description>Device type the client is using to access the application</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-ip\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Client IP</auth:DisplayName>\n                <auth:Description>IP address of the client</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Endpoint Path</auth:DisplayName>\n                <auth:Description>Absolute Endpoint path which can be used to determine active versus passive clients\n                </auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-proxy\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Proxy</auth:DisplayName>\n                <auth:Description>DNS name of the federation server proxy that passed the request</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/relyingpartytrustid\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Application Identifier</auth:DisplayName>\n                <auth:Description>Identifier for the Relying Party</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/applicationpolicy\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Application policies</auth:DisplayName>\n                <auth:Description>Application policies of the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType\n                Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/authoritykeyidentifier\"\n                Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Authority Key Identifier</auth:DisplayName>\n                <auth:Description>The Authority Key Identifier extension of the certificate that signed an issued\n                    certificate\n                </auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/basicconstraints\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Basic Constraint</auth:DisplayName>\n                <auth:Description>One of the basic constraints of the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/eku\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Enhanced Key Usage</auth:DisplayName>\n                <auth:Description>Describes one of the enhanced key usages of the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/issuer\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Issuer</auth:DisplayName>\n                <auth:Description>The name of the certificate authority that issued the X.509 certificate\n                </auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/issuername\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Issuer Name</auth:DisplayName>\n                <auth:Description>The distinguished name of the certificate issuer</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/keyusage\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Key Usage</auth:DisplayName>\n                <auth:Description>One of the key usages of the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/notafter\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Not After</auth:DisplayName>\n                <auth:Description>Date in local time after which a certificate is no longer valid</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/notbefore\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Not Before</auth:DisplayName>\n                <auth:Description>The date in local time on which a certificate becomes valid</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/certificatepolicy\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Certificate Policies</auth:DisplayName>\n                <auth:Description>The policies under which the certificate has been issued</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/rsa\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Public Key</auth:DisplayName>\n                <auth:Description>Public Key of the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/rawdata\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Certificate Raw Data</auth:DisplayName>\n                <auth:Description>The raw data of the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/san\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Subject Alternative Name</auth:DisplayName>\n                <auth:Description>One of the alternative names of the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/serialnumber\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Serial Number</auth:DisplayName>\n                <auth:Description>The serial number of a certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/signaturealgorithm\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Signature Algorithm</auth:DisplayName>\n                <auth:Description>The algorithm used to create the signature of a certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/subject\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Subject</auth:DisplayName>\n                <auth:Description>The subject from the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/subjectkeyidentifier\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Subject Key Identifier</auth:DisplayName>\n                <auth:Description>Describes the subject key identifier of the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/subjectname\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Subject Name</auth:DisplayName>\n                <auth:Description>The subject distinguished name from a certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType\n                Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/certificatetemplateinformation\"\n                Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>V2 Template Name</auth:DisplayName>\n                <auth:Description>The name of the version 2 certificate template used when issuing or renewing a\n                    certificate. The extension is Microsoft specific.\n                </auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType\n                Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/certificatetemplatename\"\n                Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>V1 Template Name</auth:DisplayName>\n                <auth:Description>The name of the version 1 certificate template used when issuing or renewing a\n                    certificate. The extension is Microsoft specific.\n                </auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/thumbprint\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Thumbprint</auth:DisplayName>\n                <auth:Description>Thumbprint of the certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/x509version\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>X.509 Version</auth:DisplayName>\n                <auth:Description>The X.509 format version of a certificate</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Inside Corporate Network</auth:DisplayName>\n                <auth:Description>Used to indicate if a request originated inside corporate network</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2012/01/passwordexpirationtime\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Password Expiration Time</auth:DisplayName>\n                <auth:Description>Used to display the time when the password expires</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2012/01/passwordexpirationdays\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Password Expiration Days</auth:DisplayName>\n                <auth:Description>Used to display the number of days to password expiry</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/ws/2012/01/passwordchangeurl\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Update Password URL</auth:DisplayName>\n                <auth:Description>Used to display the web address of update password service</auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/claims/authnmethodsreferences\" Optional=\"true\"\n                            xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Authentication Methods References</auth:DisplayName>\n                <auth:Description>Used to indicate all authentication methods used to authenticate the user\n                </auth:Description>\n            </auth:ClaimType>\n            <auth:ClaimType Uri=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/client-request-id\"\n                            Optional=\"true\" xmlns:auth=\"http://docs.oasis-open.org/wsfed/authorization/200706\">\n                <auth:DisplayName>Client Request ID</auth:DisplayName>\n                <auth:Description>Identifier for a user session</auth:Description>\n            </auth:ClaimType>\n        </fed:ClaimTypesOffered>\n        <fed:SecurityTokenServiceEndpoint>\n            <EndpointReference xmlns=\"http://www.w3.org/2005/08/addressing\">\n                <Address>https://adfs.example.com/adfs/services/trust/2005/certificatemixed</Address>\n                <Metadata>\n                    <Metadata xmlns=\"http://schemas.xmlsoap.org/ws/2004/09/mex\"\n                              xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\"\n                              xmlns:wsx=\"http://schemas.xmlsoap.org/ws/2004/09/mex\">\n                        <wsx:MetadataSection Dialect=\"http://schemas.xmlsoap.org/ws/2004/09/mex\" xmlns=\"\">\n                            <wsx:MetadataReference>\n                                <Address xmlns=\"http://www.w3.org/2005/08/addressing\">\n                                    https://adfs.example.com/adfs/services/trust/mex\n                                </Address>\n                            </wsx:MetadataReference>\n                        </wsx:MetadataSection>\n                    </Metadata>\n                </Metadata>\n            </EndpointReference>\n        </fed:SecurityTokenServiceEndpoint>\n        <fed:PassiveRequestorEndpoint>\n            <EndpointReference xmlns=\"http://www.w3.org/2005/08/addressing\">\n                <Address>https://adfs.example.com/adfs/ls/</Address>\n            </EndpointReference>\n        </fed:PassiveRequestorEndpoint>\n    </RoleDescriptor>\n    <SPSSODescriptor WantAssertionsSigned=\"true\" protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n        <KeyDescriptor use=\"encryption\">\n            <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n                <X509Data>\n                    <X509Certificate>--</X509Certificate>\n                </X509Data>\n            </KeyInfo>\n        </KeyDescriptor>\n        <KeyDescriptor use=\"signing\">\n            <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n                <X509Data>\n                    <X509Certificate>--</X509Certificate>\n                </X509Data>\n            </KeyInfo>\n        </KeyDescriptor>\n        <SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"\n                             Location=\"https://adfs.example.com/adfs/ls/\"/>\n        <SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n                             Location=\"https://adfs.example.com/adfs/ls/\"/>\n        <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>\n        <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>\n        <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n        <AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n                                  Location=\"https://adfs.example.com/adfs/ls/\" index=\"0\" isDefault=\"true\"/>\n        <AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Artifact\"\n                                  Location=\"https://adfs.example.com/adfs/ls/\" index=\"1\"/>\n        <AssertionConsumerService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"\n                                  Location=\"https://adfs.example.com/adfs/ls/\" index=\"2\"/>\n    </SPSSODescriptor>\n    <IDPSSODescriptor protocolSupportEnumeration=\"urn:oasis:names:tc:SAML:2.0:protocol\">\n        <KeyDescriptor use=\"encryption\">\n            <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n                <X509Data>\n                    <X509Certificate>--</X509Certificate>\n                </X509Data>\n            </KeyInfo>\n        </KeyDescriptor>\n        <KeyDescriptor use=\"signing\">\n            <KeyInfo xmlns=\"http://www.w3.org/2000/09/xmldsig#\">\n                <X509Data>\n                    <X509Certificate>--</X509Certificate>\n                </X509Data>\n            </KeyInfo>\n        </KeyDescriptor>\n        <SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"\n                             Location=\"https://adfs.example.com/adfs/ls/\"/>\n        <SingleLogoutService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n                             Location=\"https://adfs.example.com/adfs/ls/\"/>\n        <NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>\n        <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</NameIDFormat>\n        <NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:transient</NameIDFormat>\n        <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect\"\n                             Location=\"https://adfs.example.com/adfs/ls/\"/>\n        <SingleSignOnService Binding=\"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST\"\n                             Location=\"https://adfs.example.com/adfs/ls/\"/>\n        <Attribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"E-Mail Address\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Given Name\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Name\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"UPN\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.xmlsoap.org/claims/CommonName\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Common Name\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.xmlsoap.org/claims/EmailAddress\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"AD FS 1.x E-Mail Address\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.xmlsoap.org/claims/Group\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Group\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.xmlsoap.org/claims/UPN\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"AD FS 1.x UPN\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/role\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Role\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Surname\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"PPID\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Name ID\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationinstant\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Authentication time stamp\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/authenticationmethod\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Authentication method\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/denyonlysid\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Deny only group SID\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/denyonlyprimarysid\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Deny only primary SID\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/denyonlyprimarygroupsid\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\"\n                   FriendlyName=\"Deny only primary group SID\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/groupsid\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Group SID\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/primarygroupsid\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Primary group SID\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/primarysid\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Primary SID\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Windows account name\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/isregistereduser\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Is Registered User\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/identifier\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Device Identifier\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/registrationid\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\"\n                   FriendlyName=\"Device Registration Identifier\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/displayname\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\"\n                   FriendlyName=\"Device Registration DisplayName\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/ostype\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Device OS type\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/osversion\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Device OS Version\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/01/devicecontext/claims/ismanaged\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Is Managed Device\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-forwarded-client-ip\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Forwarded Client IP\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-application\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Client Application\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-user-agent\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Client User Agent\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-client-ip\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Client IP\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-endpoint-absolute-path\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Endpoint Path\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/x-ms-proxy\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Proxy\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/relyingpartytrustid\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Application Identifier\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/applicationpolicy\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Application policies\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/authoritykeyidentifier\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Authority Key Identifier\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/basicconstraints\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Basic Constraint\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/eku\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Enhanced Key Usage\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/issuer\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Issuer\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/issuername\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Issuer Name\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/keyusage\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Key Usage\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/notafter\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Not After\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/notbefore\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Not Before\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/certificatepolicy\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Certificate Policies\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/rsa\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Public Key\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/rawdata\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Certificate Raw Data\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/san\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Subject Alternative Name\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/ws/2008/06/identity/claims/serialnumber\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Serial Number\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/signaturealgorithm\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Signature Algorithm\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/subject\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Subject\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/subjectkeyidentifier\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Subject Key Identifier\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/subjectname\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Subject Name\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute\n            Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/certificatetemplateinformation\"\n            NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"V2 Template Name\"\n            xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/extension/certificatetemplatename\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"V1 Template Name\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/thumbprint\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Thumbprint\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/12/certificatecontext/field/x509version\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"X.509 Version\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/ws/2012/01/insidecorporatenetwork\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Inside Corporate Network\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/ws/2012/01/passwordexpirationtime\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Password Expiration Time\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/ws/2012/01/passwordexpirationdays\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Password Expiration Days\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/ws/2012/01/passwordchangeurl\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Update Password URL\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/claims/authnmethodsreferences\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\"\n                   FriendlyName=\"Authentication Methods References\" xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n        <Attribute Name=\"http://schemas.microsoft.com/2012/01/requestcontext/claims/client-request-id\"\n                   NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"Client Request ID\"\n                   xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\"/>\n    </IDPSSODescriptor>\n    <ContactPerson contactType=\"support\">\n        <EmailAddress/>\n        <TelephoneNumber/>\n    </ContactPerson>\n</EntityDescriptor>\n"
  },
  {
    "path": "tests/mock_files/adfs-openid-configuration.json",
    "content": "{\n  \"issuer\": \"https://adfs.example.com/adfs\",\n  \"authorization_endpoint\": \"https://adfs.example.com/adfs/oauth2/authorize/\",\n  \"token_endpoint\": \"https://adfs.example.com/adfs/oauth2/token/\",\n  \"jwks_uri\": \"https://adfs.example.com/adfs/discovery/keys\",\n  \"token_endpoint_auth_methods_supported\": [\n    \"client_secret_post\",\n    \"client_secret_basic\",\n    \"private_key_jwt\",\n    \"windows_client_authentication\"\n  ],\n  \"response_types_supported\": [\n    \"code\",\n    \"id_token\",\n    \"code id_token\",\n    \"id_token token\",\n    \"code token\",\n    \"code id_token token\"\n  ],\n  \"response_modes_supported\": [\n    \"query\",\n    \"fragment\",\n    \"form_post\"\n  ],\n  \"grant_types_supported\": [\n    \"authorization_code\",\n    \"refresh_token\",\n    \"client_credentials\",\n    \"urn:ietf:params:oauth:grant-type:jwt-bearer\",\n    \"implicit\",\n    \"password\",\n    \"srv_challenge\"\n  ],\n  \"subject_types_supported\": [\n    \"pairwise\"\n  ],\n  \"scopes_supported\": [\n    \"user_impersonation\",\n    \"openid\",\n    \"winhello_cert\",\n    \"aza\",\n    \"vpn_cert\",\n    \"profile\",\n    \"allatclaims\",\n    \"email\",\n    \"logon_cert\"\n  ],\n  \"id_token_signing_alg_values_supported\": [\n    \"RS256\"\n  ],\n  \"token_endpoint_auth_signing_alg_values_supported\": [\n    \"RS256\"\n  ],\n  \"access_token_issuer\": \"http://adfs.example.com/adfs/services/trust\",\n  \"claims_supported\": [\n    \"aud\",\n    \"iss\",\n    \"iat\",\n    \"exp\",\n    \"auth_time\",\n    \"nonce\",\n    \"at_hash\",\n    \"c_hash\",\n    \"sub\",\n    \"upn\",\n    \"unique_name\",\n    \"pwd_url\",\n    \"pwd_exp\",\n    \"sid\"\n  ],\n  \"microsoft_multi_refresh_token\": true,\n  \"userinfo_endpoint\": \"https://adfs.example.com/adfs/userinfo\",\n  \"capabilities\": [],\n  \"end_session_endpoint\": \"https://adfs.example.com/adfs/oauth2/logout\",\n  \"as_access_token_token_binding_supported\": true,\n  \"as_refresh_token_token_binding_supported\": true,\n  \"resource_access_token_token_binding_supported\": true,\n  \"op_id_token_token_binding_supported\": true,\n  \"rp_id_token_token_binding_supported\": true,\n  \"frontchannel_logout_supported\": true,\n  \"frontchannel_logout_session_supported\": true\n}"
  },
  {
    "path": "tests/mock_files/azure-openid-configuration-v2.json",
    "content": "{\n    \"authorization_endpoint\": \"https://login.microsoftonline.com/01234567-89ab-cdef-0123-456789abcdef/oauth2/v2.0/authorize\",\n    \"token_endpoint\": \"https://login.microsoftonline.com/01234567-89ab-cdef-0123-456789abcdef/oauth2/v2.0/token\",\n    \"token_endpoint_auth_methods_supported\": [\n      \"client_secret_post\",\n      \"private_key_jwt\",\n      \"client_secret_basic\"\n    ],\n    \"jwks_uri\": \"https://login.microsoftonline.com/common/discovery/keys\",\n    \"response_modes_supported\": [\n      \"query\",\n      \"fragment\",\n      \"form_post\"\n    ],\n    \"subject_types_supported\": [\n      \"pairwise\"\n    ],\n    \"id_token_signing_alg_values_supported\": [\n      \"RS256\"\n    ],\n    \"http_logout_supported\": true,\n    \"frontchannel_logout_supported\": true,\n    \"end_session_endpoint\": \"https://login.microsoftonline.com/01234567-89ab-cdef-0123-456789abcdef/oauth2/v2.0/logout\",\n    \"response_types_supported\": [\n      \"code\",\n      \"id_token\",\n      \"code id_token\",\n      \"token id_token\",\n      \"token\"\n    ],\n    \"scopes_supported\": [\n      \"openid\"\n    ],\n    \"issuer\": \"https://sts.windows.net/01234567-89ab-cdef-0123-456789abcdef/\",\n    \"claims_supported\": [\n      \"sub\",\n      \"iss\",\n      \"cloud_instance_name\",\n      \"cloud_instance_host_name\",\n      \"cloud_graph_host_name\",\n      \"msgraph_host\",\n      \"aud\",\n      \"exp\",\n      \"iat\",\n      \"auth_time\",\n      \"acr\",\n      \"amr\",\n      \"nonce\",\n      \"email\",\n      \"given_name\",\n      \"family_name\",\n      \"nickname\"\n    ],\n    \"microsoft_multi_refresh_token\": true,\n    \"check_session_iframe\": \"https://login.microsoftonline.com/01234567-89ab-cdef-0123-456789abcdef/oauth2/v2.0/checksession\",\n    \"userinfo_endpoint\": \"https://login.microsoftonline.com/01234567-89ab-cdef-0123-456789abcdef/openid/userinfo\",\n    \"tenant_region_scope\": \"EU\",\n    \"cloud_instance_name\": \"microsoftonline.com\",\n    \"cloud_graph_host_name\": \"graph.windows.net\",\n    \"msgraph_host\": \"graph.microsoft.com\",\n    \"rbac_url\": \"https://pas.windows.net\"\n  }"
  },
  {
    "path": "tests/mock_files/azure-openid-configuration.json",
    "content": "{\n  \"authorization_endpoint\": \"https://login.microsoftonline.com/01234567-89ab-cdef-0123-456789abcdef/oauth2/authorize\",\n  \"token_endpoint\": \"https://login.microsoftonline.com/01234567-89ab-cdef-0123-456789abcdef/oauth2/token\",\n  \"token_endpoint_auth_methods_supported\": [\n    \"client_secret_post\",\n    \"private_key_jwt\",\n    \"client_secret_basic\"\n  ],\n  \"jwks_uri\": \"https://login.microsoftonline.com/common/discovery/keys\",\n  \"response_modes_supported\": [\n    \"query\",\n    \"fragment\",\n    \"form_post\"\n  ],\n  \"subject_types_supported\": [\n    \"pairwise\"\n  ],\n  \"id_token_signing_alg_values_supported\": [\n    \"RS256\"\n  ],\n  \"http_logout_supported\": true,\n  \"frontchannel_logout_supported\": true,\n  \"end_session_endpoint\": \"https://login.microsoftonline.com/01234567-89ab-cdef-0123-456789abcdef/oauth2/logout\",\n  \"response_types_supported\": [\n    \"code\",\n    \"id_token\",\n    \"code id_token\",\n    \"token id_token\",\n    \"token\"\n  ],\n  \"scopes_supported\": [\n    \"openid\"\n  ],\n  \"issuer\": \"https://sts.windows.net/01234567-89ab-cdef-0123-456789abcdef/\",\n  \"claims_supported\": [\n    \"sub\",\n    \"iss\",\n    \"cloud_instance_name\",\n    \"cloud_instance_host_name\",\n    \"cloud_graph_host_name\",\n    \"msgraph_host\",\n    \"aud\",\n    \"exp\",\n    \"iat\",\n    \"auth_time\",\n    \"acr\",\n    \"amr\",\n    \"nonce\",\n    \"email\",\n    \"given_name\",\n    \"family_name\",\n    \"nickname\"\n  ],\n  \"microsoft_multi_refresh_token\": true,\n  \"check_session_iframe\": \"https://login.microsoftonline.com/01234567-89ab-cdef-0123-456789abcdef/oauth2/checksession\",\n  \"userinfo_endpoint\": \"https://login.microsoftonline.com/01234567-89ab-cdef-0123-456789abcdef/openid/userinfo\",\n  \"tenant_region_scope\": \"EU\",\n  \"cloud_instance_name\": \"microsoftonline.com\",\n  \"cloud_graph_host_name\": \"graph.windows.net\",\n  \"msgraph_host\": \"graph.microsoft.com\",\n  \"rbac_url\": \"https://pas.windows.net\"\n}"
  },
  {
    "path": "tests/models.py",
    "content": "from django.db import models\nfrom django.contrib.auth.models import User\n\n\nclass Profile(models.Model):\n    user = models.OneToOneField(User, related_name=\"profile\", on_delete=models.CASCADE)\n    employee_id = models.IntegerField(blank=True, null=True)\n"
  },
  {
    "path": "tests/settings.py",
    "content": "SECRET_KEY = 'secret'\n\nDATABASES = {\n    'default': {\n        'ENGINE': 'django.db.backends.sqlite3',\n        'NAME': ':memory:',\n        'USER': '',\n        'PASSWORD': '',\n        'HOST': '',\n        'PORT': '',\n    }\n}\n\nTEMPLATES = [\n    {\n        'BACKEND': 'django.template.backends.django.DjangoTemplates',\n        'APP_DIRS': True,\n        'OPTIONS': {\n            'context_processors': [\n                'django.template.context_processors.debug',\n                'django.template.context_processors.request',\n                'django.contrib.auth.context_processors.auth',\n                'django.contrib.messages.context_processors.messages',\n            ],\n        },\n        'DIRS': 'templates'\n    },\n]\n\nMIDDLEWARE = (\n    'django.contrib.sessions.middleware.SessionMiddleware',\n    'django.middleware.common.CommonMiddleware',\n    'django.middleware.csrf.CsrfViewMiddleware',\n    'django.contrib.auth.middleware.AuthenticationMiddleware',\n    'django.contrib.messages.middleware.MessageMiddleware',\n    'django.middleware.clickjacking.XFrameOptionsMiddleware',\n\n    'django_auth_adfs.middleware.LoginRequiredMiddleware',\n)\n\nINSTALLED_APPS = (\n    'django.contrib.admin',\n    'django.contrib.auth',\n    'django.contrib.contenttypes',\n    'django.contrib.sessions',\n    'django.contrib.messages',\n    'django.contrib.staticfiles',\n\n    'django_auth_adfs',\n    'tests',\n)\n\nAUTHENTICATION_BACKENDS = (\n    \"django.contrib.auth.backends.ModelBackend\",\n    'django_auth_adfs.backend.AdfsAuthCodeBackend',\n    'django_auth_adfs.backend.AdfsAccessTokenBackend',\n)\n\nROOT_URLCONF = 'tests.urls'\n\nSTATIC_ROOT = '/tmp/'  # Dummy\nSTATIC_URL = '/static/'\n\nAUTH_ADFS = {\n    \"SERVER\": \"adfs.example.com\",\n    \"CLIENT_ID\": \"your-configured-client-id\",\n    \"RELYING_PARTY_ID\": \"your-adfs-RPT-name\",\n    \"AUDIENCE\": \"microsoft:identityserver:your-RelyingPartyTrust-identifier\",\n    \"CA_BUNDLE\": \"/path/to/ca-bundle.pem\",\n    \"CLAIM_MAPPING\": {\"first_name\": \"given_name\",\n                      \"last_name\": \"family_name\",\n                      \"email\": \"email\"},\n    \"BOOLEAN_CLAIM_MAPPING\": {\"is_staff\": \"user_is_staff\",\n                              \"is_superuser\": \"user_is_superuser\"},\n    \"CONFIG_RELOAD_INTERVAL\": 0,  # Always reload settings\n}\n\nLOGIN_URL = \"django_auth_adfs:login\"\nLOGIN_REDIRECT_URL = \"/\"\n"
  },
  {
    "path": "tests/test_authentication.py",
    "content": "import base64\n\nfrom django_auth_adfs.exceptions import MFARequired\n\ntry:\n    from urllib.parse import parse_qs, urlparse\nexcept ImportError:  # Python 2.7\n    from urlparse import urlparse, parse_qs\n\nfrom copy import deepcopy\n\nfrom django.contrib.auth.models import Group, User\nfrom django.core.exceptions import ObjectDoesNotExist, PermissionDenied\nfrom django.db.models.signals import post_save\nfrom django.test import RequestFactory, TestCase\nfrom mock import Mock, patch\n\nfrom django_auth_adfs import signals\nfrom django_auth_adfs.backend import AdfsAuthCodeBackend\nfrom django_auth_adfs.config import ProviderConfig, Settings\n\nfrom .models import Profile\nfrom .utils import mock_adfs\n\n\nclass AuthenticationTests(TestCase):\n    def setUp(self):\n        Group.objects.create(name='group1')\n        Group.objects.create(name='group2')\n        Group.objects.create(name='group3')\n        self.request = RequestFactory().get('/oauth2/callback')\n        self.signal_handler = Mock()\n        signals.post_authenticate.connect(self.signal_handler)\n\n    @mock_adfs(\"2012\")\n    def test_post_authenticate_signal_send(self):\n        backend = AdfsAuthCodeBackend()\n        backend.authenticate(self.request, authorization_code=\"dummycode\")\n        self.assertEqual(self.signal_handler.call_count, 1)\n\n    @mock_adfs(\"2012\")\n    def test_with_auth_code_2012(self):\n        backend = AdfsAuthCodeBackend()\n        user = backend.authenticate(self.request, authorization_code=\"dummycode\")\n        self.assertIsInstance(user, User)\n        self.assertEqual(user.first_name, \"John\")\n        self.assertEqual(user.last_name, \"Doe\")\n        self.assertEqual(user.email, \"john.doe@example.com\")\n        self.assertEqual(len(user.groups.all()), 2)\n        self.assertEqual(user.groups.all()[0].name, \"group1\")\n        self.assertEqual(user.groups.all()[1].name, \"group2\")\n\n    @mock_adfs(\"2016\")\n    def test_with_auth_code_2016(self):\n        backend = AdfsAuthCodeBackend()\n        user = backend.authenticate(self.request, authorization_code=\"dummycode\")\n        self.assertIsInstance(user, User)\n        self.assertEqual(user.first_name, \"John\")\n        self.assertEqual(user.last_name, \"Doe\")\n        self.assertEqual(user.email, \"john.doe@example.com\")\n        self.assertEqual(len(user.groups.all()), 2)\n        self.assertEqual(user.groups.all()[0].name, \"group1\")\n        self.assertEqual(user.groups.all()[1].name, \"group2\")\n\n    @mock_adfs(\"2016\", mfa_error=True)\n    def test_mfa_error_backends(self):\n        with self.assertRaises(MFARequired):\n            backend = AdfsAuthCodeBackend()\n            backend.authenticate(self.request, authorization_code=\"dummycode\")\n\n    @mock_adfs(\"azure\")\n    def test_with_auth_code_azure(self):\n        from django_auth_adfs.config import django_settings\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"dummy_tenant_id\"\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with patch(\"django_auth_adfs.config.settings\", Settings()):\n                with patch(\"django_auth_adfs.backend.provider_config\", ProviderConfig()):\n                    backend = AdfsAuthCodeBackend()\n                    user = backend.authenticate(self.request, authorization_code=\"dummycode\")\n                    self.assertIsInstance(user, User)\n                    self.assertEqual(user.first_name, \"John\")\n                    self.assertEqual(user.last_name, \"Doe\")\n                    self.assertEqual(user.email, \"john.doe@example.com\")\n                    self.assertEqual(len(user.groups.all()), 2)\n                    self.assertEqual(user.groups.all()[0].name, \"group1\")\n                    self.assertEqual(user.groups.all()[1].name, \"group2\")\n\n    @mock_adfs(\"azure\", guest=True)\n    def test_with_auth_code_azure_guest_block(self):\n        from django_auth_adfs.config import django_settings\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"dummy_tenant_id\"\n        settings.AUTH_ADFS[\"BLOCK_GUEST_USERS\"] = True\n        # Patch audience since we're patching django_auth_adfs.backend.settings to load Settings() as well\n        settings.AUTH_ADFS[\"AUDIENCE\"] = 'microsoft:identityserver:your-RelyingPartyTrust-identifier'\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with patch('django_auth_adfs.backend.settings', Settings()):\n                with patch(\"django_auth_adfs.config.settings\", Settings()):\n                    with patch(\"django_auth_adfs.backend.provider_config\", ProviderConfig()):\n                        with self.assertRaises(PermissionDenied, msg=''):\n                            backend = AdfsAuthCodeBackend()\n                            _ = backend.authenticate(self.request, authorization_code=\"dummycode\")\n\n    @mock_adfs(\"azure\", guest=True)\n    def test_with_auth_code_azure_guest_no_block(self):\n        from django_auth_adfs.config import django_settings\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"dummy_tenant_id\"\n        settings.AUTH_ADFS[\"BLOCK_GUEST_USERS\"] = False\n        # Patch audience since we're patching django_auth_adfs.backend.settings to load Settings() as well\n        settings.AUTH_ADFS[\"AUDIENCE\"] = 'microsoft:identityserver:your-RelyingPartyTrust-identifier'\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with patch('django_auth_adfs.backend.settings', Settings()):\n                with patch(\"django_auth_adfs.config.settings\", Settings()):\n                    with patch(\"django_auth_adfs.backend.provider_config\", ProviderConfig()):\n                        backend = AdfsAuthCodeBackend()\n                        user = backend.authenticate(self.request, authorization_code=\"dummycode\")\n                        self.assertIsInstance(user, User)\n                        self.assertEqual(user.first_name, \"John\")\n                        self.assertEqual(user.last_name, \"Doe\")\n                        self.assertEqual(user.email, \"john.doe@example.com\")\n                        self.assertEqual(len(user.groups.all()), 2)\n                        self.assertEqual(user.groups.all()[0].name, \"group1\")\n                        self.assertEqual(user.groups.all()[1].name, \"group2\")\n\n    @mock_adfs(\"azure\", version='v2.0')\n    def test_version_two_endpoint_calls_correct_url(self):\n        from django_auth_adfs.config import django_settings\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"dummy_tenant_id\"\n        settings.AUTH_ADFS[\"VERSION\"] = 'v2.0'\n        # Patch audience since we're patching django_auth_adfs.backend.settings to load Settings() as well\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with patch('django_auth_adfs.backend.settings', Settings()):\n                with patch(\"django_auth_adfs.config.settings\", Settings()):\n                    with patch(\"django_auth_adfs.backend.provider_config\", ProviderConfig()):\n                        backend = AdfsAuthCodeBackend()\n                        user = backend.authenticate(self.request, authorization_code=\"dummycode\")\n                        self.assertIsInstance(user, User)\n                        self.assertEqual(user.first_name, \"John\")\n                        self.assertEqual(user.last_name, \"Doe\")\n                        self.assertEqual(user.email, \"john.doe@example.com\")\n                        self.assertEqual(len(user.groups.all()), 2)\n                        self.assertEqual(user.groups.all()[0].name, \"group1\")\n                        self.assertEqual(user.groups.all()[1].name, \"group2\")\n\n    @mock_adfs(\"2016\")\n    def test_empty(self):\n        backend = AdfsAuthCodeBackend()\n        self.assertIsNone(backend.authenticate(self.request))\n\n    @mock_adfs(\"2016\")\n    def test_group_claim(self):\n        backend = AdfsAuthCodeBackend()\n        with patch(\"django_auth_adfs.backend.settings.GROUPS_CLAIM\", \"nonexisting\"):\n            user = backend.authenticate(self.request, authorization_code=\"dummycode\")\n            self.assertIsInstance(user, User)\n            self.assertEqual(user.first_name, \"John\")\n            self.assertEqual(user.last_name, \"Doe\")\n            self.assertEqual(user.email, \"john.doe@example.com\")\n            self.assertEqual(len(user.groups.all()), 0)\n\n    @mock_adfs(\"2016\")\n    def test_no_group_claim(self):\n        backend = AdfsAuthCodeBackend()\n        with patch(\"django_auth_adfs.backend.settings.GROUPS_CLAIM\", None):\n            user = backend.authenticate(self.request, authorization_code=\"dummycode\")\n            self.assertIsInstance(user, User)\n            self.assertEqual(user.first_name, \"John\")\n            self.assertEqual(user.last_name, \"Doe\")\n            self.assertEqual(user.email, \"john.doe@example.com\")\n            self.assertEqual(len(user.groups.all()), 0)\n\n    @mock_adfs(\"2016\")\n    def test_group_claim_with_mirror_groups(self):\n        # Remove one group\n        Group.objects.filter(name=\"group1\").delete()\n\n        backend = AdfsAuthCodeBackend()\n        with patch(\"django_auth_adfs.backend.settings.MIRROR_GROUPS\", True):\n            user = backend.authenticate(self.request, authorization_code=\"dummycode\")\n            self.assertIsInstance(user, User)\n            self.assertEqual(user.first_name, \"John\")\n            self.assertEqual(user.last_name, \"Doe\")\n            self.assertEqual(user.email, \"john.doe@example.com\")\n            # group1 is restored\n            group_names = user.groups.order_by(\"name\").values_list(\"name\", flat=True)\n            self.assertSequenceEqual(group_names, ['group1', 'group2'])\n\n    @mock_adfs(\"2016\")\n    def test_group_claim_without_mirror_groups(self):\n        # Remove one group\n        Group.objects.filter(name=\"group1\").delete()\n\n        backend = AdfsAuthCodeBackend()\n        with patch(\"django_auth_adfs.backend.settings.MIRROR_GROUPS\", False):\n            user = backend.authenticate(self.request, authorization_code=\"dummycode\")\n            self.assertIsInstance(user, User)\n            self.assertEqual(user.first_name, \"John\")\n            self.assertEqual(user.last_name, \"Doe\")\n            self.assertEqual(user.email, \"john.doe@example.com\")\n            # User is not added to group1 because the group doesn't exist\n            group_names = user.groups.values_list(\"name\", flat=True)\n            self.assertSequenceEqual(group_names, ['group2'])\n\n    @mock_adfs(\"2016\", empty_keys=True)\n    def test_empty_keys(self):\n        backend = AdfsAuthCodeBackend()\n        with patch(\"django_auth_adfs.config.provider_config.signing_keys\", []):\n            self.assertRaises(PermissionDenied, backend.authenticate, self.request, authorization_code='testcode')\n\n    @mock_adfs(\"2016\")\n    def test_group_removal(self):\n        user, created = User.objects.get_or_create(**{\n            User.USERNAME_FIELD: \"testuser\"\n        })\n        group = Group.objects.get(name=\"group3\")\n        user.groups.add(group)\n        user.set_unusable_password()\n        user.save()\n\n        self.assertEqual(user.groups.all()[0].name, \"group3\")\n        self.assertEqual(len(user.groups.all()), 1)\n\n        backend = AdfsAuthCodeBackend()\n\n        user = backend.authenticate(self.request, authorization_code=\"dummycode\")\n        self.assertIsInstance(user, User)\n        self.assertEqual(user.first_name, \"John\")\n        self.assertEqual(user.last_name, \"Doe\")\n        self.assertEqual(user.email, \"john.doe@example.com\")\n        self.assertEqual(len(user.groups.all()), 2)\n        self.assertEqual(user.groups.all()[0].name, \"group1\")\n        self.assertEqual(user.groups.all()[1].name, \"group2\")\n\n    @mock_adfs(\"2016\")\n    def test_group_removal_overlap(self):\n        user, created = User.objects.get_or_create(**{\n            User.USERNAME_FIELD: \"testuser\"\n        })\n        group_one = Group.objects.get(name=\"group1\")\n        group_three = Group.objects.get(name=\"group3\")\n        user.groups.add(group_one, group_three)\n        user.set_unusable_password()\n        user.save()\n\n        self.assertEqual(user.groups.all()[0].name, \"group1\")\n        self.assertEqual(user.groups.all()[1].name, \"group3\")\n        self.assertEqual(len(user.groups.all()), 2)\n\n        backend = AdfsAuthCodeBackend()\n\n        user = backend.authenticate(self.request, authorization_code=\"dummycode\")\n        self.assertIsInstance(user, User)\n        self.assertEqual(user.first_name, \"John\")\n        self.assertEqual(user.last_name, \"Doe\")\n        self.assertEqual(user.email, \"john.doe@example.com\")\n        self.assertEqual(len(user.groups.all()), 2)\n        self.assertEqual(user.groups.all()[0].name, \"group1\")\n        self.assertEqual(user.groups.all()[1].name, \"group2\")\n\n    @mock_adfs(\"2016\")\n    def test_group_to_flag_mapping(self):\n        group_to_flag_mapping = {\n            \"is_staff\": [\"group1\", \"group4\"],\n            \"is_superuser\": \"group2\",\n        }\n        with patch(\"django_auth_adfs.backend.settings.GROUP_TO_FLAG_MAPPING\", group_to_flag_mapping):\n            with patch(\"django_auth_adfs.backend.settings.BOOLEAN_CLAIM_MAPPING\", {}):\n                backend = AdfsAuthCodeBackend()\n\n                user = backend.authenticate(self.request, authorization_code=\"dummycode\")\n                self.assertIsInstance(user, User)\n                self.assertEqual(user.first_name, \"John\")\n                self.assertEqual(user.last_name, \"Doe\")\n                self.assertEqual(user.email, \"john.doe@example.com\")\n                self.assertEqual(len(user.groups.all()), 2)\n                self.assertTrue(user.is_staff)\n                self.assertTrue(user.is_superuser)\n\n    @mock_adfs(\"2016\")\n    def test_boolean_claim_mapping(self):\n        boolean_claim_mapping = {\n            \"is_superuser\": \"user_is_superuser\",\n        }\n        with patch(\"django_auth_adfs.backend.settings.BOOLEAN_CLAIM_MAPPING\", boolean_claim_mapping):\n            backend = AdfsAuthCodeBackend()\n\n            user = backend.authenticate(self.request, authorization_code=\"dummycode\")\n            self.assertIsInstance(user, User)\n            self.assertEqual(user.first_name, \"John\")\n            self.assertEqual(user.last_name, \"Doe\")\n            self.assertEqual(user.email, \"john.doe@example.com\")\n            self.assertEqual(len(user.groups.all()), 2)\n            self.assertFalse(user.is_staff)\n            self.assertTrue(user.is_superuser)\n\n    @mock_adfs(\"2016\")\n    def test_extended_model_claim_mapping_missing_instance(self):\n\n        claim_mapping = {\n            \"first_name\": \"given_name\",\n            \"last_name\": \"family_name\",\n            \"email\": \"email\",\n            \"profile\": {\n                \"employee_id\": \"custom_employee_id\",\n            },\n        }\n        with patch(\"django_auth_adfs.backend.settings.CLAIM_MAPPING\", claim_mapping):\n            backend = AdfsAuthCodeBackend()\n\n            user = backend.authenticate(self.request, authorization_code=\"dummycode\")\n            self.assertIsInstance(user, User)\n            self.assertEqual(user.first_name, \"John\")\n            self.assertEqual(user.last_name, \"Doe\")\n            self.assertEqual(user.email, \"john.doe@example.com\")\n            with self.assertRaises(ObjectDoesNotExist):\n                user.profile  # noqa\n\n    @mock_adfs(\"2016\")\n    def test_extended_model_claim_mapping(self):\n\n        def create_profile(sender, instance, created, **kwargs):\n            \"\"\"Create a profile for any user that's created.\"\"\"\n            if created:\n                Profile.objects.create(user=instance)\n        post_save.connect(create_profile, sender=User)\n\n        claim_mapping = {\n            \"first_name\": \"given_name\",\n            \"last_name\": \"family_name\",\n            \"email\": \"email\",\n            \"profile\": {\n                \"employee_id\": \"custom_employee_id\",\n            },\n        }\n        with patch(\"django_auth_adfs.backend.settings.CLAIM_MAPPING\", claim_mapping):\n            backend = AdfsAuthCodeBackend()\n\n            user = backend.authenticate(self.request, authorization_code=\"dummycode\")\n            self.assertIsInstance(user, User)\n            self.assertEqual(user.first_name, \"John\")\n            self.assertEqual(user.last_name, \"Doe\")\n            self.assertEqual(user.email, \"john.doe@example.com\")\n            self.assertEqual(user.profile.employee_id, 182)\n        post_save.disconnect(create_profile, sender=User)\n\n    @mock_adfs(\"2016\")\n    def test_authentication(self):\n        response = self.client.get(\"/oauth2/callback\", {'code': 'testcode'})\n        self.assertEqual(response.status_code, 302)\n        self.assertEqual(response['Location'], \"/\")\n\n    @mock_adfs(\"2016\")\n    def test_mfa_error(self):\n        with patch('django_auth_adfs.views.authenticate') as mock_auth:\n            mock_auth.side_effect = MFARequired('Mock error')\n            response = self.client.get(\"/oauth2/callback\", {'code': 'testcode'})\n            self.assertEqual(response.status_code, 302)\n            self.assertEqual(\n                response['Location'],\n                \"https://adfs.example.com/adfs/oauth2/authorize/?response_type=code&\"\n                \"client_id=your-configured-client-id&resource=your-adfs-RPT-name&\"\n                \"redirect_uri=http%3A%2F%2Ftestserver%2Foauth2%2Fcallback&state=Lw%3D%3D&scope=openid&\"\n                \"amr_values=ngcmfa\"\n            )\n\n    @mock_adfs(\"2016\")\n    def test_callback_redir(self):\n        state = base64.urlsafe_b64encode(\"/test/\".encode())\n        response = self.client.get(\"/oauth2/callback\", {'code': 'testcode', \"state\": state})\n        self.assertEqual(response.status_code, 302)\n        self.assertEqual(response['Location'], \"/test/\")\n\n    @mock_adfs(\"2016\")\n    def test_missing_code(self):\n        response = self.client.get(\"/oauth2/callback\")\n        self.assertEqual(response.status_code, 400)\n\n    @mock_adfs(\"2016\")\n    def test_login_redir(self):\n        response = self.client.get(\"/test/\")\n        self.assertEqual(response.status_code, 302)\n        self.assertEqual(response[\"Location\"], '/oauth2/login?next=/test/')\n\n    @mock_adfs(\"2012\")\n    def test_oauth_redir_2012(self):\n        response = self.client.get(\"/oauth2/login?next=/test/\")\n        self.assertEqual(response.status_code, 302)\n        redir = urlparse(response[\"Location\"])\n        qs = parse_qs(redir.query)\n        sq_expected = {\n            'client_id': ['your-configured-client-id'],\n            'state': ['L3Rlc3Qv'],\n            'response_type': ['code'],\n            'resource': ['your-adfs-RPT-name'],\n            'redirect_uri': ['http://testserver/oauth2/callback']\n        }\n        self.assertEqual(redir.scheme, 'https')\n        self.assertEqual(redir.hostname, 'adfs.example.com')\n        self.assertEqual(redir.path.rstrip(\"/\"), '/adfs/oauth2/authorize')\n        self.assertEqual(qs, sq_expected)\n\n    @mock_adfs(\"2016\")\n    def test_oauth_redir_2016(self):\n        response = self.client.get(\"/oauth2/login?next=/test/\")\n        self.assertEqual(response.status_code, 302)\n        redir = urlparse(response[\"Location\"])\n        qs = parse_qs(redir.query)\n        qs_expected = {\n            'scope': ['openid'],\n            'client_id': ['your-configured-client-id'],\n            'state': ['L3Rlc3Qv'],\n            'response_type': ['code'],\n            'resource': ['your-adfs-RPT-name'],\n            'redirect_uri': ['http://testserver/oauth2/callback']\n        }\n        self.assertEqual(redir.scheme, 'https')\n        self.assertEqual(redir.hostname, 'adfs.example.com')\n        self.assertEqual(redir.path.rstrip(\"/\"), '/adfs/oauth2/authorize')\n        self.assertEqual(qs, qs_expected)\n\n    @mock_adfs(\"azure\")\n    def test_oauth_redir_azure_version_one(self):\n        from django_auth_adfs.config import django_settings\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"dummy_tenant_id\"\n        with patch(\"django_auth_adfs.config.django_settings\", settings), \\\n                patch(\"django_auth_adfs.config.settings\", Settings()), \\\n                patch(\"django_auth_adfs.views.provider_config\", ProviderConfig()):\n            response = self.client.get(\"/oauth2/login?next=/test/\")\n            self.assertEqual(response.status_code, 302)\n            redir = urlparse(response[\"Location\"])\n            qs = parse_qs(redir.query)\n            sq_expected = {\n                'scope': ['openid'],\n                'client_id': ['your-configured-client-id'],\n                'state': ['L3Rlc3Qv'],\n                'response_type': ['code'],\n                'resource': ['your-adfs-RPT-name'],\n                'redirect_uri': ['http://testserver/oauth2/callback']\n            }\n            self.assertEqual(redir.scheme, 'https')\n            self.assertEqual(redir.hostname, 'login.microsoftonline.com')\n            self.assertEqual(redir.path.rstrip(\"/\"), '/01234567-89ab-cdef-0123-456789abcdef/oauth2/authorize')\n            self.assertEqual(qs, sq_expected)\n\n    @mock_adfs(\"azure\")\n    def test_oauth_redir_azure_version_two(self):\n        from django_auth_adfs.config import django_settings\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"dummy_tenant_id\"\n        settings.AUTH_ADFS[\"VERSION\"] = 'v2.0'\n        with patch(\"django_auth_adfs.config.django_settings\", settings), \\\n                patch(\"django_auth_adfs.config.settings\", Settings()), \\\n                patch(\"django_auth_adfs.views.provider_config\", ProviderConfig()):\n            response = self.client.get(\"/oauth2/login?next=/test/\")\n            self.assertEqual(response.status_code, 302)\n            redir = urlparse(response[\"Location\"])\n            qs = parse_qs(redir.query)\n            sq_expected = {\n                'scope': ['openid api://your-adfs-RPT-name/.default'],\n                'client_id': ['your-configured-client-id'],\n                'state': ['L3Rlc3Qv'],\n                'response_type': ['code'],\n                'redirect_uri': ['http://testserver/oauth2/callback']\n            }\n            self.assertEqual(redir.scheme, 'https')\n            self.assertEqual(redir.hostname, 'login.microsoftonline.com')\n            self.assertEqual(redir.path.rstrip(\"/\"), '/01234567-89ab-cdef-0123-456789abcdef/oauth2/authorize')\n            self.assertEqual(qs, sq_expected)\n\n    @mock_adfs(\"azure\")\n    def test_scopes_generated_correctly(self):\n        from django_auth_adfs.config import django_settings\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"dummy_tenant_id\"\n        settings.AUTH_ADFS[\"VERSION\"] = 'v2.0'\n        settings.AUTH_ADFS[\"SCOPES\"] = ['openid', 'api://your-configured-client-id/user_impersonation']\n        with patch(\"django_auth_adfs.config.django_settings\", settings), \\\n                patch(\"django_auth_adfs.config.settings\", Settings()), \\\n                patch(\"django_auth_adfs.views.provider_config\", ProviderConfig()):\n            response = self.client.get(\"/oauth2/login?next=/test/\")\n            self.assertEqual(response.status_code, 302)\n            redir = urlparse(response[\"Location\"])\n            qs = parse_qs(redir.query)\n            sq_expected = {\n                'scope': ['openid api://your-configured-client-id/user_impersonation'],\n                'client_id': ['your-configured-client-id'],\n                'state': ['L3Rlc3Qv'],\n                'response_type': ['code'],\n                'redirect_uri': ['http://testserver/oauth2/callback']\n            }\n            self.assertEqual(redir.scheme, 'https')\n            self.assertEqual(redir.hostname, 'login.microsoftonline.com')\n            self.assertEqual(redir.path.rstrip(\"/\"), '/01234567-89ab-cdef-0123-456789abcdef/oauth2/authorize')\n            self.assertEqual(qs, sq_expected)\n\n    @mock_adfs(\"2016\")\n    def test_inactive_user(self):\n        user = User.objects.create(**{\n            User.USERNAME_FIELD: \"testuser\",\n            \"is_active\": False\n        })\n        response = self.client.get(\"/oauth2/callback\", {'code': 'testcode'})\n        self.assertContains(response, \"Your account is disabled\", status_code=403)\n        user.delete()\n\n    @mock_adfs(\"2016\")\n    def test_nonexisting_user(self):\n        from django_auth_adfs.config import django_settings\n        settings = deepcopy(django_settings)\n        settings.AUTH_ADFS[\"CREATE_NEW_USERS\"] = False\n        with patch(\"django_auth_adfs.config.django_settings\", settings), \\\n                patch(\"django_auth_adfs.backend.settings\", Settings()):\n            backend = AdfsAuthCodeBackend()\n            self.assertRaises(PermissionDenied, backend.authenticate, self.request, authorization_code='testcode')\n"
  },
  {
    "path": "tests/test_drf_integration.py",
    "content": "import json\nfrom copy import deepcopy\n\nfrom django.test import RequestFactory, TestCase\nfrom mock import patch\nfrom rest_framework import exceptions\nfrom rest_framework.exceptions import AuthenticationFailed\n\nfrom django.contrib.auth.models import Group\nfrom django_auth_adfs.config import ProviderConfig, Settings\nfrom django_auth_adfs.rest_framework import AdfsAccessTokenAuthentication\nfrom .utils import build_access_token_adfs, build_access_token_azure, build_access_token_azure_guest, \\\n    build_access_token_azure_guest_no_upn, build_access_token_azure_not_guest, \\\n    build_access_token_azure_guest_with_idp, build_access_token_azure_groups_in_claim_source, \\\n    mock_adfs\n\n\nclass RestFrameworkIntegrationTests(TestCase):\n    def setUp(self):\n        self.drf_auth_class = AdfsAccessTokenAuthentication()\n\n        adfs_response = build_access_token_adfs(RequestFactory().get('/'))[2]\n        self.access_token_adfs = json.loads(adfs_response)['access_token']\n\n        azure_response = build_access_token_azure(RequestFactory().get('/'))[2]\n        self.access_token_azure = json.loads(azure_response)['access_token']\n\n        azure_response_guest = build_access_token_azure_guest(RequestFactory().get('/'))[2]\n        self.access_token_azure_guest = json.loads(azure_response_guest)['access_token']\n\n        azure_response_no_guest = build_access_token_azure_not_guest(RequestFactory().get('/'))[2]\n        self.access_token_azure_no_guest = json.loads(azure_response_no_guest)['access_token']\n\n        azure_response_guest = build_access_token_azure_guest_no_upn(RequestFactory().get('/'))[2]\n        self.access_token_azure_guest_no_upn = json.loads(azure_response_guest)['access_token']\n\n        azure_response_guest = build_access_token_azure_guest_with_idp(RequestFactory().get('/'))[2]\n        self.access_token_azure_guest_with_idp = json.loads(azure_response_guest)['access_token']\n\n        azure_response = build_access_token_azure_groups_in_claim_source(RequestFactory().get('/'))[2]\n        self.access_token_azure_groups_in_claim_source = json.loads(azure_response)['access_token']\n\n        Group.objects.create(name='group1')\n        Group.objects.create(name='group2')\n        Group.objects.create(name='group3')\n\n    @mock_adfs(\"2012\")\n    def test_access_token_2012(self):\n        access_token_header = \"Bearer {}\".format(self.access_token_adfs)\n        request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header)\n\n        user, token = self.drf_auth_class.authenticate(request)\n        self.assertEqual(user.username, \"testuser\")\n        self.assertEqual(token, self.access_token_adfs.encode())\n\n    @mock_adfs(\"2016\")\n    def test_access_token_2016(self):\n        access_token_header = \"Bearer {}\".format(self.access_token_adfs)\n        request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header)\n\n        user, token = self.drf_auth_class.authenticate(request)\n        self.assertEqual(user.username, \"testuser\")\n        self.assertEqual(token, self.access_token_adfs.encode())\n\n    @mock_adfs(\"azure\")\n    def test_access_token_azure(self):\n        access_token_header = \"Bearer {}\".format(self.access_token_azure)\n        request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header)\n\n        from django_auth_adfs.config import django_settings\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"dummy_tenant_id\"\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with patch(\"django_auth_adfs.config.settings\", Settings()):\n                with patch(\"django_auth_adfs.backend.provider_config\", ProviderConfig()):\n                    user, token = self.drf_auth_class.authenticate(request)\n                    self.assertEqual(user.username, \"testuser\")\n\n    @mock_adfs(\"azure\")\n    def test_access_token_azure_guest(self):\n        access_token_header = \"Bearer {}\".format(self.access_token_azure_guest)\n        request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header)\n\n        from django_auth_adfs.config import django_settings\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"dummy_tenant_id\"\n        settings.AUTH_ADFS[\"BLOCK_GUEST_USERS\"] = True\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with patch('django_auth_adfs.backend.settings', Settings()):\n                with patch(\"django_auth_adfs.config.settings\", Settings()):\n                    with patch(\"django_auth_adfs.backend.provider_config\", ProviderConfig()):\n                        with self.assertRaises(AuthenticationFailed):\n                            user, token = self.drf_auth_class.authenticate(request)\n\n    @mock_adfs(\"azure\")\n    def test_access_token_azure_no_guest(self):\n        access_token_header = \"Bearer {}\".format(self.access_token_azure_no_guest)\n        request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header)\n\n        from django_auth_adfs.config import django_settings\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"dummy_tenant_id\"\n        settings.AUTH_ADFS[\"BLOCK_GUEST_USERS\"] = True\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with patch('django_auth_adfs.backend.settings', Settings()):\n                with patch(\"django_auth_adfs.config.settings\", Settings()):\n                    with patch(\"django_auth_adfs.backend.provider_config\", ProviderConfig()):\n                        user, token = self.drf_auth_class.authenticate(request)\n                        self.assertEqual(user.username, \"testuser\")\n\n    @mock_adfs(\"azure\")\n    def test_access_token_azure_guest_but_no_upn(self):\n        access_token_header = \"Bearer {}\".format(self.access_token_azure_guest_no_upn)\n        request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header)\n        from django_auth_adfs.config import django_settings\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"dummy_tenant_id\"\n        settings.AUTH_ADFS[\"GUEST_USERNAME_CLAIM\"] = \"email\"\n        settings.AUTH_ADFS[\"BLOCK_GUEST_USERS\"] = False\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with patch('django_auth_adfs.backend.settings', Settings()):\n                with patch(\"django_auth_adfs.config.settings\", Settings()):\n                    with patch(\"django_auth_adfs.backend.provider_config\", ProviderConfig()):\n                        user, token = self.drf_auth_class.authenticate(request)\n                        self.assertEqual(user.username, \"john.doe@example.com\")\n\n    @mock_adfs(\"azure\")\n    def test_access_token_azure_guest_with_idp(self):\n        access_token_header = \"Bearer {}\".format(self.access_token_azure_guest_with_idp)\n        request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header)\n        from django_auth_adfs.config import django_settings\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"dummy_tenant_id\"\n        settings.AUTH_ADFS[\"GUEST_USERNAME_CLAIM\"] = \"email\"\n        settings.AUTH_ADFS[\"BLOCK_GUEST_USERS\"] = False\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with patch('django_auth_adfs.backend.settings', Settings()):\n                with patch(\"django_auth_adfs.config.settings\", Settings()):\n                    with patch(\"django_auth_adfs.backend.provider_config\", ProviderConfig()):\n                        user, token = self.drf_auth_class.authenticate(request)\n                        self.assertEqual(user.username, \"john.doe@example.com\")\n\n    @mock_adfs(\"azure\")\n    def test_access_token_azure_guest_but_no_upn_but_no_guest_username_claim(self):\n        access_token_header = \"Bearer {}\".format(self.access_token_azure_guest_no_upn)\n        request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header)\n        from django_auth_adfs.config import django_settings\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"dummy_tenant_id\"\n        settings.AUTH_ADFS[\"GUEST_USERNAME_CLAIM\"] = None  # <--- Set to None, should not be validated as OK\n        settings.AUTH_ADFS[\"BLOCK_GUEST_USERS\"] = False\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with patch('django_auth_adfs.backend.settings', Settings()):\n                with patch(\"django_auth_adfs.config.settings\", Settings()):\n                    with patch(\"django_auth_adfs.backend.provider_config\", ProviderConfig()):\n                        with self.assertRaises(exceptions.AuthenticationFailed):\n                            self.drf_auth_class.authenticate(request)\n\n    @mock_adfs(\"azure\", requires_obo=True)\n    def test_process_group_claim_from_ms_graph(self):\n        access_token_header = \"Bearer {}\".format(self.access_token_azure_groups_in_claim_source)\n        request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header)\n\n        from django_auth_adfs.config import django_settings\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"dummy_tenant_id\"\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with patch('django_auth_adfs.backend.settings', Settings()):\n                with patch(\"django_auth_adfs.config.settings\", Settings()):\n                    with patch(\"django_auth_adfs.backend.provider_config\", ProviderConfig()):\n                        user, _ = self.drf_auth_class.authenticate(request)\n                        self.assertEqual(user.username, \"testuser\")\n                        self.assertEqual(user.groups.all()[0].name, \"group1\")\n                        self.assertEqual(user.groups.all()[1].name, \"group2\")\n\n    @mock_adfs(\"azure\", requires_obo=True, mfa_error=True)\n    def test_get_obo_access_token_mfa_error(self):\n        access_token_header = \"Bearer {}\".format(self.access_token_azure_groups_in_claim_source)\n        request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header)\n\n        from django_auth_adfs.config import django_settings\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"dummy_tenant_id\"\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with patch('django_auth_adfs.backend.settings', Settings()):\n                with patch(\"django_auth_adfs.config.settings\", Settings()):\n                    with patch(\"django_auth_adfs.backend.provider_config\", ProviderConfig()):\n                        with self.assertRaises(AuthenticationFailed):\n                            self.drf_auth_class.authenticate(request)\n\n    @mock_adfs(\"azure\", requires_obo=True, version='v2.0')\n    def test_get_obo_access_token_version_2(self):\n        access_token_header = \"Bearer {}\".format(self.access_token_azure_groups_in_claim_source)\n        request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header)\n\n        from django_auth_adfs.config import django_settings\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"dummy_tenant_id\"\n        settings.AUTH_ADFS[\"VERSION\"] = 'v2.0'\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with patch('django_auth_adfs.backend.settings', Settings()):\n                with patch(\"django_auth_adfs.config.settings\", Settings()):\n                    with patch(\"django_auth_adfs.backend.provider_config\", ProviderConfig()):\n                        user, _ = self.drf_auth_class.authenticate(request)\n                        self.assertEqual(user.username, \"testuser\")\n                        self.assertEqual(user.groups.all()[0].name, \"group1\")\n                        self.assertEqual(user.groups.all()[1].name, \"group2\")\n\n    @mock_adfs(\"azure\", requires_obo=True, missing_graph_group_perm=True)\n    def test_missing_ms_graph_group_permission(self):\n        access_token_header = \"Bearer {}\".format(self.access_token_azure_groups_in_claim_source)\n        request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header)\n\n        from django_auth_adfs.config import django_settings\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"dummy_tenant_id\"\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with patch('django_auth_adfs.backend.settings', Settings()):\n                with patch(\"django_auth_adfs.config.settings\", Settings()):\n                    with patch(\"django_auth_adfs.backend.provider_config\", ProviderConfig()):\n                        with self.assertRaises(AuthenticationFailed):\n                            self.drf_auth_class.authenticate(request)\n\n    @mock_adfs(\"2012\")\n    def test_access_token_exceptions(self):\n        access_token_header = \"Bearer non-existing-token\"\n        request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header)\n\n        with self.assertRaises(exceptions.AuthenticationFailed):\n            self.drf_auth_class.authenticate(request)\n\n        # use the azure token on adfs should not work\n        access_token_header = \"Bearer {}\".format(self.access_token_azure)\n        request = RequestFactory().get('/api', HTTP_AUTHORIZATION=access_token_header)\n\n        with self.assertRaises(exceptions.AuthenticationFailed):\n            self.drf_auth_class.authenticate(request)\n"
  },
  {
    "path": "tests/test_settings.py",
    "content": "import sys\nfrom copy import deepcopy\n\nfrom django.core.exceptions import ImproperlyConfigured\nfrom django.test import TestCase, SimpleTestCase, override_settings\nfrom mock import patch\nfrom django_auth_adfs.config import django_settings\nfrom django_auth_adfs.config import Settings\nfrom django_auth_adfs.config import ProviderConfig\nfrom .custom_config import Settings as CustomSettings\n\n\nclass SettingsTests(TestCase):\n    def test_no_settings(self):\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with self.assertRaises(ImproperlyConfigured):\n                Settings()\n\n    def test_claim_mapping_overlapping_username_field(self):\n        settings = deepcopy(django_settings)\n        settings.AUTH_ADFS[\"CLAIM_MAPPING\"] = {\"username\": \"samaccountname\"}\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with self.assertRaises(ImproperlyConfigured):\n                Settings()\n\n    def test_tenant_and_server(self):\n        settings = deepcopy(django_settings)\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"abc\"\n        settings.AUTH_ADFS[\"SERVER\"] = \"abc\"\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with self.assertRaises(ImproperlyConfigured):\n                Settings()\n\n    def test_no_tenant_but_block_guest(self):\n        settings = deepcopy(django_settings)\n        settings.AUTH_ADFS[\"SERVER\"] = \"abc\"\n        settings.AUTH_ADFS[\"BLOCK_GUEST_USERS\"] = True\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with self.assertRaises(ImproperlyConfigured):\n                Settings()\n\n    def test_tenant_with_block_users(self):\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"abc\"\n        settings.AUTH_ADFS[\"BLOCK_GUEST_USERS\"] = True\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            current_settings = Settings()\n            self.assertTrue(current_settings.BLOCK_GUEST_USERS)\n\n    def test_unknown_setting(self):\n        settings = deepcopy(django_settings)\n        settings.AUTH_ADFS[\"dummy\"] = \"abc\"\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with self.assertRaises(ImproperlyConfigured):\n                Settings()\n\n    def test_required_setting(self):\n        settings = deepcopy(django_settings)\n        del settings.AUTH_ADFS[\"AUDIENCE\"]\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with self.assertRaises(ImproperlyConfigured):\n                Settings()\n\n    def test_default_failed_response_setting(self):\n        settings = deepcopy(django_settings)\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            s = Settings()\n            self.assertTrue(callable(s.CUSTOM_FAILED_RESPONSE_VIEW))\n\n    def test_dotted_path_failed_response_setting(self):\n        settings = deepcopy(django_settings)\n        settings.AUTH_ADFS[\"CUSTOM_FAILED_RESPONSE_VIEW\"] = 'tests.views.test_failed_response'\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            s = Settings()\n            self.assertTrue(callable(s.CUSTOM_FAILED_RESPONSE_VIEW))\n\n    def test_settings_version(self):\n        settings = deepcopy(django_settings)\n        current_settings = Settings()\n        self.assertEqual(current_settings.VERSION, \"v1.0\")\n        settings.AUTH_ADFS[\"TENANT_ID\"] = \"abc\"\n        del settings.AUTH_ADFS[\"SERVER\"]\n        settings.AUTH_ADFS[\"VERSION\"] = \"v2.0\"\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            current_settings = Settings()\n            self.assertEqual(current_settings.VERSION, \"v2.0\")\n\n    def test_not_azure_but_version_is_set(self):\n        settings = deepcopy(django_settings)\n        settings.AUTH_ADFS[\"SERVER\"] = \"abc\"\n        settings.AUTH_ADFS[\"VERSION\"] = \"v2.0\"\n        with patch(\"django_auth_adfs.config.django_settings\", settings):\n            with self.assertRaises(ImproperlyConfigured):\n                Settings()\n\n    def test_configured_proxy(self):\n        settings = Settings()\n        settings.PROXIES = {'http': '10.0.0.1'}\n        with patch(\"django_auth_adfs.config.settings\", settings):\n            provider_config = ProviderConfig()\n            self.assertEqual(provider_config.session.proxies, {'http': '10.0.0.1'})\n\n    def test_no_configured_proxy(self):\n        provider_config = ProviderConfig()\n        self.assertIsNone(provider_config.session.proxies)\n\n\nclass CustomSettingsTests(SimpleTestCase):\n    def setUp(self):\n        sys.modules.pop('django_auth_adfs.config', None)\n\n    def tearDown(self):\n        sys.modules.pop('django_auth_adfs.config', None)\n\n    def test_dotted_path(self):\n        auth_adfs = deepcopy(django_settings).AUTH_ADFS\n        auth_adfs['SETTINGS_CLASS'] = 'tests.custom_config.Settings'\n\n        with override_settings(AUTH_ADFS=auth_adfs):\n            from django_auth_adfs.config import settings\n            self.assertIsInstance(settings, CustomSettings)\n"
  },
  {
    "path": "tests/urls.py",
    "content": "from django.urls import include, re_path\n\nurlpatterns = [\n    re_path(r'^oauth2/', include('django_auth_adfs.urls')),\n    re_path(r'^oauth2/', include('django_auth_adfs.drf_urls')),\n]\n"
  },
  {
    "path": "tests/utils.py",
    "content": "import base64\nimport json\nimport os\nimport re\nimport time\nfrom datetime import datetime, tzinfo, timedelta\nfrom functools import partial\n\nimport jwt\nimport responses\nfrom cryptography import x509\nfrom cryptography.hazmat.backends import default_backend as crypto_default_backend\nfrom cryptography.hazmat.primitives import hashes\nfrom cryptography.hazmat.primitives import serialization as crypto_serialization\nfrom cryptography.hazmat.primitives.asymmetric import rsa\nfrom cryptography.x509.oid import NameOID\n\n\ndef generate_key_and_cert():\n    signing_key = rsa.generate_private_key(\n        backend=crypto_default_backend(),\n        public_exponent=65537,\n        key_size=2048\n    )\n    subject = issuer = x509.Name([\n        x509.NameAttribute(NameOID.COUNTRY_NAME, u\"US\"),\n        x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u\"CA\"),\n        x509.NameAttribute(NameOID.LOCALITY_NAME, u\"San Francisco\"),\n        x509.NameAttribute(NameOID.ORGANIZATION_NAME, u\"My Company\"),\n        x509.NameAttribute(NameOID.COMMON_NAME, u\"example.com\"),\n    ])\n    signing_cert = x509.CertificateBuilder().subject_name(\n        subject\n    ).issuer_name(\n        issuer\n    ).public_key(\n        signing_key.public_key()\n    ).serial_number(\n        x509.random_serial_number()\n    ).not_valid_before(\n        datetime.utcnow()\n    ).not_valid_after(\n        # Our certificate will be valid for 10 days\n        datetime.utcnow() + timedelta(days=10)\n        # Sign our certificate with our private key\n    ).sign(\n        signing_key, hashes.SHA256(), crypto_default_backend()\n    ).public_bytes(crypto_serialization.Encoding.DER)\n    return signing_key, signing_cert\n\n\nclass SimpleUtc(tzinfo):\n    def tzname(self, dt):\n        return \"UTC\"\n\n    def utcoffset(self, dt):\n        return timedelta(0)\n\n\ndef load_json(file):\n    with open(os.path.join(os.path.dirname(__file__), file), mode=\"r\") as f:\n        data = json.load(f)\n    return data\n\n\ndef build_access_token_adfs(request):\n    issuer = \"http://adfs.example.com/adfs/services/trust\"\n    return do_build_access_token(request, issuer)\n\n\ndef build_access_token_azure(request):\n    issuer = \"https://sts.windows.net/01234567-89ab-cdef-0123-456789abcdef/\"\n    return do_build_access_token(request, issuer)\n\n\ndef build_access_token_azure_not_guest(request):\n    issuer = \"https://sts.windows.net/01234567-89ab-cdef-0123-456789abcdef/\"\n    return do_build_access_token(request, issuer, schema='dummy_tenant_id')\n\n\ndef build_access_token_azure_guest(request):\n    issuer = \"https://sts.windows.net/01234567-89ab-cdef-0123-456789abcdef/\"\n    return do_build_access_token(request, issuer, schema='guest_tenant_id')\n\n\ndef build_access_token_azure_guest_no_upn(request):\n    issuer = \"https://sts.windows.net/01234567-89ab-cdef-0123-456789abcdef/\"\n    return do_build_access_token(request, issuer, schema='guest_tenant_id', no_upn=True)\n\n\ndef build_access_token_azure_guest_with_idp(request):\n    issuer = \"https://sts.windows.net/01234567-89ab-cdef-0123-456789abcdef/\"\n    return do_build_access_token(request, issuer, schema='dummy_tenant_id', no_upn=True, idp=\"guest_idp\")\n\n\ndef build_access_token_azure_groups_in_claim_source(request):\n    issuer = \"https://sts.windows.net/01234567-89ab-cdef-0123-456789abcdef/\"\n    return do_build_access_token(request, issuer, groups_in_claim_names=True)\n\n\ndef do_build_mfa_error(request):\n    response = {'error_description': 'AADSTS50076'}\n    return 400, [], json.dumps(response)\n\n\ndef do_build_graph_response(request):\n    return do_build_ms_graph_groups(request)\n\n\ndef do_build_graph_response_no_group_perm(request):\n    return do_build_ms_graph_groups(request, missing_group_names=True)\n\n\ndef do_build_access_token(request, issuer, schema=None, no_upn=False, idp=None, groups_in_claim_names=False):\n    issued_at = int(time.time())\n    expires = issued_at + 3600\n    auth_time = datetime.utcnow()\n    auth_time = auth_time.replace(tzinfo=SimpleUtc(), microsecond=0)\n    claims = {\n        \"aud\": \"microsoft:identityserver:your-RelyingPartyTrust-identifier\",\n        \"iss\": issuer,\n        \"idp\": idp or issuer,\n        \"iat\": issued_at,\n        \"exp\": expires,\n        \"winaccountname\": \"testuser\",\n        \"group\": [\"group1\", \"group2\"],\n        \"given_name\": \"John\",\n        \"family_name\": \"Doe\",\n        \"email\": \"john.doe@example.com\",\n        \"sub\": \"john.doe@example.com\",\n        \"custom_employee_id\": 182,\n        \"user_is_staff\": \"True\",\n        \"user_is_superuser\": \"yes\",\n        \"appid\": \"your-configured-client-id\",\n        \"auth_time\": auth_time.isoformat(),\n        \"authmethod\": \"urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport\",\n        \"ver\": \"1.0\"\n    }\n    if schema:\n        claims['tid'] = schema\n    if issuer.startswith('https://sts.windows.net'):\n        claims['upn'] = 'testuser'\n        claims['groups'] = claims['group']\n    if no_upn:\n        del claims['upn']\n    if groups_in_claim_names:\n        if 'groups' in claims:\n            del claims['groups']\n        del claims['group']\n        claims['_claim_names'] = {\n            \"groups\": \"src1\",\n        }\n        claims['_claim_sources'] = {\n            \"src1\": {\n                \"endpoint\": (\n                    \"https://graph.windows.net/01234567-89ab-cdef-0123-456789abcdef\"\n                    \"/users/23456789-01bc-defg-1234-56789bcdefg/getMemberObjects\"\n                ),\n            }\n        }\n    token = jwt.encode(claims, signing_key_b, algorithm=\"RS256\")\n    response = {\n        'resource': 'django_website.adfs.relying_party_id',\n        'token_type': 'bearer',\n        'refresh_token_expires_in': 28799,\n        'refresh_token': 'random_refresh_token',\n        'expires_in': 3600,\n        'id_token': 'not_used',\n        'access_token': token.decode() if isinstance(token, bytes) else token  # PyJWT>=2 returns a str instead of bytes\n    }\n    return 200, [], json.dumps(response)\n\n\ndef do_build_obo_access_token(request):\n    obo_token = {\n        \"aud\": \"https://graph.microsoft.com\",\n        \"iss\": \"https://sts.windows.net/01234567-89ab-cdef-0123-456789abcdef/\",\n        \"iat\": 1660851337,\n        \"nbf\": 1660851337,\n        \"exp\": 1660856510,\n        \"acct\": 0,\n        \"acr\": \"1\",\n        \"aio\": (\n            \"AUQAu/8TBCDAcvfLrjwjR53Uci8V5KCONDvJXGEFM/gMeVSp6/LV338RTspRjxIhbmNLcAGa80KVXXglM7+ea1uqRKkRNCa9bQ==\"\n        ),\n        \"amr\": [\n            \"wia\",\n            \"mfa\"\n        ],\n        \"app_displayname\": \"AppName\",\n        \"appid\": \"2345a5bc-123a-0a1b-0a12-a12345b6cd7e\",\n        \"appidacr\": \"1\",\n        \"family_name\": \"Doe\",\n        \"given_name\": \"John\",\n        \"idtyp\": \"user\",\n        \"ipaddr\": \"1.2.3.4\",\n        \"name\": \"Doe, John (Expert)\",\n        \"oid\": \"2345a5bc-123a-0a1b-0a12-a12345b6cd7e\",\n        \"onprem_sid\": \"S-1-5-21-456123456-1364589140-123456543-563809\",\n        \"platf\": \"5\",\n        \"puid\": \"10030000AD9D1530\",\n        \"rh\": \"0.AS8A1AA4aCjPK0uCpKTt25xSNwMAAAAAAAAAwAAAAAAAAAAvAEQ.\",\n        \"scp\": \"email GroupMember.Read.All openid profile User.Read\",\n        \"signin_state\": [\n            \"inknownntwk\"\n        ],\n        \"sub\": \"PZBipRglYn2dgemAP_qDM3QzF1nosfdylWx8hsEwzYA\",\n        \"tenant_region_scope\": \"EU\",\n        \"tid\": \"01234567-89ab-cdef-0123-456789abcdef\",\n        \"unique_name\": \"john.doe@example.com\",\n        \"upn\": \"john.doe@example.com\",\n        \"uti\": \"D8NUc9MAwkutG-iBUnsBAA\",\n        \"ver\": \"1.0\",\n        \"wids\": [\n            \"2345a5bc-123a-0a1b-0a12-a12345b6cd7e\",\n        ],\n        \"xms_tcdt\": 1467198948\n    }\n    token = jwt.encode(obo_token, signing_key_b, algorithm=\"RS256\")\n    response = {\n        'token_type': 'bearer',\n        'scope': 'email GroupMember.Read.All openid profile User.Read',\n        'expires_in': '4872',\n        'ext_expires_in': '4872',\n        'expires_on': '1660856510',\n        'not_before': '1660851337',\n        'resource': 'https://graph.microsoft.com',\n        'refresh_token': 'not_used',\n        'access_token': token.decode() if isinstance(token, bytes) else token  # PyJWT>=2 returns a str instead of bytes\n    }\n    return 200, [], json.dumps(response)\n\n\ndef do_build_ms_graph_groups(request, missing_group_names=False):\n    response = {\n        \"@odata.context\": \"https://graph.microsoft.com/v1.0/$metadata#groups\",\n        \"value\": [\n            {\n                \"id\": \"12ab345c-6abc-427f-85ca-93fc0cc7f00d\",\n                \"deletedDateTime\": None,\n                \"classification\": None,\n                \"createdDateTime\": \"2020-11-02T13:06:02Z\",\n                \"creationOptions\": [],\n                \"description\": None,\n                \"displayName\": \"group1\",\n                \"expirationDateTime\": None,\n                \"groupTypes\": [],\n                \"isAssignableToRole\": None,\n                \"mail\": None,\n                \"mailEnabled\": False,\n                \"mailNickname\": \"group1\",\n                \"membershipRule\": None,\n                \"membershipRuleProcessingState\": None,\n                \"onPremisesDomainName\": \"example.com\",\n                \"onPremisesLastSyncDateTime\": \"2022-08-18T19:32:43Z\",\n                \"onPremisesNetBiosName\": \"COMPANY\",\n                \"onPremisesSamAccountName\": \"group1\",\n                \"onPremisesSecurityIdentifier\": \"S-1-5-21-1234567891-1234567891-1234567891-123456\",\n                \"onPremisesSyncEnabled\": True,\n                \"preferredDataLocation\": None,\n                \"preferredLanguage\": None,\n                \"proxyAddresses\": [],\n                \"renewedDateTime\": \"2020-11-02T13:06:02Z\",\n                \"resourceBehaviorOptions\": [],\n                \"resourceProvisioningOptions\": [],\n                \"securityEnabled\": False,\n                \"securityIdentifier\": \"S-1-12-1-1234567891-1234567891-1234567891-1234567891\",\n                \"theme\": None,\n                \"visibility\": None,\n                \"onPremisesProvisioningErrors\": []\n            },\n            {\n                \"id\": \"23ab456c-7abc-427f-85ca-93fc0cc7f00d\",\n                \"deletedDateTime\": None,\n                \"classification\": None,\n                \"createdDateTime\": \"2020-11-02T13:06:02Z\",\n                \"creationOptions\": [],\n                \"description\": None,\n                \"displayName\": \"group2\",\n                \"expirationDateTime\": None,\n                \"groupTypes\": [],\n                \"isAssignableToRole\": None,\n                \"mail\": None,\n                \"mailEnabled\": False,\n                \"mailNickname\": \"group2\",\n                \"membershipRule\": None,\n                \"membershipRuleProcessingState\": None,\n                \"onPremisesDomainName\": \"example.com\",\n                \"onPremisesLastSyncDateTime\": \"2022-08-18T19:32:43Z\",\n                \"onPremisesNetBiosName\": \"COMPANY\",\n                \"onPremisesSamAccountName\": \"group2\",\n                \"onPremisesSecurityIdentifier\": \"S-1-5-21-1234567891-1234567891-1234567891-123456\",\n                \"onPremisesSyncEnabled\": True,\n                \"preferredDataLocation\": None,\n                \"preferredLanguage\": None,\n                \"proxyAddresses\": [],\n                \"renewedDateTime\": \"2020-11-02T13:06:02Z\",\n                \"resourceBehaviorOptions\": [],\n                \"resourceProvisioningOptions\": [],\n                \"securityEnabled\": False,\n                \"securityIdentifier\": \"S-1-12-1-1234567891-1234567891-1234567891-1234567891\",\n                \"theme\": None,\n                \"visibility\": None,\n                \"onPremisesProvisioningErrors\": []\n            },\n        ]\n    }\n    if missing_group_names:\n        for group in response[\"value\"]:\n            group[\"displayName\"] = None\n    return 200, [], json.dumps(response)\n\n\ndef build_openid_keys(request, empty_keys=False):\n    if empty_keys:\n        keys = {\"keys\": []}\n    else:\n        keys = {\n            \"keys\": [\n                {\n                    \"kty\": \"RSA\",\n                    \"use\": \"sig\",\n                    \"kid\": \"dummythumbprint\",\n                    \"x5t\": \"dummythumbprint\",\n                    \"n\": \"somebase64encodedmodulus\",\n                    \"e\": \"somebase64encodedexponent\",\n                    \"x5c\": [base64.b64encode(signing_cert_a).decode(), ]\n                },\n                {\n                    \"kty\": \"RSA\",\n                    \"use\": \"sig\",\n                    \"kid\": \"dummythumbprint\",\n                    \"x5t\": \"dummythumbprint\",\n                    \"n\": \"somebase64encodedmodulus\",\n                    \"e\": \"somebase64encodedexponent\",\n                    \"x5c\": [base64.b64encode(signing_cert_b).decode(), ]\n                },\n            ]\n        }\n    return 200, [], json.dumps(keys)\n\n\ndef build_adfs_meta(request):\n    with open(os.path.join(os.path.dirname(__file__), \"mock_files/FederationMetadata.xml\"), mode=\"r\") as f:\n        data = \"\".join(f.readlines())\n    data = data.replace(\"REPLACE_WITH_CERT_A\", base64.b64encode(signing_cert_a).decode())\n    data = data.replace(\"REPLACE_WITH_CERT_B\", base64.b64encode(signing_cert_b).decode())\n    return 200, [], data\n\n\ndef mock_adfs(\n    adfs_version,\n    empty_keys=False,\n    mfa_error=False,\n    guest=False,\n    version=None,\n    requires_obo=False,\n    missing_graph_group_perm=False,\n):\n    if adfs_version not in [\"2012\", \"2016\", \"azure\"]:\n        raise NotImplementedError(\"This version of ADFS is not implemented\")\n\n    def do_mock(test_func):\n        def wrapper(*original_args, **original_kwargs):\n            prefix_table = {\n                \"2012\": \"https://adfs.example.com\",\n                \"2016\": \"https://adfs.example.com\",\n                \"azure\": \"https://login.microsoftonline.com\",\n            }\n            prefix = prefix_table[adfs_version]\n            ms_graph_endpoint = \"https://graph.microsoft.com/\"\n            if version == \"v2.0\":\n                openid_cfg = re.compile(prefix + r\".*{}/\\.well-known/openid-configuration\".format(version))\n                token_endpoint = re.compile(prefix + r\".*/oauth2/{}/token\".format(version))\n            else:\n                openid_cfg = re.compile(prefix + r\".*\\.well-known/openid-configuration\")\n                token_endpoint = re.compile(prefix + r\".*/oauth2/token\")\n            openid_keys = re.compile(prefix + r\".*/discovery/keys\")\n            adfs_meta = re.compile(prefix + r\".*/FederationMetadata/2007-06/FederationMetadata\\.xml\")\n            ms_graph_groups = re.compile(ms_graph_endpoint + r\".*/transitiveMemberOf/microsoft.graph.group\")\n            with responses.RequestsMock(assert_all_requests_are_fired=False) as rsps:\n                # https://github.com/getsentry/responses\n                if adfs_version == \"2016\":\n                    rsps.add(\n                        rsps.GET, openid_cfg,\n                        json=load_json(\"mock_files/adfs-openid-configuration.json\")\n                    )\n                    rsps.add_callback(\n                        rsps.GET, openid_keys,\n                        callback=partial(build_openid_keys, empty_keys=empty_keys),\n                        content_type='application/json',\n                    )\n                elif adfs_version == \"azure\":\n                    if version == \"v2.0\":\n                        rsps.add(\n                            rsps.GET, openid_cfg,\n                            json=load_json(\"mock_files/azure-openid-configuration-v2.json\")\n                        )\n                    else:\n                        rsps.add(\n                            rsps.GET, openid_cfg,\n                            json=load_json(\"mock_files/azure-openid-configuration.json\")\n                        )\n                    rsps.add_callback(\n                        rsps.GET, openid_keys,\n                        callback=partial(build_openid_keys, empty_keys=empty_keys),\n                        content_type='application/json',\n                    )\n                else:\n                    rsps.add(\n                        rsps.GET, openid_cfg,\n                        status=404\n                    )\n                    rsps.add(\n                        rsps.GET, openid_keys,\n                        status=404\n                    )\n\n                rsps.add_callback(\n                    rsps.GET, adfs_meta,\n                    callback=build_adfs_meta,\n                    content_type='application/xml',\n                )\n                if adfs_version == \"azure\":\n                    if guest:\n                        rsps.add_callback(\n                            rsps.POST, token_endpoint,\n                            callback=build_access_token_azure_guest,\n                            content_type='application/json',\n                        )\n                    rsps.add_callback(\n                        rsps.POST, token_endpoint,\n                        callback=build_access_token_azure,\n                        content_type='application/json',\n                    )\n                    if requires_obo:\n                        if mfa_error:\n                            rsps.add_callback(\n                                rsps.GET, token_endpoint,\n                                callback=do_build_mfa_error,\n                                content_type='application/json',\n                            )\n                        else:\n                            rsps.add_callback(\n                                rsps.GET, token_endpoint,\n                                callback=do_build_obo_access_token,\n                                content_type='application/json'\n                            )\n                        if missing_graph_group_perm:\n                            rsps.add_callback(\n                                rsps.GET, ms_graph_groups,\n                                callback=do_build_graph_response_no_group_perm,\n                                content_type='application/json',\n                            )\n                        else:\n                            rsps.add_callback(\n                                rsps.GET, ms_graph_groups,\n                                callback=do_build_graph_response,\n                                content_type='application/json',\n                            )\n                else:\n                    if mfa_error:\n                        rsps.add_callback(\n                            rsps.POST, token_endpoint,\n                            callback=do_build_mfa_error,\n                            content_type='application/json',\n                        )\n                    else:\n                        rsps.add_callback(\n                            rsps.POST, token_endpoint,\n                            callback=build_access_token_adfs,\n                            content_type='application/json',\n                        )\n\n                test_func(*original_args, **original_kwargs)\n\n        return wrapper\n\n    return do_mock\n\n\nsigning_key_a, signing_cert_a = generate_key_and_cert()\nsigning_key_b, signing_cert_b = generate_key_and_cert()\n"
  },
  {
    "path": "tests/views.py",
    "content": "def test_failed_response(request, error_message, status):\n    pass\n"
  },
  {
    "path": "vagrant/01-setup-domain.ps1",
    "content": "# ########## SETTINGS #########\n$domainName = \"example.com\"\n$netbiosName = \"EXAMPLE\"\n$safeModePwd = \"Password123\"\n# #############################\nSet-LocalUser `\n    -name \"administrator\" `\n    -AccountNeverExpires `\n    -Password (Convertto-SecureString -AsPlainText \"Vagrant123\" -Force) `\n    -PasswordNeverExpires $true\n# Install and configure domain controller role\n# --------------------------------------------\nWrite-Host \"Installing domain features...\"\nInstall-WindowsFeature -Name AD-Domain-Services -IncludeManagementTools\nWrite-Host \"Promoting DC...\"\nImport-Module ADDSDeployment\nInstall-ADDSForest `\n  -CreateDnsDelegation:$false `\n  -DatabasePath \"C:\\Windows\\NTDS\" `\n  -DomainMode \"WinThreshold\" `\n  -DomainName $domainName `\n  -DomainNetbiosName $netbiosName `\n  -ForestMode \"WinThreshold\" `\n  -InstallDns:$true `\n  -LogPath \"C:\\Windows\\NTDS\" `\n  -SysvolPath \"C:\\Windows\\SYSVOL\" `\n  -Force:$true `\n  -SafeModeAdministratorPassword (Convertto-SecureString -AsPlainText $safeModePwd -Force) `\n  -NoRebootOnCompletion\n"
  },
  {
    "path": "vagrant/02-setup-vagrant-user.ps1",
    "content": "Write-Host \"Waiting for domain controller to become reachable.\"\n$isUp = $false\nwhile($isUp -eq $false) {\n    Try {\n        $domain = Get-ADDomain\n        $isUp = $true\n    } Catch [Microsoft.ActiveDirectory.Management.ADServerDownException] {\n        Write-Host \"Retrying in 30 seconds\"\n        $isUp = $false\n        Start-Sleep 30\n    }\n}\n\nAdd-ADGroupMember -Identity \"Domain Admins\" -Members vagrant\nAdd-ADGroupMember -Identity \"Enterprise Admins\" -Members vagrant\nAdd-ADGroupMember -Identity \"Schema Admins\" -Members vagrant\n"
  },
  {
    "path": "vagrant/03-setup-adfs.ps1",
    "content": "# ########## SETTINGS #########\n$adfsHost = \"adfs\"\n# #############################\n\nWrite-Host \"Waiting for domain controller to become reachable.\"\n$isUp = $false\nwhile($isUp -eq $false) {\n    Try {\n        $domain = Get-ADDomain\n        $isUp = $true\n    } Catch [Microsoft.ActiveDirectory.Management.ADServerDownException] {\n        Write-Host \"Retrying in 30 seconds\"\n        $isUp = $false\n        Start-Sleep 30\n    }\n}\n\n# Install the ADFS role\n# ---------------------\nWrite-Host \"Installing ADFS role...\"\nInstall-WindowsFeature -Name ADFS-Federation -IncludeManagementTools\n\n# Add ADFS DNS record\n# -------------------\nWrite-Host \"Adding DNS record...\"\n$ip = Get-NetIPAddress -InterfaceAlias \"Ethernet 2\" -AddressFamily ipv4\nAdd-DnsServerResourceRecordA -Name $adfsHost -IPv4Address $ip.IPAddress -ZoneName (Get-ADDomain).Forest\n\n\n# Generate ADFS certificate\n# -------------------------\nWrite-Host \"Generating self signed certificate for ADFS...\"\n\nImport-Module \\\\vboxsrv\\vagrant\\vagrant\\New-SelfSignedCertificateEx.ps1\n$cert = New-SelfSignedCertificateEx `\n-Subject (\"CN=\"+$adfsHost+\".\"+(Get-ADDomain).Forest) `\n-SubjectAlternativeName ($adfsHost+\".\"+(Get-ADDomain).Forest) `\n-AlgorithmName RSA `\n-KeyLength 2048 `\n-SignatureAlgorithm SHA256 `\n-StoreLocation LocalMachine\n\n# Configure ADFS\n# --------------\nWrite-Host \"Configure ADFS...\"\n#  Needed to be able to create a group Managed Service Account\n# set-service kdssvc -StartupType Automatic\nAdd-KdsRootKey -EffectiveTime (Get-Date).AddHours(-10)\n\nWrite-Host \"Creating Group Managed Service Account...\"\n$Name = 'FsGmsa'\n$DNS_Name = $adfsHost+\".\"+(Get-ADDomain).Forest\nNew-ADServiceAccount -Name $Name -DNSHostName $DNS_Name -PrincipalsAllowedToRetrieveManagedPassword \"$env:computername`$\"\n\nImport-Module ADFS\nInstall-AdfsFarm `\n-CertificateThumbprint $cert.Thumbprint `\n-FederationServiceDisplayName \"Example Corp\" `\n-FederationServiceName ($adfsHost+\".\"+(Get-ADDomain).Forest) `\n-GroupServiceAccountIdentifier ((Get-ADDomain).NetBIOSName + \"\\FsGmsa`$\") `\n-OverwriteConfiguration\n\n# https://social.technet.microsoft.com/Forums/office/en-US/a290c5c0-3112-409f-8cb0-ff23e083e5d1/ad-fs-windows-2012-r2-adfssrv-hangs-in-starting-mode?forum=winserverDS\nsc.exe triggerinfo kdssvc start/networkon\n"
  },
  {
    "path": "vagrant/04-example-adfs-config.ps1",
    "content": "# ########## SETTINGS #########\n$webIP = \"10.10.10.10\"\n$webName = \"web\"\n$appName = \"Django Application\"\n$clientId = \"487d8ff7-80a8-4f62-b926-c2852ab06e94\"\n$relyingPartyId = \"web.example.com\"\n# #############################\n\nWrite-Host \"Waiting for domain controller to become reachable.\"\n$isUp = $false\nwhile($isUp -eq $false) {\n    Try {\n        $domain = Get-ADDomain\n        $isUp = $true\n    } Catch {\n        Write-Host \"Retrying in 15 seconds\"\n        $isUp = $false\n        Start-Sleep 15\n    }\n}\n\n# Add webserver DNS record\n# ------------------------\nWrite-Host \"Adding DNS record...\"\nAdd-DnsServerResourceRecordA -Name $webName -IPv4Address $webIP -ZoneName (Get-ADDomain).Forest\n\n# Add example users and groups\n# ----------------------------\nWrite-Host \"Creating Django Admins group\"\n$staffGroup = New-ADGroup `\n-Name \"Django Admins\" `\n-SamAccountName django_admins `\n-GroupCategory Security `\n-GroupScope Global `\n-Passthru\n\nWrite-Host \"Creating user Alice...\"\nNew-ADUser `\n-Name \"Alice\" `\n-GivenName Alice `\n-SurName Wonder `\n-SamAccountName alice `\n-EmailAddress (\"alice@\"+(Get-ADDomain).Forest) `\n-UserPrincipalName (\"alice@\"+(Get-ADDomain).Forest) `\n-AccountPassword (convertto-securestring \"Password123\" -asplaintext -force) `\n-Enabled $true\n\nWrite-Host \"Creating user Bob...\"\n$bob = New-ADUser `\n-Name \"Bob\" `\n-GivenName Bob `\n-SurName Builder `\n-SamAccountName bob `\n-EmailAddress (\"bob@\"+(Get-ADDomain).Forest) `\n-UserPrincipalName (\"bob@\"+(Get-ADDomain).Forest) `\n-AccountPassword (convertto-securestring \"Password123\" -asplaintext -force) `\n-Enabled $true `\n-Passthru\n\nAdd-ADGroupMember -Identity django_admins -Members $bob\n\nWrite-Host \"Disabling Internet Explorer Enhanced Security Configuration\"\n$AdminKey = \"HKLM:\\SOFTWARE\\Microsoft\\Active Setup\\Installed Components\\{A509B1A7-37EF-4b3f-8CFC-4F3A74704073}\"\n$UserKey = \"HKLM:\\SOFTWARE\\Microsoft\\Active Setup\\Installed Components\\{A509B1A8-37EF-4b3f-8CFC-4F3A74704073}\"\nSet-ItemProperty -Path $AdminKey -Name \"IsInstalled\" -Value 0\nSet-ItemProperty -Path $UserKey -Name \"IsInstalled\" -Value 0\n\n# Add ADFS config\n# ---------------\n\nWrite-Host \"Waiting for Federation Server to become reachable.\"\n$isUp = $false\nwhile($isUp -eq $false) {\n    Try {\n        $domain = Get-AdfsProperties\n        $isUp = $true\n    } Catch {\n        Write-Host \"Retrying in 15 seconds\"\n        $isUp = $false\n        Start-Sleep 15\n    }\n}\n\nWrite-Host \"Adding application group $appName\"\nNew-AdfsApplicationGroup -Name $appName -ApplicationGroupIdentifier $appName\n\nWrite-Host \"Adding native application\"\nAdd-AdfsNativeClientApplication `\n-name \"$appName - Native application\" `\n-Identifier $clientId `\n-ApplicationGroupIdentifier $appName `\n-RedirectUri (\"http://$webName.\"+(Get-ADDomain).Forest+\":8000/oauth2/callback\")\n\nWrite-Host \"Adding web application\"\nAdd-AdfsWebApiApplication `\n-Name \"$appName - Web application\" `\n-Identifier $relyingPartyId `\n-AccessControlPolicyName \"Permit everyone\" `\n-ApplicationGroupIdentifier $appName `\n-IssuanceTransformRules (\n    '@RuleTemplate = \"LdapClaims\"\n     @RuleName = \"User attribute claims\"\n     c:[\n        Type == \"http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname\",\n        Issuer == \"AD AUTHORITY\"\n     ]\n     => issue(\n        store = \"Active Directory\",\n        types = (\n            \"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress\",\n            \"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname\",\n            \"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname\",\n            \"http://schemas.xmlsoap.org/claims/Group\",\n            \"http://schemas.microsoft.com/ws/2008/06/identity/claims/windowsaccountname\"\n        ),\n        query = \";mail,givenName,sn,tokenGroups,sAMAccountName;{0}\",\n        param = c.Value\n     );'\n)\n\nWrite-Host \"Adding native application\"\nGrant-AdfsApplicationPermission `\n-ClientRoleIdentifier $clientId `\n-ServerRoleIdentifier $relyingPartyId `\n-ScopeNames \"openid\"\n"
  },
  {
    "path": "vagrant/New-SelfSignedCertificateEx.ps1",
    "content": "﻿#####################################################################\n# New-SelfSignedCertificateEx.ps1\n# Version 1.2\n#\n# Creates self-signed certificate. This tool is a base replacement\n# for deprecated makecert.exe\n#\n# Vadims Podans (c) 2013 - 2016\n# http://en-us.sysadmins.lv/\n#####################################################################\n#requires -Version 2.0\n\nfunction New-SelfSignedCertificateEx {\n<#\n.Synopsis\n\tThis cmdlet generates a self-signed certificate.\n.Description\n\tThis cmdlet generates a self-signed certificate with the required data.\n.Parameter Subject\n\tSpecifies the certificate subject in a X500 distinguished name format.\n\tExample: CN=Test Cert, OU=Sandbox\n.Parameter NotBefore\n\tSpecifies the date and time when the certificate become valid. By default previous day\n\tdate is used.\n.Parameter NotAfter\n\tSpecifies the date and time when the certificate expires. By default, the certificate is\n\tvalid for 1 year.\n.Parameter SerialNumber\n\tSpecifies the desired serial number in a hex format.\n\tExample: 01a4ff2\n.Parameter ProviderName\n\tSpecifies the Cryptography Service Provider (CSP) name. You can use either legacy CSP\n\tand Key Storage Providers (KSP). By default \"Microsoft Enhanced Cryptographic Provider v1.0\"\n\tCSP is used.\n.Parameter AlgorithmName\n\tSpecifies the public key algorithm. By default RSA algorithm is used. RSA is the only\n\talgorithm supported by legacy CSPs. With key storage providers (KSP) you can use CNG\n\talgorithms, like ECDH. For CNG algorithms you must use full name:\n\tECDH_P256\n\tECDH_P384\n\tECDH_P521\n\t\n\tIn addition, KeyLength parameter must be specified explicitly when non-RSA algorithm is used.\n.Parameter KeyLength\n\tSpecifies the key length to generate. By default 2048-bit key is generated.\n.Parameter KeySpec\n\tSpecifies the public key operations type. The possible values are: Exchange and Signature.\n\tDefault value is Exchange.\n.Parameter EnhancedKeyUsage\n\tSpecifies the intended uses of the public key contained in a certificate. You can\n\tspecify either, EKU friendly name (for example 'Server Authentication') or\n\tobject identifier (OID) value (for example '1.3.6.1.5.5.7.3.1').\n.Parameter KeyUsages\n\tSpecifies restrictions on the operations that can be performed by the public key contained in the certificate.\n\tPossible values (and their respective integer values to make bitwise operations) are:\n\tEncipherOnly\n\tCrlSign\n\tKeyCertSign\n\tKeyAgreement\n\tDataEncipherment\n\tKeyEncipherment\n\tNonRepudiation\n\tDigitalSignature\n\tDecipherOnly\n\t\n\tyou can combine key usages values by using bitwise OR operation. when combining multiple\n\tflags, they must be enclosed in quotes and separated by a comma character. For example,\n\tto combine KeyEncipherment and DigitalSignature flags you should type:\n\t\"KeyEncipherment, DigitalSignature\".\n\t\n\tIf the certificate is CA certificate (see IsCA parameter), key usages extension is generated\n\tautomatically with the following key usages: Certificate Signing, Off-line CRL Signing, CRL Signing.\n.Parameter SubjectAlternativeName\n\tSpecifies alternative names for the subject. Unlike Subject field, this extension\n\tallows to specify more than one name. Also, multiple types of alternative names\n\tare supported. The cmdlet supports the following SAN types:\n\tRFC822 Name\n\tIP address (both, IPv4 and IPv6)\n\tGuid\n\tDirectory name\n\tDNS name\n.Parameter IsCA\n\tSpecifies whether the certificate is CA (IsCA = $true) or end entity (IsCA = $false)\n\tcertificate. If this parameter is set to $false, PathLength parameter is ignored.\n\tBasic Constraints extension is marked as critical.\n.PathLength\n\tSpecifies the number of additional CA certificates in the chain under this certificate. If\n\tPathLength parameter is set to zero, then no additional (subordinate) CA certificates are\n\tpermitted under this CA.\n.CustomExtension\n\tSpecifies the custom extension to include to a self-signed certificate. This parameter\n\tmust not be used to specify the extension that is supported via other parameters. In order\n\tto use this parameter, the extension must be formed in a collection of initialized\n\tSystem.Security.Cryptography.X509Certificates.X509Extension objects.\n.Parameter SignatureAlgorithm\n\tSpecifies signature algorithm used to sign the certificate. By default 'SHA1'\n\talgorithm is used.\n.Parameter FriendlyName\n\tSpecifies friendly name for the certificate.\n.Parameter StoreLocation\n\tSpecifies the store location to store self-signed certificate. Possible values are:\n\t'CurrentUser' and 'LocalMachine'. 'CurrentUser' store is intended for user certificates\n\tand computer (as well as CA) certificates must be stored in 'LocalMachine' store.\n.Parameter StoreName\n\tSpecifies the container name in the certificate store. Possible container names are:\n\tAddressBook\n\tAuthRoot\n\tCertificateAuthority\n\tDisallowed\n\tMy\n\tRoot\n\tTrustedPeople\n\tTrustedPublisher\n.Parameter Path\n\tSpecifies the path to a PFX file to export a self-signed certificate.\n.Parameter Password\n\tSpecifies the password for PFX file.\n.Parameter AllowSMIME\n\tEnables Secure/Multipurpose Internet Mail Extensions for the certificate.\n.Parameter Exportable\n\tMarks private key as exportable. Smart card providers usually do not allow\n\texportable keys.\n.Example\n\tNew-SelfsignedCertificateEx -Subject \"CN=Test Code Signing\" -EKU \"Code Signing\" -KeySpec \"Signature\" `\n\t-KeyUsage \"DigitalSignature\" -FriendlyName \"Test code signing\" -NotAfter $([datetime]::now.AddYears(5))\n\t\n\tCreates a self-signed certificate intended for code signing and which is valid for 5 years. Certificate\n\tis saved in the Personal store of the current user account.\n.Example\n\tNew-SelfsignedCertificateEx -Subject \"CN=www.domain.com\" -EKU \"Server Authentication\", \"Client authentication\" `\n\t-KeyUsage \"KeyEcipherment, DigitalSignature\" -SAN \"sub.domain.com\",\"www.domain.com\",\"192.168.1.1\" `\n\t-AllowSMIME -Path C:\\test\\ssl.pfx -Password (ConvertTo-SecureString \"P@ssw0rd\" -AsPlainText -Force) -Exportable `\n\t-StoreLocation \"LocalMachine\"\n\t\n\tCreates a self-signed SSL certificate with multiple subject names and saves it to a file. Additionally, the\n\tcertificate is saved in the Personal store of the Local Machine store. Private key is marked as exportable,\n\tso you can export the certificate with a associated private key to a file at any time. The certificate\n\tincludes SMIME capabilities.\n.Example\n\tNew-SelfsignedCertificateEx -Subject \"CN=www.domain.com\" -EKU \"Server Authentication\", \"Client authentication\" `\n\t-KeyUsage \"KeyEcipherment, DigitalSignature\" -SAN \"sub.domain.com\",\"www.domain.com\",\"192.168.1.1\" `\n\t-StoreLocation \"LocalMachine\" -ProviderName \"Microsoft Software Key Storae Provider\" -AlgorithmName ecdh_256 `\n\t-KeyLength 256 -SignatureAlgorithm sha256\n\t\n\tCreates a self-signed SSL certificate with multiple subject names and saves it to a file. Additionally, the\n\tcertificate is saved in the Personal store of the Local Machine store. Private key is marked as exportable,\n\tso you can export the certificate with a associated private key to a file at any time. Certificate uses\n\tEllyptic Curve Cryptography (ECC) key algorithm ECDH with 256-bit key. The certificate is signed by using\n\tSHA256 algorithm.\n.Example\n\tNew-SelfsignedCertificateEx -Subject \"CN=Test Root CA, OU=Sandbox\" -IsCA $true -ProviderName `\n\t\"Microsoft Software Key Storage Provider\" -Exportable\n\t\n\tCreates self-signed root CA certificate.\n#>\n[OutputType('[System.Security.Cryptography.X509Certificates.X509Certificate2]')]\n[CmdletBinding(DefaultParameterSetName = '__store')]\n\tparam (\n\t\t[Parameter(Mandatory = $true, Position = 0)]\n\t\t[string]$Subject,\n\t\t[Parameter(Position = 1)]\n\t\t[datetime]$NotBefore = [DateTime]::Now.AddDays(-1),\n\t\t[Parameter(Position = 2)]\n\t\t[datetime]$NotAfter = $NotBefore.AddDays(365),\n\t\t[string]$SerialNumber,\n\t\t[Alias('CSP')]\n\t\t[string]$ProviderName = \"Microsoft Enhanced Cryptographic Provider v1.0\",\n\t\t[string]$AlgorithmName = \"RSA\",\n\t\t[int]$KeyLength = 2048,\n\t\t[validateSet(\"Exchange\",\"Signature\")]\n\t\t[string]$KeySpec = \"Exchange\",\n\t\t[Alias('EKU')]\n\t\t[Security.Cryptography.Oid[]]$EnhancedKeyUsage,\n\t\t[Alias('KU')]\n\t\t[Security.Cryptography.X509Certificates.X509KeyUsageFlags]$KeyUsage,\n\t\t[Alias('SAN')]\n\t\t[String[]]$SubjectAlternativeName,\n\t\t[bool]$IsCA,\n\t\t[int]$PathLength = -1,\n\t\t[Security.Cryptography.X509Certificates.X509ExtensionCollection]$CustomExtension,\n\t\t[ValidateSet('MD5','SHA1','SHA256','SHA384','SHA512')]\n\t\t[string]$SignatureAlgorithm = \"SHA1\",\n\t\t[string]$FriendlyName,\n\t\t[Parameter(ParameterSetName = '__store')]\n\t\t[Security.Cryptography.X509Certificates.StoreLocation]$StoreLocation = \"CurrentUser\",\n\t\t[Parameter(Mandatory = $true, ParameterSetName = '__file')]\n\t\t[Alias('OutFile','OutPath','Out')]\n\t\t[IO.FileInfo]$Path,\n\t\t[Parameter(Mandatory = $true, ParameterSetName = '__file')]\n\t\t[Security.SecureString]$Password,\n\t\t[switch]$AllowSMIME,\n\t\t[switch]$Exportable\n\t)\n\t$ErrorActionPreference = \"Stop\"\n\tif ([Environment]::OSVersion.Version.Major -lt 6) {\n\t\t$NotSupported = New-Object NotSupportedException -ArgumentList \"Windows XP and Windows Server 2003 are not supported!\"\n\t\tthrow $NotSupported\n\t}\n\t$ExtensionsToAdd = @()\n\n#region constants\n\t# contexts\n\tNew-Variable -Name UserContext -Value 0x1 -Option Constant\n\tNew-Variable -Name MachineContext -Value 0x2 -Option Constant\n\t# encoding\n\tNew-Variable -Name Base64Header -Value 0x0 -Option Constant\n\tNew-Variable -Name Base64 -Value 0x1 -Option Constant\n\tNew-Variable -Name Binary -Value 0x3 -Option Constant\n\tNew-Variable -Name Base64RequestHeader -Value 0x4 -Option Constant\n\t# SANs\n\tNew-Variable -Name OtherName -Value 0x1 -Option Constant\n\tNew-Variable -Name RFC822Name -Value 0x2 -Option Constant\n\tNew-Variable -Name DNSName -Value 0x3 -Option Constant\n\tNew-Variable -Name DirectoryName -Value 0x5 -Option Constant\n\tNew-Variable -Name URL -Value 0x7 -Option Constant\n\tNew-Variable -Name IPAddress -Value 0x8 -Option Constant\n\tNew-Variable -Name RegisteredID -Value 0x9 -Option Constant\n\tNew-Variable -Name Guid -Value 0xa -Option Constant\n\tNew-Variable -Name UPN -Value 0xb -Option Constant\n\t# installation options\n\tNew-Variable -Name AllowNone -Value 0x0 -Option Constant\n\tNew-Variable -Name AllowNoOutstandingRequest -Value 0x1 -Option Constant\n\tNew-Variable -Name AllowUntrustedCertificate -Value 0x2 -Option Constant\n\tNew-Variable -Name AllowUntrustedRoot -Value 0x4 -Option Constant\n\t# PFX export options\n\tNew-Variable -Name PFXExportEEOnly -Value 0x0 -Option Constant\n\tNew-Variable -Name PFXExportChainNoRoot -Value 0x1 -Option Constant\n\tNew-Variable -Name PFXExportChainWithRoot -Value 0x2 -Option Constant\n#endregion\n\t\n#region Subject processing\n\t# http://msdn.microsoft.com/en-us/library/aa377051(VS.85).aspx\n\t$SubjectDN = New-Object -ComObject X509Enrollment.CX500DistinguishedName\n\t$SubjectDN.Encode($Subject, 0x0)\n#endregion\n\n#region Extensions\n\n#region Enhanced Key Usages processing\n\tif ($EnhancedKeyUsage) {\n\t\t$OIDs = New-Object -ComObject X509Enrollment.CObjectIDs\n\t\t$EnhancedKeyUsage | ForEach-Object {\n\t\t\t$OID = New-Object -ComObject X509Enrollment.CObjectID\n\t\t\t$OID.InitializeFromValue($_.Value)\n\t\t\t# http://msdn.microsoft.com/en-us/library/aa376785(VS.85).aspx\n\t\t\t$OIDs.Add($OID)\n\t\t}\n\t\t# http://msdn.microsoft.com/en-us/library/aa378132(VS.85).aspx\n\t\t$EKU = New-Object -ComObject X509Enrollment.CX509ExtensionEnhancedKeyUsage\n\t\t$EKU.InitializeEncode($OIDs)\n\t\t$ExtensionsToAdd += \"EKU\"\n\t}\n#endregion\n\n#region Key Usages processing\n\tif ($KeyUsage -ne $null) {\n\t\t$KU = New-Object -ComObject X509Enrollment.CX509ExtensionKeyUsage\n\t\t$KU.InitializeEncode([int]$KeyUsage)\n\t\t$KU.Critical = $true\n\t\t$ExtensionsToAdd += \"KU\"\n\t}\n#endregion\n\n#region Basic Constraints processing\n\tif ($PSBoundParameters.Keys.Contains(\"IsCA\")) {\n\t\t# http://msdn.microsoft.com/en-us/library/aa378108(v=vs.85).aspx\n\t\t$BasicConstraints = New-Object -ComObject X509Enrollment.CX509ExtensionBasicConstraints\n\t\tif (!$IsCA) {$PathLength = -1}\n\t\t$BasicConstraints.InitializeEncode($IsCA,$PathLength)\n\t\t$BasicConstraints.Critical = $IsCA\n\t\t$ExtensionsToAdd += \"BasicConstraints\"\n\t}\n#endregion\n\n#region SAN processing\n\tif ($SubjectAlternativeName) {\n\t\t$SAN = New-Object -ComObject X509Enrollment.CX509ExtensionAlternativeNames\n\t\t$Names = New-Object -ComObject X509Enrollment.CAlternativeNames\n\t\tforeach ($altname in $SubjectAlternativeName) {\n\t\t\t$Name = New-Object -ComObject X509Enrollment.CAlternativeName\n\t\t\tif ($altname.Contains(\"@\")) {\n\t\t\t\t$Name.InitializeFromString($RFC822Name,$altname)\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\t$Bytes = [Net.IPAddress]::Parse($altname).GetAddressBytes()\n\t\t\t\t\t$Name.InitializeFromRawData($IPAddress,$Base64,[Convert]::ToBase64String($Bytes))\n\t\t\t\t} catch {\n\t\t\t\t\ttry {\n\t\t\t\t\t\t$Bytes = [Guid]::Parse($altname).ToByteArray()\n\t\t\t\t\t\t$Name.InitializeFromRawData($Guid,$Base64,[Convert]::ToBase64String($Bytes))\n\t\t\t\t\t} catch {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t$Bytes = ([Security.Cryptography.X509Certificates.X500DistinguishedName]$altname).RawData\n\t\t\t\t\t\t\t$Name.InitializeFromRawData($DirectoryName,$Base64,[Convert]::ToBase64String($Bytes))\n\t\t\t\t\t\t} catch {$Name.InitializeFromString($DNSName,$altname)}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t$Names.Add($Name)\n\t\t}\n\t\t$SAN.InitializeEncode($Names)\n\t\t$ExtensionsToAdd += \"SAN\"\n\t}\n#endregion\n\n#region Custom Extensions\n\tif ($CustomExtension) {\n\t\t$count = 0\n\t\tforeach ($ext in $CustomExtension) {\n\t\t\t# http://msdn.microsoft.com/en-us/library/aa378077(v=vs.85).aspx\n\t\t\t$Extension = New-Object -ComObject X509Enrollment.CX509Extension\n\t\t\t$EOID = New-Object -ComObject X509Enrollment.CObjectId\n\t\t\t$EOID.InitializeFromValue($ext.Oid.Value)\n\t\t\t$EValue = [Convert]::ToBase64String($ext.RawData)\n\t\t\t$Extension.Initialize($EOID,$Base64,$EValue)\n\t\t\t$Extension.Critical = $ext.Critical\n\t\t\tNew-Variable -Name (\"ext\" + $count) -Value $Extension\n\t\t\t$ExtensionsToAdd += (\"ext\" + $count)\n\t\t\t$count++\n\t\t}\n\t}\n#endregion\n\n#endregion\n\n#region Private Key\n\t# http://msdn.microsoft.com/en-us/library/aa378921(VS.85).aspx\n\t$PrivateKey = New-Object -ComObject X509Enrollment.CX509PrivateKey\n\t$PrivateKey.ProviderName = $ProviderName\n\t$AlgID = New-Object -ComObject X509Enrollment.CObjectId\n\t$AlgID.InitializeFromValue(([Security.Cryptography.Oid]$AlgorithmName).Value)\n\t$PrivateKey.Algorithm = $AlgID\n\t# http://msdn.microsoft.com/en-us/library/aa379409(VS.85).aspx\n\t$PrivateKey.KeySpec = switch ($KeySpec) {\"Exchange\" {1}; \"Signature\" {2}}\n\t$PrivateKey.Length = $KeyLength\n\t# key will be stored in current user certificate store\n\tswitch ($PSCmdlet.ParameterSetName) {\n\t\t'__store' {\n\t\t\t$PrivateKey.MachineContext = if ($StoreLocation -eq \"LocalMachine\") {$true} else {$false}\n\t\t}\n\t\t'__file' {\n\t\t\t$PrivateKey.MachineContext = $false\n\t\t}\n\t}\n\t$PrivateKey.ExportPolicy = if ($Exportable) {1} else {0}\n\t$PrivateKey.Create()\n#endregion\n\n\t# http://msdn.microsoft.com/en-us/library/aa377124(VS.85).aspx\n\t$Cert = New-Object -ComObject X509Enrollment.CX509CertificateRequestCertificate\n\tif ($PrivateKey.MachineContext) {\n\t\t$Cert.InitializeFromPrivateKey($MachineContext,$PrivateKey,\"\")\n\t} else {\n\t\t$Cert.InitializeFromPrivateKey($UserContext,$PrivateKey,\"\")\n\t}\n\t$Cert.Subject = $SubjectDN\n\t$Cert.Issuer = $Cert.Subject\n\t$Cert.NotBefore = $NotBefore\n\t$Cert.NotAfter = $NotAfter\n\tforeach ($item in $ExtensionsToAdd) {$Cert.X509Extensions.Add((Get-Variable -Name $item -ValueOnly))}\n\tif (![string]::IsNullOrEmpty($SerialNumber)) {\n\t\tif ($SerialNumber -match \"[^0-9a-fA-F]\") {throw \"Invalid serial number specified.\"}\n\t\tif ($SerialNumber.Length % 2) {$SerialNumber = \"0\" + $SerialNumber}\n\t\t$Bytes = $SerialNumber -split \"(.{2})\" | Where-Object {$_} | ForEach-Object{[Convert]::ToByte($_,16)}\n\t\t$ByteString = [Convert]::ToBase64String($Bytes)\n\t\t$Cert.SerialNumber.InvokeSet($ByteString,1)\n\t}\n\tif ($AllowSMIME) {$Cert.SmimeCapabilities = $true}\n\t$SigOID = New-Object -ComObject X509Enrollment.CObjectId\n\t$SigOID.InitializeFromValue(([Security.Cryptography.Oid]$SignatureAlgorithm).Value)\n\t$Cert.SignatureInformation.HashAlgorithm = $SigOID\n\t# completing certificate request template building\n\t$Cert.Encode()\n\t\n\t# interface: http://msdn.microsoft.com/en-us/library/aa377809(VS.85).aspx\n\t$Request = New-Object -ComObject X509Enrollment.CX509enrollment\n\t$Request.InitializeFromRequest($Cert)\n\t$Request.CertificateFriendlyName = $FriendlyName\n\t$endCert = $Request.CreateRequest($Base64)\n\t$Request.InstallResponse($AllowUntrustedCertificate,$endCert,$Base64,\"\")\n\tswitch ($PSCmdlet.ParameterSetName) {\n\t\t'__file' {\n\t\t\t$PFXString = $Request.CreatePFX(\n\t\t\t\t[Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)),\n\t\t\t\t$PFXExportEEOnly,\n\t\t\t\t$Base64\n\t\t\t)\n\t\t\tSet-Content -Path $Path -Value ([Convert]::FromBase64String($PFXString)) -Encoding Byte\n\t\t}\n\t}\n\t[Byte[]]$CertBytes = [Convert]::FromBase64String($endCert)\n\tNew-Object Security.Cryptography.X509Certificates.X509Certificate2 @(,$CertBytes)\n}\n# SIG # Begin signature block\n# MIIcgAYJKoZIhvcNAQcCoIIccTCCHG0CAQExDzANBglghkgBZQMEAgEFADB5Bgor\n# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG\n# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCAmMWwZnveROeiP\n# Okrv0onByV5n94ickqih9JS7E9E/HKCCF4owggUTMIID+6ADAgECAhABn3Jtjtqs\n# sQ4D4Fge9iqaMA0GCSqGSIb3DQEBCwUAMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK\n# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNV\n# BAMTKERpZ2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0EwHhcN\n# MTUxMjE4MDAwMDAwWhcNMTYxMjIyMTIwMDAwWjBQMQswCQYDVQQGEwJMVjENMAsG\n# A1UEBxMEUmlnYTEYMBYGA1UEChMPU3lzYWRtaW5zIExWIElLMRgwFgYDVQQDEw9T\n# eXNhZG1pbnMgTFYgSUswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDo\n# UVviPttwGnu8WAEbA2zvYj3+eJLxrpWtcokvyZALEd8hf7m19yCIruChB3b3Cszt\n# OMjgV+a4MoPNEjMdnbFVstO+nCxbh/J1W6ArjqEIaYX6H4ZJNwfFD7S22JNeKHW7\n# /Z//jdsPSTRvSugWuGFzix0DxdfTDATuq10J6ivi1Tk9DZJpMfEKMnz6ze24UfJU\n# FX1XxcbeDgTdK2nd1RGAMKnxYQhn4Gzv+TrbLJWs976aLR/tJ8td4UqtlK/BE0PB\n# S3G7Xb4dNjm4e1nVFz7FNf6DqQQ34ZDk+XgVVQINxNbB2WmkOMEJFX2G3+F539d4\n# V6EfRAF0+v1U9Ofm1m6TAgMBAAGjggHFMIIBwTAfBgNVHSMEGDAWgBRaxLl7Kgqj\n# pepxA8Bg+S32ZXUOWDAdBgNVHQ4EFgQU/3BF2aoFQv5rK3jP1wW8I1t/uoMwDgYD\n# VR0PAQH/BAQDAgeAMBMGA1UdJQQMMAoGCCsGAQUFBwMDMHcGA1UdHwRwMG4wNaAz\n# oDGGL2h0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9zaGEyLWFzc3VyZWQtY3MtZzEu\n# Y3JsMDWgM6Axhi9odHRwOi8vY3JsNC5kaWdpY2VydC5jb20vc2hhMi1hc3N1cmVk\n# LWNzLWcxLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG/WwDATAqMCgGCCsGAQUFBwIB\n# FhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BTMAgGBmeBDAEEATCBhAYIKwYB\n# BQUHAQEEeDB2MCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20w\n# TgYIKwYBBQUHMAKGQmh0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy\n# dFNIQTJBc3N1cmVkSURDb2RlU2lnbmluZ0NBLmNydDAMBgNVHRMBAf8EAjAAMA0G\n# CSqGSIb3DQEBCwUAA4IBAQBRqP0FyPMXdmGf4C+ubIeHSeFRcunS6kFdyokn8tKK\n# HFqAvea8QCmdFqMPTTet0WK/2O8RiiscWADDbmyHTC9KMNOufeabWtNCbwwaBeg0\n# xir8eo2deX1JVWfji4ZdwHTlqJR5hnCM+i1iD60zWOx7+8WAF6toCs5O1+CDqt5P\n# hvv0Re0Y17DeFWe9NNanOdy/t+cpTuJZmX3TR5dhRZJTMZZnTdzi4qTWIAaRX4m/\n# fUehKfBwd5pzoZwlZ0RC/5RnRMpdUtankwKPdrSjLPSObJwDwxoZvZwpAKhwm1wa\n# 49Rv1bHg/r090IrClnAUA6Os1PJAYRWMU8ayMMQuM496MIIFMDCCBBigAwIBAgIQ\n# BAkYG1/Vu2Z1U0O1b5VQCDANBgkqhkiG9w0BAQsFADBlMQswCQYDVQQGEwJVUzEV\n# MBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29t\n# MSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgQ0EwHhcNMTMxMDIy\n# MTIwMDAwWhcNMjgxMDIyMTIwMDAwWjByMQswCQYDVQQGEwJVUzEVMBMGA1UEChMM\n# RGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMTEwLwYDVQQD\n# EyhEaWdpQ2VydCBTSEEyIEFzc3VyZWQgSUQgQ29kZSBTaWduaW5nIENBMIIBIjAN\n# BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+NOzHH8OEa9ndwfTCzFJGc/Q+0WZ\n# sTrbRPV/5aid2zLXcep2nQUut4/6kkPApfmJ1DcZ17aq8JyGpdglrA55KDp+6dFn\n# 08b7KSfH03sjlOSRI5aQd4L5oYQjZhJUM1B0sSgmuyRpwsJS8hRniolF1C2ho+mI\n# LCCVrhxKhwjfDPXiTWAYvqrEsq5wMWYzcT6scKKrzn/pfMuSoeU7MRzP6vIK5Fe7\n# SrXpdOYr/mzLfnQ5Ng2Q7+S1TqSp6moKq4TzrGdOtcT3jNEgJSPrCGQ+UpbB8g8S\n# 9MWOD8Gi6CxR93O8vYWxYoNzQYIH5DiLanMg0A9kczyen6Yzqf0Z3yWT0QIDAQAB\n# o4IBzTCCAckwEgYDVR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAYYwEwYD\n# VR0lBAwwCgYIKwYBBQUHAwMweQYIKwYBBQUHAQEEbTBrMCQGCCsGAQUFBzABhhho\n# dHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYBBQUHMAKGN2h0dHA6Ly9jYWNl\n# cnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcnQwgYEG\n# A1UdHwR6MHgwOqA4oDaGNGh0dHA6Ly9jcmw0LmRpZ2ljZXJ0LmNvbS9EaWdpQ2Vy\n# dEFzc3VyZWRJRFJvb3RDQS5jcmwwOqA4oDaGNGh0dHA6Ly9jcmwzLmRpZ2ljZXJ0\n# LmNvbS9EaWdpQ2VydEFzc3VyZWRJRFJvb3RDQS5jcmwwTwYDVR0gBEgwRjA4Bgpg\n# hkgBhv1sAAIEMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNv\n# bS9DUFMwCgYIYIZIAYb9bAMwHQYDVR0OBBYEFFrEuXsqCqOl6nEDwGD5LfZldQ5Y\n# MB8GA1UdIwQYMBaAFEXroq/0ksuCMS1Ri6enIZ3zbcgPMA0GCSqGSIb3DQEBCwUA\n# A4IBAQA+7A1aJLPzItEVyCx8JSl2qB1dHC06GsTvMGHXfgtg/cM9D8Svi/3vKt8g\n# VTew4fbRknUPUbRupY5a4l4kgU4QpO4/cY5jDhNLrddfRHnzNhQGivecRk5c/5Cx\n# GwcOkRX7uq+1UcKNJK4kxscnKqEpKBo6cSgCPC6Ro8AlEeKcFEehemhor5unXCBc\n# 2XGxDI+7qPjFEmifz0DLQESlE/DmZAwlCEIysjaKJAL+L3J+HNdJRZboWR3p+nRk\n# a7LrZkPas7CM1ekN3fYBIM6ZMWM9CBoYs4GbT8aTEAb8B4H6i9r5gkn3Ym6hU/oS\n# lBiFLpKR6mhsRDKyZqHnGKSaZFHvMIIGajCCBVKgAwIBAgIQAwGaAjr/WLFr1tXq\n# 5hfwZjANBgkqhkiG9w0BAQUFADBiMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGln\n# aUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhE\n# aWdpQ2VydCBBc3N1cmVkIElEIENBLTEwHhcNMTQxMDIyMDAwMDAwWhcNMjQxMDIy\n# MDAwMDAwWjBHMQswCQYDVQQGEwJVUzERMA8GA1UEChMIRGlnaUNlcnQxJTAjBgNV\n# BAMTHERpZ2lDZXJ0IFRpbWVzdGFtcCBSZXNwb25kZXIwggEiMA0GCSqGSIb3DQEB\n# AQUAA4IBDwAwggEKAoIBAQCjZF38fLPggjXg4PbGKuZJdTvMbuBTqZ8fZFnmfGt/\n# a4ydVfiS457VWmNbAklQ2YPOb2bu3cuF6V+l+dSHdIhEOxnJ5fWRn8YUOawk6qhL\n# LJGJzF4o9GS2ULf1ErNzlgpno75hn67z/RJ4dQ6mWxT9RSOOhkRVfRiGBYxVh3lI\n# RvfKDo2n3k5f4qi2LVkCYYhhchhoubh87ubnNC8xd4EwH7s2AY3vJ+P3mvBMMWSN\n# 4+v6GYeofs/sjAw2W3rBerh4x8kGLkYQyI3oBGDbvHN0+k7Y/qpA8bLOcEaD6dpA\n# oVk62RUJV5lWMJPzyWHM0AjMa+xiQpGsAsDvpPCJEY93AgMBAAGjggM1MIIDMTAO\n# BgNVHQ8BAf8EBAMCB4AwDAYDVR0TAQH/BAIwADAWBgNVHSUBAf8EDDAKBggrBgEF\n# BQcDCDCCAb8GA1UdIASCAbYwggGyMIIBoQYJYIZIAYb9bAcBMIIBkjAoBggrBgEF\n# BQcCARYcaHR0cHM6Ly93d3cuZGlnaWNlcnQuY29tL0NQUzCCAWQGCCsGAQUFBwIC\n# MIIBVh6CAVIAQQBuAHkAIAB1AHMAZQAgAG8AZgAgAHQAaABpAHMAIABDAGUAcgB0\n# AGkAZgBpAGMAYQB0AGUAIABjAG8AbgBzAHQAaQB0AHUAdABlAHMAIABhAGMAYwBl\n# AHAAdABhAG4AYwBlACAAbwBmACAAdABoAGUAIABEAGkAZwBpAEMAZQByAHQAIABD\n# AFAALwBDAFAAUwAgAGEAbgBkACAAdABoAGUAIABSAGUAbAB5AGkAbgBnACAAUABh\n# AHIAdAB5ACAAQQBnAHIAZQBlAG0AZQBuAHQAIAB3AGgAaQBjAGgAIABsAGkAbQBp\n# AHQAIABsAGkAYQBiAGkAbABpAHQAeQAgAGEAbgBkACAAYQByAGUAIABpAG4AYwBv\n# AHIAcABvAHIAYQB0AGUAZAAgAGgAZQByAGUAaQBuACAAYgB5ACAAcgBlAGYAZQBy\n# AGUAbgBjAGUALjALBglghkgBhv1sAxUwHwYDVR0jBBgwFoAUFQASKxOYspkH7R7f\n# or5XDStnAs0wHQYDVR0OBBYEFGFaTSS2STKdSip5GoNL9B6Jwcp9MH0GA1UdHwR2\n# MHQwOKA2oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3Vy\n# ZWRJRENBLTEuY3JsMDigNqA0hjJodHRwOi8vY3JsNC5kaWdpY2VydC5jb20vRGln\n# aUNlcnRBc3N1cmVkSURDQS0xLmNybDB3BggrBgEFBQcBAQRrMGkwJAYIKwYBBQUH\n# MAGGGGh0dHA6Ly9vY3NwLmRpZ2ljZXJ0LmNvbTBBBggrBgEFBQcwAoY1aHR0cDov\n# L2NhY2VydHMuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEQ0EtMS5jcnQw\n# DQYJKoZIhvcNAQEFBQADggEBAJ0lfhszTbImgVybhs4jIA+Ah+WI//+x1GosMe06\n# FxlxF82pG7xaFjkAneNshORaQPveBgGMN/qbsZ0kfv4gpFetW7easGAm6mlXIV00\n# Lx9xsIOUGQVrNZAQoHuXx/Y/5+IRQaa9YtnwJz04HShvOlIJ8OxwYtNiS7Dgc6aS\n# wNOOMdgv420XEwbu5AO2FKvzj0OncZ0h3RTKFV2SQdr5D4HRmXQNJsQOfxu19aDx\n# xncGKBXp2JPlVRbwuwqrHNtcSCdmyKOLChzlldquxC5ZoGHd2vNtomHpigtt7BIY\n# vfdVVEADkitrwlHCCkivsNRu4PQUCjob4489yq9qjXvc2EQwggbNMIIFtaADAgEC\n# AhAG/fkDlgOt6gAK6z8nu7obMA0GCSqGSIb3DQEBBQUAMGUxCzAJBgNVBAYTAlVT\n# MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n# b20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFzc3VyZWQgSUQgUm9vdCBDQTAeFw0wNjEx\n# MTAwMDAwMDBaFw0yMTExMTAwMDAwMDBaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK\n# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV\n# BAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EtMTCCASIwDQYJKoZIhvcNAQEBBQAD\n# ggEPADCCAQoCggEBAOiCLZn5ysJClaWAc0Bw0p5WVFypxNJBBo/JM/xNRZFcgZ/t\n# LJz4FlnfnrUkFcKYubR3SdyJxArar8tea+2tsHEx6886QAxGTZPsi3o2CAOrDDT+\n# GEmC/sfHMUiAfB6iD5IOUMnGh+s2P9gww/+m9/uizW9zI/6sVgWQ8DIhFonGcIj5\n# BZd9o8dD3QLoOz3tsUGj7T++25VIxO4es/K8DCuZ0MZdEkKB4YNugnM/JksUkK5Z\n# ZgrEjb7SzgaurYRvSISbT0C58Uzyr5j79s5AXVz2qPEvr+yJIvJrGGWxwXOt1/HY\n# zx4KdFxCuGh+t9V3CidWfA9ipD8yFGCV/QcEogkCAwEAAaOCA3owggN2MA4GA1Ud\n# DwEB/wQEAwIBhjA7BgNVHSUENDAyBggrBgEFBQcDAQYIKwYBBQUHAwIGCCsGAQUF\n# BwMDBggrBgEFBQcDBAYIKwYBBQUHAwgwggHSBgNVHSAEggHJMIIBxTCCAbQGCmCG\n# SAGG/WwAAQQwggGkMDoGCCsGAQUFBwIBFi5odHRwOi8vd3d3LmRpZ2ljZXJ0LmNv\n# bS9zc2wtY3BzLXJlcG9zaXRvcnkuaHRtMIIBZAYIKwYBBQUHAgIwggFWHoIBUgBB\n# AG4AeQAgAHUAcwBlACAAbwBmACAAdABoAGkAcwAgAEMAZQByAHQAaQBmAGkAYwBh\n# AHQAZQAgAGMAbwBuAHMAdABpAHQAdQB0AGUAcwAgAGEAYwBjAGUAcAB0AGEAbgBj\n# AGUAIABvAGYAIAB0AGgAZQAgAEQAaQBnAGkAQwBlAHIAdAAgAEMAUAAvAEMAUABT\n# ACAAYQBuAGQAIAB0AGgAZQAgAFIAZQBsAHkAaQBuAGcAIABQAGEAcgB0AHkAIABB\n# AGcAcgBlAGUAbQBlAG4AdAAgAHcAaABpAGMAaAAgAGwAaQBtAGkAdAAgAGwAaQBh\n# AGIAaQBsAGkAdAB5ACAAYQBuAGQAIABhAHIAZQAgAGkAbgBjAG8AcgBwAG8AcgBh\n# AHQAZQBkACAAaABlAHIAZQBpAG4AIABiAHkAIAByAGUAZgBlAHIAZQBuAGMAZQAu\n# MAsGCWCGSAGG/WwDFTASBgNVHRMBAf8ECDAGAQH/AgEAMHkGCCsGAQUFBwEBBG0w\n# azAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEMGCCsGAQUF\n# BzAChjdodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVk\n# SURSb290Q0EuY3J0MIGBBgNVHR8EejB4MDqgOKA2hjRodHRwOi8vY3JsMy5kaWdp\n# Y2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3JsMDqgOKA2hjRodHRw\n# Oi8vY3JsNC5kaWdpY2VydC5jb20vRGlnaUNlcnRBc3N1cmVkSURSb290Q0EuY3Js\n# MB0GA1UdDgQWBBQVABIrE5iymQftHt+ivlcNK2cCzTAfBgNVHSMEGDAWgBRF66Kv\n# 9JLLgjEtUYunpyGd823IDzANBgkqhkiG9w0BAQUFAAOCAQEARlA+ybcoJKc4HbZb\n# Ka9Sz1LpMUerVlx71Q0LQbPv7HUfdDjyslxhopyVw1Dkgrkj0bo6hnKtOHisdV0X\n# FzRyR4WUVtHruzaEd8wkpfMEGVWp5+Pnq2LN+4stkMLA0rWUvV5PsQXSDj0aqRRb\n# poYxYqioM+SbOafE9c4deHaUJXPkKqvPnHZL7V/CSxbkS3BMAIke/MV5vEwSV/5f\n# 4R68Al2o/vsHOE8Nxl2RuQ9nRc3Wg+3nkg2NsWmMT/tZ4CMP0qquAHzunEIOz5HX\n# J7cW7g/DvXwKoO4sCFWFIrjrGBpN/CohrUkxg0eVd3HcsRtLSxwQnHcUwZ1PL1qV\n# CCkQJjGCBEwwggRIAgEBMIGGMHIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdp\n# Q2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xMTAvBgNVBAMTKERp\n# Z2lDZXJ0IFNIQTIgQXNzdXJlZCBJRCBDb2RlIFNpZ25pbmcgQ0ECEAGfcm2O2qyx\n# DgPgWB72KpowDQYJYIZIAWUDBAIBBQCggYQwGAYKKwYBBAGCNwIBDDEKMAigAoAA\n# oQKAADAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4w\n# DAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgCCPKt4WoiY1sbAfnZKmxtY0e\n# oDPZ+qZBOsAu+KHmiGQwDQYJKoZIhvcNAQEBBQAEggEAwBaoh3PcM+OcihX5zILU\n# 8lE2Ph0f3sEPqmr/5Tzs/S6XDZqy2ux/Uh3sDLcsdk9gywFhBOr2g0G4AFucN+N/\n# E6LFPKPS1po34+wK6w7Z8mcRU+7vNyxPxc7Lycm2HzefwYaFcSA6xbAhHvNiNLd+\n# 1/BumSqlJPBwdDr9H4Ri86CSYj7xRDpafZ6WncqgAMaXsYWsTiwbbiekIcExvZgg\n# BnQ3fEDNBdmK0WZCdZX93DZEYKLdSH6r6lSWRW5IHgzlCWL5OP415GzkHgxthx2m\n# utxvJHuF+LpZfDACQswPX61XwGo61Y6qX+l7g4clG5SeFoK8wbgrR12P4EgECKzy\n# 66GCAg8wggILBgkqhkiG9w0BCQYxggH8MIIB+AIBATB2MGIxCzAJBgNVBAYTAlVT\n# MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j\n# b20xITAfBgNVBAMTGERpZ2lDZXJ0IEFzc3VyZWQgSUQgQ0EtMQIQAwGaAjr/WLFr\n# 1tXq5hfwZjAJBgUrDgMCGgUAoF0wGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAc\n# BgkqhkiG9w0BCQUxDxcNMTYwOTExMTkwMzE5WjAjBgkqhkiG9w0BCQQxFgQUBFvE\n# Qx3Ondofr5v3IdqidXufRVUwDQYJKoZIhvcNAQEBBQAEggEANXU9soYMqHDm1v0E\n# QcIMRTYPkv2xB/xDIc4JmhfFKMO3SUDA1m6lS2w6WWbhNu0GRTFT2Bed712y8qXI\n# t9uO1BDTxDyl0lBYAKjUDnWI17cZpHEcx5snT1D2CYv29TfOpH9/+gzlddWrdB9q\n# VTcEo1/hFH0B4ffgE8YNcw4jg+e7b3nKpGjBDwr6SqMWwTZv37qj/xvLubtzEKEi\n# i1g6VG4tnvWcH2TD5bGmtzLZ8N1fyktdju8B5oscnSYeiX/Z26ZM2JN8coHNSW1k\n# krCJqqlTJrihEOvqs/NiDKyq1lBh3Es/o6LaNxYDeHr/8ntIqfMpbI6ZB+7RW0XC\n# B6o+Cw==\n# SIG # End signature block\n"
  },
  {
    "path": "vagrant/README.rst",
    "content": "ADFS Setup Scripts for vagrant\n==============================\n\nThis directory contains scripts used by Vagrant while bringing up test virtual machines.\nyou can use these scripts as an example for setting up your own ADFS environment.\n\n.. warning::\n\n    These scripts are meant for setting up a lab and are not meant for setting up a secure production environment.\n"
  }
]