This commit is contained in:
Philippe Luickx 2015-04-30 10:25:34 +00:00
commit 391dada483
5 changed files with 226 additions and 28 deletions

View File

@ -73,3 +73,4 @@ Basing on example from installation section :doc:`Installation </installation>`
- /rest-auth/facebook/ (POST) - /rest-auth/facebook/ (POST)
- access_token - access_token
- code

View File

@ -6,10 +6,11 @@ from allauth.socialaccount.helpers import complete_social_login
class SocialLoginSerializer(serializers.Serializer): class SocialLoginSerializer(serializers.Serializer):
access_token = serializers.CharField(required=True) access_token = serializers.CharField(required=False)
code = serializers.CharField(required=False)
def validate(self, attrs): def validate(self, attrs):
access_token = attrs.get('access_token')
view = self.context.get('view') view = self.context.get('view')
request = self.context.get('request') request = self.context.get('request')
if not isinstance(request, HttpRequest): if not isinstance(request, HttpRequest):
@ -19,20 +20,71 @@ class SocialLoginSerializer(serializers.Serializer):
raise serializers.ValidationError( raise serializers.ValidationError(
'View is not defined, pass it as a context variable' 'View is not defined, pass it as a context variable'
) )
self.adapter_class = getattr(view, 'adapter_class', None) self.adapter_class = getattr(view, 'adapter_class', None)
if not self.adapter_class: if not self.adapter_class:
raise serializers.ValidationError('Define adapter_class in view') raise serializers.ValidationError(
'Define adapter_class in view'
)
self.adapter = self.adapter_class() self.adapter = self.adapter_class()
app = self.adapter.get_provider().get_app(request) app = self.adapter.get_provider().get_app(request)
# More info on code vs access_token
# http://stackoverflow.com/questions/8666316/facebook-oauth-2-0-code-and-token
# We have the access_token straight
if('access_token' in attrs):
access_token = attrs.get('access_token')
# We did not get the access_token, but authorization code instead
elif('code' in attrs):
self.callback_url = getattr(view, 'callback_url', None)
self.client_class = getattr(view, 'client_class', None)
if not self.callback_url:
raise serializers.ValidationError(
'Define callback_url in view'
)
if not self.client_class:
raise serializers.ValidationError(
'Define client_class in view'
)
if not self.callback_url:
raise serializers.ValidationError(
'Define callback_url in view'
)
if not self.client_class:
raise serializers.ValidationError(
'Define client_class in view'
)
code = attrs.get('code')
provider = self.adapter.get_provider()
scope = provider.get_scope(request)
client = self.client_class(
request,
app.client_id,
app.secret,
self.adapter.access_token_method,
self.adapter.access_token_url,
self.callback_url,
scope
)
token = client.get_access_token(code)
access_token = token['access_token']
token = self.adapter.parse_token({'access_token': access_token}) token = self.adapter.parse_token({'access_token': access_token})
token.app = app token.app = app
try: try:
login = self.adapter.complete_login(request, app, token, login = self.adapter.complete_login(
response=access_token) request,
app,
token,
response=access_token,
)
login.token = token login.token = token
complete_social_login(request, login) complete_social_login(request, login)

View File

@ -2,20 +2,41 @@ from django.http import HttpRequest
from rest_framework.views import APIView from rest_framework.views import APIView
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.permissions import AllowAny from rest_framework.permissions import AllowAny
from rest_framework.authtoken.models import Token
from rest_framework import status from rest_framework import status
from allauth.account.views import SignupView, ConfirmEmailView from allauth.account.views import SignupView, ConfirmEmailView
from allauth.account.utils import complete_signup from allauth.account.utils import complete_signup
from allauth.account import app_settings from allauth.account import app_settings
from rest_auth.app_settings import UserDetailsSerializer from rest_auth.app_settings import (
UserDetailsSerializer,
TokenSerializer,
)
from rest_auth.registration.serializers import SocialLoginSerializer from rest_auth.registration.serializers import SocialLoginSerializer
from rest_auth.views import Login from rest_auth.views import (
Login,
EverybodyCanAuthentication,
)
class Register(APIView, SignupView): class Register(APIView, SignupView):
"""
Accepts the credentials and creates a new user
if user does not exist already
Return the REST Token and the user object
if the credentials are valid and authenticated.
Calls allauth complete_signup method
Accept the following POST parameters: username, password
Return the REST Framework Token Object's key
and user object.
"""
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
authentication_classes = (EverybodyCanAuthentication,)
token_model = Token
token_serializer = TokenSerializer
user_serializer_class = UserDetailsSerializer user_serializer_class = UserDetailsSerializer
allowed_methods = ('POST', 'OPTIONS', 'HEAD') allowed_methods = ('POST', 'OPTIONS', 'HEAD')
@ -27,6 +48,8 @@ class Register(APIView, SignupView):
def form_valid(self, form): def form_valid(self, form):
self.user = form.save(self.request) self.user = form.save(self.request)
self.token, created = self.token_model.objects.get_or_create(
user=self.user)
if isinstance(self.request, HttpRequest): if isinstance(self.request, HttpRequest):
request = self.request request = self.request
else: else:
@ -47,8 +70,10 @@ class Register(APIView, SignupView):
return self.get_response_with_errors() return self.get_response_with_errors()
def get_response(self): def get_response(self):
serializer = self.user_serializer_class(instance=self.user) response = self.token_serializer(self.token).data
return Response(serializer.data, status=status.HTTP_201_CREATED) user = self.user_serializer_class(instance=self.user).data
response['user'] = user
return Response(response, status=status.HTTP_201_CREATED)
def get_response_with_errors(self): def get_response_with_errors(self):
return Response(self.form.errors, status=status.HTTP_400_BAD_REQUEST) return Response(self.form.errors, status=status.HTTP_400_BAD_REQUEST)
@ -72,11 +97,25 @@ class VerifyEmail(APIView, ConfirmEmailView):
class SocialLogin(Login): class SocialLogin(Login):
""" """
class used for social authentications class used for social authentications
example usage for facebook example usage for facebook with access_token
-------------
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
class FacebookLogin(SocialLogin): class FacebookLogin(SocialLogin):
adapter_class = FacebookOAuth2Adapter adapter_class = FacebookOAuth2Adapter
-------------
example usage for facebook with code
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
from allauth.socialaccount.providers.oauth2.client import OAuth2Client
class FacebookLogin(SocialLogin):
adapter_class = FacebookOAuth2Adapter
client_class = OAuth2Client
callback_url = 'localhost:8000'
-------------
""" """
serializer_class = SocialLoginSerializer serializer_class = SocialLoginSerializer

View File

@ -1,5 +1,7 @@
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.contrib.auth import authenticate
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm
try: try:
from django.utils.http import urlsafe_base64_decode as uid_decoder from django.utils.http import urlsafe_base64_decode as uid_decoder
@ -8,16 +10,76 @@ except:
from django.utils.http import base36_to_int as uid_decoder from django.utils.http import base36_to_int as uid_decoder
from django.contrib.auth.tokens import default_token_generator from django.contrib.auth.tokens import default_token_generator
from rest_framework import serializers from rest_framework import (
exceptions,
serializers,
)
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from rest_framework.authtoken.serializers import AuthTokenSerializer # from rest_framework.authtoken.serializers import AuthTokenSerializer
from rest_framework.exceptions import ValidationError from rest_framework.exceptions import ValidationError
class LoginSerializer(AuthTokenSerializer): class LoginSerializer(serializers.Serializer):
username = serializers.CharField(required=False)
email = serializers.EmailField(required=False)
password = serializers.CharField(style={'input_type': 'password'})
def validate(self, attrs): def validate(self, attrs):
attrs = super(LoginSerializer, self).validate(attrs) username = attrs.get('username')
email = attrs.get('email')
password = attrs.get('password')
if 'allauth' in settings.INSTALLED_APPS:
from allauth.account import app_settings
# Authentication through email
if app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.EMAIL:
if email and password:
user = authenticate(email=email, password=password)
else:
msg = _('Must include "email" and "password".')
raise exceptions.ValidationError(msg)
# Authentication through username
elif app_settings.AUTHENTICATION_METHOD == app_settings.AuthenticationMethod.USERNAME:
if username and password:
user = authenticate(username=username, password=password)
else:
msg = _('Must include "username" and "password".')
raise exceptions.ValidationError(msg)
# Authentication through either username or email
else:
if email and password:
user = authenticate(email=email, password=password)
elif username and password:
user = authenticate(username=username, password=password)
else:
msg = _('Must include either "username" or "email" and "password".')
raise exceptions.ValidationError(msg)
if user:
if not user.is_active:
msg = _('User account is disabled.')
raise exceptions.ValidationError(msg)
else:
msg = _('Unable to log in with provided credentials.')
raise exceptions.ValidationError(msg)
elif username and password:
user = authenticate(username=username, password=password)
if user:
if not user.is_active:
msg = _('User account is disabled.')
raise exceptions.ValidationError(msg)
else:
msg = _('Unable to log in with provided credentials.')
raise exceptions.ValidationError(msg)
else:
msg = _('Must include "username" and "password".')
raise exceptions.ValidationError(msg)
attrs['user'] = user
if 'rest_auth.registration' in settings.INSTALLED_APPS: if 'rest_auth.registration' in settings.INSTALLED_APPS:
from allauth.account import app_settings from allauth.account import app_settings
if app_settings.EMAIL_VERIFICATION == app_settings.EmailVerificationMethod.MANDATORY: if app_settings.EMAIL_VERIFICATION == app_settings.EmailVerificationMethod.MANDATORY:
@ -25,9 +87,24 @@ class LoginSerializer(AuthTokenSerializer):
email_address = user.emailaddress_set.get(email=user.email) email_address = user.emailaddress_set.get(email=user.email)
if not email_address.verified: if not email_address.verified:
raise serializers.ValidationError('E-mail is not verified.') raise serializers.ValidationError('E-mail is not verified.')
return attrs return attrs
# class LoginSerializer(AuthTokenSerializer):
# def validate(self, attrs):
# attrs = super(LoginSerializer, self).validate(attrs)
# if 'rest_auth.registration' in settings.INSTALLED_APPS:
# from allauth.account import app_settings
# if app_settings.EMAIL_VERIFICATION == app_settings.EmailVerificationMethod.MANDATORY:
# user = attrs['user']
# email_address = user.emailaddress_set.get(email=user.email)
# if not email_address.verified:
# raise serializers.ValidationError('E-mail is not verified.')
# return attrs
class TokenSerializer(serializers.ModelSerializer): class TokenSerializer(serializers.ModelSerializer):
""" """
Serializer for Token model. Serializer for Token model.

View File

@ -8,29 +8,44 @@ from rest_framework.generics import GenericAPIView
from rest_framework.permissions import IsAuthenticated, AllowAny from rest_framework.permissions import IsAuthenticated, AllowAny
from rest_framework.authtoken.models import Token from rest_framework.authtoken.models import Token
from rest_framework.generics import RetrieveUpdateAPIView from rest_framework.generics import RetrieveUpdateAPIView
from rest_framework.authentication import SessionAuthentication
from .app_settings import ( from .app_settings import (
TokenSerializer, UserDetailsSerializer, LoginSerializer, TokenSerializer,
PasswordResetSerializer, PasswordResetConfirmSerializer, UserDetailsSerializer,
PasswordChangeSerializer LoginSerializer,
PasswordResetSerializer,
PasswordResetConfirmSerializer,
PasswordChangeSerializer,
) )
# http://bytefilia.com/titanium-mobile-facebook-application-django-allauth-sign-sign/
class EverybodyCanAuthentication(SessionAuthentication):
def authenticate(self, request):
return None
class Login(GenericAPIView): class Login(GenericAPIView):
""" """
Check the credentials and return the REST Token Check the credentials and return the REST Token
and the user object
if the credentials are valid and authenticated. if the credentials are valid and authenticated.
Calls Django Auth login method to register User ID Calls Django Auth login method to register User ID
in Django session framework in Django session framework
Accept the following POST parameters: username, password Accept the following POST parameters: username, password
Return the REST Framework Token Object's key. Return the REST Framework Token Object's key
and user object.
""" """
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
authentication_classes = (EverybodyCanAuthentication,)
serializer_class = LoginSerializer serializer_class = LoginSerializer
token_model = Token token_model = Token
response_serializer = TokenSerializer response_serializer = TokenSerializer
user_serializer = UserDetailsSerializer
def login(self): def login(self):
self.user = self.serializer.validated_data['user'] self.user = self.serializer.validated_data['user']
@ -40,13 +55,18 @@ class Login(GenericAPIView):
login(self.request, self.user) login(self.request, self.user)
def get_response(self): def get_response(self):
response = self.response_serializer(self.token).data
user = self.user_serializer(instance=self.user).data
response['user'] = user
return Response( return Response(
self.response_serializer(self.token).data, status=status.HTTP_200_OK response,
status=status.HTTP_200_OK
) )
def get_error_response(self): def get_error_response(self):
return Response( return Response(
self.serializer.errors, status=status.HTTP_400_BAD_REQUEST self.serializer.errors,
status=status.HTTP_400_BAD_REQUEST
) )
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
@ -75,8 +95,10 @@ class Logout(APIView):
logout(request) logout(request)
return Response({"success": "Successfully logged out."}, return Response(
status=status.HTTP_200_OK) {"success": "Successfully logged out."},
status=status.HTTP_200_OK
)
class UserDetails(RetrieveUpdateAPIView): class UserDetails(RetrieveUpdateAPIView):
@ -127,7 +149,8 @@ class PasswordReset(GenericAPIView):
class PasswordResetConfirm(GenericAPIView): class PasswordResetConfirm(GenericAPIView):
""" """
Password reset e-mail link is confirmed, therefore this resets the user's password. Password reset e-mail link is confirmed,
therefore this resets the user's password.
Accepts the following POST parameters: new_password1, new_password2 Accepts the following POST parameters: new_password1, new_password2
Accepts the following Django URL arguments: token, uid Accepts the following Django URL arguments: token, uid
@ -141,10 +164,13 @@ class PasswordResetConfirm(GenericAPIView):
serializer = self.get_serializer(data=request.DATA) serializer = self.get_serializer(data=request.DATA)
if not serializer.is_valid(): if not serializer.is_valid():
return Response( return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST serializer.errors,
status=status.HTTP_400_BAD_REQUEST
) )
serializer.save() serializer.save()
return Response({"success": "Password has been reset with the new password."}) return Response(
{"success": "Password has been reset with the new password."}
)
class PasswordChange(GenericAPIView): class PasswordChange(GenericAPIView):
@ -163,7 +189,10 @@ class PasswordChange(GenericAPIView):
serializer = self.get_serializer(data=request.DATA) serializer = self.get_serializer(data=request.DATA)
if not serializer.is_valid(): if not serializer.is_valid():
return Response( return Response(
serializer.errors, status=status.HTTP_400_BAD_REQUEST serializer.errors,
status=status.HTTP_400_BAD_REQUEST
) )
serializer.save() serializer.save()
return Response({"success": "New password has been saved."}) return Response(
{"success": "New password has been saved."}
)