Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NotImplementedError on authentication while trying to access APIView method #42

Closed
adheus opened this issue Mar 7, 2016 · 9 comments
Closed
Labels

Comments

@adheus
Copy link

adheus commented Mar 7, 2016

Hi, I just setup django-rest-framework-social-oauth2, converted a token succefully(from facebook token to my app token) but when I'm trying to access an @apiview, it keep me showing this error:


File "/Library/Python/2.7/site-packages/social/backends/base.py", line 85, in pipeline
    out = self.run_pipeline(pipeline, pipeline_index, *args, **kwargs)
  File "/Library/Python/2.7/site-packages/social/backends/base.py", line 112, in run_pipeline
    result = func(*args, **out) or {}
  File "/Library/Python/2.7/site-packages/social/pipeline/social_auth.py", line 6, in social_details
    return {'details': dict(backend.get_user_details(response), **details)}
  File "/Library/Python/2.7/site-packages/social/backends/base.py", line 165, in get_user_details
    raise NotImplementedError('Implement in subclass')
NotImplementedError: Implement in subclass

I am using this header "Authorization: Bearer MyApp <converted_token>".

Here is my view:

class VoucherList(APIView):
    permission_classes = (IsAuthenticated,)
    """
    List all user's vouchers
    """
    def get(self, request, format=None):
        vouchers = Voucher.objects.filter(owner=request.user)
        serializer = VoucherSerializer(vouchers, many=True)
        return Response(serializer.data)

And here is my current settings:

"""
Django settings for MyApp project.

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

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

For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.9/ref/settings/
"""

import os

# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
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/1.9/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'mhqk8#$_mq#+btw4vx^g4#e4(o^38$539)672z4wl7hjetij3@'

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True

ALLOWED_HOSTS = []


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'oauth2_provider',
    'social.apps.django_app.default',
    'rest_framework_social_oauth2',
    'djangobower',
    'api',
    'website',
]

PROPRIETARY_BACKEND_NAME = 'MyApp'

AUTHENTICATION_BACKENDS = (

    # Facebook OAuth2
    'social.backends.facebook.FacebookAppOAuth2',
    'social.backends.facebook.FacebookOAuth2',

    # django-rest-framework-social-oauth2
    'rest_framework_social_oauth2.backends.DjangoOAuth2',

    # Django
    'django.contrib.auth.backends.ModelBackend',
)

SOCIAL_AUTH_FACEBOOK_SCOPE = ['email']
SOCIAL_AUTH_FACEBOOK_KEY = '*****'
SOCIAL_AUTH_FACEBOOK_SECRET = '*****'
SOCIAL_AUTH_LOGIN_REDIRECT_URL = '/'

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

ROOT_URLCONF = 'myapp.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',
                'social.apps.django_app.context_processors.backends',
                'social.apps.django_app.context_processors.login_redirect',
            ],
        },
    },
]


REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        # OAuth
        'oauth2_provider.ext.rest_framework.OAuth2Authentication',
        'rest_framework_social_oauth2.authentication.SocialAuthentication',
    )
}

WSGI_APPLICATION = 'myapp.wsgi.application'


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

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
    }
}


# Password validation
# https://docs.djangoproject.com/en/1.9/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/1.9/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_L10N = True

USE_TZ = True


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

STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')

#Media files (JPEG, PNG, VIDEOS)
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')

# Django bower settings
#
STATICFILES_FINDERS = (
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
    'djangobower.finders.BowerFinder',
)

BOWER_COMPONENTS_ROOT = os.path.abspath(os.path.join(BASE_DIR, 'components'))

BOWER_INSTALLED_APPS = (
    'bootstrap-material-design',
    'moment',
    'lrsjng/jquery-qrcode'
)

@adheus
Copy link
Author

adheus commented Mar 7, 2016

I worked my way out of this by creating a superclass on DjangoOAuth2 that implemented get_user_details method and returning an empty dict.


class MyAppOAuth2(DjangoOAuth2):

    def get_user_details(self, response):
        return {}

@PhilipGarnero
Copy link
Collaborator

Yes sorry about the lack of documentation about this.
I didn't implement a default behavior because everyone is doing things differently so in the end inheriting from it seemed the best way to do it.
You figured it out by yourself so I guess it's fine like this.

@adheus
Copy link
Author

adheus commented Mar 8, 2016

I know this is not an issue, but for people who end up here, I will leave my final class implementation. It's a simple workaround that fits my case:

from rest_framework_social_oauth2.backends import DjangoOAuth2
from oauth2_provider.models import AccessToken
from django.contrib.auth.models import User

class MyAppOAuth2(DjangoOAuth2):

    def get_user_details(self, response):
        if response.get(self.ID_KEY, None):
            user = User.objects.get(pk=response[self.ID_KEY])
            return {'username': user.username,
                     'email': user.email,
                     'fullname': user.get_full_name(),
                     'first_name': user.first_name,
                     'last_name': user.last_name
                    }
        return {}

    def user_data(self, access_token, *args, **kwargs):
        try:
            user_id = AccessToken.objects.get(token=access_token).user.pk
            return {self.ID_KEY: user_id}
        except AccessToken.DoesNotExist:
            return None

    def do_auth(self, access_token, *args, **kwargs):
        """Finish the auth process once the access_token was retrieved"""
        data = self.user_data(access_token, *args, **kwargs)
        response = kwargs.get('response') or {}
        response.update(data or {})
        kwargs.update({'response': response, 'backend': self})
        if response.get(self.ID_KEY, None):
            user = User.objects.get(pk=response[self.ID_KEY])
            return user
        else:
            return None

Thank you, @PhilipGarnero for your attention and library.

@arnoldlaishram
Copy link

Where do i put this line of code?

@ivanff
Copy link

ivanff commented Jan 27, 2017

Hi, I don't know why DjangoOAuth2 not finished for work. This class is not completed!!!

@PhilipGarnero
Copy link
Collaborator

PhilipGarnero commented Jan 28, 2017

@ivanff Open source work is provided freely and people offer support on their free time. You are not entitled to anything. Instead of complaining and waiting for others to do your work, do it yourself.

@arealdeadone
Copy link

class MyAppOAuth2(DjangoOAuth2):

def get_user_details(self, response):
    if response.get(self.ID_KEY, None):
        user = User.objects.get(pk=response[self.ID_KEY])
        return {'username': user.username,
                 'email': user.email,
                 'fullname': user.get_full_name(),
                 'first_name': user.first_name,
                 'last_name': user.last_name
                }
    return {}

def user_data(self, access_token, *args, **kwargs):
    try:
        user_id = AccessToken.objects.get(token=access_token).user.pk
        return {self.ID_KEY: user_id}
    except AccessToken.DoesNotExist:
        return None

def do_auth(self, access_token, *args, **kwargs):
    """Finish the auth process once the access_token was retrieved"""
    data = self.user_data(access_token, *args, **kwargs)
    response = kwargs.get('response') or {}
    response.update(data or {})
    kwargs.update({'response': response, 'backend': self})
    if response.get(self.ID_KEY, None):
        user = User.objects.get(pk=response[self.ID_KEY])
        return user
    else:
        return None

Hi, can you please elaborate on where exactly this snippet should go, I tried every possible file, but still end up with the same error.
Help is much appreciated.
Thanks

@ojgenbar
Copy link

ojgenbar commented Jun 8, 2020

In case anyone stuck with where you should put this snippet (as I am).
You need to specify this new backend in settings.py.

If you put this code in file myapp/inner_oauth.py, your backends in settings should be like:


AUTHENTICATION_BACKENDS = (

    # Facebook OAuth2
    'social.backends.facebook.FacebookAppOAuth2',
    'social.backends.facebook.FacebookOAuth2',

    # MyAppOAuth2 based on django-rest-framework-social-oauth2
    'myapp.inner_oauth.MyAppOAuth2',

    # Django
    'django.contrib.auth.backends.ModelBackend',
)

@MilanZiaran
Copy link

Hi, thank you for this solution, it works just fine.

The problem I have is that I can still use expired access_tokens. After calling /convert-token, I acquire an access_token that should expire in 10hrs. But after 10hrs, the token is still usable. I managed to fix this by adding a simple check in the do_auth method, but I'm not sure about the correctness of this solution, as this is pretty important to not be checked within the package.

Maybe I'm missing something? Here's the improved do_auth method.

    def do_auth(self, access_token, *args, **kwargs):
        """Finish the auth process once the access_token was retrieved"""

        token = AccessToken.objects.get(token=access_token)
        if token.is_expired():
            raise AuthenticationFailed(f"Token {token.token} has expired.")
        data = self.user_data(token, *args, **kwargs)
        response = kwargs.get('response') or {}

        response.update(data or {})
        kwargs.update({"response": response, "backend": self})
        if response.get(self.ID_KEY, None):
            user = User.objects.get(pk=response[self.ID_KEY])
            return user
        else:
            return None

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

7 participants