pax_global_header00006660000000000000000000000064147533671600014526gustar00rootroot0000000000000052 comment=95e921d8b508fb203169f20cde228bd612cba138 social-auth-app-django-5.4.3/000077500000000000000000000000001475336716000157665ustar00rootroot00000000000000social-auth-app-django-5.4.3/.coveragerc000066400000000000000000000001411475336716000201030ustar00rootroot00000000000000[run] branch = True omit = .venv/* .tox/* concurrency = multiprocessing [paths] source = . social-auth-app-django-5.4.3/.github/000077500000000000000000000000001475336716000173265ustar00rootroot00000000000000social-auth-app-django-5.4.3/.github/matchers/000077500000000000000000000000001475336716000211345ustar00rootroot00000000000000social-auth-app-django-5.4.3/.github/matchers/flake8.json000066400000000000000000000004441475336716000232030ustar00rootroot00000000000000{ "problemMatcher": [ { "owner": "flake8", "pattern": [ { "code": 4, "column": 3, "file": 1, "line": 2, "message": 5, "regexp": "^([^:]*):(\\d+):(\\d+): (\\w+\\d\\d\\d) (.*)$" } ] } ] } social-auth-app-django-5.4.3/.github/renovate.json000066400000000000000000000015461475336716000220520ustar00rootroot00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "config:recommended", ":dependencyDashboard" ], "automerge": true, "automergeType": "pr", "automergeStrategy": "rebase", "platformAutomerge": true, "pre-commit": { "enabled": true }, "customManagers": [ { "customType": "regex", "fileMatch": [ "\\.pre-commit-config\\.yaml" ], "matchStrings": [ "(?[^'\" ]+)==(?[^'\" ,\\s]+)" ], "datasourceTemplate": "pypi", "versioningTemplate": "pep440" }, { "customType": "regex", "fileMatch": [ "\\.pre-commit-config\\.yaml" ], "matchStrings": [ "(?[^'\" ]+)@(?[^'\" ,\\s]+)" ], "datasourceTemplate": "npm", "versioningTemplate": "npm" } ] } social-auth-app-django-5.4.3/.github/workflows/000077500000000000000000000000001475336716000213635ustar00rootroot00000000000000social-auth-app-django-5.4.3/.github/workflows/pre-commit.yml000066400000000000000000000002411475336716000241570ustar00rootroot00000000000000name: pre-commit check on: push: pull_request: jobs: pre-commit: uses: python-social-auth/social-core/.github/workflows/pre-commit-shared.yml@master social-auth-app-django-5.4.3/.github/workflows/release.yml000066400000000000000000000016721475336716000235340ustar00rootroot00000000000000name: Release on: push: pull_request: jobs: release: runs-on: ubuntu-latest permissions: id-token: write steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v5 - run: uv build - name: Verify wheel install run: | uv venv venv-install-whl source venv-install-whl/bin/activate uv pip install dist/*.whl - name: Verify source install run: | uv venv venv-install-tar source venv-install-tar/bin/activate uv pip install dist/*.tar.gz - uses: actions/upload-artifact@v4 if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') with: name: dist path: | dist/*.tar.gz dist/*.whl - run: uvx twine check dist/* - if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') run: uv publish --verbose --verbose --trusted-publishing always social-auth-app-django-5.4.3/.github/workflows/test.yml000066400000000000000000000054421475336716000230720ustar00rootroot00000000000000name: Tests on: push: pull_request: schedule: - cron: 0 0 * * 0 jobs: test: runs-on: ubuntu-latest strategy: matrix: python-version: - '3.9' - '3.10' - '3.11' - '3.12' - '3.13' env: PYTHON_VERSION: ${{ matrix.python-version }} PYTHONUNBUFFERED: 1 steps: - uses: actions/checkout@v4 with: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Install System dependencies run: | sudo apt update -qq -y 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 3.2.x Test run: | tox -e "py${PYTHON_VERSION/\./}-django32" if: ${{ env.PYTHON_VERSION == '3.8' || env.PYTHON_VERSION == '3.9' || env.PYTHON_VERSION == '3.10' }} - name: Django 4.1.x Test run: | tox -e "py${PYTHON_VERSION/\./}-django41" if: ${{ env.PYTHON_VERSION == '3.8' || env.PYTHON_VERSION == '3.9' || env.PYTHON_VERSION == '3.10' || env.PYTHON_VERSION == '3.11' }} - name: Django 4.2.x Test run: | tox -e "py${PYTHON_VERSION/\./}-django42" if: ${{ env.PYTHON_VERSION == '3.8' || env.PYTHON_VERSION == '3.9' || env.PYTHON_VERSION == '3.10' || env.PYTHON_VERSION == '3.11' }} - name: Django 5.0.x Test run: | tox -e "py${PYTHON_VERSION/\./}-django50" if: ${{ env.PYTHON_VERSION == '3.10' || env.PYTHON_VERSION == '3.11' || env.PYTHON_VERSION == '3.12' || env.PYTHON_VERSION == '3.13' }} - name: Django 5.1.x Test run: | tox -e "py${PYTHON_VERSION/\./}-django51" if: ${{ env.PYTHON_VERSION == '3.10' || env.PYTHON_VERSION == '3.11' || env.PYTHON_VERSION == '3.12' || env.PYTHON_VERSION == '3.13' }} - name: Django 5.2.x Test run: | tox -e "py${PYTHON_VERSION/\./}-django52" if: ${{ env.PYTHON_VERSION == '3.10' || env.PYTHON_VERSION == '3.11' || env.PYTHON_VERSION == '3.12' || env.PYTHON_VERSION == '3.13' }} - name: Django main Test run: | tox -e "py${PYTHON_VERSION/\./}-djangomain" if: ${{ env.PYTHON_VERSION == '3.12' || env.PYTHON_VERSION == '3.13' }} - name: social-core master Test run: | tox -e "py${PYTHON_VERSION/\./}-socialmaster" if: ${{ env.PYTHON_VERSION == '3.10' || env.PYTHON_VERSION == '3.11' || env.PYTHON_VERSION == '3.13' }} - name: Coverage run: | coverage combine coverage xml - uses: codecov/codecov-action@v5 with: flags: unittests name: Python ${{ matrix.python-version }} social-auth-app-django-5.4.3/.gitignore000066400000000000000000000006631475336716000177630ustar00rootroot00000000000000*.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 .coverage.* .tox # 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 .venv/ social-auth-app-django-5.4.3/.pre-commit-config.yaml000066400000000000000000000026311475336716000222510ustar00rootroot00000000000000# See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 hooks: - id: trailing-whitespace - id: end-of-file-fixer - id: check-merge-conflict - id: check-yaml - id: check-json - id: check-toml - id: check-merge-conflict - id: debug-statements - id: mixed-line-ending args: [--fix=lf] - id: pretty-format-json args: [--no-sort-keys, --autofix, --no-ensure-ascii] - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.9.6 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] - id: ruff-format - repo: meta hooks: - id: check-hooks-apply - id: check-useless-excludes - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks rev: v2.14.0 hooks: - id: pretty-format-yaml args: [--autofix, --indent, '2'] - id: pretty-format-toml args: [--autofix] - repo: https://github.com/abravalheri/validate-pyproject rev: v0.23 hooks: - id: validate-pyproject - repo: https://github.com/executablebooks/mdformat rev: 0.7.22 hooks: - id: mdformat additional_dependencies: - mdformat-gfm==0.4.1 - mdformat-ruff==0.1.3 - mdformat-shfmt==0.2.0 - mdformat_tables==1.0.0 - repo: https://github.com/codespell-project/codespell rev: v2.4.1 hooks: - id: codespell additional_dependencies: - tomli social-auth-app-django-5.4.3/.well-known/000077500000000000000000000000001475336716000201415ustar00rootroot00000000000000social-auth-app-django-5.4.3/.well-known/funding-manifest-urls000066400000000000000000000001051475336716000243010ustar00rootroot00000000000000https://github.com/python-social-auth/.github/blob/main/funding.json social-auth-app-django-5.4.3/CHANGELOG.md000066400000000000000000000166421475336716000176100ustar00rootroot00000000000000# 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.4.3](https://github.com/python-social-auth/social-app-django/releases/tag/5.4.3) - 2025-02-13 ### Changed - Tested with recent Django and Python - Modernized build system - Fixed rollback of migrations ## [5.4.2](https://github.com/python-social-auth/social-app-django/releases/tag/5.4.2) - 2024-07-12 ### Changed - Fixed `UserSocialAuth` creation by allowing `JSONField` to be blank - Fixed the assumption that UID can only be an integer (#571) - Fixed revert of migration `0013_migrate_extra_data.py` ## [5.4.1](https://github.com/python-social-auth/social-app-django/releases/tag/5.4.1) - 2024-04-24 ### Changed - Added reverse migration for JSON field - Fixed improper handling of case sensitivity with MySQL/MariaDB (CVE-2024-32879) ## [5.4.0](https://github.com/python-social-auth/social-app-django/releases/tag/5.4.0) - 2023-10-17 ### Changed - Improved JSON field migration performance - Introduce configuration to request POST only requests for social authentication - Updated list of supported Django and Python versions ## [5.3.0](https://github.com/python-social-auth/social-app-django/releases/tag/5.3.0) - 2023-09-01 ### Changed - Uses Django native JSON field ## [5.2.0](https://github.com/python-social-auth/social-app-django/releases/tag/5.2.0) - 2023-03-31 ### Changed - Removed support for Django\<3.2 - Fixed missing migration issue ## [5.1.0](https://github.com/python-social-auth/social-app-django/releases/tag/5.1.0) - 2023-03-15 ### Changed - Compatibility with recent Django and Python versions - Coding style improvements - Improved error handling in SocialAuthExceptionMiddleware ## [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 - Added `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-auth-app-django-5.4.3/LICENSE000066400000000000000000000027711475336716000170020ustar00rootroot00000000000000Copyright (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-auth-app-django-5.4.3/MANIFEST.in000066400000000000000000000001711475336716000175230ustar00rootroot00000000000000global-include *.py include *.txt CHANGELOG.md LICENSE README.md recursive-exclude social_django *.pyc exclude .tox .git social-auth-app-django-5.4.3/README.md000066400000000000000000000032651475336716000172530ustar00rootroot00000000000000# Python Social Auth - Django 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 https://python-social-auth.readthedocs.io/. ## Setup ```shell $ pip install social-auth-app-django ``` ## Contributing Contributions are welcome! Only the core and Django modules are currently in development. All others are in maintenance only mode, and maintainers are especially welcome there. See the [https://github.com/python-social-auth/.github/blob/main/CONTRIBUTING.md](CONTRIBUTING.md) document for details. ## Versioning This project follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html). ## License This project follows the BSD license. See the [LICENSE](LICENSE) for details. ## Donations This project welcomes donations to make the development sustainable, you can fund Python Social Auth on following platforms: - [GitHub Sponsors](https://github.com/sponsors/python-social-auth/) - [Open Collective](https://opencollective.com/python-social-auth) social-auth-app-django-5.4.3/manage.py000066400000000000000000000003701475336716000175700ustar00rootroot00000000000000#!/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-auth-app-django-5.4.3/pyproject.toml000066400000000000000000000032551475336716000207070ustar00rootroot00000000000000[build-system] build-backend = "setuptools.build_meta" requires = ["setuptools"] [dependency-groups] dev = [ "tox", "pre-commit", "coverage>=3.6" ] [project] authors = [ {name = "Matias Aguirre", email = "matiasaguirre@gmail.com"} ] classifiers = [ "Development Status :: 5 - Production/Stable", "Environment :: Web Environment", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.9", "Topic :: Internet" ] dependencies = [ "Django>=3.2", "social-auth-core~=4.4" ] description = "Python Social Authentication, Django integration." keywords = ["django", "openid", "oauth", "saml", "social auth"] license = {text = "BSD"} name = "social-auth-app-django" readme = "README.md" requires-python = ">=3.9" version = "5.4.3" [project.optional-dependencies] # This is present until pip implements supports for PEP 735 # see https://github.com/pypa/pip/issues/12963 dev = [ "coverage>=3.6" ] [project.urls] Homepage = "https://github.com/python-social-auth/social-app-django" [tool.ruff] # Ignore some well known paths exclude = [ ".tox", ".venv", "*.egg", "build", "db/env.py", "db/versions/*.py", "dist", "doc", "site" ] ignore = [] line-length = 120 output-format = "github" select = ["E", "F", "I", "PLC", "PLE", "UP"] target-version = "py37" [tool.ruff.mccabe] max-complexity = 10 [tool.setuptools] license-files = [] packages = ["social_django"] social-auth-app-django-5.4.3/social_django/000077500000000000000000000000001475336716000205625ustar00rootroot00000000000000social-auth-app-django-5.4.3/social_django/__init__.py000066400000000000000000000014501475336716000226730ustar00rootroot00000000000000import importlib.metadata __version__ = importlib.metadata.version("social-auth-app-django") from social_core.backends.base import BaseAuth # django.contrib.auth.load_backend() will import and instantiate 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 social-auth-app-django-5.4.3/social_django/admin.py000066400000000000000000000040661475336716000222320ustar00rootroot00000000000000"""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 Association, Nonce, UserSocialAuth @admin.register(UserSocialAuth) class UserSocialAuthOption(admin.ModelAdmin): """Social Auth user options""" list_display = ("user", "id", "provider", "uid", "created", "modified") list_filter = ("provider",) raw_id_fields = ("user",) readonly_fields = ("created", "modified") 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)) @admin.register(Nonce) class NonceOption(admin.ModelAdmin): """Nonce options""" list_display = ("id", "server_url", "timestamp", "salt") search_fields = ("server_url",) @admin.register(Association) class AssociationOption(admin.ModelAdmin): """Association options""" list_display = ("id", "server_url", "assoc_type") list_filter = ("assoc_type",) search_fields = ("server_url",) social-auth-app-django-5.4.3/social_django/apps.py000066400000000000000000000007761475336716000221110ustar00rootroot00000000000000from 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.BigAutoField" # 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-auth-app-django-5.4.3/social_django/config.py000066400000000000000000000002071475336716000224000ustar00rootroot00000000000000# For backward compatibility. You should use the configuration from apps module from .apps import PythonSocialAuthConfig # noqa: F401 social-auth-app-django-5.4.3/social_django/context_processors.py000066400000000000000000000032601475336716000251030ustar00rootroot00000000000000from urllib.parse import quote from django.conf import settings from django.contrib.auth import REDIRECT_FIELD_NAME from django.http.multipartparser import MultiPartParserError from django.utils.functional import SimpleLazyObject 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.""" try: value = ( request.method == "POST" and request.POST.get(REDIRECT_FIELD_NAME) or request.GET.get(REDIRECT_FIELD_NAME) ) except MultiPartParserError: # request POST may be malformed value = None 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-auth-app-django-5.4.3/social_django/fields.py000066400000000000000000000017121475336716000224030ustar00rootroot00000000000000""" This is legacy code used only in the database migrations. """ from django.conf import settings from django.db import models from social_core.utils import setting_name POSTGRES_JSONFIELD = getattr(settings, setting_name("POSTGRES_JSONFIELD"), False) if POSTGRES_JSONFIELD: 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 JSONField = import_string(JSONFIELD_CUSTOM) else: try: from django.db.models import JSONField except ImportError: from django.contrib.postgres.fields import JSONField else: JSONField = models.TextField social-auth-app-django-5.4.3/social_django/management/000077500000000000000000000000001475336716000226765ustar00rootroot00000000000000social-auth-app-django-5.4.3/social_django/management/__init__.py000066400000000000000000000000001475336716000247750ustar00rootroot00000000000000social-auth-app-django-5.4.3/social_django/management/commands/000077500000000000000000000000001475336716000244775ustar00rootroot00000000000000social-auth-app-django-5.4.3/social_django/management/commands/__init__.py000066400000000000000000000000001475336716000265760ustar00rootroot00000000000000social-auth-app-django-5.4.3/social_django/management/commands/clearsocial.py000066400000000000000000000015611475336716000273350ustar00rootroot00000000000000from 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-auth-app-django-5.4.3/social_django/managers.py000066400000000000000000000006031475336716000227300ustar00rootroot00000000000000from 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-auth-app-django-5.4.3/social_django/middleware.py000066400000000000000000000046741475336716000232640ustar00rootroot00000000000000from urllib.parse import quote from 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 social_core.exceptions import SocialAuthBaseException from social_core.utils import social_logger class SocialAuthExceptionMiddleware: """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 __init__(self, get_response): self.get_response = get_response def __call__(self, request): return self.get_response(request) 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=f"social-auth {backend_name}") except MessageFailure: if url: url += ("?" in url and "&" or "?") + f"message={quote(message)}&backend={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", 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-auth-app-django-5.4.3/social_django/migrations/000077500000000000000000000000001475336716000227365ustar00rootroot00000000000000social-auth-app-django-5.4.3/social_django/migrations/0001_initial.py000066400000000000000000000114201475336716000253770ustar00rootroot00000000000000from django.conf import settings from django.db import migrations, models 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-auth-app-django-5.4.3/social_django/migrations/0002_add_related_name.py000066400000000000000000000014221475336716000272000ustar00rootroot00000000000000from django.conf import settings from django.db import migrations, models 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-auth-app-django-5.4.3/social_django/migrations/0003_alter_email_max_length.py000066400000000000000000000011611475336716000304350ustar00rootroot00000000000000from django.conf import settings from django.db import migrations, models 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-auth-app-django-5.4.3/social_django/migrations/0004_auto_20160423_0400.py000066400000000000000000000007611475336716000263530ustar00rootroot00000000000000from 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-auth-app-django-5.4.3/social_django/migrations/0005_auto_20160727_2333.py000066400000000000000000000006561475336716000263750ustar00rootroot00000000000000# 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-auth-app-django-5.4.3/social_django/migrations/0006_partial.py000066400000000000000000000021721475336716000254130ustar00rootroot00000000000000# 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-auth-app-django-5.4.3/social_django/migrations/0007_code_timestamp.py000066400000000000000000000007571475336716000267640ustar00rootroot00000000000000# 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-auth-app-django-5.4.3/social_django/migrations/0008_partial_timestamp.py000066400000000000000000000007711475336716000275030ustar00rootroot00000000000000# 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-auth-app-django-5.4.3/social_django/migrations/0009_auto_20191118_0520.py000066400000000000000000000012361475336716000263660ustar00rootroot00000000000000# Generated by Django 2.2.7 on 2019-11-18 05:20 import django.utils.timezone from django.db import migrations, models 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-auth-app-django-5.4.3/social_django/migrations/0010_uid_db_index.py000066400000000000000000000007771475336716000264000ustar00rootroot00000000000000from django.conf import settings from django.db import migrations, models 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-auth-app-django-5.4.3/social_django/migrations/0011_alter_id_fields.py000066400000000000000000000024131475336716000270620ustar00rootroot00000000000000# Generated by Django 3.2 on 2021-09-23 12:14 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ("social_django", "0010_uid_db_index"), ] operations = [ migrations.AlterField( model_name="association", name="id", field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID"), ), migrations.AlterField( model_name="code", name="id", field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID"), ), migrations.AlterField( model_name="nonce", name="id", field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID"), ), migrations.AlterField( model_name="partial", name="id", field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID"), ), migrations.AlterField( model_name="usersocialauth", name="id", field=models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID"), ), ] social-auth-app-django-5.4.3/social_django/migrations/0012_usersocialauth_extra_data_new.py000066400000000000000000000010501475336716000320460ustar00rootroot00000000000000# Generated by Django 4.0 on 2023-06-10 07:10 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ("social_django", "0011_alter_id_fields"), ] operations = [ migrations.AddField( model_name="usersocialauth", name="extra_data_new", field=models.JSONField(default=dict), ), migrations.AddField( model_name="partial", name="data_new", field=models.JSONField(default=dict), ), ] social-auth-app-django-5.4.3/social_django/migrations/0013_migrate_extra_data.py000066400000000000000000000056201475336716000276020ustar00rootroot00000000000000# Generated by Django 4.0 on 2023-06-10 07:10 import json from django.db import migrations, models def migrate_json_field(apps, schema_editor): UserSocialAuth = apps.get_model("social_django", "UserSocialAuth") Partial = apps.get_model("social_django", "Partial") db_alias = schema_editor.connection.alias to_be_updated = [] for auth in UserSocialAuth.objects.using(db_alias).exclude(extra_data='""').iterator(): old_value = auth.extra_data if isinstance(old_value, str): try: old_value = json.loads(old_value) except json.JSONDecodeError as error: print(f"Failed to migrate extra_data {old_value}: {error}") auth.extra_data_new = old_value to_be_updated.append(auth) if len(to_be_updated) >= 1000: UserSocialAuth.objects.bulk_update(to_be_updated, ["extra_data_new"]) to_be_updated.clear() if to_be_updated: UserSocialAuth.objects.bulk_update(to_be_updated, ["extra_data_new"]) to_be_updated.clear() for auth in Partial.objects.using(db_alias).all(): old_value = auth.data if isinstance(old_value, str): try: old_value = json.loads(old_value) except json.JSONDecodeError as error: print(f"Failed to migrate data {old_value}: {error}") auth.data_new = old_value auth.save(update_fields=["data_new"]) def migrate_json_field_backwards(apps, schema_editor): UserSocialAuth = apps.get_model("social_django", "UserSocialAuth") Partial = apps.get_model("social_django", "Partial") db_alias = schema_editor.connection.alias to_be_updated = [] is_text_field = isinstance( UserSocialAuth._meta.get_field("extra_data"), models.TextField, ) for auth in UserSocialAuth.objects.using(db_alias).iterator(): new_value = auth.extra_data_new if is_text_field: new_value = json.dumps(new_value) auth.extra_data = new_value to_be_updated.append(auth) if len(to_be_updated) >= 1000: UserSocialAuth.objects.bulk_update(to_be_updated, ["extra_data"]) to_be_updated.clear() if to_be_updated: UserSocialAuth.objects.bulk_update(to_be_updated, ["extra_data"]) to_be_updated.clear() is_text_field = issubclass( type(Partial._meta.get_field("data")), models.TextField, ) for auth in Partial.objects.using(db_alias).all(): new_value = auth.data_new if is_text_field: new_value = json.dumps(new_value) auth.data = new_value auth.save(update_fields=["data"]) class Migration(migrations.Migration): dependencies = [ ("social_django", "0012_usersocialauth_extra_data_new"), ] operations = [ migrations.RunPython(migrate_json_field, migrate_json_field_backwards, elidable=True), ] social-auth-app-django-5.4.3/social_django/migrations/0014_remove_usersocialauth_extra_data.py000066400000000000000000000006751475336716000325700ustar00rootroot00000000000000# Generated by Django 4.0 on 2023-06-10 07:18 from django.db import migrations class Migration(migrations.Migration): dependencies = [ ("social_django", "0013_migrate_extra_data"), ] operations = [ migrations.RemoveField( model_name="usersocialauth", name="extra_data", ), migrations.RemoveField( model_name="partial", name="data", ), ] 0015_rename_extra_data_new_usersocialauth_extra_data.py000066400000000000000000000010331475336716000355160ustar00rootroot00000000000000social-auth-app-django-5.4.3/social_django/migrations# Generated by Django 4.0 on 2023-06-10 07:18 from django.db import migrations class Migration(migrations.Migration): dependencies = [ ("social_django", "0014_remove_usersocialauth_extra_data"), ] operations = [ migrations.RenameField( model_name="usersocialauth", old_name="extra_data_new", new_name="extra_data", ), migrations.RenameField( model_name="partial", old_name="data_new", new_name="data", ), ] social-auth-app-django-5.4.3/social_django/migrations/0016_alter_usersocialauth_extra_data.py000066400000000000000000000006741475336716000324030ustar00rootroot00000000000000# Generated by Django 3.2.25 on 2024-05-28 19:28 from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ ("social_django", "0015_rename_extra_data_new_usersocialauth_extra_data"), ] operations = [ migrations.AlterField( model_name="usersocialauth", name="extra_data", field=models.JSONField(blank=True, default=dict), ), ] social-auth-app-django-5.4.3/social_django/migrations/__init__.py000066400000000000000000000000001475336716000250350ustar00rootroot00000000000000social-auth-app-django-5.4.3/social_django/models.py000066400000000000000000000112031475336716000224140ustar00rootroot00000000000000"""Django ORM models for Social Auth""" from typing import Union from django.conf import settings from django.db import models from django.db.utils import IntegrityError from social_core.utils import setting_name from .managers import UserSocialAuthManager from .storage import ( BaseDjangoStorage, DjangoAssociationMixin, DjangoCodeMixin, DjangoNonceMixin, DjangoPartialMixin, 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) 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 = models.JSONField(default=dict, blank=True) 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: str, uid: Union[str, int]): if not isinstance(uid, str): uid = str(uid) for social in cls.objects.select_related("user").filter(provider=provider, uid=uid): # We need to compare to filter out case-insensitive lookups in # some databases (MySQL/MariaDB) if social.uid == uid: return social 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 = models.JSONField(default=dict) 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-auth-app-django-5.4.3/social_django/storage.py000066400000000000000000000163321475336716000226050ustar00rootroot00000000000000"""Django ORM models for Social Auth""" import base64 from django.core.exceptions import FieldDoesNotExist from django.db import router, transaction from django.db.utils import IntegrityError from social_core.storage import ( AssociationMixin, BaseStorage, CodeMixin, NonceMixin, PartialMixin, UserMixin, ) 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-auth-app-django-5.4.3/social_django/strategy.py000066400000000000000000000122621475336716000230010ustar00rootroot00000000000000from django.conf import settings from django.contrib.auth import authenticate from django.contrib.contenttypes.models import ContentType from django.db.models import Model from django.http import HttpResponse from django.shortcuts import redirect, resolve_url from django.template import TemplateDoesNotExist, engines, loader 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-auth-app-django-5.4.3/social_django/urls.py000066400000000000000000000012641475336716000221240ustar00rootroot00000000000000"""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-auth-app-django-5.4.3/social_django/utils.py000066400000000000000000000037421475336716000223020ustar00rootroot00000000000000from functools import wraps from django.conf import settings from django.http import Http404 from django.urls import reverse from django.views.decorators.http import require_POST from social_core.exceptions import MissingBackend from social_core.utils import get_strategy, module_member, setting_name STRATEGY = getattr(settings, setting_name("STRATEGY"), "social_django.strategy.DjangoStrategy") STORAGE = getattr(settings, setting_name("STORAGE"), "social_django.models.DjangoStorage") REQUIRE_POST = setting_name("REQUIRE_POST") 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 def maybe_require_post(func): @wraps(func) def wrapper(request, backend, *args, **kwargs): require_post = getattr(settings, REQUIRE_POST, False) if require_post: return require_POST(func)(request, backend, *args, **kwargs) return func(request, backend, *args, **kwargs) return wrapper social-auth-app-django-5.4.3/social_django/views.py000066400000000000000000000127271475336716000223020ustar00rootroot00000000000000from django.conf import settings from django.contrib.auth import REDIRECT_FIELD_NAME, login from django.contrib.auth.decorators import login_required from django.views.decorators.cache import never_cache from django.views.decorators.csrf import csrf_exempt, csrf_protect from django.views.decorators.http import require_POST from social_core.actions import do_auth, do_complete, do_disconnect from social_core.utils import setting_name from .utils import maybe_require_post, 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 @maybe_require_post @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-auth-app-django-5.4.3/tests/000077500000000000000000000000001475336716000171305ustar00rootroot00000000000000social-auth-app-django-5.4.3/tests/__init__.py000066400000000000000000000000001475336716000212270ustar00rootroot00000000000000social-auth-app-django-5.4.3/tests/settings.py000066400000000000000000000030151475336716000213410ustar00rootroot00000000000000import 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-auth-app-django-5.4.3/tests/templates/000077500000000000000000000000001475336716000211265ustar00rootroot00000000000000social-auth-app-django-5.4.3/tests/templates/test.html000066400000000000000000000000051475336716000227660ustar00rootroot00000000000000test social-auth-app-django-5.4.3/tests/test_admin.py000066400000000000000000000020561475336716000216340ustar00rootroot00000000000000from 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-auth-app-django-5.4.3/tests/test_context_processors.py000066400000000000000000000021541475336716000245110ustar00rootroot00000000000000from django.test import RequestFactory, TestCase, 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", }, ) def test_login_redirect_malformed_post(self): request = self.request_factory.post("/", data="no boundary", content_type="multipart/form-data") result = login_redirect(request) self.assertEqual( result, { "REDIRECT_FIELD_NAME": "next", "REDIRECT_FIELD_VALUE": None, "REDIRECT_QUERYSTRING": "", }, ) social-auth-app-django-5.4.3/tests/test_middleware.py000066400000000000000000000036541475336716000226660ustar00rootroot00000000000000import 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-auth-app-django-5.4.3/tests/test_migrations.py000066400000000000000000000010511475336716000227120ustar00rootroot00000000000000from io import StringIO from django.core.management import call_command from django.test import TestCase class PendingMigrationsTests(TestCase): def test_no_pending_migrations(self): out = StringIO() try: call_command( "makemigrations", "--dry-run", "--check", stdout=out, stderr=StringIO(), ) except SystemExit: # pragma: no cover raise AssertionError("Pending migrations:\n" + out.getvalue()) from None social-auth-app-django-5.4.3/tests/test_models.py000066400000000000000000000207121475336716000220260ustar00rootroot00000000000000from 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_int_uid(self): usa = self.usa int_uid = int(usa.uid) # Model self.assertEqual(UserSocialAuth.get_social_auth(provider=usa.provider, uid=int_uid), usa) # Mixin self.assertEqual( super(AbstractUserSocialAuth, usa).get_social_auth(provider=usa.provider, uid=usa.uid), usa, ) # Manager self.assertEqual( UserSocialAuth.get_social_auth(provider=usa.provider, uid=int_uid), usa, ) 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-auth-app-django-5.4.3/tests/test_strategy.py000066400000000000000000000105651475336716000224120ustar00rootroot00000000000000from 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 HttpResponse, QueryDict from django.test import RequestFactory, TestCase from django.utils.translation import gettext_lazy from social_django.utils import load_backend, load_strategy 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\n") 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\n") 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-auth-app-django-5.4.3/tests/test_views.py000066400000000000000000000121151475336716000216760ustar00rootroot00000000000000from 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) def test_require_post_works(self): with override_settings(SOCIAL_AUTH_REQUIRE_POST=True): response = self.client.get(reverse("social:begin", kwargs={"backend": "facebook"})) self.assertEqual(response.status_code, 405) @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-auth-app-django-5.4.3/tests/urls.py000066400000000000000000000003001475336716000204600ustar00rootroot00000000000000from django.contrib import admin from django.urls import include, path urlpatterns = [ path("admin/", admin.site.urls), path("", include("social_django.urls", namespace="social")), ] social-auth-app-django-5.4.3/tox.ini000066400000000000000000000012621475336716000173020ustar00rootroot00000000000000[tox] envlist = py{39,310}-django32 py{39,310,311}-django41 py{39,310,311}-django42 py{310,311,312,313}-django50 py{310,311,312,313}-django51 py{310,311,312,313}-django52 py{312,313}-djangomain py{310,311,312,312}-socialmaster [testenv] passenv = * commands = coverage run manage.py test deps = django32: Django>=3.2,<3.3 django41: Django>=4.1,<4.2 django42: Django>=4.2,<4.3 django50: Django>=5.0,<5.1 django51: Django>=5.1,<5.2 django52: Django>=5.2a1,<5.3 djangomain: https://github.com/django/django/archive/main.tar.gz socialmaster: https://github.com/python-social-auth/social-core/archive/master.tar.gz .[dev]