pax_global_header00006660000000000000000000000064141030247310014505gustar00rootroot0000000000000052 comment=fa5c926d8ff011c18d67b063063702963bb6bb88 social-app-django-5.0.0/000077500000000000000000000000001410302473100147775ustar00rootroot00000000000000social-app-django-5.0.0/.coveragerc000066400000000000000000000001411410302473100171140ustar00rootroot00000000000000[run] branch = True omit = .venv/* .tox/* concurrency = multiprocessing [paths] source = . social-app-django-5.0.0/.github/000077500000000000000000000000001410302473100163375ustar00rootroot00000000000000social-app-django-5.0.0/.github/ISSUE_TEMPLATE.md000066400000000000000000000014231410302473100210440ustar00rootroot00000000000000 ### Expected behaviour Describe what should happen. ### Actual behaviour Describe what happens instead and why is it an issue. ### What are the steps to reproduce this issue? Input clear steps to reproduce the issue for a maintainer. 1. ... 2. ... 3. ... ### Any logs, error output, etc? Add any code, log or error output that you see fit for this issue, wrap any code and / or console output with the proper code blocks. ### Any other comments? Expand the issue with any details you find appropriate to solve or reproduce it. social-app-django-5.0.0/.github/PULL_REQUEST_TEMPLATE.md000066400000000000000000000030201410302473100221330ustar00rootroot00000000000000 ## Proposed changes Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. ## Types of changes Please check the type of change your PR introduces: - [ ] Bugfix (non-breaking change which fixes an issue) - [ ] New feature (non-breaking change which adds functionality) - [ ] Code style update (PEP8, lint, formatting, renaming, etc) - [ ] Refactoring (no functional changes, no api changes) - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) - [ ] Build related changes (build process, tests runner, etc) - [ ] Other (please describe): ## Checklist _Put an `x` in the boxes that apply. You can also fill these out after creating the PR. If you're unsure about any of them, don't hesitate to ask. We're here to help! This is simply a reminder of what we are going to look for before merging your code._ - [ ] Lint and unit tests pass locally with my changes - [ ] I have added tests that prove my fix is effective or that my feature works ## Other information Any other information that is important to this PR such as screenshots of how the component looks before and after the change. social-app-django-5.0.0/.github/matchers/000077500000000000000000000000001410302473100201455ustar00rootroot00000000000000social-app-django-5.0.0/.github/matchers/flake8.json000066400000000000000000000004441410302473100222140ustar00rootroot00000000000000{ "problemMatcher": [ { "owner": "flake8", "pattern": [ { "code": 4, "column": 3, "file": 1, "line": 2, "message": 5, "regexp": "^([^:]*):(\\d+):(\\d+): (\\w+\\d\\d\\d) (.*)$" } ] } ] } social-app-django-5.0.0/.github/workflows/000077500000000000000000000000001410302473100203745ustar00rootroot00000000000000social-app-django-5.0.0/.github/workflows/flake8.yml000066400000000000000000000010241410302473100222660ustar00rootroot00000000000000name: Flake8 on: push: pull_request: jobs: flake8: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup Python uses: actions/setup-python@v2 with: python-version: 3.8 - name: Install flake run: | python -m pip install --upgrade pip wheel pip install flake8 - name: Run flake8 run: | echo "::add-matcher::.github/matchers/flake8.json" flake8 echo "::remove-matcher owner=flake8::" social-app-django-5.0.0/.github/workflows/release.yml000066400000000000000000000026451410302473100225460ustar00rootroot00000000000000name: Release on: release: types: [published] jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.9' - name: Install Python dependencies run: | python -m pip install --upgrade pip pip install wheel twine - name: Verify tag is documented run: | CURRENT_TAG=${GITHUB_REF#refs/tags/} CURRENT_VERSION=$(head -n1 social_django/__init__.py | awk '{print $3}' | sed 's/[^0-9\.]//g') if [ "${CURRENT_VERSION}" != "${CURRENT_TAG}" ]; then echo "========================================================================" echo "Error: tag '${CURRENT_TAG}' and version '${CURRENT_VERSION}' don't match" echo "========================================================================" exit 1; fi - name: Build dist run: python setup.py sdist bdist_wheel --python-tag py3 - name: Archive dist uses: actions/upload-artifact@v2 with: name: dist path: | dist/*.tar.gz dist/*.whl - name: Verify long description rendering run: twine check dist/* - name: Publish env: PYPI_API_TOKEN: ${{ secrets.PYPI_API_TOKEN }} run: | twine upload --non-interactive -u __token__ -p "${PYPI_API_TOKEN}" dist/* social-app-django-5.0.0/.github/workflows/test.yml000066400000000000000000000026201410302473100220760ustar00rootroot00000000000000name: Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: [3.6, 3.7, 3.8, 3.9] env: PYTHON_VERSION: ${{ matrix.python-version }} steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install System dependencies run: sudo apt install -qq -y --no-install-recommends libxmlsec1-dev swig - name: Install Python dependencies run: | python -m pip install --upgrade pip pip install tox coverage - name: Django 2.2.x Test run: | tox -e "py${PYTHON_VERSION/\./}-django-22" - name: Django 3.0.x Test run: | tox -e "py${PYTHON_VERSION/\./}-django-30" - name: Django 3.1.x Test run: | tox -e "py${PYTHON_VERSION/\./}-django-31" - name: Django 3.2.x Test run: | tox -e "py${PYTHON_VERSION/\./}-django-32" - name: Django main Test run: | tox -e "py${PYTHON_VERSION/\./}-django-main" if: ${{ env.PYTHON_VERSION == '3.8' || env.PYTHON_VERSION == '3.9' }} - name: Coverage run: | coverage combine coverage xml - uses: codecov/codecov-action@v1 with: flags: unittests name: Python ${{ matrix.python-version }} social-app-django-5.0.0/.gitignore000066400000000000000000000006761410302473100170000ustar00rootroot00000000000000*.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject # PyCharm .idea/ test.db local_settings.py sessions/ _build/ fabfile.py changelog.sh .DS_Store .\#* \#*\# .python-version files/local.env social-app-django-5.0.0/.landscape.yaml000066400000000000000000000001311410302473100176660ustar00rootroot00000000000000doc-warnings: no test-warnings: no strictness: medium max-line-length: 80 autodetect: no social-app-django-5.0.0/CHANGELOG.md000066400000000000000000000134551410302473100166200ustar00rootroot00000000000000# Change Log All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [5.0.0](https://github.com/python-social-auth/social-app-django/releases/tag/5.0.0) - 2021-08-05 ### Changed - Removed compat shims for obsolete Django versions - Switch from deprecated `django.conf.urls.url` to `django.urls.path` - Use query `.exists()` instead of `.count() > 0` - Added testing for Django 3.0 - Drop support for Python 2 - Django generic `JSONField` support, details documented [here](https://python-social-auth.readthedocs.io/en/latest/configuration/django.html#json-field-support) - Django 3.2+ compatibility - Use `_default_manager` instead of `objects` ## [4.0.0](https://github.com/python-social-auth/social-app-django/releases/tag/4.0.0) - 2020-06-20 ### Changed - Dropped support for older Django versions (1.8, 1.9, 1.10, 2.0) - Fix `TypeError` when continuing a pipeline in Django 2.1 ## [3.4.0](https://github.com/python-social-auth/social-app-django/releases/tag/3.4.0) - 2020-05-30 ### Changed - Correct release mechanism ## [3.3.0](https://github.com/python-social-auth/social-app-django/releases/tag/3.3.0) - 2020-05-30 ### Changed - Updated release and tests mechanism ## [3.2.0](https://github.com/python-social-auth/social-app-django/releases/tag/3.2.0) - 2020-05-30 ### Changed - Increase social-core dependency version ### Added - Implement `get` and `delete` class methods for `DjangoNonceMixin` - Added `created` and `modified` fields to `UserSocialAuth` model ## [3.1.0](https://github.com/python-social-auth/social-app-django/releases/tag/3.1.0) - 2018-10-31 ### Changed - Updated `JSONField.from_db_value` signature to support multiple Django versions by accepting just the needed parameters. ## [3.0.0](https://github.com/python-social-auth/social-app-django/releases/tag/3.0.0) - 2018-10-28 ### Changed - Reduce log level of exceptions to `INFO` if messages app is installed - Encode association secret with `encodebytes` if available - Decode association secret for proper storage - Remove obsolete code from JSONField - Pass `user` as keyword argument to `do_complete` - Cleanup `username` when using email as username - Drop Python 3.3 support - Correct spelling errors - Correct version that renamed `field.rel` - Reduce error logs in `SocialAuthExceptionMiddleware` ## [2.1.0](https://github.com/python-social-auth/social-app-django/releases/tag/2.1.0) - 2017-12-22 ### Changed - Use Django `urlquote` since it handles unicode - Remove version check in favor of import error catch - Remove call to deprecated method `_get_val_from_obj()` - Drop Python 3.3 support ## [2.0.0](https://github.com/python-social-auth/social-app-django/releases/tag/2.0.0) - 2017-10-28 ### Changed - Better default when checking if the middleware should raise the exception - Update `JSONField` default value to `dict` callable - Updated `authenticate()` parameters cleanup to avoid double arguments errors - Fix imports to bring Django 2.0 support - Admin friendly label - Old Django versions (1.8 and below) compatibility dropped - Python 3.6 and Django 2.0 tests - Management command to clean stale data (partial sessions and codes) ### Added - Added `JSONField` support PostgreSQL builtin option if configured - Added strategy / models / views tests - Added timestamps to Partial and Code models ## [1.2.0](https://github.com/python-social-auth/social-app-django/releases/tag/1.2.0) - 2017-05-06 ### Added - Check for a `MAX_SESSION_LENGTH` setting when logging in and setting session expiry. ### Changed - Addded `on_cascade` clauses to migrations. - Restrict association URL to just integer ids ## [1.1.0](https://github.com/python-social-auth/social-app-django/releases/tag/1.1.0) - 2017-02-10 ### Added - Authenticate cleanup method override to discard request parameter getting passed starting from Django 1.11 ## [1.0.1](https://github.com/python-social-auth/social-app-django/releases/tag/1.0.1) - 2017-01-29 ### Changed - Remove migration replacement to nonexistent reference - Ensure atomic transaction if active ## [1.0.0](https://github.com/python-social-auth/social-app-django/releases/tag/1.0.0) - 2017-01-22 ### Added - Partial pipeline DB storage implementation - Explicit app_label definition in model classes ### Changed - Monkey patch BaseAuth to load the current strategy to workaround django load_backend() call - Remove usage of set/get current strategy methods - Remove usage of `social_auth` related name since it should be consider a simple helper. ## [0.1.0](https://github.com/python-social-auth/social-app-django/releases/tag/0.1.0) - 2016-12-28 ### Added - Let Django resolve URL when getting from settings (port of [#905](https://github.com/omab/python-social-auth/pull/905) by webjunkie) - Add setting to fine-tune admin search fields (port of [#1035](https://github.com/omab/python-social-auth/pull/1035) by atugushev) ### Changed - Fixed `REDIRECT_URL_VALUE` value to be quoted by default. Refs [#875](https://github.com/omab/python-social-auth/issues/875) - Django strategy should respect X-Forwarded-Port (port of [#841](https://github.com/omab/python-social-auth/pull/841) by omarkhan) - Fixed use of old private API (port of [#822](https://github.com/omab/python-social-auth/pull/822) by eranmarom) - Add ON DELETE CASCADE for user fk (port of [#1015](https://github.com/omab/python-social-auth/pull/1015) by artofhuman) - Avoid usage of SubfieldBase on 1.8 and 1.9 versions (port of [#1008](https://github.com/omab/python-social-auth/pull/1008) by tom-dalton-fanduel) ## [0.0.1](https://github.com/python-social-auth/social-app-django/releases/tag/0.0.1) - 2016-11-27 ### Changed - Split from the monolitic [python-social-auth](https://github.com/omab/python-social-auth) codebase social-app-django-5.0.0/CONTRIBUTING.md000066400000000000000000000050671410302473100172400ustar00rootroot00000000000000# How to contribute I like to encourage you to contribute to the repository. This should be as easy as possible for you but there are a few things to consider when contributing. The following guidelines for contribution should be followed if you want to submit a pull request. ## How to prepare * You need a [GitHub account](https://github.com/signup/free) * Submit an [issue ticket](https://github.com/python-social-auth/social-app-django/issues) for your issue if there is no one yet. * Describe the issue and include steps to reproduce if it's a bug. * Ensure to mention the earliest version that you know is affected. * If you are able and want to fix this, fork the repository on GitHub ## Make Changes * In your forked repository, create a topic branch for your upcoming patch. (e.g. `feature/new-backend` or `bug/auth-fails`) * Usually this is based on the `master` branch. * Create a branch based on master `git branch bug/auth-fails master` then checkout the new branch with `git checkout bug/auth-fails`. Please avoid working directly on the `master` branch. * Make commits of logical units and describe them properly. * Make sure you stick to [PEP8](https://www.python.org/dev/peps/pep-0008/) coding style that is used already. * If possible, submit tests to your patch / new feature so it can be tested easily. * Assure nothing is broken by running all the tests. * Add a meaningful entry to the `CHANGELOG.md` document. ## Submit Changes * Push your changes to a topic branch in your fork of the repository. * Open a pull request to the original repository and choose the right original branch you want to patch. * If not done in commit messages (which you really should do) please reference and update your issue with the code changes. But _please do not close the issue yourself_. * Even if you have write access to the repository, do not directly push or merge pull-requests. Let another team member review your pull request and approve. # Additional Resources * [General GitHub documentation](http://help.github.com/) * [GitHub pull request documentation](https://help.github.com/en/github/collaborating-with-issues-and-pull-requests/creating-a-pull-request) * [Read the Issue Guidelines by @necolas](https://github.com/necolas/issue-guidelines/blob/master/CONTRIBUTING.md) for more details # Notes This documented is based in the work from [anselmh/CONTRIBUTING.md](https://github.com/anselmh/CONTRIBUTING.md), licensed as [Creative Commons Attribution 3.0 Unported License](https://github.com/anselmh/CONTRIBUTING.md/blob/master/README.md#license). social-app-django-5.0.0/Dockerfile000066400000000000000000000002431410302473100167700ustar00rootroot00000000000000FROM themattrix/tox-base MAINTAINER Matías Aguirre RUN apt-get update RUN apt-get install -y make libxml2-dev libxmlsec1-dev pkg-config social-app-django-5.0.0/LICENSE000066400000000000000000000027711410302473100160130ustar00rootroot00000000000000Copyright (c) 2012-2016, Matías Aguirre All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name of this project nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. social-app-django-5.0.0/MANIFEST.in000066400000000000000000000001711410302473100165340ustar00rootroot00000000000000global-include *.py include *.txt CHANGELOG.md LICENSE README.md recursive-exclude social_django *.pyc exclude .tox .git social-app-django-5.0.0/Makefile000066400000000000000000000005131410302473100164360ustar00rootroot00000000000000build: @ python setup.py sdist @ python setup.py bdist_wheel --python-tag py3 publish: @ twine upload dist/* release: @ docker-compose run social-release tests: @ docker-compose run social-tests clean: @ find . -name '*.py[co]' -delete @ find . -name '__pycache__' -delete @ rm -rf *.egg-info dist build .PHONY: tests social-app-django-5.0.0/README.md000066400000000000000000000042211410302473100162550ustar00rootroot00000000000000# Python Social Auth - Django ![Build Status](https://github.com/python-social-auth/social-app-django/workflows/Flake8/badge.svg) ![Build Status](https://github.com/python-social-auth/social-app-django/workflows/Tests/badge.svg) [![PyPI version](https://badge.fury.io/py/social-auth-app-django.svg)](https://badge.fury.io/py/social-auth-app-django) [![Donate](https://img.shields.io/badge/Donate-PayPal-orange.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=matiasaguirre%40gmail%2ecom&lc=US&item_name=Python%20Social%20Auth&no_note=0¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHostedGuest) Python Social Auth is an easy to setup social authentication/registration mechanism with support for several frameworks and auth providers. ## Description This is the [Django](https://www.djangoproject.com/) component of the [python-social-auth ecosystem](https://github.com/python-social-auth/social-core), it implements the needed functionality to integrate [social-auth-core](https://github.com/python-social-auth/social-core) in a Django based project. ## Django version This project will focus on the currently supported Django releases as stated on the [Django Project Supported Versions table](https://www.djangoproject.com/download/#supported-versions). Backward compatibility with unsupported versions won't be enforced. ## Documentation Project documentation is available at http://python-social-auth.readthedocs.org/. ## Setup ```shell $ pip install social-auth-app-django ``` ## Contributing See the [CONTRIBUTING.md](CONTRIBUTING.md) document for details. ## Versioning This project follows [Semantic Versioning 2.0.0](http://semver.org/spec/v2.0.0.html). ## License This project follows the BSD license. See the [LICENSE](LICENSE) for details. ## Donations This project is maintained on my spare time, consider donating to keep it improving. [![Donate](https://img.shields.io/badge/Donate-PayPal-orange.svg)](https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=matiasaguirre%40gmail%2ecom&lc=US&item_name=Python%20Social%20Auth&no_note=0¤cy_code=USD&bn=PP%2dDonationsBF%3abtn_donate_SM%2egif%3aNonHostedGuest) social-app-django-5.0.0/dev-requirements.txt000066400000000000000000000000561410302473100210400ustar00rootroot00000000000000codecov==2.0.7 tox==2.7.0 -r requirements.txt social-app-django-5.0.0/docker-compose.yml000066400000000000000000000011221410302473100204300ustar00rootroot00000000000000version: "3.7" services: social-release: image: omab/social-auth-release build: context: . dockerfile: ./files/release/Dockerfile environment: - PROJECT_NAME=social-app-django - PROJECT_DIR=social_django env_file: - ./files/local.env volumes: - .:/code social-tests: image: omab/social-auth-tests build: context: . dockerfile: ./files/tests/Dockerfile args: - PYTHON_VERSIONS=3.6.12 3.7.9 3.8.7 3.9.1 environment: - PYTHON_VERSIONS=3.6.12 3.7.9 3.8.7 3.9.1 volumes: - .:/code social-app-django-5.0.0/files/000077500000000000000000000000001410302473100161015ustar00rootroot00000000000000social-app-django-5.0.0/files/local.env.template000066400000000000000000000000361410302473100215160ustar00rootroot00000000000000PYPI_USERNAME= PYPI_PASSWORD= social-app-django-5.0.0/files/release/000077500000000000000000000000001410302473100175215ustar00rootroot00000000000000social-app-django-5.0.0/files/release/Dockerfile000066400000000000000000000005401410302473100215120ustar00rootroot00000000000000FROM python:3.8.2-slim-buster RUN apt-get update && \ apt-get install -y --no-install-recommends make gettext git curl && \ pip install -U pip && \ pip install -U setuptools && \ pip install -U twine COPY ./files/release/pypirc.template / COPY ./files/release/entrypoint.sh / ADD . /code WORKDIR /code ENTRYPOINT ["/entrypoint.sh"] social-app-django-5.0.0/files/release/entrypoint.sh000077500000000000000000000031401410302473100222710ustar00rootroot00000000000000#!/usr/bin/env bash set -e if [ -z "${PYPI_USERNAME}" ] || [ -z "${PYPI_PASSWORD}" ]; then echo "=====================================================================" echo "Error: missing PYPI_USERNAME or PYPI_PASSWORD environment values" echo "=====================================================================" exit 1; fi if [ -z "${PROJECT_DIR}" ] || [ -z "${PROJECT_NAME}" ]; then echo "=====================================================================" echo "Error: missing PROJECT_DIR or PROJECT_NAME environment values" echo "=====================================================================" exit 1; fi envsubst < /pypirc.template > ~/.pypirc # This will fail if tag doesn't exist CURRENT_VERSION=$(head -n1 ${PROJECT_DIR}/__init__.py | awk '{print $3}' | sed 's/[^0-9\.]//g') CURRENT_TAG=$(git describe --tags --abbrev=0) if [ "${CURRENT_VERSION}" != "${CURRENT_TAG}" ]; then echo "=====================================================================" echo "Error: version '${CURRENT_VERSION}' not tagged" echo "=====================================================================" exit 1; fi PYPI_URL="https://pypi.org/project/${PROJECT_NAME}/${CURRENT_VERSION}/" VERSION_PAGE_STATUS=$(curl -s -I ${PYPI_URL} | head -n1 | awk '{print $2}') if [ "${VERSION_PAGE_STATUS}" == "200" ]; then echo "=====================================================================" echo "Error: version '${CURRENT_VERSION}' already exists" echo "=====================================================================" exit 1; fi make clean build publish social-app-django-5.0.0/files/release/pypirc.template000066400000000000000000000001431410302473100225620ustar00rootroot00000000000000[distutils] index-servers = pypi [pypi] username: ${PYPI_USERNAME} password: ${PYPI_PASSWORD} social-app-django-5.0.0/files/tests/000077500000000000000000000000001410302473100172435ustar00rootroot00000000000000social-app-django-5.0.0/files/tests/Dockerfile000066400000000000000000000011361410302473100212360ustar00rootroot00000000000000FROM python:3.8.2-slim-buster ARG PYTHON_VERSIONS=${PYTHON_VERSIONS} ENV PYTHON_VERSIONS=${PYTHON_VERSIONS} RUN apt-get update && \ apt-get install -y --no-install-recommends \ make git pkg-config ca-certificates wget curl llvm build-essential \ python-openssl libssl-dev zlib1g-dev libbz2-dev libreadline-dev \ libsqlite3-dev libncurses5-dev libncursesw5-dev xz-utils libxml2-dev \ libxmlsec1-dev libffi-dev tk-dev liblzma-dev COPY ./files/tests/pyenv.sh / RUN /pyenv.sh COPY ./files/tests/entrypoint.sh / ADD . /code WORKDIR /code ENTRYPOINT ["/entrypoint.sh"] social-app-django-5.0.0/files/tests/entrypoint.sh000077500000000000000000000002051410302473100220120ustar00rootroot00000000000000#!/usr/bin/env bash set -e export PATH=~/.pyenv/shims:~/.pyenv/bin:${PATH} export PYENV_ROOT=~/.pyenv eval "$(pyenv init -)" tox social-app-django-5.0.0/files/tests/pyenv.sh000077500000000000000000000007311410302473100207440ustar00rootroot00000000000000#!/usr/bin/env bash set -e export PATH=~/.pyenv/shims:~/.pyenv/bin:${PATH} export PYENV_ROOT=~/.pyenv export PYTHON_VERSIONS=${PYTHON_VERSIONS} curl -L https://raw.githubusercontent.com/yyuu/pyenv-installer/master/bin/pyenv-installer | bash eval "$(pyenv init -)" for version in ${PYTHON_VERSIONS}; do pyenv install "${version}" pyenv local "${version}" pip install --upgrade setuptools pip tox pyenv local --unset done pyenv local ${PYTHON_VERSIONS} social-app-django-5.0.0/manage.py000066400000000000000000000003701410302473100166010ustar00rootroot00000000000000#!/usr/bin/env python import os import sys if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.settings") from django.core.management import execute_from_command_line execute_from_command_line(sys.argv) social-app-django-5.0.0/requirements.txt000066400000000000000000000000321410302473100202560ustar00rootroot00000000000000social-auth-core >= 4.1.0 social-app-django-5.0.0/setup.cfg000066400000000000000000000003361410302473100166220ustar00rootroot00000000000000[flake8] max-line-length = 119 # Ignore some well known paths exclude = .venv,.tox,dist,doc,build,*.egg,db/env.py,db/versions/*.py,site [nosetests] verbosity=2 with-coverage=1 cover-erase=1 cover-package=social rednose=1 social-app-django-5.0.0/setup.py000066400000000000000000000034221410302473100165120ustar00rootroot00000000000000"""Setup file for easy installation""" import re from os.path import join, dirname from setuptools import setup VERSION_RE = re.compile(r'__version__ = \'([\d\.]+)\'') def read_version(): with open('social_django/__init__.py') as file: version_line = [line for line in file.readlines() if line.startswith('__version__')][0] return VERSION_RE.match(version_line).groups()[0] def long_description(): return open(join(dirname(__file__), 'README.md')).read() def load_requirements(): return open(join(dirname(__file__), 'requirements.txt')).readlines() setup( name='social-auth-app-django', version=read_version(), author='Matias Aguirre', author_email='matiasaguirre@gmail.com', description='Python Social Authentication, Django integration.', license='BSD', keywords='django, social auth', url='https://github.com/python-social-auth/social-app-django', packages=[ 'social_django', 'social_django.migrations', 'social_django.management', 'social_django.management.commands', ], long_description=long_description(), long_description_content_type='text/markdown', python_requires='>=3.6', install_requires=load_requirements(), classifiers=[ 'Development Status :: 4 - Beta', 'Topic :: Internet', 'License :: OSI Approved :: BSD License', 'Intended Audience :: Developers', 'Environment :: Web Environment', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', ], zip_safe=False ) social-app-django-5.0.0/social_django/000077500000000000000000000000001410302473100175735ustar00rootroot00000000000000social-app-django-5.0.0/social_django/__init__.py000066400000000000000000000015161410302473100217070ustar00rootroot00000000000000__version__ = '5.0.0' import django from social_core.backends.base import BaseAuth # django.contrib.auth.load_backend() will import and instanciate the # authentication backend ignoring the possibility that it might # require more arguments. Here we set a monkey patch to # BaseAuth.__init__ to ignore the mandatory strategy argument and load # it. def baseauth_init_workaround(original_init): def fake_init(self, strategy=None, *args, **kwargs): from .utils import load_strategy original_init(self, strategy or load_strategy(), *args, **kwargs) return fake_init if not getattr(BaseAuth, '__init_patched', False): BaseAuth.__init__ = baseauth_init_workaround(BaseAuth.__init__) BaseAuth.__init_patched = True if django.VERSION < (3, 2): default_app_config = 'social_django.apps.PythonSocialAuthConfig' social-app-django-5.0.0/social_django/admin.py000066400000000000000000000042421410302473100212370ustar00rootroot00000000000000"""Admin settings""" from itertools import chain from django.conf import settings from django.contrib import admin from social_core.utils import setting_name from .models import UserSocialAuth, Nonce, Association class UserSocialAuthOption(admin.ModelAdmin): """Social Auth user options""" list_display = ('user', 'id', 'provider', 'uid') list_filter = ('provider',) raw_id_fields = ('user',) list_select_related = True def get_search_fields(self, request=None): search_fields = getattr( settings, setting_name('ADMIN_USER_SEARCH_FIELDS'), None ) if search_fields is None: _User = UserSocialAuth.user_model() username = getattr(_User, 'USERNAME_FIELD', None) or \ hasattr(_User, 'username') and 'username' or \ None fieldnames = ('first_name', 'last_name', 'email', username) all_names = self._get_all_field_names(_User._meta) search_fields = [name for name in fieldnames if name and name in all_names] return ['user__' + name for name in search_fields] + \ getattr(settings, setting_name('ADMIN_SEARCH_FIELDS'), []) @staticmethod def _get_all_field_names(model): names = chain.from_iterable( (field.name, field.attname) if hasattr(field, 'attname') else (field.name,) for field in model.get_fields() # For complete backwards compatibility, you may want to exclude # GenericForeignKey from the results. if not (field.many_to_one and field.related_model is None) ) return list(set(names)) class NonceOption(admin.ModelAdmin): """Nonce options""" list_display = ('id', 'server_url', 'timestamp', 'salt') search_fields = ('server_url',) class AssociationOption(admin.ModelAdmin): """Association options""" list_display = ('id', 'server_url', 'assoc_type') list_filter = ('assoc_type',) search_fields = ('server_url',) admin.site.register(UserSocialAuth, UserSocialAuthOption) admin.site.register(Nonce, NonceOption) admin.site.register(Association, AssociationOption) social-app-django-5.0.0/social_django/apps.py000066400000000000000000000007731410302473100211170ustar00rootroot00000000000000from django.apps import AppConfig class PythonSocialAuthConfig(AppConfig): # Explicitly set default auto field type to avoid migrations in Django 3.2+ default_auto_field = 'django.db.models.AutoField' # Full Python path to the application eg. 'django.contrib.admin'. name = 'social_django' # Last component of the Python path to the application eg. 'admin'. label = 'social_django' # Human-readable name for the application eg. "Admin". verbose_name = 'Python Social Auth' social-app-django-5.0.0/social_django/config.py000066400000000000000000000002071410302473100214110ustar00rootroot00000000000000# For backward compatibility. You should use the configuration from apps module from .apps import PythonSocialAuthConfig # noqa: F401 social-app-django-5.0.0/social_django/context_processors.py000066400000000000000000000031701410302473100241140ustar00rootroot00000000000000from django.contrib.auth import REDIRECT_FIELD_NAME from django.conf import settings from django.utils.functional import SimpleLazyObject from urllib.parse import quote try: from django.utils.functional import empty as _empty empty = _empty except ImportError: # django < 1.4 empty = None from social_core.backends.utils import user_backends_data from .utils import Storage class LazyDict(SimpleLazyObject): """Lazy dict initialization.""" def __getitem__(self, name): if self._wrapped is empty: self._setup() return self._wrapped[name] def __setitem__(self, name, value): if self._wrapped is empty: self._setup() self._wrapped[name] = value def backends(request): """Load Social Auth current user data to context under the key 'backends'. Will return the output of social_core.backends.utils.user_backends_data.""" return {'backends': LazyDict(lambda: user_backends_data(request.user, settings.AUTHENTICATION_BACKENDS, Storage))} def login_redirect(request): """Load current redirect to context.""" value = request.method == 'POST' and \ request.POST.get(REDIRECT_FIELD_NAME) or \ request.GET.get(REDIRECT_FIELD_NAME) if value: value = quote(value) querystring = REDIRECT_FIELD_NAME + '=' + value else: querystring = '' return { 'REDIRECT_FIELD_NAME': REDIRECT_FIELD_NAME, 'REDIRECT_FIELD_VALUE': value, 'REDIRECT_QUERYSTRING': querystring } social-app-django-5.0.0/social_django/fields.py000066400000000000000000000061441410302473100214200ustar00rootroot00000000000000import json import warnings from django.core.exceptions import ValidationError from django.conf import settings from django.db import models from django.utils.encoding import force_str from social_core.utils import setting_name POSTGRES_JSONFIELD = getattr(settings, setting_name('POSTGRES_JSONFIELD'), False) if POSTGRES_JSONFIELD: warnings.warn( 'SOCIAL_AUTH_POSTGRES_JSONFIELD has been renamed to ' 'SOCIAL_AUTH_JSONFIELD_ENABLED and will be removed in the next release.' ) JSONFIELD_ENABLED = True else: JSONFIELD_ENABLED = getattr(settings, setting_name('JSONFIELD_ENABLED'), False) if JSONFIELD_ENABLED: JSONFIELD_CUSTOM = getattr(settings, setting_name('JSONFIELD_CUSTOM'), None) if JSONFIELD_CUSTOM is not None: try: from django.utils.module_loading import import_string except ImportError: from importlib import import_module as import_string JSONFieldBase = import_string(JSONFIELD_CUSTOM) else: try: from django.db.models import JSONField as JSONFieldBase except ImportError: from django.contrib.postgres.fields import \ JSONField as JSONFieldBase else: JSONFieldBase = models.TextField class JSONField(JSONFieldBase): """Simple JSON field that stores python structures as JSON strings on database. """ def __init__(self, *args, **kwargs): kwargs.setdefault('default', dict) super().__init__(*args, **kwargs) def from_db_value(self, value, *args, **kwargs): return self.to_python(value) def to_python(self, value): """ Convert the input JSON value into python structures, raises django.core.exceptions.ValidationError if the data can't be converted. """ if self.blank and not value: return {} value = value or '{}' if isinstance(value, bytes): value = str(value, 'utf-8') if isinstance(value, str): try: return json.loads(value) except Exception as err: raise ValidationError(str(err)) else: return value def validate(self, value, model_instance): """Check value is a valid JSON string, raise ValidationError on error.""" if isinstance(value, str): super().validate(value, model_instance) try: json.loads(value) except Exception as err: raise ValidationError(str(err)) def get_prep_value(self, value): """Convert value to JSON string before save""" try: return json.dumps(value) except Exception as err: raise ValidationError(str(err)) def value_to_string(self, obj): """Return value from object converted to string properly""" return force_str(self.value_from_object(obj)) def value_from_object(self, obj): """Return value dumped to string.""" orig_val = super().value_from_object(obj) return self.get_prep_value(orig_val) social-app-django-5.0.0/social_django/management/000077500000000000000000000000001410302473100217075ustar00rootroot00000000000000social-app-django-5.0.0/social_django/management/__init__.py000066400000000000000000000000001410302473100240060ustar00rootroot00000000000000social-app-django-5.0.0/social_django/management/commands/000077500000000000000000000000001410302473100235105ustar00rootroot00000000000000social-app-django-5.0.0/social_django/management/commands/__init__.py000066400000000000000000000000001410302473100256070ustar00rootroot00000000000000social-app-django-5.0.0/social_django/management/commands/clearsocial.py000066400000000000000000000016501410302473100263450ustar00rootroot00000000000000from datetime import timedelta from django.core.management.base import BaseCommand from django.utils import timezone from social_django.models import Code, Partial class Command(BaseCommand): help = 'removes old not used verification codes and partials' def add_arguments(self, parser): super().add_arguments(parser) parser.add_argument( '--age', action='store', type=int, dest='age', default=14, help='how long to keep unused data (in days, defaults to 14)' ) def handle(self, *args, **options): age = timezone.now() - timedelta(days=options['age']) # Delete old not verified codes Code.objects.filter( verified=False, timestamp__lt=age ).delete() # Delete old partial data Partial.objects.filter( timestamp__lt=age ).delete() social-app-django-5.0.0/social_django/managers.py000066400000000000000000000006661410302473100217520ustar00rootroot00000000000000from django.db import models class UserSocialAuthManager(models.Manager): """Manager for the UserSocialAuth django model.""" class Meta: app_label = "social_django" def get_social_auth(self, provider, uid): try: return self.select_related('user').get(provider=provider, uid=uid) except self.model.DoesNotExist: return None social-app-django-5.0.0/social_django/middleware.py000066400000000000000000000047631410302473100222740ustar00rootroot00000000000000from django.apps import apps from django.conf import settings from django.contrib import messages from django.contrib.messages.api import MessageFailure from django.shortcuts import redirect from django.utils.deprecation import MiddlewareMixin from urllib.parse import quote from social_core.exceptions import SocialAuthBaseException from social_core.utils import social_logger class SocialAuthExceptionMiddleware(MiddlewareMixin): """Middleware that handles Social Auth AuthExceptions by providing the user with a message, logging an error, and redirecting to some next location. By default, the exception message itself is sent to the user and they are redirected to the location specified in the SOCIAL_AUTH_LOGIN_ERROR_URL setting. This middleware can be extended by overriding the get_message or get_redirect_uri methods, which each accept request and exception. """ def process_exception(self, request, exception): strategy = getattr(request, 'social_strategy', None) if strategy is None or self.raise_exception(request, exception): return if isinstance(exception, SocialAuthBaseException): backend = getattr(request, 'backend', None) backend_name = getattr(backend, 'name', 'unknown-backend') message = self.get_message(request, exception) url = self.get_redirect_uri(request, exception) if apps.is_installed('django.contrib.messages'): social_logger.info(message) try: messages.error(request, message, extra_tags='social-auth ' + backend_name) except MessageFailure: if url: url += ('?' in url and '&' or '?') + \ 'message={}&backend={}'.format(quote(message), backend_name) else: social_logger.error(message) if url: return redirect(url) def raise_exception(self, request, exception): strategy = getattr(request, 'social_strategy', None) if strategy is not None: return strategy.setting('RAISE_EXCEPTIONS') or settings.DEBUG def get_message(self, request, exception): return str(exception) def get_redirect_uri(self, request, exception): strategy = getattr(request, 'social_strategy', None) return strategy.setting('LOGIN_ERROR_URL') social-app-django-5.0.0/social_django/migrations/000077500000000000000000000000001410302473100217475ustar00rootroot00000000000000social-app-django-5.0.0/social_django/migrations/0001_initial.py000066400000000000000000000103201410302473100244060ustar00rootroot00000000000000from django.db import models, migrations from django.conf import settings from social_core.utils import setting_name from ..fields import JSONField from ..storage import DjangoAssociationMixin, DjangoCodeMixin, \ DjangoNonceMixin, DjangoUserMixin USER_MODEL = getattr(settings, setting_name('USER_MODEL'), None) or \ getattr(settings, 'AUTH_USER_MODEL', None) or \ 'auth.User' UID_LENGTH = getattr(settings, setting_name('UID_LENGTH'), 255) NONCE_SERVER_URL_LENGTH = getattr( settings, setting_name('NONCE_SERVER_URL_LENGTH'), 255 ) ASSOCIATION_SERVER_URL_LENGTH = getattr( settings, setting_name('ASSOCIATION_SERVER_URL_LENGTH'), 255 ) ASSOCIATION_HANDLE_LENGTH = getattr( settings, setting_name('ASSOCIATION_HANDLE_LENGTH'), 255 ) class Migration(migrations.Migration): replaces = [ ('default', '0001_initial'), ('social_auth', '0001_initial') ] dependencies = [ migrations.swappable_dependency(USER_MODEL), ] operations = [ migrations.CreateModel( name='Association', fields=[ ('id', models.AutoField( verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('server_url', models.CharField(max_length=ASSOCIATION_SERVER_URL_LENGTH)), ('handle', models.CharField(max_length=ASSOCIATION_HANDLE_LENGTH)), ('secret', models.CharField(max_length=255)), ('issued', models.IntegerField()), ('lifetime', models.IntegerField()), ('assoc_type', models.CharField(max_length=64)), ], options={ 'db_table': 'social_auth_association', }, bases=( models.Model, DjangoAssociationMixin ), ), migrations.CreateModel( name='Code', fields=[ ('id', models.AutoField( verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('email', models.EmailField(max_length=75)), ('code', models.CharField(max_length=32, db_index=True)), ('verified', models.BooleanField(default=False)), ], options={ 'db_table': 'social_auth_code', }, bases=(models.Model, DjangoCodeMixin), ), migrations.CreateModel( name='Nonce', fields=[ ('id', models.AutoField( verbose_name='ID', serialize=False, auto_created=True, primary_key=True )), ('server_url', models.CharField(max_length=NONCE_SERVER_URL_LENGTH)), ('timestamp', models.IntegerField()), ('salt', models.CharField(max_length=65)), ], options={ 'db_table': 'social_auth_nonce', }, bases=(models.Model, DjangoNonceMixin), ), migrations.CreateModel( name='UserSocialAuth', fields=[ ('id', models.AutoField( verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('provider', models.CharField(max_length=32)), ('uid', models.CharField(max_length=UID_LENGTH)), ('extra_data', JSONField(default='{}')), ('user', models.ForeignKey( related_name='social_auth', to=USER_MODEL, on_delete=models.CASCADE)), ], options={ 'db_table': 'social_auth_usersocialauth', }, bases=(models.Model, DjangoUserMixin), ), migrations.AlterUniqueTogether( name='usersocialauth', unique_together={('provider', 'uid')}, ), migrations.AlterUniqueTogether( name='code', unique_together={('email', 'code')}, ), migrations.AlterUniqueTogether( name='nonce', unique_together={('server_url', 'timestamp', 'salt')}, ), ] social-app-django-5.0.0/social_django/migrations/0002_add_related_name.py000066400000000000000000000014071410302473100262140ustar00rootroot00000000000000from django.db import models, migrations from django.conf import settings from social_core.utils import setting_name USER_MODEL = getattr(settings, setting_name('USER_MODEL'), None) or \ getattr(settings, 'AUTH_USER_MODEL', None) or \ 'auth.User' class Migration(migrations.Migration): replaces = [ ('default', '0002_add_related_name'), ('social_auth', '0002_add_related_name') ] dependencies = [ ('social_django', '0001_initial'), ] operations = [ migrations.AlterField( model_name='usersocialauth', name='user', field=models.ForeignKey( related_name='social_auth', to=USER_MODEL, on_delete=models.CASCADE, ) ), ] social-app-django-5.0.0/social_django/migrations/0003_alter_email_max_length.py000066400000000000000000000011611410302473100274460ustar00rootroot00000000000000from django.conf import settings from django.db import models, migrations from social_core.utils import setting_name EMAIL_LENGTH = getattr(settings, setting_name('EMAIL_LENGTH'), 254) class Migration(migrations.Migration): replaces = [ ('default', '0003_alter_email_max_length'), ('social_auth', '0003_alter_email_max_length') ] dependencies = [ ('social_django', '0002_add_related_name'), ] operations = [ migrations.AlterField( model_name='code', name='email', field=models.EmailField(max_length=EMAIL_LENGTH), ), ] social-app-django-5.0.0/social_django/migrations/0004_auto_20160423_0400.py000066400000000000000000000007601410302473100253630ustar00rootroot00000000000000from django.db import migrations from ..fields import JSONField class Migration(migrations.Migration): replaces = [ ('default', '0004_auto_20160423_0400'), ('social_auth', '0004_auto_20160423_0400') ] dependencies = [ ('social_django', '0003_alter_email_max_length'), ] operations = [ migrations.AlterField( model_name='usersocialauth', name='extra_data', field=JSONField(default=dict), ) ] social-app-django-5.0.0/social_django/migrations/0005_auto_20160727_2333.py000066400000000000000000000006741410302473100254060ustar00rootroot00000000000000# Generated by Django 1.9.5 on 2016-07-28 02:33 from django.db import migrations class Migration(migrations.Migration): replaces = [ ('social_auth', '0005_auto_20160727_2333') ] dependencies = [ ('social_django', '0004_auto_20160423_0400'), ] operations = [ migrations.AlterUniqueTogether( name='association', unique_together={('server_url', 'handle')}, ), ] social-app-django-5.0.0/social_django/migrations/0006_partial.py000066400000000000000000000017101410302473100244210ustar00rootroot00000000000000# Generated by Django 1.10.4 on 2017-01-02 11:54 from django.db import migrations, models import social_django.fields import social_django.storage class Migration(migrations.Migration): dependencies = [ ('social_django', '0005_auto_20160727_2333'), ] operations = [ migrations.CreateModel( name='Partial', fields=[ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), ('token', models.CharField(db_index=True, max_length=32)), ('next_step', models.PositiveSmallIntegerField(default=0)), ('backend', models.CharField(max_length=32)), ('data', social_django.fields.JSONField(default=dict)), ], options={ 'db_table': 'social_auth_partial', }, bases=(models.Model, social_django.storage.DjangoPartialMixin), ), ] social-app-django-5.0.0/social_django/migrations/0007_code_timestamp.py000066400000000000000000000010751410302473100257670ustar00rootroot00000000000000# Generated by Django 1.10.7 on 2017-06-08 06:54 from django.db import migrations, models from django.utils import timezone class Migration(migrations.Migration): dependencies = [ ('social_django', '0006_partial'), ] operations = [ migrations.AddField( model_name='code', name='timestamp', field=models.DateTimeField(auto_now_add=True, db_index=True, default=timezone.now), preserve_default=False ), ] social-app-django-5.0.0/social_django/migrations/0008_partial_timestamp.py000066400000000000000000000011101410302473100265000ustar00rootroot00000000000000# Generated by Django 1.10.7 on 2017-06-08 06:57 from django.db import migrations, models from django.utils import timezone class Migration(migrations.Migration): dependencies = [ ('social_django', '0007_code_timestamp'), ] operations = [ migrations.AddField( model_name='partial', name='timestamp', field=models.DateTimeField(auto_now_add=True, db_index=True, default=timezone.now), preserve_default=False, ), ] social-app-django-5.0.0/social_django/migrations/0009_auto_20191118_0520.py000066400000000000000000000012371410302473100254000ustar00rootroot00000000000000# Generated by Django 2.2.7 on 2019-11-18 05:20 from django.db import migrations, models import django.utils.timezone class Migration(migrations.Migration): dependencies = [ ('social_django', '0008_partial_timestamp'), ] operations = [ migrations.AddField( model_name='usersocialauth', name='created', field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now), preserve_default=False, ), migrations.AddField( model_name='usersocialauth', name='modified', field=models.DateTimeField(auto_now=True), ), ] social-app-django-5.0.0/social_django/migrations/0010_uid_db_index.py000066400000000000000000000010001410302473100253650ustar00rootroot00000000000000from django.conf import settings from django.db import models, migrations from social_core.utils import setting_name UID_LENGTH = getattr(settings, setting_name('UID_LENGTH'), 255) class Migration(migrations.Migration): dependencies = [ ('social_django', '0009_auto_20191118_0520'), ] operations = [ migrations.AlterField( model_name='usersocialauth', name='uid', field=models.CharField(max_length=UID_LENGTH, db_index=True), ), ] social-app-django-5.0.0/social_django/migrations/__init__.py000066400000000000000000000000001410302473100240460ustar00rootroot00000000000000social-app-django-5.0.0/social_django/models.py000066400000000000000000000106761410302473100214420ustar00rootroot00000000000000"""Django ORM models for Social Auth""" from django.db import models from django.conf import settings from django.db.utils import IntegrityError from social_core.utils import setting_name from .storage import DjangoUserMixin, DjangoAssociationMixin, \ DjangoNonceMixin, DjangoCodeMixin, \ DjangoPartialMixin, BaseDjangoStorage from .fields import JSONField from .managers import UserSocialAuthManager USER_MODEL = getattr(settings, setting_name('USER_MODEL'), None) or \ getattr(settings, 'AUTH_USER_MODEL', None) or \ 'auth.User' UID_LENGTH = getattr(settings, setting_name('UID_LENGTH'), 255) EMAIL_LENGTH = getattr(settings, setting_name('EMAIL_LENGTH'), 254) NONCE_SERVER_URL_LENGTH = getattr( settings, setting_name('NONCE_SERVER_URL_LENGTH'), 255) ASSOCIATION_SERVER_URL_LENGTH = getattr( settings, setting_name('ASSOCIATION_SERVER_URL_LENGTH'), 255) ASSOCIATION_HANDLE_LENGTH = getattr( settings, setting_name('ASSOCIATION_HANDLE_LENGTH'), 255) class AbstractUserSocialAuth(models.Model, DjangoUserMixin): """Abstract Social Auth association model""" user = models.ForeignKey(USER_MODEL, related_name='social_auth', on_delete=models.CASCADE) provider = models.CharField(max_length=32) uid = models.CharField(max_length=UID_LENGTH, db_index=True) extra_data = JSONField() created = models.DateTimeField(auto_now_add=True) modified = models.DateTimeField(auto_now=True) objects = UserSocialAuthManager() def __str__(self): return str(self.user) class Meta: app_label = "social_django" abstract = True @classmethod def get_social_auth(cls, provider, uid): try: return cls.objects.select_related('user').get(provider=provider, uid=uid) except cls.DoesNotExist: return None @classmethod def username_max_length(cls): username_field = cls.username_field() field = cls.user_model()._meta.get_field(username_field) return field.max_length @classmethod def user_model(cls): return cls._meta.get_field('user').remote_field.model class UserSocialAuth(AbstractUserSocialAuth): """Social Auth association model""" class Meta: """Meta data""" app_label = "social_django" unique_together = ('provider', 'uid') db_table = 'social_auth_usersocialauth' class Nonce(models.Model, DjangoNonceMixin): """One use numbers""" server_url = models.CharField(max_length=NONCE_SERVER_URL_LENGTH) timestamp = models.IntegerField() salt = models.CharField(max_length=65) class Meta: app_label = "social_django" unique_together = ('server_url', 'timestamp', 'salt') db_table = 'social_auth_nonce' class Association(models.Model, DjangoAssociationMixin): """OpenId account association""" server_url = models.CharField(max_length=ASSOCIATION_SERVER_URL_LENGTH) handle = models.CharField(max_length=ASSOCIATION_HANDLE_LENGTH) secret = models.CharField(max_length=255) # Stored base64 encoded issued = models.IntegerField() lifetime = models.IntegerField() assoc_type = models.CharField(max_length=64) class Meta: app_label = "social_django" db_table = 'social_auth_association' unique_together = ( ('server_url', 'handle',) ) class Code(models.Model, DjangoCodeMixin): email = models.EmailField(max_length=EMAIL_LENGTH) code = models.CharField(max_length=32, db_index=True) verified = models.BooleanField(default=False) timestamp = models.DateTimeField(auto_now_add=True, db_index=True) class Meta: app_label = "social_django" db_table = 'social_auth_code' unique_together = ('email', 'code') class Partial(models.Model, DjangoPartialMixin): token = models.CharField(max_length=32, db_index=True) next_step = models.PositiveSmallIntegerField(default=0) backend = models.CharField(max_length=32) data = JSONField() timestamp = models.DateTimeField(auto_now_add=True, db_index=True) class Meta: app_label = "social_django" db_table = 'social_auth_partial' class DjangoStorage(BaseDjangoStorage): user = UserSocialAuth nonce = Nonce association = Association code = Code partial = Partial @classmethod def is_integrity_error(cls, exception): return exception.__class__ is IntegrityError social-app-django-5.0.0/social_django/storage.py000066400000000000000000000165541410302473100216240ustar00rootroot00000000000000"""Django ORM models for Social Auth""" import base64 from django.core.exceptions import FieldDoesNotExist from django.db import transaction, router from django.db.utils import IntegrityError from social_core.storage import UserMixin, AssociationMixin, NonceMixin, \ CodeMixin, PartialMixin, BaseStorage class DjangoUserMixin(UserMixin): """Social Auth association model""" @classmethod def changed(cls, user): user.save() def set_extra_data(self, extra_data=None): if super().set_extra_data(extra_data): self.save() @classmethod def allowed_to_disconnect(cls, user, backend_name, association_id=None): if association_id is not None: qs = cls.objects.exclude(id=association_id) else: qs = cls.objects.exclude(provider=backend_name) qs = qs.filter(user=user) if hasattr(user, 'has_usable_password'): valid_password = user.has_usable_password() else: valid_password = True return valid_password or qs.exists() @classmethod def disconnect(cls, entry): entry.delete() @classmethod def username_field(cls): return getattr(cls.user_model(), 'USERNAME_FIELD', 'username') @classmethod def user_exists(cls, *args, **kwargs): """ Return True/False if a User instance exists with the given arguments. Arguments are directly passed to filter() manager method. """ if 'username' in kwargs: kwargs[cls.username_field()] = kwargs.pop('username') return cls.user_model()._default_manager.filter(*args, **kwargs).exists() @classmethod def get_username(cls, user): return getattr(user, cls.username_field(), None) @classmethod def create_user(cls, *args, **kwargs): username_field = cls.username_field() if 'username' in kwargs: if username_field not in kwargs: kwargs[username_field] = kwargs.pop('username') else: # If username_field is 'email' and there is no field named "username" # then latest should be removed from kwargs. try: cls.user_model()._meta.get_field('username') except FieldDoesNotExist: kwargs.pop('username') try: if hasattr(transaction, 'atomic'): # In Django versions that have an "atomic" transaction decorator / context # manager, there's a transaction wrapped around this call. # If the create fails below due to an IntegrityError, ensure that the transaction # stays undamaged by wrapping the create in an atomic. using = router.db_for_write(cls.user_model()) with transaction.atomic(using=using): user = cls.user_model()._default_manager.create_user(*args, **kwargs) else: user = cls.user_model()._default_manager.create_user(*args, **kwargs) except IntegrityError as exc: # If email comes in as None it won't get found in the get if kwargs.get('email', True) is None: kwargs['email'] = '' try: user = cls.user_model()._default_manager.get(*args, **kwargs) except cls.user_model().DoesNotExist: raise exc return user @classmethod def get_user(cls, pk=None, **kwargs): if pk: kwargs = {'pk': pk} try: return cls.user_model()._default_manager.get(**kwargs) except cls.user_model().DoesNotExist: return None @classmethod def get_users_by_email(cls, email): user_model = cls.user_model() email_field = getattr(user_model, 'EMAIL_FIELD', 'email') return user_model._default_manager.filter(**{email_field + '__iexact': email}) @classmethod def get_social_auth(cls, provider, uid): if not isinstance(uid, str): uid = str(uid) try: return cls.objects.get(provider=provider, uid=uid) except cls.DoesNotExist: return None @classmethod def get_social_auth_for_user(cls, user, provider=None, id=None): qs = cls.objects.filter(user=user) if provider: qs = qs.filter(provider=provider) if id: qs = qs.filter(id=id) return qs @classmethod def create_social_auth(cls, user, uid, provider): if not isinstance(uid, str): uid = str(uid) if hasattr(transaction, 'atomic'): # In Django versions that have an "atomic" transaction decorator / context # manager, there's a transaction wrapped around this call. # If the create fails below due to an IntegrityError, ensure that the transaction # stays undamaged by wrapping the create in an atomic. using = router.db_for_write(cls) with transaction.atomic(using=using): social_auth = cls.objects.create(user=user, uid=uid, provider=provider) else: social_auth = cls.objects.create(user=user, uid=uid, provider=provider) return social_auth class DjangoNonceMixin(NonceMixin): @classmethod def use(cls, server_url, timestamp, salt): return cls.objects.get_or_create(server_url=server_url, timestamp=timestamp, salt=salt)[1] @classmethod def get(cls, server_url, salt): return cls.objects.get( server_url=server_url, salt=salt, ) @classmethod def delete(cls, nonce): nonce.delete() class DjangoAssociationMixin(AssociationMixin): @classmethod def store(cls, server_url, association): # Don't use get_or_create because issued cannot be null try: assoc = cls.objects.get(server_url=server_url, handle=association.handle) except cls.DoesNotExist: assoc = cls(server_url=server_url, handle=association.handle) try: assoc.secret = base64.encodebytes(association.secret).decode() except AttributeError: assoc.secret = base64.encodestring(association.secret).decode() assoc.issued = association.issued assoc.lifetime = association.lifetime assoc.assoc_type = association.assoc_type assoc.save() @classmethod def get(cls, *args, **kwargs): return cls.objects.filter(*args, **kwargs) @classmethod def remove(cls, ids_to_delete): cls.objects.filter(pk__in=ids_to_delete).delete() class DjangoCodeMixin(CodeMixin): @classmethod def get_code(cls, code): try: return cls.objects.get(code=code) except cls.DoesNotExist: return None class DjangoPartialMixin(PartialMixin): @classmethod def load(cls, token): try: return cls.objects.get(token=token) except cls.DoesNotExist: return None @classmethod def destroy(cls, token): partial = cls.load(token) if partial: partial.delete() class BaseDjangoStorage(BaseStorage): user = DjangoUserMixin nonce = DjangoNonceMixin association = DjangoAssociationMixin code = DjangoCodeMixin social-app-django-5.0.0/social_django/strategy.py000066400000000000000000000123411410302473100220100ustar00rootroot00000000000000from django.conf import settings from django.http import HttpResponse from django.db.models import Model from django.contrib.contenttypes.models import ContentType from django.contrib.auth import authenticate from django.shortcuts import redirect, resolve_url from django.template import TemplateDoesNotExist, loader, engines from django.utils.crypto import get_random_string from django.utils.encoding import force_str from django.utils.functional import Promise from django.utils.translation import get_language from social_core.strategy import BaseStrategy, BaseTemplateStrategy def render_template_string(request, html, context=None): """Take a template in the form of a string and render it for the given context""" template = engines['django'].from_string(html) return template.render(context=context, request=request) class DjangoTemplateStrategy(BaseTemplateStrategy): def render_template(self, tpl, context): template = loader.get_template(tpl) return template.render(context=context, request=self.strategy.request) def render_string(self, html, context): return render_template_string(self.strategy.request, html, context) class DjangoStrategy(BaseStrategy): DEFAULT_TEMPLATE_STRATEGY = DjangoTemplateStrategy def __init__(self, storage, request=None, tpl=None): self.request = request self.session = request.session if request else {} super().__init__(storage, tpl) def get_setting(self, name): value = getattr(settings, name) # Force text on URL named settings that are instance of Promise if name.endswith('_URL'): if isinstance(value, Promise): value = force_str(value) value = resolve_url(value) return value def request_data(self, merge=True): if not self.request: return {} if merge: data = self.request.GET.copy() data.update(self.request.POST) elif self.request.method == 'POST': data = self.request.POST else: data = self.request.GET return data def request_host(self): if self.request: return self.request.get_host() def request_is_secure(self): """Is the request using HTTPS?""" return self.request.is_secure() def request_path(self): """path of the current request""" return self.request.path def request_port(self): """Port in use for this request""" return self.request.get_port() def request_get(self): """Request GET data""" return self.request.GET.copy() def request_post(self): """Request POST data""" return self.request.POST.copy() def redirect(self, url): return redirect(url) def html(self, content): return HttpResponse(content, content_type='text/html;charset=UTF-8') def render_html(self, tpl=None, html=None, context=None): if not tpl and not html: raise ValueError('Missing template or html parameters') context = context or {} try: template = loader.get_template(tpl) return template.render(context=context, request=self.request) except (TypeError, TemplateDoesNotExist): return render_template_string(self.request, html, context) def authenticate(self, backend, *args, **kwargs): kwargs['strategy'] = self kwargs['storage'] = self.storage kwargs['backend'] = backend return authenticate(*args, **kwargs) def clean_authenticate_args(self, request, *args, **kwargs): # pipelines don't want a positional request argument kwargs['request'] = request return args, kwargs def session_get(self, name, default=None): return self.session.get(name, default) def session_set(self, name, value): self.session[name] = value if hasattr(self.session, 'modified'): self.session.modified = True def session_pop(self, name): return self.session.pop(name, None) def session_setdefault(self, name, value): return self.session.setdefault(name, value) def build_absolute_uri(self, path=None): if self.request: return self.request.build_absolute_uri(path) else: return path def random_string(self, length=12, chars=BaseStrategy.ALLOWED_CHARS): return get_random_string(length, chars) def to_session_value(self, val): """Converts values that are instance of Model to a dictionary with enough information to retrieve the instance back later.""" if isinstance(val, Model): val = { 'pk': val.pk, 'ctype': ContentType.objects.get_for_model(val).pk } return val def from_session_value(self, val): """Converts back the instance saved by self._ctype function.""" if isinstance(val, dict) and 'pk' in val and 'ctype' in val: ctype = ContentType.objects.get_for_id(val['ctype']) ModelClass = ctype.model_class() val = ModelClass._default_manager.get(pk=val['pk']) return val def get_language(self): """Return current language""" return get_language() social-app-django-5.0.0/social_django/urls.py000066400000000000000000000012711410302473100211330ustar00rootroot00000000000000"""URLs module""" from django.conf import settings from django.urls import path from social_core.utils import setting_name from . import views extra = getattr(settings, setting_name('TRAILING_SLASH'), True) and '/' or '' app_name = 'social' urlpatterns = [ # authentication / association path(f'login/{extra}', views.auth, name='begin'), path(f'complete/{extra}', views.complete, name='complete'), # disconnection path(f'disconnect/{extra}', views.disconnect, name='disconnect'), path(f'disconnect//{extra}', views.disconnect, name='disconnect_individual'), ] social-app-django-5.0.0/social_django/utils.py000066400000000000000000000032611410302473100213070ustar00rootroot00000000000000from functools import wraps from django.conf import settings from django.http import Http404 from django.urls import reverse from social_core.utils import setting_name, module_member, get_strategy from social_core.exceptions import MissingBackend STRATEGY = getattr(settings, setting_name('STRATEGY'), 'social_django.strategy.DjangoStrategy') STORAGE = getattr(settings, setting_name('STORAGE'), 'social_django.models.DjangoStorage') Strategy = module_member(STRATEGY) Storage = module_member(STORAGE) def load_strategy(request=None): return get_strategy(STRATEGY, STORAGE, request) def load_backend(strategy, name, redirect_uri): return strategy.get_backend(name, redirect_uri=redirect_uri) def psa(redirect_uri=None, load_strategy=load_strategy): def decorator(func): @wraps(func) def wrapper(request, backend, *args, **kwargs): uri = redirect_uri if uri and not uri.startswith('/'): uri = reverse(redirect_uri, args=(backend,)) request.social_strategy = load_strategy(request) # backward compatibility in attribute name, only if not already # defined if not hasattr(request, 'strategy'): request.strategy = request.social_strategy try: request.backend = load_backend(request.social_strategy, backend, redirect_uri=uri) except MissingBackend: raise Http404('Backend not found') return func(request, backend, *args, **kwargs) return wrapper return decorator social-app-django-5.0.0/social_django/views.py000066400000000000000000000127201410302473100213040ustar00rootroot00000000000000from django.conf import settings from django.contrib.auth import login, REDIRECT_FIELD_NAME from django.contrib.auth.decorators import login_required from django.views.decorators.csrf import csrf_exempt, csrf_protect from django.views.decorators.http import require_POST from django.views.decorators.cache import never_cache from social_core.utils import setting_name from social_core.actions import do_auth, do_complete, do_disconnect from .utils import psa NAMESPACE = getattr(settings, setting_name('URL_NAMESPACE'), None) or 'social' # Calling `session.set_expiry(None)` results in a session lifetime equal to # platform default session lifetime. DEFAULT_SESSION_TIMEOUT = None @never_cache @psa(f'{NAMESPACE}:complete') def auth(request, backend): return do_auth(request.backend, redirect_name=REDIRECT_FIELD_NAME) @never_cache @csrf_exempt @psa(f'{NAMESPACE}:complete') def complete(request, backend, *args, **kwargs): """Authentication complete view""" return do_complete(request.backend, _do_login, user=request.user, redirect_name=REDIRECT_FIELD_NAME, request=request, *args, **kwargs) @never_cache @login_required @psa() @require_POST @csrf_protect def disconnect(request, backend, association_id=None): """Disconnects given backend from current logged in user.""" return do_disconnect(request.backend, request.user, association_id, redirect_name=REDIRECT_FIELD_NAME) def get_session_timeout(social_user, enable_session_expiration=False, max_session_length=None): if enable_session_expiration: # Retrieve an expiration date from the social user who just finished # logging in; this value was set by the social auth backend, and was # typically received from the server. expiration = social_user.expiration_datetime() # We've enabled session expiration. Check to see if we got # a specific expiration time from the provider for this user; # if not, use the platform default expiration. if expiration: received_expiration_time = expiration.total_seconds() else: received_expiration_time = DEFAULT_SESSION_TIMEOUT # Check to see if the backend set a value as a maximum length # that a session may be; if they did, then we should use the minimum # of that and the received session expiration time, if any, to # set the session length. if received_expiration_time is None and max_session_length is None: # We neither received an expiration length, nor have a maximum # session length. Use the platform default. session_expiry = DEFAULT_SESSION_TIMEOUT elif received_expiration_time is None and max_session_length is not None: # We only have a maximum session length; use that. session_expiry = max_session_length elif received_expiration_time is not None and max_session_length is None: # We only have an expiration time received by the backend # from the provider, with no set maximum. Use that. session_expiry = received_expiration_time else: # We received an expiration time from the backend, and we also # have a set maximum session length. Use the smaller of the two. session_expiry = min(received_expiration_time, max_session_length) else: # If there's an explicitly-set maximum session length, use that # even if we don't want to retrieve session expiry times from # the backend. If there isn't, then use the platform default. if max_session_length is None: session_expiry = DEFAULT_SESSION_TIMEOUT else: session_expiry = max_session_length return session_expiry def _do_login(backend, user, social_user): user.backend = f'{backend.__module__}.{backend.__class__.__name__}' # Get these details early to avoid any issues involved in the # session switch that happens when we call login(). enable_session_expiration = backend.setting('SESSION_EXPIRATION', False) max_session_length_setting = backend.setting('MAX_SESSION_LENGTH', None) # Log the user in, creating a new session. login(backend.strategy.request, user) # Make sure that the max_session_length value is either an integer or # None. Because we get this as a setting from the backend, it can be set # to whatever the backend creator wants; we want to be resilient against # unexpected types being presented to us. try: max_session_length = int(max_session_length_setting) except (TypeError, ValueError): # We got a response that doesn't look like a number; use the default. max_session_length = None # Get the session expiration length based on the maximum session length # setting, combined with any session length received from the backend. session_expiry = get_session_timeout( social_user, enable_session_expiration=enable_session_expiration, max_session_length=max_session_length, ) try: # Set the session length to our previously determined expiry length. backend.strategy.request.session.set_expiry(session_expiry) except OverflowError: # The timestamp we used wasn't in the range of values supported by # Django for session length; use the platform default. We tried. backend.strategy.request.session.set_expiry(DEFAULT_SESSION_TIMEOUT) social-app-django-5.0.0/tests/000077500000000000000000000000001410302473100161415ustar00rootroot00000000000000social-app-django-5.0.0/tests/__init__.py000066400000000000000000000000001410302473100202400ustar00rootroot00000000000000social-app-django-5.0.0/tests/settings.py000066400000000000000000000030141410302473100203510ustar00rootroot00000000000000import os BASE_DIR = os.path.dirname(os.path.abspath(__file__)) DEBUG = True USE_TZ = True DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:", } } ROOT_URLCONF = "tests.urls" INSTALLED_APPS = [ "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.sites", "django.contrib.messages", "django.contrib.admin", "social_django", ] SITE_ID = 1 MIDDLEWARE = ( 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'social_django.middleware.SocialAuthExceptionMiddleware', ) AUTHENTICATION_BACKENDS = ( 'social_core.backends.facebook.FacebookOAuth2', 'django.contrib.auth.backends.ModelBackend', ) TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'APP_DIRS': True, 'DIRS': [os.path.join(BASE_DIR, 'templates')], 'OPTIONS': { 'context_processors': [ 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'django.template.context_processors.request', 'social_django.context_processors.backends', 'social_django.context_processors.login_redirect', ], } }, ] SECRET_KEY = '6p%gef2(6kvjsgl*7!51a7z8c3=u4uc&6ulpua0g1^&sthiifp' STATIC_URL = '/static/' social-app-django-5.0.0/tests/templates/000077500000000000000000000000001410302473100201375ustar00rootroot00000000000000social-app-django-5.0.0/tests/templates/test.html000066400000000000000000000000041410302473100217760ustar00rootroot00000000000000testsocial-app-django-5.0.0/tests/test_admin.py000066400000000000000000000020241410302473100206400ustar00rootroot00000000000000from django.contrib.auth import get_user_model from django.test import TestCase from django.urls import reverse from social_django.models import UserSocialAuth class SocialAdminTest(TestCase): @classmethod def setUpTestData(cls): User = get_user_model() User._default_manager.create_superuser( username='admin', email='admin@test.com', first_name='Admin', password='super-duper-test' ) def test_admin_app_name(self): """The App name in the admin index page""" self.client.login(username='admin', password='super-duper-test') response = self.client.get(reverse('admin:index')) self.assertContains(response, "Python Social Auth") def test_social_auth_changelist(self): """The App name in the admin index page""" self.client.login(username='admin', password='super-duper-test') meta = UserSocialAuth._meta url_name = f'admin:{meta.app_label}_{meta.model_name}_changelist' self.client.get(reverse(url_name)) social-app-django-5.0.0/tests/test_context_processors.py000066400000000000000000000012771410302473100235270ustar00rootroot00000000000000from django.test import TestCase, RequestFactory, override_settings from social_django.context_processors import login_redirect @override_settings(REDIRECT_FIELD_NAME='next') class TestContextProcessors(TestCase): def setUp(self): self.request_factory = RequestFactory() def test_login_redirect_unicode_quote(self): request = self.request_factory.get('/', data={'next': 'profile/sjó'}) result = login_redirect(request) self.assertEqual( result, { 'REDIRECT_FIELD_NAME': 'next', 'REDIRECT_FIELD_VALUE': 'profile/sj%C3%B3', 'REDIRECT_QUERYSTRING': 'next=profile/sj%C3%B3' } ) social-app-django-5.0.0/tests/test_middleware.py000066400000000000000000000037361410302473100217000ustar00rootroot00000000000000import logging from unittest import mock from django.contrib.messages import MessageFailure from django.http import HttpResponseRedirect from django.test import TestCase, override_settings from django.urls import reverse from social_core.exceptions import AuthCanceled class MockAuthCanceled(AuthCanceled): def __init__(self, *args, **kwargs): if not args: kwargs.setdefault('backend', None) super().__init__(*args, **kwargs) @mock.patch('social_core.backends.base.BaseAuth.request', side_effect=MockAuthCanceled) class TestMiddleware(TestCase): def setUp(self): session = self.client.session session['facebook_state'] = '1' session.save() self.complete_url = reverse('social:complete', kwargs={'backend': 'facebook'}) self.complete_url += '?code=2&state=1' def test_exception(self, mocked): with self.assertRaises(MockAuthCanceled): self.client.get(self.complete_url) @override_settings(DEBUG=True) def test_exception_debug(self, mocked): logging.disable(logging.CRITICAL) with self.assertRaises(MockAuthCanceled): self.client.get(self.complete_url) logging.disable(logging.NOTSET) @override_settings(SOCIAL_AUTH_LOGIN_ERROR_URL='/') def test_login_error_url(self, mocked): response = self.client.get(self.complete_url) self.assertTrue(isinstance(response, HttpResponseRedirect)) self.assertEqual(response.url, '/') @override_settings(SOCIAL_AUTH_LOGIN_ERROR_URL='/') @mock.patch('django.contrib.messages.error', side_effect=MessageFailure) def test_message_failure(self, mocked_request, mocked_error): response = self.client.get(self.complete_url) self.assertTrue(isinstance(response, HttpResponseRedirect)) self.assertEqual( response.url, '/?message=Authentication%20process%20canceled&backend=facebook' ) social-app-django-5.0.0/tests/test_models.py000066400000000000000000000203211410302473100210330ustar00rootroot00000000000000from datetime import timedelta from unittest import mock from django.contrib.auth import get_user_model from django.core.management import call_command from django.db import IntegrityError from django.test import TestCase from social_django.models import AbstractUserSocialAuth, Association, Code, \ DjangoStorage, Nonce, Partial, UserSocialAuth class TestSocialAuthUser(TestCase): def test_user_relationship_none(self): """Accessing User.social_user outside of the pipeline doesn't work""" User = get_user_model() user = User._default_manager.create_user(username="randomtester") with self.assertRaises(AttributeError): user.social_user def test_user_existing_relationship(self): """Accessing User.social_user outside of the pipeline doesn't work""" User = get_user_model() user = User._default_manager.create_user(username="randomtester") UserSocialAuth.objects.create(user=user, provider='my-provider', uid='1234') with self.assertRaises(AttributeError): user.social_user def test_get_social_auth(self): User = get_user_model() user = User._default_manager.create_user(username="randomtester") user_social = UserSocialAuth.objects.create(user=user, provider='my-provider', uid='1234') other = UserSocialAuth.get_social_auth('my-provider', '1234') self.assertEqual(other, user_social) def test_get_social_auth_none(self): other = UserSocialAuth.get_social_auth('my-provider', '1234') self.assertIsNone(other) def test_cleanup(self): Code.objects.create(email='first@example.com') Code.objects.create(email='second@example.com') code = Code.objects.create(email='expire@example.com') code.timestamp -= timedelta(days=30) code.save() Partial.objects.create() partial = Partial.objects.create() partial.timestamp -= timedelta(days=30) partial.save() call_command('clearsocial') self.assertEqual(2, Code.objects.count()) self.assertEqual(1, Partial.objects.count()) class TestUserSocialAuth(TestCase): def setUp(self): self.user_model = get_user_model() self.user = self.user_model._default_manager.create_user( username='randomtester', email='user@example.com') self.usa = UserSocialAuth.objects.create( user=self.user, provider='my-provider', uid='1234') def test_changed(self): self.user.email = eml = 'test@example.com' UserSocialAuth.changed(user=self.user) db_eml = self.user_model._default_manager.get(username=self.user.username).email self.assertEqual(db_eml, eml) def test_set_extra_data(self): self.usa.set_extra_data({'a': 'b'}) self.usa.refresh_from_db() db_data = UserSocialAuth.objects.get(id=self.usa.id).extra_data self.assertEqual(db_data, {'a': 'b'}) def test_disconnect(self): m = mock.Mock() UserSocialAuth.disconnect(m) self.assertListEqual(m.method_calls, [mock.call.delete()]) def test_username_field(self): self.assertEqual(UserSocialAuth.username_field(), 'username') with mock.patch('social_django.models.UserSocialAuth.user_model', return_value=mock.Mock(USERNAME_FIELD='test')): self.assertEqual(UserSocialAuth.username_field(), 'test') def test_user_exists(self): self.assertTrue(UserSocialAuth.user_exists(username=self.user.username)) self.assertFalse(UserSocialAuth.user_exists(username='test')) def test_get_username(self): self.assertEqual(UserSocialAuth.get_username(self.user), self.user.username) def test_create_user(self): # Catch integrity error and find existing user UserSocialAuth.create_user(username=self.user.username) def test_create_user_reraise(self): with self.assertRaises(IntegrityError): UserSocialAuth.create_user(username=self.user.username, email=None) @mock.patch('social_django.models.UserSocialAuth.username_field', return_value='email') @mock.patch('django.contrib.auth.models.UserManager.create_user', side_effect=IntegrityError) def test_create_user_custom_username(self, *args): UserSocialAuth.create_user(username=self.user.email) @mock.patch('social_django.storage.transaction', spec=[]) def test_create_user_without_transaction_atomic(self, *args): UserSocialAuth.create_user(username='test') self.assertTrue(self.user_model._default_manager.filter( username='test').exists()) def test_get_user(self): self.assertEqual(UserSocialAuth.get_user(pk=self.user.pk), self.user) self.assertIsNone(UserSocialAuth.get_user(pk=123)) def test_get_users_by_email(self): qs = UserSocialAuth.get_users_by_email(email=self.user.email) self.assertEqual(qs.count(), 1) def test_get_social_auth(self): usa = self.usa # Model self.assertEqual(UserSocialAuth.get_social_auth( provider=usa.provider, uid=usa.uid), usa) self.assertIsNone(UserSocialAuth.get_social_auth(provider='a', uid=1)) # Mixin self.assertEqual(super(AbstractUserSocialAuth, usa).get_social_auth( provider=usa.provider, uid=usa.uid), usa) self.assertIsNone(super(AbstractUserSocialAuth, usa).get_social_auth( provider='a', uid=1)) # Manager self.assertEqual(UserSocialAuth.objects.get_social_auth( provider=usa.provider, uid=usa.uid), usa) self.assertIsNone(UserSocialAuth.objects.get_social_auth( provider='a', uid=1)) def test_get_social_auth_for_user(self): qs = UserSocialAuth.get_social_auth_for_user( user=self.user, provider=self.usa.provider, id=self.usa.id) self.assertEqual(qs.count(), 1) def test_create_social_auth(self): usa = UserSocialAuth.create_social_auth( user=self.user, provider='test', uid=1) self.assertEqual(usa.uid, '1') self.assertEqual(str(usa), str(self.user)) @mock.patch('social_django.storage.transaction', spec=[]) def test_create_social_auth_without_transaction_atomic(self, *args): with self.assertRaises(IntegrityError): UserSocialAuth.create_social_auth( user=self.user, provider=self.usa.provider, uid=self.usa.uid) def test_username_max_length(self): self.assertEqual(UserSocialAuth.username_max_length(), 150) class TestNonce(TestCase): def test_use(self): self.assertEqual(Nonce.objects.count(), 0) self.assertTrue(Nonce.use(server_url='/', timestamp=1, salt='1')) self.assertFalse(Nonce.use(server_url='/', timestamp=1, salt='1')) self.assertEqual(Nonce.objects.count(), 1) class TestAssociation(TestCase): def test_store_get_remove(self): Association.store(server_url='/', association=mock.Mock( handle='a', secret=b'b', issued=1, lifetime=2, assoc_type='c')) qs = Association.get(handle='a') self.assertEqual(qs.count(), 1) self.assertEqual(qs[0].secret, 'Yg==\n') Association.remove(ids_to_delete=[qs.first().id]) self.assertEqual(Association.objects.count(), 0) class TestCode(TestCase): def test_get_code(self): code1 = Code.objects.create(email='test@example.com', code='abc') code2 = Code.get_code(code='abc') self.assertEqual(code1, code2) self.assertIsNone(Code.get_code(code='xyz')) class TestPartial(TestCase): def test_load_destroy(self): p = Partial.objects.create(token='x', backend='y', data={}) self.assertEqual(Partial.load(token='x'), p) self.assertIsNone(Partial.load(token='y')) Partial.destroy(token='x') self.assertEqual(Partial.objects.count(), 0) class TestDjangoStorage(TestCase): def test_is_integrity_error(self): self.assertTrue(DjangoStorage.is_integrity_error(IntegrityError())) social-app-django-5.0.0/tests/test_strategy.py000066400000000000000000000110401410302473100214100ustar00rootroot00000000000000from unittest import mock from django.contrib.auth import get_user_model from django.contrib.contenttypes.models import ContentType from django.contrib.sessions.middleware import SessionMiddleware from django.http import QueryDict, HttpResponse from django.test import TestCase, RequestFactory from django.utils.translation import gettext_lazy from social_django.utils import load_strategy, load_backend class TestStrategy(TestCase): def setUp(self): self.request_factory = RequestFactory() self.request = self.request_factory.get('/', data={'x': '1'}) SessionMiddleware(lambda: None).process_request(self.request) self.strategy = load_strategy(request=self.request) def test_request_methods(self): self.assertEqual(self.strategy.request_port(), '80') self.assertEqual(self.strategy.request_path(), '/') self.assertEqual(self.strategy.request_host(), 'testserver') self.assertEqual(self.strategy.request_is_secure(), False) self.assertEqual(self.strategy.request_data(), QueryDict('x=1')) self.assertEqual(self.strategy.request_get(), QueryDict('x=1')) self.assertEqual(self.strategy.request_post(), {}) self.request.method = 'POST' self.assertEqual(self.strategy.request_data(merge=False), {}) def test_build_absolute_uri(self): self.assertEqual(self.strategy.build_absolute_uri('/'), 'http://testserver/') def test_settings(self): with self.settings(LOGIN_ERROR_URL='/'): self.assertEqual(self.strategy.get_setting('LOGIN_ERROR_URL'), '/') with self.settings(LOGIN_ERROR_URL=gettext_lazy('/')): self.assertEqual(self.strategy.get_setting('LOGIN_ERROR_URL'), '/') def test_session_methods(self): self.strategy.session_set('k', 'v') self.assertEqual(self.strategy.session_get('k'), 'v') self.assertEqual(self.strategy.session_setdefault('k', 'x'), 'v') self.assertEqual(self.strategy.session_pop('k'), 'v') def test_random_string(self): rs1 = self.strategy.random_string() self.assertEqual(len(rs1), 12) self.assertNotEqual(rs1, self.strategy.random_string()) def test_session_value(self): user_model = get_user_model() user = user_model._default_manager.create_user(username="test") ctype = ContentType.objects.get_for_model(user_model) val = self.strategy.to_session_value(val=user) self.assertEqual(val, {'pk': user.pk, 'ctype': ctype.pk}) instance = self.strategy.from_session_value(val=val) self.assertEqual(instance, user) def test_get_language(self): self.assertEqual(self.strategy.get_language(), 'en-us') def test_html(self): result = self.strategy.render_html(tpl='test.html') self.assertEqual(result, 'test') result = self.strategy.render_html(html='xoxo') self.assertEqual(result, 'xoxo') with self.assertRaisesMessage( ValueError, 'Missing template or html parameters'): self.strategy.render_html() result = self.strategy.html(content='xoxo') self.assertIsInstance(result, HttpResponse) self.assertEqual(result.content, b'xoxo') ctx = {'x': 1} result = self.strategy.tpl.render_template(tpl='test.html', context=ctx) self.assertEqual(result, 'test') result = self.strategy.tpl.render_string(html='xoxo', context=ctx) self.assertEqual(result, 'xoxo') def test_authenticate(self): backend = load_backend(strategy=self.strategy, name='facebook', redirect_uri='/') user = mock.Mock() with mock.patch('social_core.backends.base.BaseAuth.pipeline', return_value=user): result = self.strategy.authenticate(backend=backend, response=mock.Mock()) self.assertEqual(result, user) self.assertEqual(result.backend, 'social_core.backends.facebook.FacebookOAuth2') def test_clean_authenticate_args(self): args, kwargs = self.strategy.clean_authenticate_args(self.request) self.assertEqual(args, ()) self.assertEqual(kwargs, {'request': self.request}) def test_clean_authenticate_args_none(self): # When called from continue_pipeline(), request is None. Issue #222 args, kwargs = self.strategy.clean_authenticate_args(None) self.assertEqual(args, ()) self.assertEqual(kwargs, {'request': None}) social-app-django-5.0.0/tests/test_views.py000066400000000000000000000122021410302473100207040ustar00rootroot00000000000000from unittest import mock from django.contrib.auth import get_user_model from django.contrib.auth.models import AbstractBaseUser from django.test import TestCase, override_settings from django.urls import reverse from social_django.models import UserSocialAuth from social_django.views import get_session_timeout @override_settings(SOCIAL_AUTH_FACEBOOK_KEY='1', SOCIAL_AUTH_FACEBOOK_SECRET='2') class TestViews(TestCase): def setUp(self): session = self.client.session session['facebook_state'] = '1' session.save() def test_begin_view(self): response = self.client.get(reverse('social:begin', kwargs={'backend': 'facebook'})) self.assertEqual(response.status_code, 302) url = reverse('social:begin', kwargs={'backend': 'blabla'}) response = self.client.get(url) self.assertEqual(response.status_code, 404) @mock.patch('social_core.backends.base.BaseAuth.request') def test_complete(self, mock_request): url = reverse('social:complete', kwargs={'backend': 'facebook'}) url += '?code=2&state=1' mock_request.return_value.json.return_value = {'access_token': '123'} with mock.patch('django.contrib.sessions.backends.base.SessionBase' '.set_expiry', side_effect=[OverflowError, None]): response = self.client.get(url) self.assertEqual(response.status_code, 302) self.assertEqual(response.url, '/accounts/profile/') @mock.patch('social_core.backends.base.BaseAuth.request') def test_disconnect(self, mock_request): user_model = get_user_model() user = user_model._default_manager.create_user(username='test', password='pwd') UserSocialAuth.objects.create(user=user, provider='facebook') self.client.login(username='test', password='pwd') url = reverse('social:disconnect', kwargs={'backend': 'facebook'}) response = self.client.post(url) self.assertEqual(response.status_code, 302) self.assertEqual(response.url, 'http://testserver/accounts/profile/') url = reverse('social:disconnect_individual', kwargs={'backend': 'facebook', 'association_id': '123'}) hup = AbstractBaseUser.has_usable_password del AbstractBaseUser.has_usable_password response = self.client.post(url) self.assertEqual(response.status_code, 302) self.assertEqual(response.url, 'http://testserver/accounts/profile/') AbstractBaseUser.has_usable_password = hup class TestGetSessionTimeout(TestCase): """ Ensure that the branching logic of get_session_timeout behaves as expected. """ def setUp(self): self.social_user = mock.MagicMock() self.social_user.expiration_datetime.return_value = None super().setUp() def set_user_expiration(self, seconds): self.social_user.expiration_datetime.return_value = mock.MagicMock( total_seconds=mock.MagicMock(return_value=seconds) ) def test_expiration_disabled_no_max(self): self.set_user_expiration(60) expiration_length = get_session_timeout( self.social_user, enable_session_expiration=False ) self.assertIsNone(expiration_length) def test_expiration_disabled_with_max(self): expiration_length = get_session_timeout( self.social_user, enable_session_expiration=False, max_session_length=60 ) self.assertEqual(expiration_length, 60) def test_expiration_disabled_with_zero_max(self): expiration_length = get_session_timeout( self.social_user, enable_session_expiration=False, max_session_length=0 ) self.assertEqual(expiration_length, 0) def test_user_has_session_length_no_max(self): self.set_user_expiration(60) expiration_length = get_session_timeout( self.social_user, enable_session_expiration=True ) self.assertEqual(expiration_length, 60) def test_user_has_session_length_larger_max(self): self.set_user_expiration(60) expiration_length = get_session_timeout( self.social_user, enable_session_expiration=True, max_session_length=90 ) self.assertEqual(expiration_length, 60) def test_user_has_session_length_smaller_max(self): self.set_user_expiration(60) expiration_length = get_session_timeout( self.social_user, enable_session_expiration=True, max_session_length=30 ) self.assertEqual(expiration_length, 30) def test_user_has_no_session_length_with_max(self): expiration_length = get_session_timeout( self.social_user, enable_session_expiration=True, max_session_length=60 ) self.assertEqual(expiration_length, 60) def test_user_has_no_session_length_no_max(self): expiration_length = get_session_timeout( self.social_user, enable_session_expiration=True ) self.assertIsNone(expiration_length) social-app-django-5.0.0/tests/urls.py000066400000000000000000000003011410302473100174720ustar00rootroot00000000000000from django.urls import include, path from django.contrib import admin urlpatterns = [ path('admin/', admin.site.urls), path('', include('social_django.urls', namespace='social')), ] social-app-django-5.0.0/tox.ini000066400000000000000000000010041410302473100163050ustar00rootroot00000000000000[tox] envlist = py{36,37,38,39}-django-22 py{36,37,38,39}-django-30 py{36,37,38,39}-django-31 py{38,39}-django-main [testenv] passenv = * commands = coverage run manage.py test deps = py{36,37,38,39}-django-22: Django>=2.2,<2.3 py{36,37,38,39}-django-30: Django>=3.0,<3.1 py{36,37,38,39}-django-31: Django>=3.1,<3.2 py{36,37,38,39}-django-32: Django>=3.2,<3.3 py{38,39}-django-main: https://github.com/django/django/archive/main.tar.gz -r{toxinidir}/dev-requirements.txt