python-social-auth / social-app-django

Python Social Auth - Application - Django
BSD 3-Clause "New" or "Revised" License
2.01k stars 374 forks source link

AttributeError: 'NoneType' object has no attribute 'get' #286

Open cheslijones opened 3 years ago

cheslijones commented 3 years ago

Been trying to fix this for a few days now.

Error:

[api] Internal Server Error: /api/social/azuread-tenant-oauth2/
[api] Traceback (most recent call last):
[api]   File "/usr/local/lib/python3.8/site-packages/django/core/handlers/exception.py", line 47, in inner
[api]     response = get_response(request)
[api]   File "/usr/local/lib/python3.8/site-packages/django/core/handlers/base.py", line 179, in _get_response
[api]     response = wrapped_callback(request, *callback_args, **callback_kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
[api]     return view_func(*args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/django/views/generic/base.py", line 70, in view
[api]     return self.dispatch(request, *args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 509, in dispatch
[api]     response = self.handle_exception(exc)
[api]   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 469, in handle_exception
[api]     self.raise_uncaught_exception(exc)
[api]   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 480, in raise_uncaught_exception
[api]     raise exc
[api]   File "/usr/local/lib/python3.8/site-packages/rest_framework/views.py", line 506, in dispatch
[api]     response = handler(request, *args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/rest_framework/decorators.py", line 50, in handler
[api]     return func(*args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/social_django/utils.py", line 49, in wrapper
[api]     return func(request, backend, *args, **kwargs)
[api]   File "/app/users/views.py", line 85, in exchange_token
[api]     user = request.backend.do_auth(
[api]   File "/usr/local/lib/python3.8/site-packages/social_core/utils.py", line 251, in wrapper
[api]     return func(*args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/social_core/backends/oauth.py", line 410, in do_auth
[api]     data = self.user_data(access_token, *args, **kwargs)
[api]   File "/usr/local/lib/python3.8/site-packages/social_core/backends/azuread_tenant.py", line 97, in user_data
[api]     id_token = response.get('id_token')
[api] AttributeError: 'NoneType' object has no attribute 'get'
[api] HTTP POST /api/social/azuread-tenant-oauth2/ 500 [0.12, 172.17.0.6:36484]
# requirements.py

asgiref==3.2.10
astroid==2.4.2
attrs==20.2.0
autobahn==20.7.1
Automat==20.2.0
autopep8==1.5.4
certifi==2020.6.20
cffi==1.14.3
channels==2.4.0
chardet==3.0.4
constantly==15.1.0
cryptography==3.1.1
daphne==2.5.0
defusedxml==0.7.0rc1
Django==3.1.2
django-oauth-toolkit==1.3.3
djangorestframework==3.12.1
djangorestframework-simplejwt==4.4.0
hyperlink==20.0.1
idna==2.10
incremental==17.5.0
isort==5.6.4
lazy-object-proxy==1.4.3
mccabe==0.6.1
oauthlib==3.1.0
psycopg2-binary==2.8.6
pyasn1==0.4.8
pyasn1-modules==0.2.8
pycodestyle==2.6.0
pycparser==2.20
PyHamcrest==2.0.2
PyJWT==1.7.1
pylint==2.6.0
pyOpenSSL==19.1.0
python3-openid==3.2.0
pytz==2020.1
requests==2.24.0
requests-oauthlib==1.3.0
service-identity==18.1.0
six==1.15.0
social-auth-app-django==4.0.0
social-auth-core==3.3.3
sqlparse==0.4.1
toml==0.10.1
Twisted==20.3.0
txaio==20.4.1
urllib3==1.25.11
whitenoise==5.2.0
wrapt==1.12.1
zope.interface==5.1.2
# settings.py

"""
Django settings for config project.

Generated by 'django-admin startproject' using Django 3.1.2.

For more information on this file, see
https://docs.djangoproject.com/en/3.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/
"""
import os
from datetime import timedelta

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ['SECRET_KEY']

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.environ['DEBUG'] == 'True'

ALLOWED_HOSTS = [
    os.environ['DOMAIN'],
    'companyapp.local',
    '.company.com'
]

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    # Third-party packages
    'channels',
    'rest_framework',
    'rest_framework.authtoken',
    'social_django',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
]

ROOT_URLCONF = 'config.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',             
            ],
        },
    },
]

WSGI_APPLICATION = 'config.wsgi.application'
ASGI_APPLICATION = "config.routing.application"

# Database
# https://docs.djangoproject.com/en/3.1/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ['PGDATABASE'],
        'USER': os.environ['PGUSER'],
        'PASSWORD': os.environ['PGPASSWORD'],
        'HOST': os.environ['PGHOST'],
        'PORT': 5432
    }
}

# REST Framework settings
# https://www.django-rest-framework.org/
REST_FRAMEWORK = {
    'DEFAULT_RENDERER_CLASSES': [
        'rest_framework.renderers.JSONRenderer',
    ],
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.AllowAny',
    ],
}

# SIMPLE_JWT Settings
# https://github.com/davesque/django-rest-framework-simplejwt
SIMPLE_JWT = {
    'ACCESS_TOKEN_LIFETIME': timedelta(days=3),
    'REFRESH_TOKEN_LIFETIME': timedelta(days=1),
    'SIGNING_KEY': os.environ['SECRET_KEY'],
}

AUTHENTICATION_BACKENDS = (
    'social_core.backends.azuread_tenant.AzureADTenantOAuth2',
    'django.contrib.auth.backends.ModelBackend',
)

# SOCIAL_AUTH_POSTGRES_JSONFIELD = True

SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_KEY = '<client_id>'
SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_SECRET = '<client_secret>'
# SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_RESOURCE = ''
SOCIAL_AUTH_AZUREAD_TENANT_OAUTH2_TENANT_ID = '<tenant_id>'

SOCIAL_AUTH_PIPELINE = (
    'social_core.pipeline.social_auth.social_details',
    'social_core.pipeline.social_auth.social_uid',
    'social_core.pipeline.social_auth.auth_allowed',
    'social_core.pipeline.social_auth.social_user',
    'social_core.pipeline.user.get_username',
    'social_core.pipeline.social_auth.associate_by_email', 
    'social_core.pipeline.user.create_user',
    'social_core.pipeline.social_auth.associate_user',
    'social_core.pipeline.social_auth.load_extra_data',
    'social_core.pipeline.user.user_details',
)

# Password validation
# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

# Internationalization
# https://docs.djangoproject.com/en/3.1/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'America/Los_Angeles'

USE_I18N = True

USE_L10N = True

USE_TZ = True

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.1/howto/static-files/

STATIC_URL = '/static/'

# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/2.2/howto/static-files/
# STATIC_URL = '/static/'
STATIC_URL = '/api/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

# Define the upload and download directories
DOWNLOAD_ROOT = '/mnt/company-files/client-downloads/'
MEDIA_ROOT = '/mnt/company-files/client-submissions/'
# urls.py

from django.contrib import admin
from django.urls import path, include

from users.views import exchange_token

urlpatterns = [
    path('api/admin/', admin.site.urls),
    path('api/social/<str:backend>/', exchange_token),
]
# views.py

from django.conf import settings
from rest_framework.authentication import TokenAuthentication
from rest_framework import serializers
from rest_framework import status
from rest_framework.authtoken.models import Token
from rest_framework.decorators import api_view, permission_classes, authentication_classes
from rest_framework.permissions import AllowAny
from rest_framework.response import Response

from requests.exceptions import HTTPError

from social_django.utils import psa

class SocialSerializer(serializers.Serializer):
    """
    Serializer which accepts an OAuth2 access token.
    """
    access_token = serializers.CharField(
        allow_blank=False,
        trim_whitespace=True,
    )

@api_view(http_method_names=['POST'])
@permission_classes([AllowAny])
@authentication_classes([TokenAuthentication])
@psa()
def exchange_token(request, backend):
    """
    Exchange an OAuth2 access token for one for this site.
    This simply defers the entire OAuth2 process to the front end.
    The front end becomes responsible for handling the entirety of the
    OAuth2 process; we just step in at the end and use the access token
    to populate some user identity.
    The URL at which this view lives must include a backend field, like:
        url(API_ROOT + r'social/(?P<backend>[^/]+)/$', exchange_token),
    Using that example, you could call this endpoint using i.e.
        POST API_ROOT + 'social/facebook/'
        POST API_ROOT + 'social/google-oauth2/'
    Note that those endpoint examples are verbatim according to the
    PSA backends which we configured in settings.py. If you wish to enable
    other social authentication backends, they'll get their own endpoints
    automatically according to PSA.
    ## Request format
    Requests must include the following field
    - `access_token`: The OAuth2 access token provided by the provider
    """
    serializer = SocialSerializer(data=request.data)
    if serializer.is_valid(raise_exception=True):
        # set up non-field errors key
        # http://www.django-rest-framework.org/api-guide/exceptions/#exception-handling-in-rest-framework-views
        try:
            nfe = settings.NON_FIELD_ERRORS_KEY
        except AttributeError:
            nfe = 'non_field_errors'

        try:
            # this line, plus the psa decorator above, are all that's necessary to
            # get and populate a user object for any properly enabled/configured backend
            # which python-social-auth can handle.
            print(serializer.validated_data['access_token'])
            user = request.backend.do_auth(
                serializer.validated_data['access_token'])
        except HTTPError as e:
            # An HTTPError bubbled up from the request to the social auth provider.
            # This happens, at least in Google's case, every time you send a malformed
            # or incorrect access key.
            return Response(
                {'errors': {
                    'token': 'Invalid token',
                    'detail': str(e),
                }},
                status=status.HTTP_400_BAD_REQUEST,
            )

        if user:
            if user.is_active:
                token, _ = Token.objects.get_or_create(user=user)
                return Response({'token': token.key})
            else:
                # user is not active; at some point they deleted their account,
                # or were banned by a superuser. They can't just log in with their
                # normal credentials anymore, so they can't log in with social
                # credentials either.
                return Response(
                    {'errors': {nfe: 'This user account is inactive'}},
                    status=status.HTTP_400_BAD_REQUEST,
                )
        else:
            # Unfortunately, PSA swallows any information the backend provider
            # generated as to why specifically the authentication failed;
            # this makes it tough to debug except by examining the server logs.
            return Response(
                {'errors': {nfe: "Authentication Failed"}},
                status=status.HTTP_400_BAD_REQUEST,
            )

This is the line it refers to in the traceback:

https://github.com/python-social-auth/social-core/blob/9d93069564a60495e0ebd697b33e16fcff14195b/social_core/backends/azuread_tenant.py#L97

I've verified my ReactJS FE is correctly sending the access_token as I can print it out server-side with print(request.data) and print(serializer.validated_data['access_token']). Everything seems to be flowing properly until it gets to verifying the access_token server-side at:

            user = request.backend.do_auth(
                serializer.validated_data['access_token'])

The user it is trying to validate should not be in the database currently, if that helps.

Suggestions for what I'm doing wrong here or how to resolve it?

asencis commented 3 years ago

Quite disappointing this didn't receive any sort of reply ...

avvijeet commented 3 years ago

I think its supposed to be serializer.is_valid(raise_exception=True) serializer.data

TejashwiniDuluri commented 3 years ago

Try changing this line user = request.backend.do_auth( serializer.validated_data['access_token'])

with

user = backend.do_auth( access_token, response={"id_token": id_token}) #Id token you will get along with access token.

Jayanth1812 commented 3 years ago

I am getting the same AttributeError:

AttributeError: 'NoneType' object has no attribute 'get'

Traceback (most recent call last) File "C:\Users\Jayanth.virtualenvs\helloworld-CPu4KsHf\lib\site-packages\flask\app.py", line 2088, in call return self.wsgi_app(environ, start_response) File "C:\Users\Jayanth.virtualenvs\helloworld-CPu4KsHf\lib\site-packages\flask\app.py", line 2073, in wsgi_app response = self.handle_exception(e) File "C:\Users\Jayanth.virtualenvs\helloworld-CPu4KsHf\lib\site-packages\flask\app.py", line 2070, in wsgi_app response = self.full_dispatch_request() File "C:\Users\Jayanth.virtualenvs\helloworld-CPu4KsHf\lib\site-packages\flask\app.py", line 1515, in full_dispatch_request rv = self.handle_user_exception(e) File "C:\Users\Jayanth.virtualenvs\helloworld-CPu4KsHf\lib\site-packages\flask\app.py", line 1513, in full_dispatch_request rv = self.dispatch_request() File "C:\Users\Jayanth.virtualenvs\helloworld-CPu4KsHf\lib\site-packages\flask\app.py", line 1499, in dispatch_request return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args) File "D:\ExcelSoft\venil\Cognitive-Behavioural-Chatbot-master\helloworld\web.py", line 26, in webhook res = processRequest(req) File "D:\ExcelSoft\venil\Cognitive-Behavioural-Chatbot-master\helloworld\web.py", line 36, in processRequest print ("starting processRequest...",req.get("result").get("action")) AttributeError: 'NoneType' object has no attribute 'get'

The code is: web.txt

ArshadNm commented 2 years ago

How to correct what is written below (python 3):


Exception in Tkinter callback
Traceback (most recent call last):
  File "C:\Users\Dani Brothers\Anaconda3\lib\tkinter\__init__.py", line 1705, in __call__
    return self.func(*args)
  File "D:/Python/Book Bank/New folder/PyCharm/Final/Excel.py", line 57, in SaveBook
    e_pissue.get(),
AttributeError: 'NoneType' object has no attribute 'get'

Process finished with exit code 0