Skip to content

Commit

Permalink
Merge ec5024f into e4fe94a
Browse files Browse the repository at this point in the history
  • Loading branch information
Georgeygigz committed Jan 25, 2019
2 parents e4fe94a + ec5024f commit b093fc5
Show file tree
Hide file tree
Showing 11 changed files with 232 additions and 11 deletions.
7 changes: 7 additions & 0 deletions .env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,10 @@ export PASSWORD_RESET_URL="127.0.0.1:8000/api"
export DEFAULT_FROM_EMAIL="senders email"
export SECRET_KEY="enter your secret key"
export EMAIL_VERIFICATION_URL="http://127.0.0.1:8000/"

export FACEBOOK_APP_ID="your_facebook_app_id"
export FACEBOOK_APP_SECRET="your_facebook_app_secret"
export OAUTH2_KEY="your_google_app_secret"
export OAUTH2_SECRET="your_google_secret"
export TWITTER_KEY="your_twitter_key"
export TWITTER_SECRET="your_twitter_secret"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ var/
*.egg
.idea/


# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
Expand Down
4 changes: 2 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ services:
- postgresql

addons:
postgresql: "9.6"
postgresql: '9.6'

before-script:
- psql -c 'CREATE DATABASE test;' -U postgres
- python manage.py makemigrations
- python manage.py makemigrations
- python manage.py migrate

script:
Expand Down
4 changes: 3 additions & 1 deletion authors/apps/authentication/backends.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import jwt

from django.conf import settings
from django.http import HttpResponse
from rest_framework.authentication import BaseAuthentication
from rest_framework.authentication import get_authorization_header
from rest_framework import status, exceptions
import jwt
from .models import User


Expand Down Expand Up @@ -50,3 +51,4 @@ def authenticate_credentials(self, token):
status=status.HTTP_403_FORBIDDEN)

return user, token

2 changes: 1 addition & 1 deletion authors/apps/authentication/migrations/0001_initial.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by Django 2.1.4 on 2019-01-08 14:41
# Generated by Django 2.1.4 on 2019-01-18 07:38

from django.db import migrations, models

Expand Down
7 changes: 7 additions & 0 deletions authors/apps/authentication/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,10 @@ def update(self, instance, validated_data):
instance.save()

return instance


class SocialAuthSerializer(serializers.Serializer):
"""Social auth serializers."""
provider = serializers.CharField(max_length=255, required=True)
access_token = serializers.CharField(max_length=1024, required=True, trim_whitespace=True)

87 changes: 87 additions & 0 deletions authors/apps/authentication/tests/test_social_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import json
import os
from django.urls import reverse
from rest_framework.views import status
from rest_framework.test import APITestCase, APIClient


class SocialAuthTest(APITestCase):
"""Test social authentication funcionality."""
client = APIClient

def setUp(self):
self.social_oauth_url = reverse('authentication:social_auth')
self.google_access_token = os.getenv('GOOGLE_TOKEN')
self.facebook_access_token = os.getenv('FACEBOOK_TOKEN')
self.oauth1_access_token = os.getenv('TWITTER_TOKEN')
self.oauth1_access_token_secret = os.getenv('TWITTER_SECRET_TOKEN')

self.invalid_provider = {
"provider": "google-oauth21",
"access_token": self.google_access_token

}
self.google_provider = {
"provider": "google-oauth2",
"access_token": self.google_access_token
}
self.facebook_provider = {
"provider": "facebook",
"access_token": self.facebook_access_token
}
self.twitter_provider = {
"provider": "twitter",
"access_token": self.oauth1_access_token,
"access_token_secret": self.oauth1_access_token_secret
}

def test_rejects_invalid_provider_name(self):
"""Test invalid provider name."""
response = self.client.post(
self.social_oauth_url, self.invalid_provider, format='json')
self.assertEqual(response.data['error'],
'Please enter a valid social provider')
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

def test_login_with_google(self):
"""Test login with google"""
response = self.client.post(
self.social_oauth_url, self.google_provider, format='json')
self.assertIn('email', response.data)
self.assertIn('token', response.data)
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_login_with_facebook(self):
"""Test login with facebook."""
response = self.client.post(
self.social_oauth_url, self.facebook_provider, format='json')
self.assertIn('email', response.data)
self.assertIn('token', response.data)
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_login_with_twitter(self):
"""Test login with twitter."""
response = self.client.post(
self.social_oauth_url, self.twitter_provider, format='json')
self.assertIn('email', response.data)
self.assertIn('token', response.data)
self.assertEqual(response.status_code, status.HTTP_200_OK)

def test_rejects_login_missing_access_token(self):
"""Test missing token."""
response = self.client.post(self.social_oauth_url,
data={"provider": 'facebook'},
format='json')
self.assertEqual(json.loads(response.content),
{"errors": {"access_token":
["This field is required."]}})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)

def test_rejects_login_missing_provider_name(self):
"""Test missing provider."""
response = self.client.post(
self.social_oauth_url,
data={"access_token": self.google_access_token}, format='json')
self.assertEqual(json.loads(response.content), {"errors": {"provider":
["This field is required."]}})
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
6 changes: 3 additions & 3 deletions authors/apps/authentication/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

from .views import (
LoginAPIView, RegistrationAPIView, UserRetrieveUpdateAPIView,
ResetPasswordAPIView, UpdatePasswordAPIView, VerifyAPIView,ResendVerifyAPIView
ResetPasswordAPIView, UpdatePasswordAPIView, VerifyAPIView,
ResendVerifyAPIView, SocialAuthenticationView
)

app_name = "authentication"
Expand All @@ -15,6 +16,5 @@
path('users/passwordresetdone/<token>', UpdatePasswordAPIView.as_view(), name="passwordresetdone"),
path('users/verify/<token>/', VerifyAPIView.as_view(), name="auth-verify"),
path('users/resend-verification/', ResendVerifyAPIView.as_view(), name="auth-reverify"),


path('login/oauth/', SocialAuthenticationView.as_view(), name="social_auth")
]
81 changes: 77 additions & 4 deletions authors/apps/authentication/views.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,32 @@
import os
import jwt
from datetime import datetime, timedelta
from social_django.utils import load_strategy, load_backend
from social_core.exceptions import MissingBackend, AuthAlreadyAssociated
from social_core.backends.oauth import BaseOAuth1, BaseOAuth2

from django.conf import settings
from django.core.mail import send_mail
from django.template.loader import render_to_string
from django.db import IntegrityError
from rest_framework import status, generics
from rest_framework.permissions import AllowAny, IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework.renderers import BrowsableAPIRenderer
from .renderers import UserJSONRenderer


from . import models
from .renderers import UserJSONRenderer
from .serializers import (
LoginSerializer, RegistrationSerializer, UserSerializer,
ResetSerializerEmail, ResetSerializerPassword
ResetSerializerEmail, ResetSerializerPassword, SocialAuthSerializer
)
from .models import User
from .utils import send_link


class RegistrationAPIView(generics.CreateAPIView):

# Allow any user (authenticated or not) to hit this endpoint.
permission_classes = (AllowAny,)
renderer_classes = (UserJSONRenderer,)
Expand Down Expand Up @@ -194,4 +201,70 @@ def put(self, request, token, **kwargs):
except jwt.ExpiredSignatureError:
return Response({"The link expired"},
status=status.HTTP_400_BAD_REQUEST)



class SocialAuthenticationView(generics.CreateAPIView):
"""Social authentication."""
permission_classes = (AllowAny,)
serializer_class = SocialAuthSerializer
render_classes = (UserJSONRenderer,)

def create(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
provider = request.data['provider']
strategy = load_strategy(request)
authenticated_user = request.user if not request.user.is_anonymous else None

try:
backend = load_backend(
strategy=strategy,
name=provider,
redirect_uri=None
)

if isinstance(backend, BaseOAuth1):
if "access_token_secret" in request.data:
token = {
'oauth_token': request.data['access_token'],
'oauth_token_secret':
request.data['access_token_secret']
}
else:
return Response({'error':
'Please enter your secret token'},
status=status.HTTP_400_BAD_REQUEST)
elif isinstance(backend, BaseOAuth2):
token = serializer.data.get('access_token')
except MissingBackend:
return Response({
'error': 'Please enter a valid social provider'
}, status=status.HTTP_400_BAD_REQUEST)

try:
user = backend.do_auth(token, user=authenticated_user)
except (AuthAlreadyAssociated, IntegrityError):
return Response({
"errors": "You are already logged in with another account"},
status=status.HTTP_400_BAD_REQUEST)
except BaseException:
return Response({
"errors": "Invalid token"},
status=status.HTTP_500_INTERNAL_SERVER_ERROR)
if user:
user.is_active = True
username = user.username
email = user.email

date = datetime.now() + timedelta(days=20)
payload = {
'email': email,
'username': username,
'exp': int(date.strftime('%s'))
}
user_token = jwt.encode(
payload, settings.SECRET_KEY, algorithm='HS256')
serializer = UserSerializer(user)
serialized_details = serializer.data
serialized_details["token"] = user_token
return Response(serialized_details, status.HTTP_200_OK)
39 changes: 39 additions & 0 deletions authors/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,17 @@
'rest_framework',
'taggit',


'authors.apps.authentication',
'authors.apps.core',
'authors.apps.profiles',
'authors.apps.friends',
'authors.apps.articles',
'oauth2_provider',
'social_django',
'rest_framework_social_oauth2',



# Enables API to be documented using Swagger
'rest_framework_swagger',
Expand Down Expand Up @@ -153,7 +159,40 @@
'authors.apps.authentication.backends.JWTAuthentication',
),
}
AUTHENTICATION_BACKENDS = (
'social_core.backends.google.GoogleOAuth2',
'social_core.backends.twitter.TwitterOAuth',
'social_core.backends.facebook.FacebookOAuth2',
'django.contrib.auth.backends.ModelBackend',
)

SOCIAL_AUTH_FACEBOOK_KEY = os.getenv('FACEBOOK_APP_ID')
SOCIAL_AUTH_FACEBOOK_SECRET = os.getenv('FACEBOOK_APP_SECRET')
SOCIAL_AUTH_FACEBOOK_SCOPE = ['email']
SOCIAL_AUTH_FACEBOOK_PROFILE_EXTRA_PARAMS = {
'fields': 'id, name, email'
}

SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = os.getenv('OAUTH2_KEY')
SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = os.getenv('OAUTH2_SECRET')
SOCIAL_AUTH_GOOGLE_OAUTH_SCOPE = ['email', 'username', 'password']

SOCIAL_AUTH_TWITTER_KEY = os.getenv('TWITTER_KEY')
SOCIAL_AUTH_TWITTER_SECRET = os.getenv('TWITTER_SECRET')
SOCIAL_AUTH_TWITTER_SCOPE = ['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',
)
django_heroku.settings(locals())

# Set the settings for Swagger documentation
Expand Down
5 changes: 5 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,8 @@ six==1.12.0
pytz==2018.9
cloudinary==1.15.0
Pillow==5.4.1
social-auth-app-django==3.1.0
social-auth-core==3.0.0
django-rest-framework-social-oauth2==1.1.0
django-oauth-toolkit==1.2.0
coverage==4.5.2

0 comments on commit b093fc5

Please sign in to comment.