Skip to content

Commit

Permalink
feature(social login): integrate social login
Browse files Browse the repository at this point in the history
- add social authentication packages
- add social authentication endpoint
- add migrations to flake8 excluded folders

[finishes #164046249]
  • Loading branch information
ElMonstro committed Mar 11, 2019
1 parent 1ba3ed9 commit 4aee58a
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .flake8
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[flake8]
ignore = W391
max-line-length = 80
exclude = */tests/* */settings/*
exclude = */tests/* */settings/* */migrations/*
2 changes: 2 additions & 0 deletions authors/apps/authentication/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ class User(AbstractBaseUser, PermissionsMixin):
# but we can still analyze the data.
is_active = models.BooleanField(default=True)

is_verified = models.BooleanField(default=False)

# The `is_staff` flag is expected by Django to determine who can and cannot
# log into the Django admin site. For most users, this flag will always be
# falsed.
Expand Down
8 changes: 8 additions & 0 deletions authors/apps/authentication/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,11 @@ def update(self, instance, validated_data):
instance.profile.save()

return instance


class SocialAuthenticationSerializer(serializers.Serializer):
""" Holder for provider, acces token , and access_token_secret"""
access_token = serializers.CharField(max_length=500, required=True)
access_token_secret = serializers.CharField(
max_length=500, allow_blank=True)
provider = serializers.CharField(max_length=500, required=True)
65 changes: 65 additions & 0 deletions authors/apps/authentication/tests/test_social_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import os
from django.urls import reverse
from rest_framework import status
from rest_framework.test import APITestCase, APIClient



class Test_Social_Authentication(APITestCase):
"""Tests social authentication"""
url = reverse('authentication:social_login')

def setUp(self):
self.provider = "twitter"
self.data = {
"access_token": os.environ['TWITTER_ACCESS_TOKEN'],
"access_token_secret": os.environ['TWITTER_ACCESS_TOKEN_SECRET'],
"provider": self.provider
}

def test_user_successful_social_login(self):
"""Test for successful user login"""
response = self.client.post(self.url, self.data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_wrong_provider_message(self):
self.data = {
"access_token": os.environ['TWITTER_ACCESS_TOKEN'],
"access_token_secret": os.environ['TWITTER_ACCESS_TOKEN_SECRET'],
"provider": "twitt"
}
error_msg = """Provider not supported, Please use 'google-oauth2',
'facebook', or 'twitter'."""
response = self.client.post(self.url, self.data, format='json')
self.assertEqual(response.data['error'], error_msg)

def test_wrong_credentials(self):
"""Test wrong credentials"""
self.data = {
"access_token": os.environ['TWITTER_ACCESS_TOKEN_INVALID'],
"access_token_secret": os.environ['TWITTER_ACCESS_TOKEN_SECRET'],
"provider": "twitter"
}
response = self.client.post(self.url, self.data, format='json')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

def test_facebook_oauth(self):
"""Test facebook Oauth"""
self.data = {
"access_token": os.environ['FACEBOOK_ACCESS_TOKEN'],
"access_token_secret": os.environ['TWITTER_ACCESS_TOKEN_SECRET'],
"provider": "facebook"
}
response = self.client.post(self.url, self.data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_google_auth(self):
"""Test Google Oauth"""
self.data = {
"access_token": os.environ['GOOGLE_ACCESS_TOKEN'],
"access_token_secret": os.environ['TWITTER_ACCESS_TOKEN_SECRET'],
"provider": "google-oauth2"
}

response = self.client.post(self.url, self.data, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
5 changes: 4 additions & 1 deletion authors/apps/authentication/urls.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
from django.urls import path

from .views import (LoginAPIView, RegistrationAPIView,
UserRetrieveUpdateAPIView)
UserRetrieveUpdateAPIView, SocialAuthenticationView)

app_name = 'authentication'

urlpatterns = [
path('user/', UserRetrieveUpdateAPIView.as_view(), name='get users'),
path('users/', RegistrationAPIView.as_view(), name='register'),
path('users/login/', LoginAPIView.as_view(), name='login'),
path('users/oauth/', SocialAuthenticationView.as_view(),
name='social_login')

]
61 changes: 59 additions & 2 deletions authors/apps/authentication/views.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
from rest_framework import status
from rest_framework.generics import RetrieveUpdateAPIView
from rest_framework.generics import RetrieveUpdateAPIView, CreateAPIView
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from drf_yasg.utils import swagger_auto_schema

from social_django.utils import load_strategy, load_backend
from social_core.exceptions import MissingBackend
from social_core.backends.oauth import BaseOAuth1, BaseOAuth2

from .renderers import UserJSONRenderer
from .serializers import (LoginSerializer, RegistrationSerializer,
UserSerializer)
UserSerializer, SocialAuthenticationSerializer)
from .utils import validate_image


Expand Down Expand Up @@ -124,3 +128,56 @@ def update(self, request, *args, **kwargs):
serializer.save()

return Response(serializer.data, status=status.HTTP_200_OK)


class SocialAuthenticationView(CreateAPIView):
"""
Login to the site via social authentication
services (Google, Twitter, Facebook)
"""
permission_classes = (AllowAny,)
serializer_class = SocialAuthenticationSerializer
renderer_classes = (UserJSONRenderer,)

def create(self, request):
"""Creates user if not present and returns an authentication token"""
serializer = self.serializer_class(data=request.data)

serializer.is_valid(raise_exception=True)
provider = serializer.data.get("provider")
authenticated_user = request.user if not \
request.user.is_anonymous else None
strategy = load_strategy(request)

# Load backend associated with the provider
try:

backend = load_backend(
strategy=strategy, name=provider, redirect_uri=None)
if isinstance(backend, BaseOAuth1):
access_token = {
'oauth_token': request.data['access_token'],
'oauth_token_secret': request.data['access_token_secret']
}
elif isinstance(backend, BaseOAuth2):
access_token = serializer.data.get("access_token")

except MissingBackend:
error_msg = """Provider not supported, Please use 'google-oauth2',
'facebook', or 'twitter'."""
return Response({"error": error_msg},
status=status.HTTP_400_BAD_REQUEST)

try:
user = backend.do_auth(access_token, user=authenticated_user)

except BaseException as error:
return Response({"error": str(error)},
status=status.HTTP_400_BAD_REQUEST)

if user and user.is_active:
user.is_verified = True
user.save()
serializer = UserSerializer(user)
serializer.instance = user
return Response(serializer.data, status=status.HTTP_200_OK)
46 changes: 46 additions & 0 deletions authors/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
'django_extensions',
'rest_framework',
'drf_yasg',
'social_django',

'authors.apps.authentication',
'authors.apps.core',
Expand Down Expand Up @@ -72,6 +73,8 @@
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'social_django.context_processors.backends',
'social_django.context_processors.login_redirect',
],
},
},
Expand Down Expand Up @@ -138,3 +141,46 @@
'api_secret': config('CLOUDINARY_API_SECRET'),
'secure': True
}

SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = os.environ['SOCIAL_AUTH_GOOGLE_OAUTH2_KEY']
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = os.environ['SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET']
SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = ['email', 'username']

SOCIAL_AUTH_TWITTER_KEY = os.environ['SOCIAL_AUTH_TWITTER_KEY']
SOCIAL_AUTH_TWITTER_SECRET = os.environ['SOCIAL_AUTH_TWITTER_SECRET']
SOCIAL_AUTH_TWITTER_SCOPE = ['email']

SOCIAL_AUTH_FACEBOOK_KEY = os.environ['SOCIAL_AUTH_FACEBOOK_KEY']
SOCIAL_AUTH_FACEBOOK_SECRET = os.environ['SOCIAL_AUTH_FACEBOOK_SECRET']
SOCIAL_AUTH_FACEBOOK_SCOPE = ['email']
SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {
'fields': 'id, name, email',
}
FACEBOOK_EXTENDED_PERMISSIONS = ['email']


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',
)


AUTHENTICATION_BACKENDS = (
# Google OAuth2
'social_core.backends.google.GoogleOAuth2',
# Facebook OAuth2
'social_core.backends.facebook.FacebookAppOAuth2',
'social_core.backends.facebook.FacebookOAuth2',

'social_core.backends.twitter.TwitterOAuth',
# Django
'django.contrib.auth.backends.ModelBackend',
)
10 changes: 10 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ coreapi==2.3.3
coreschema==0.0.4
coverage==4.5.2
coveralls==1.6.0
defusedxml==0.5.0
dj-database-url==0.5.0
Django==2.1.7
django-braces==1.13.0
django-cors-middleware==1.3.1
django-extensions==2.1.6
django-oauth-toolkit==1.2.0
django-rest-framework-social-oauth2==1.1.0
djangorestframework==3.9.1
docopt==0.6.2
drf-yasg==1.13.0
Expand All @@ -27,17 +31,23 @@ MarkupSafe==1.1.1
mccabe==0.6.1
mock==2.0.0
pbr==5.1.3
oauthlib==3.0.1
psycopg2==2.7.7
psycopg2-binary==2.7.7
pycodestyle==2.5.0
pyflakes==2.1.0
PyJWT==1.4.2
pylint==2.3.0
python-decouple==3.1
python-social-auth==0.3.6
python3-openid==3.1.0
pytz==2018.9
requests==2.21.0
requests-oauthlib==1.2.0
ruamel.yaml==0.15.89
six==1.10.0
social-auth-app-django==3.1.0
social-auth-core==3.1.0
typed-ast==1.3.1
uritemplate==3.0.0
urllib3==1.24.1
Expand Down

0 comments on commit 4aee58a

Please sign in to comment.