Skip to content

A few fixes, cleaned up in nice commits #95

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

Merged
merged 10 commits into from
Aug 14, 2015
1 change: 1 addition & 0 deletions docs/api_endpoints.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,4 @@ Basing on example from installation section :doc:`Installation </installation>`
- /rest-auth/facebook/ (POST)

- access_token
- code
12 changes: 6 additions & 6 deletions docs/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ Configuration
You can define your custom serializers for each endpoint without overriding urls and views by adding ``REST_AUTH_SERIALIZERS`` dictionary in your django settings.
Possible key values:

- LOGIN_SERIALIZER - serializer class in ``rest_auth.views.Login``, default value ``rest_auth.serializers.LoginSerializer``
- LOGIN_SERIALIZER - serializer class in ``rest_auth.views.LoginView``, default value ``rest_auth.serializers.LoginSerializer``

- TOKEN_SERIALIZER - response for successful authentication in ``rest_auth.views.Login``, default value ``rest_auth.serializers.TokenSerializer``
- TOKEN_SERIALIZER - response for successful authentication in ``rest_auth.views.LoginView``, default value ``rest_auth.serializers.TokenSerializer``

- USER_DETAILS_SERIALIZER - serializer class in ``rest_auth.views.UserDetails``, default value ``rest_auth.serializers.UserDetailsSerializer``
- USER_DETAILS_SERIALIZER - serializer class in ``rest_auth.views.UserDetailsView``, default value ``rest_auth.serializers.UserDetailsSerializer``

- PASSWORD_RESET_SERIALIZER - serializer class in ``rest_auth.views.PasswordReset``, default value ``rest_auth.serializers.PasswordResetSerializer``
- PASSWORD_RESET_SERIALIZER - serializer class in ``rest_auth.views.PasswordResetView``, default value ``rest_auth.serializers.PasswordResetSerializer``

- PASSWORD_RESET_CONFIRM_SERIALIZER - serializer class in ``rest_auth.views.PasswordResetConfirm``, default value ``rest_auth.serializers.PasswordResetConfirmSerializer``
- PASSWORD_RESET_CONFIRM_SERIALIZER - serializer class in ``rest_auth.views.PasswordResetConfirmView``, default value ``rest_auth.serializers.PasswordResetConfirmSerializer``

- PASSWORD_CHANGE_SERIALIZER - serializer class in ``rest_auth.views.PasswordChange``, default value ``rest_auth.serializers.PasswordChangeSerializer``
- PASSWORD_CHANGE_SERIALIZER - serializer class in ``rest_auth.views.PasswordChangeView``, default value ``rest_auth.serializers.PasswordChangeSerializer``


Example configuration:
Expand Down
2 changes: 1 addition & 1 deletion docs/faq.rst
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ FAQ
# custom fields for user
company_name = models.CharField(max_length=100)

To allow update user details within one request send to rest_auth.views.UserDetails view, create serializer like this:
To allow update user details within one request send to rest_auth.views.UserDetailsView view, create serializer like this:

.. code-block:: python

Expand Down
6 changes: 3 additions & 3 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,14 @@ Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creati

3. Add Social Application in django admin panel

4. Create new view as a subclass of ``rest_auth.registration.views.SocialLogin`` with ``FacebookOAuth2Adapter`` adapter as an attribute:
4. Create new view as a subclass of ``rest_auth.registration.views.SocialLoginView`` with ``FacebookOAuth2Adapter`` adapter as an attribute:

.. code-block:: python

from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
from rest_auth.registration.views import SocialLogin
from rest_auth.registration.views import SocialLoginView

class FacebookLogin(SocialLogin):
class FacebookLogin(SocialLoginView):
adapter_class = FacebookOAuth2Adapter

5. Create url for FacebookLogin view:
Expand Down
91 changes: 75 additions & 16 deletions rest_auth/registration/serializers.py
Original file line number Diff line number Diff line change
@@ -1,40 +1,99 @@
from django.http import HttpRequest
from rest_framework import serializers
from requests.exceptions import HTTPError
from allauth.socialaccount.helpers import complete_social_login
# Import is needed only if we are using social login, in which
# case the allauth.socialaccount will be declared
try:
from allauth.socialaccount.helpers import complete_social_login
except ImportError:
pass


class SocialLoginSerializer(serializers.Serializer):
access_token = serializers.CharField(required=False)
code = serializers.CharField(required=False)

access_token = serializers.CharField(required=True)

def validate(self, attrs):
access_token = attrs.get('access_token')
view = self.context.get('view')
def _get_request(self):
request = self.context.get('request')
if not isinstance(request, HttpRequest):
request = request._request
return request

def get_social_login(self, adapter, app, token, response):
"""

:param adapter: allauth.socialaccount Adapter subclass. Usually OAuthAdapter or Auth2Adapter
:param app: `allauth.socialaccount.SocialApp` instance
:param token: `allauth.socialaccount.SocialToken` instance
:param response: Provider's response for OAuth1. Not used in the
:return: :return: A populated instance of the `allauth.socialaccount.SocialLoginView` instance
"""
request = self._get_request()
social_login = adapter.complete_login(request, app, token, response=response)
social_login.token = token
return social_login

def validate(self, attrs):
view = self.context.get('view')
request = self._get_request()

if not view:
raise serializers.ValidationError(
'View is not defined, pass it as a context variable'
)

self.adapter_class = getattr(view, 'adapter_class', None)

if not self.adapter_class:
adapter_class = getattr(view, 'adapter_class', None)
if not adapter_class:
raise serializers.ValidationError('Define adapter_class in view')

self.adapter = self.adapter_class()
app = self.adapter.get_provider().get_app(request)
token = self.adapter.parse_token({'access_token': access_token})
adapter = adapter_class()
app = 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

# Case 1: We received the access_token
if('access_token' in attrs):
access_token = attrs.get('access_token')

# Case 2: We received the authorization code
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'
)

code = attrs.get('code')

provider = adapter.get_provider()
scope = provider.get_scope(request)
client = self.client_class(
request,
app.client_id,
app.secret,
adapter.access_token_method,
adapter.access_token_url,
self.callback_url,
scope
)
token = client.get_access_token(code)
access_token = token['access_token']

else:
raise serializers.ValidationError('Incorrect input. access_token or code is required.')

token = adapter.parse_token({'access_token': access_token})
token.app = app

try:
login = self.adapter.complete_login(request, app, token,
response=access_token)

login.token = token
login = self.get_social_login(adapter, app, token, access_token)
complete_social_login(request, login)
except HTTPError:
raise serializers.ValidationError('Incorrect value')
Expand Down
6 changes: 3 additions & 3 deletions rest_auth/registration/urls.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
from django.views.generic import TemplateView
from django.conf.urls import patterns, url

from .views import Register, VerifyEmail
from .views import RegisterView, VerifyEmailView

urlpatterns = patterns(
'',
url(r'^$', Register.as_view(), name='rest_register'),
url(r'^verify-email/$', VerifyEmail.as_view(), name='rest_verify_email'),
url(r'^$', RegisterView.as_view(), name='rest_register'),
url(r'^verify-email/$', VerifyEmailView.as_view(), name='rest_verify_email'),

# This url is used by django-allauth and empty TemplateView is
# defined just to allow reverse() call inside app, for example when email
Expand Down
51 changes: 40 additions & 11 deletions rest_auth/registration/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,32 @@
from rest_framework.response import Response
from rest_framework.permissions import AllowAny
from rest_framework import status
from rest_framework.authtoken.models import Token

from allauth.account.views import SignupView, ConfirmEmailView
from allauth.account.utils import complete_signup
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.views import Login
from rest_auth.views import LoginView


class Register(APIView, SignupView):
class RegisterView(APIView, SignupView):
"""
Accepts the credentials and creates a new user
if user does not exist already
Return the REST Token if the credentials are valid and authenticated.
Calls allauth complete_signup method

Accept the following POST parameters: username, email, password
Return the REST Framework Token Object's key.
"""

permission_classes = (AllowAny,)
user_serializer_class = UserDetailsSerializer
allowed_methods = ('POST', 'OPTIONS', 'HEAD')
token_model = Token
serializer_class = TokenSerializer

def get(self, *args, **kwargs):
return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED)
Expand All @@ -27,6 +38,9 @@ def put(self, *args, **kwargs):

def form_valid(self, form):
self.user = form.save(self.request)
self.token, created = self.token_model.objects.get_or_create(
user=self.user
)
if isinstance(self.request, HttpRequest):
request = self.request
else:
Expand All @@ -37,7 +51,7 @@ def form_valid(self, form):

def post(self, request, *args, **kwargs):
self.initial = {}
self.request.POST = self.request.DATA.copy()
self.request.POST = self.request.data.copy()
form_class = self.get_form_class()
self.form = self.get_form(form_class)
if self.form.is_valid():
Expand All @@ -47,14 +61,15 @@ def post(self, request, *args, **kwargs):
return self.get_response_with_errors()

def get_response(self):
serializer = self.user_serializer_class(instance=self.user)
# serializer = self.user_serializer_class(instance=self.user)
serializer = self.serializer_class(instance=self.token)
return Response(serializer.data, status=status.HTTP_201_CREATED)

def get_response_with_errors(self):
return Response(self.form.errors, status=status.HTTP_400_BAD_REQUEST)


class VerifyEmail(APIView, ConfirmEmailView):
class VerifyEmailView(APIView, ConfirmEmailView):

permission_classes = (AllowAny,)
allowed_methods = ('POST', 'OPTIONS', 'HEAD')
Expand All @@ -63,20 +78,34 @@ def get(self, *args, **kwargs):
return Response({}, status=status.HTTP_405_METHOD_NOT_ALLOWED)

def post(self, request, *args, **kwargs):
self.kwargs['key'] = self.request.DATA.get('key', '')
self.kwargs['key'] = self.request.data.get('key', '')
confirmation = self.get_object()
confirmation.confirm(self.request)
return Response({'message': 'ok'}, status=status.HTTP_200_OK)


class SocialLogin(Login):
class SocialLoginView(LoginView):
"""
class used for social authentications
example usage for facebook
example usage for facebook with access_token
-------------
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter

class FacebookLogin(SocialLoginView):
adapter_class = FacebookOAuth2Adapter
-------------

example usage for facebook with code

-------------
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
class FacebookLogin(SocialLogin):
from allauth.socialaccount.providers.oauth2.client import OAuth2Client

class FacebookLogin(SocialLoginView):
adapter_class = FacebookOAuth2Adapter
client_class = OAuth2Client
callback_url = 'localhost:8000'
-------------
"""

serializer_class = SocialLoginSerializer
61 changes: 56 additions & 5 deletions rest_auth/serializers.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.contrib.auth import get_user_model
from django.contrib.auth import get_user_model, authenticate
from django.conf import settings
from django.contrib.auth.forms import PasswordResetForm, SetPasswordForm
try:
Expand All @@ -7,24 +7,75 @@
# make compatible with django 1.5
from django.utils.http import base36_to_int as uid_decoder
from django.contrib.auth.tokens import default_token_generator
from django.utils.translation import ugettext_lazy as _

from rest_framework import serializers
from rest_framework import serializers, exceptions
from rest_framework.authtoken.models import Token
from rest_framework.authtoken.serializers import AuthTokenSerializer
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):
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)

elif username and password:
user = authenticate(username=username, password=password)

else:
msg = _('Must include "username" and "password".')
raise exceptions.ValidationError(msg)

# Did we get back an active user?
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)

# If required, is the email verified?
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.')

attrs['user'] = user
return attrs


Expand Down
4 changes: 2 additions & 2 deletions rest_auth/test_urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter

from rest_auth.urls import urlpatterns
from rest_auth.registration.views import SocialLogin
from rest_auth.registration.views import SocialLoginView


class FacebookLogin(SocialLogin):
class FacebookLogin(SocialLoginView):
adapter_class = FacebookOAuth2Adapter

urlpatterns += patterns(
Expand Down
Loading