mirror of
https://github.com/Tivix/django-rest-auth.git
synced 2024-11-22 17:16:34 +03:00
commit
5086eebfdb
26
.coveragerc
Normal file
26
.coveragerc
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# .coveragerc to control coverage.py
|
||||||
|
[run]
|
||||||
|
omit=*site-packages*,*distutils*,*migrations*
|
||||||
|
|
||||||
|
[report]
|
||||||
|
# Regexes for lines to exclude from consideration
|
||||||
|
exclude_lines =
|
||||||
|
# Have to re-enable the standard pragma
|
||||||
|
pragma: no cover
|
||||||
|
|
||||||
|
# Don't complain about missing debug-only code:
|
||||||
|
def __repr__
|
||||||
|
if self\.debug
|
||||||
|
|
||||||
|
# Don't complain if tests don't hit defensive assertion code:
|
||||||
|
raise AssertionError
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
# Don't complain if non-runnable code isn't run:
|
||||||
|
if 0:
|
||||||
|
if __name__ == .__main__.:
|
||||||
|
|
||||||
|
ignore_errors = True
|
||||||
|
|
||||||
|
[html]
|
||||||
|
directory = coverage_html
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -35,9 +35,6 @@ nosetests.xml
|
||||||
coverage.xml
|
coverage.xml
|
||||||
coverage_html
|
coverage_html
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
|
|
||||||
# Mr Developer
|
# Mr Developer
|
||||||
.mr.developer.cfg
|
.mr.developer.cfg
|
||||||
.project
|
.project
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
- "2.7"
|
- "2.7"
|
||||||
|
- "3.5"
|
||||||
env:
|
env:
|
||||||
- DJANGO=1.7.7
|
- DJANGO=1.8.13
|
||||||
- DJANGO=1.8
|
- DJANGO=1.9.7
|
||||||
install:
|
install:
|
||||||
- pip install -q Django==$DJANGO --use-mirrors
|
- pip install -q Django==$DJANGO --use-mirrors
|
||||||
- pip install coveralls
|
- pip install coveralls
|
||||||
- pip install -r rest_auth/tests/requirements.pip
|
- pip install -r rest_auth/tests/requirements.pip
|
||||||
|
matrix:
|
||||||
|
exclude:
|
||||||
|
- python: "3.5"
|
||||||
|
env: DJANGO=1.8.13
|
||||||
script:
|
script:
|
||||||
- coverage run --source=rest_auth setup.py test
|
- coverage run --source=rest_auth setup.py test
|
||||||
after_success:
|
after_success:
|
||||||
|
|
|
@ -11,6 +11,8 @@ urlpatterns = [
|
||||||
name='email-verification'),
|
name='email-verification'),
|
||||||
url(r'^login/$', TemplateView.as_view(template_name="login.html"),
|
url(r'^login/$', TemplateView.as_view(template_name="login.html"),
|
||||||
name='login'),
|
name='login'),
|
||||||
|
url(r'^logout/$', TemplateView.as_view(template_name="logout.html"),
|
||||||
|
name='logout'),
|
||||||
url(r'^password-reset/$',
|
url(r'^password-reset/$',
|
||||||
TemplateView.as_view(template_name="password_reset.html"),
|
TemplateView.as_view(template_name="password_reset.html"),
|
||||||
name='password-reset'),
|
name='password-reset'),
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
django>=1.7.0
|
django>=1.8.0
|
||||||
django-rest-auth==0.6.0
|
django-rest-auth==0.8.1
|
||||||
django-allauth==0.24.1
|
django-allauth>=0.24.1
|
||||||
six==1.9.0
|
six==1.9.0
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
<li class="divider"></li>
|
<li class="divider"></li>
|
||||||
<!-- these pages require user token -->
|
<!-- these pages require user token -->
|
||||||
<li><a href="{% url 'user-details' %}">User details</a></li>
|
<li><a href="{% url 'user-details' %}">User details</a></li>
|
||||||
|
<li><a href="{% url 'logout' %}">Logout</a></li>
|
||||||
<li><a href="{% url 'password-change' %}">Password change</a></li>
|
<li><a href="{% url 'password-change' %}">Password change</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
20
demo/templates/fragments/logout_form.html
Normal file
20
demo/templates/fragments/logout_form.html
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<form class="form-horizontal ajax-post" role="form" action="{% url 'rest_logout' %}">{% csrf_token %}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="token" class="col-sm-2 control-label">User Token</label>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<input name="token" type="text" class="form-control" id="token" placeholder="Token">
|
||||||
|
<p class="help-block">Token received after login</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-offset-2 col-sm-10">
|
||||||
|
<button type="submit" class="btn btn-default">Login</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group api-response"></div>
|
||||||
|
</form>
|
||||||
|
{% endblock %}
|
8
demo/templates/logout.html
Normal file
8
demo/templates/logout.html
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="row">
|
||||||
|
<h3>Logout</h3><hr/>
|
||||||
|
{% include "fragments/logout_form.html" %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -11,7 +11,11 @@ Basic
|
||||||
- password (string)
|
- password (string)
|
||||||
|
|
||||||
|
|
||||||
- /rest-auth/logout/ (POST)
|
- /rest-auth/logout/ (POST, GET)
|
||||||
|
|
||||||
|
.. note:: ``ACCOUNT_LOGOUT_ON_GET = True`` to allow logout using GET (this is the exact same conf from allauth)
|
||||||
|
|
||||||
|
- token
|
||||||
|
|
||||||
- /rest-auth/password/reset/ (POST)
|
- /rest-auth/password/reset/ (POST)
|
||||||
|
|
||||||
|
@ -70,3 +74,8 @@ Basing on example from installation section :doc:`Installation </installation>`
|
||||||
|
|
||||||
- access_token
|
- access_token
|
||||||
- code
|
- code
|
||||||
|
|
||||||
|
- /rest-auth/twitter/ (POST)
|
||||||
|
|
||||||
|
- access_token
|
||||||
|
- token_secret
|
||||||
|
|
|
@ -1,6 +1,18 @@
|
||||||
Changelog
|
Changelog
|
||||||
=========
|
=========
|
||||||
|
|
||||||
|
0.8.0
|
||||||
|
-----
|
||||||
|
- added support for django-rest-framework-jwt
|
||||||
|
- bugfixes
|
||||||
|
|
||||||
|
0.7.0
|
||||||
|
-----
|
||||||
|
- Wrapped API returned strings in ugettext_lazy
|
||||||
|
- Fixed not using ``get_username`` which caused issues when using custom user model without username field
|
||||||
|
- Django 1.9 support
|
||||||
|
- Added ``TwitterLoginSerializer``
|
||||||
|
|
||||||
0.6.0
|
0.6.0
|
||||||
-----
|
-----
|
||||||
- dropped support for Python 2.6
|
- dropped support for Python 2.6
|
||||||
|
|
|
@ -10,6 +10,8 @@ Configuration
|
||||||
|
|
||||||
- TOKEN_SERIALIZER - response for successful authentication in ``rest_auth.views.LoginView``, default value ``rest_auth.serializers.TokenSerializer``
|
- TOKEN_SERIALIZER - response for successful authentication in ``rest_auth.views.LoginView``, default value ``rest_auth.serializers.TokenSerializer``
|
||||||
|
|
||||||
|
- JWT_SERIALIZER - (Using REST_USE_JWT=True) response for successful authentication in ``rest_auth.views.LoginView``, default value ``rest_auth.serializers.JWTSerializer``
|
||||||
|
|
||||||
- USER_DETAILS_SERIALIZER - serializer class in ``rest_auth.views.UserDetailsView``, 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.PasswordResetView``, default value ``rest_auth.serializers.PasswordResetSerializer``
|
- PASSWORD_RESET_SERIALIZER - serializer class in ``rest_auth.views.PasswordResetView``, default value ``rest_auth.serializers.PasswordResetSerializer``
|
||||||
|
@ -29,12 +31,12 @@ Configuration
|
||||||
...
|
...
|
||||||
}
|
}
|
||||||
|
|
||||||
- **REST_AUTH_REGISTRATION_SERIALIZERS**
|
- **REST_AUTH_REGISTER_SERIALIZERS**
|
||||||
|
|
||||||
You can define your custom serializers for registration endpoint.
|
You can define your custom serializers for registration endpoint.
|
||||||
Possible key values:
|
Possible key values:
|
||||||
|
|
||||||
- REGISTER_SERIALIZER - serializer class in ``rest_auth.register.views.RegisterView``, default value ``rest_auth.register.serializers.RegisterSerializer``
|
- REGISTER_SERIALIZER - serializer class in ``rest_auth.register.views.RegisterView``, default value ``rest_auth.registration.serializers.RegisterSerializer``
|
||||||
|
|
||||||
- **REST_AUTH_TOKEN_MODEL** - model class for tokens, default value ``rest_framework.authtoken.models``
|
- **REST_AUTH_TOKEN_MODEL** - model class for tokens, default value ``rest_framework.authtoken.models``
|
||||||
|
|
||||||
|
@ -42,6 +44,25 @@ Configuration
|
||||||
|
|
||||||
- **REST_SESSION_LOGIN** - Enable session login in Login API view (default: True)
|
- **REST_SESSION_LOGIN** - Enable session login in Login API view (default: True)
|
||||||
|
|
||||||
|
- **REST_USE_JWT** - Enable JWT Authentication instead of Token/Session based. This is built on top of django-rest-framework-jwt http://getblimp.github.io/django-rest-framework-jwt/, which must also be installed. (default: False)
|
||||||
|
|
||||||
- **OLD_PASSWORD_FIELD_ENABLED** - set it to True if you want to have old password verification on password change enpoint (default: False)
|
- **OLD_PASSWORD_FIELD_ENABLED** - set it to True if you want to have old password verification on password change enpoint (default: False)
|
||||||
|
|
||||||
- **LOGOUT_ON_PASSWORD_CHANGE** - set to False if you want to keep the current user logged in after a password change
|
- **LOGOUT_ON_PASSWORD_CHANGE** - set to False if you want to keep the current user logged in after a password change
|
||||||
|
|
||||||
|
|
||||||
|
Throttling
|
||||||
|
=============
|
||||||
|
|
||||||
|
You may specify custom throttling for ``rest_auth.register.views.RegisterView`` by specifying DRF settings:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_THROTTLE_RATES': {
|
||||||
|
'anon': '6/m',
|
||||||
|
'register_view':'1/h',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
Welcome to django-rest-auth's documentation!
|
Welcome to django-rest-auth's documentation!
|
||||||
============================================
|
============================================
|
||||||
|
|
||||||
.. warning:: Updating django-rest-auth to version **0.3.4** is highly recommended because of a security issue in PasswordResetConfirmation validation method.
|
.. warning:: Updating django-rest-auth from version **0.3.3** is highly recommended because of a security issue in PasswordResetConfirmation validation method.
|
||||||
|
|
||||||
.. note:: django-rest-auth from v0.3.3 supports django-rest-framework v3.0
|
.. note:: django-rest-auth from v0.3.3 supports django-rest-framework v3.0
|
||||||
|
|
||||||
|
|
|
@ -38,19 +38,24 @@ You're good to go now!
|
||||||
Registration (optional)
|
Registration (optional)
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
1. If you want to enable standard registration process you will need to install ``django-allauth`` by using ``pip install django-rest-auth[extras]`` or ``pip install django-rest-auth[with_social]``.
|
1. If you want to enable standard registration process you will need to install ``django-allauth`` by using ``pip install django-rest-auth[with_social]``.
|
||||||
|
|
||||||
2. Add ``allauth``, ``allauth.account`` and ``rest_auth.registration`` apps to INSTALLED_APPS in your django settings.py:
|
2. Add ``django.contrib.sites``, ``allauth``, ``allauth.account`` and ``rest_auth.registration`` apps to INSTALLED_APPS in your django settings.py:
|
||||||
|
|
||||||
|
3. Add ``SITE_ID = 1`` to your django settings.py
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
INSTALLED_APPS = (
|
INSTALLED_APPS = (
|
||||||
...,
|
...,
|
||||||
|
'django.contrib.sites',
|
||||||
'allauth',
|
'allauth',
|
||||||
'allauth.account',
|
'allauth.account',
|
||||||
'rest_auth.registration',
|
'rest_auth.registration',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
SITE_ID = 1
|
||||||
|
|
||||||
3. Add rest_auth.registration urls:
|
3. Add rest_auth.registration urls:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -65,11 +70,11 @@ Registration (optional)
|
||||||
Social Authentication (optional)
|
Social Authentication (optional)
|
||||||
--------------------------------
|
--------------------------------
|
||||||
|
|
||||||
Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creating social media authentication view. Below is an example with Facebook authentication.
|
Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creating social media authentication view.
|
||||||
|
|
||||||
.. note:: Points 1, 2 and 3 are related with ``django-allauth`` configuration, so if you have already configured social authentication, then please go to step 4. See ``django-allauth`` documentation for more details.
|
.. note:: Points 1 and 2 are related to ``django-allauth`` configuration, so if you have already configured social authentication, then please go to step 3. See ``django-allauth`` documentation for more details.
|
||||||
|
|
||||||
1. Add ``allauth.socialaccount`` and ``allauth.socialaccount.providers.facebook`` apps to INSTALLED_APPS in your django settings.py:
|
1. Add ``allauth.socialaccount`` and ``allauth.socialaccount.providers.facebook`` or ``allauth.socialaccount.providers.twitter`` apps to INSTALLED_APPS in your django settings.py:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
@ -79,16 +84,22 @@ Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creati
|
||||||
'rest_framework.authtoken',
|
'rest_framework.authtoken',
|
||||||
'rest_auth'
|
'rest_auth'
|
||||||
...,
|
...,
|
||||||
|
'django.contrib.sites',
|
||||||
'allauth',
|
'allauth',
|
||||||
'allauth.account',
|
'allauth.account',
|
||||||
'rest_auth.registration',
|
'rest_auth.registration',
|
||||||
...,
|
...,
|
||||||
'allauth.socialaccount',
|
'allauth.socialaccount',
|
||||||
'allauth.socialaccount.providers.facebook',
|
'allauth.socialaccount.providers.facebook',
|
||||||
|
'allauth.socialaccount.providers.twitter',
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
2. Add Social Application in django admin panel
|
2. Add Social Application in django admin panel
|
||||||
|
|
||||||
|
Facebook
|
||||||
|
########
|
||||||
|
|
||||||
3. Create new view as a subclass of ``rest_auth.registration.views.SocialLoginView`` with ``FacebookOAuth2Adapter`` adapter as an attribute:
|
3. Create new view as a subclass of ``rest_auth.registration.views.SocialLoginView`` with ``FacebookOAuth2Adapter`` adapter as an attribute:
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
@ -103,9 +114,50 @@ Using ``django-allauth``, ``django-rest-auth`` provides helpful class for creati
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
urlpatterns += pattern('',
|
urlpatterns += patterns('',
|
||||||
...,
|
...,
|
||||||
url(r'^rest-auth/facebook/$', FacebookLogin.as_view(), name='fb_login')
|
url(r'^rest-auth/facebook/$', FacebookLogin.as_view(), name='fb_login')
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Twitter
|
||||||
|
#######
|
||||||
|
|
||||||
|
If you are using Twitter for your social authentication, it is a bit different since Twitter uses OAuth 1.0.
|
||||||
|
|
||||||
|
3. Create new view as a subclass of ``rest_auth.views.LoginView`` with ``TwitterOAuthAdapter`` adapter and ``TwitterLoginSerializer`` as an attribute:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from allauth.socialaccount.providers.twitter.views import TwitterOAuthAdapter
|
||||||
|
from rest_auth.views import LoginView
|
||||||
|
from rest_auth.social_serializers import TwitterLoginSerializer
|
||||||
|
|
||||||
|
class TwitterLogin(LoginView):
|
||||||
|
serializer_class = TwitterLoginSerializer
|
||||||
|
adapter_class = TwitterOAuthAdapter
|
||||||
|
|
||||||
|
4. Create url for TwitterLogin view:
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
urlpatterns += patterns('',
|
||||||
|
...,
|
||||||
|
url(r'^rest-auth/twitter/$', TwitterLogin.as_view(), name='twitter_login')
|
||||||
|
)
|
||||||
.. note:: Starting from v0.21.0, django-allauth has dropped support for context processors. Check out http://django-allauth.readthedocs.org/en/latest/changelog.html#from-0-21-0 for more details.
|
.. note:: Starting from v0.21.0, django-allauth has dropped support for context processors. Check out http://django-allauth.readthedocs.org/en/latest/changelog.html#from-0-21-0 for more details.
|
||||||
|
|
||||||
|
|
||||||
|
JWT Support (optional)
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
By default, ``django-rest-auth`` uses Django's Token-based authentication. If you want to use JWT authentication, you need to install the following:
|
||||||
|
|
||||||
|
1. Install ``django-rest-framework-jwt`` http://getblimp.github.io/django-rest-framework-jwt/ . Right now this is the only supported JWT library.
|
||||||
|
|
||||||
|
2. Add the following to your settings
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
REST_USE_JWT = True
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ from django.conf import settings
|
||||||
|
|
||||||
from rest_auth.serializers import (
|
from rest_auth.serializers import (
|
||||||
TokenSerializer as DefaultTokenSerializer,
|
TokenSerializer as DefaultTokenSerializer,
|
||||||
|
JWTSerializer as DefaultJWTSerializer,
|
||||||
UserDetailsSerializer as DefaultUserDetailsSerializer,
|
UserDetailsSerializer as DefaultUserDetailsSerializer,
|
||||||
LoginSerializer as DefaultLoginSerializer,
|
LoginSerializer as DefaultLoginSerializer,
|
||||||
PasswordResetSerializer as DefaultPasswordResetSerializer,
|
PasswordResetSerializer as DefaultPasswordResetSerializer,
|
||||||
|
@ -17,6 +18,9 @@ serializers = getattr(settings, 'REST_AUTH_SERIALIZERS', {})
|
||||||
TokenSerializer = import_callable(
|
TokenSerializer = import_callable(
|
||||||
serializers.get('TOKEN_SERIALIZER', DefaultTokenSerializer))
|
serializers.get('TOKEN_SERIALIZER', DefaultTokenSerializer))
|
||||||
|
|
||||||
|
JWTSerializer = import_callable(
|
||||||
|
serializers.get('JWT_SERIALIZER', DefaultJWTSerializer))
|
||||||
|
|
||||||
UserDetailsSerializer = import_callable(
|
UserDetailsSerializer = import_callable(
|
||||||
serializers.get('USER_DETAILS_SERIALIZER', DefaultUserDetailsSerializer)
|
serializers.get('USER_DETAILS_SERIALIZER', DefaultUserDetailsSerializer)
|
||||||
)
|
)
|
||||||
|
|
BIN
rest_auth/locale/de/LC_MESSAGES/django.mo
Normal file
BIN
rest_auth/locale/de/LC_MESSAGES/django.mo
Normal file
Binary file not shown.
99
rest_auth/locale/de/LC_MESSAGES/django.po
Normal file
99
rest_auth/locale/de/LC_MESSAGES/django.po
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2016-02-02 14:11+0100\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"Language: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#: registration/serializers.py:54
|
||||||
|
msgid "View is not defined, pass it as a context variable"
|
||||||
|
msgstr "\"View\" ist nicht definiert, übergib es als Contextvariable"
|
||||||
|
|
||||||
|
#: registration/serializers.py:59
|
||||||
|
msgid "Define adapter_class in view"
|
||||||
|
msgstr "Definier \"adapter_class\" in view"
|
||||||
|
|
||||||
|
#: registration/serializers.py:78
|
||||||
|
msgid "Define callback_url in view"
|
||||||
|
msgstr "Definier \"callback_url\" in view"
|
||||||
|
|
||||||
|
#: registration/serializers.py:82
|
||||||
|
msgid "Define client_class in view"
|
||||||
|
msgstr "Definier \"client_class\" in view"
|
||||||
|
|
||||||
|
#: registration/serializers.py:102
|
||||||
|
msgid "Incorrect input. access_token or code is required."
|
||||||
|
msgstr "Falsche Eingabe. \"access_token\" oder \"code\" erforderlich."
|
||||||
|
|
||||||
|
#: registration/serializers.py:111
|
||||||
|
msgid "Incorrect value"
|
||||||
|
msgstr "Falscher Wert."
|
||||||
|
|
||||||
|
#: registration/serializers.py:140
|
||||||
|
msgid "A user is already registered with this e-mail address."
|
||||||
|
msgstr "Ein User mit dieser E-Mail Adresse ist schon registriert."
|
||||||
|
|
||||||
|
#: registration/serializers.py:148
|
||||||
|
msgid "The two password fields didn't match."
|
||||||
|
msgstr "Die beiden Passwörter sind nicht identisch."
|
||||||
|
|
||||||
|
#: registration/views.py:64
|
||||||
|
msgid "ok"
|
||||||
|
msgstr "Ok"
|
||||||
|
|
||||||
|
#: serializers.py:29
|
||||||
|
msgid "Must include \"email\" and \"password\"."
|
||||||
|
msgstr "Muss \"email\" und \"password\" enthalten."
|
||||||
|
|
||||||
|
#: serializers.py:40
|
||||||
|
msgid "Must include \"username\" and \"password\"."
|
||||||
|
msgstr "Muss \"username\" und \"password\" enthalten."
|
||||||
|
|
||||||
|
#: serializers.py:53
|
||||||
|
msgid "Must include either \"username\" or \"email\" and \"password\"."
|
||||||
|
msgstr "Muss entweder \"username\" oder \"email\" und password \"password\""
|
||||||
|
|
||||||
|
#: serializers.py:94
|
||||||
|
msgid "User account is disabled."
|
||||||
|
msgstr "Der Useraccount ist deaktiviert."
|
||||||
|
|
||||||
|
#: serializers.py:97
|
||||||
|
msgid "Unable to log in with provided credentials."
|
||||||
|
msgstr "Kann nicht mit den angegeben Zugangsdaten anmelden."
|
||||||
|
|
||||||
|
#: serializers.py:106
|
||||||
|
msgid "E-mail is not verified."
|
||||||
|
msgstr "E-Mail Adresse ist nicht verifiziert."
|
||||||
|
|
||||||
|
#: serializers.py:152
|
||||||
|
msgid "Error"
|
||||||
|
msgstr "Fehler"
|
||||||
|
|
||||||
|
#: views.py:71
|
||||||
|
msgid "Successfully logged out."
|
||||||
|
msgstr "Erfolgreich ausgeloggt."
|
||||||
|
|
||||||
|
#: views.py:111
|
||||||
|
msgid "Password reset e-mail has been sent."
|
||||||
|
msgstr "Die E-Mail zum Zurücksetzen des Passwortes wurde verschickt."
|
||||||
|
|
||||||
|
#: views.py:132
|
||||||
|
msgid "Password has been reset with the new password."
|
||||||
|
msgstr "Das Passwort wurde mit dem neuen Passwort ersetzt."
|
||||||
|
|
||||||
|
#: views.py:150
|
||||||
|
msgid "New password has been saved."
|
||||||
|
msgstr "Das neue Passwort wurde gespeichert."
|
|
@ -1,5 +1,6 @@
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from allauth.account import app_settings as allauth_settings
|
from allauth.account import app_settings as allauth_settings
|
||||||
|
@ -14,13 +15,8 @@ from rest_framework import serializers
|
||||||
from requests.exceptions import HTTPError
|
from requests.exceptions import HTTPError
|
||||||
# Import is needed only if we are using social login, in which
|
# Import is needed only if we are using social login, in which
|
||||||
# case the allauth.socialaccount will be declared
|
# case the allauth.socialaccount will be declared
|
||||||
|
|
||||||
if 'allauth.socialaccount' in settings.INSTALLED_APPS:
|
if 'allauth.socialaccount' in settings.INSTALLED_APPS:
|
||||||
try:
|
|
||||||
from allauth.socialaccount.helpers import complete_social_login
|
from allauth.socialaccount.helpers import complete_social_login
|
||||||
except ImportError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SocialLoginSerializer(serializers.Serializer):
|
class SocialLoginSerializer(serializers.Serializer):
|
||||||
|
@ -53,14 +49,14 @@ class SocialLoginSerializer(serializers.Serializer):
|
||||||
|
|
||||||
if not view:
|
if not view:
|
||||||
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')
|
||||||
)
|
)
|
||||||
|
|
||||||
adapter_class = getattr(view, 'adapter_class', None)
|
adapter_class = getattr(view, 'adapter_class', None)
|
||||||
if not adapter_class:
|
if not adapter_class:
|
||||||
raise serializers.ValidationError('Define adapter_class in view')
|
raise serializers.ValidationError(_('Define adapter_class in view'))
|
||||||
|
|
||||||
adapter = adapter_class()
|
adapter = adapter_class(request)
|
||||||
app = adapter.get_provider().get_app(request)
|
app = adapter.get_provider().get_app(request)
|
||||||
|
|
||||||
# More info on code vs access_token
|
# More info on code vs access_token
|
||||||
|
@ -77,11 +73,11 @@ class SocialLoginSerializer(serializers.Serializer):
|
||||||
|
|
||||||
if not self.callback_url:
|
if not self.callback_url:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
'Define callback_url in view'
|
_('Define callback_url in view')
|
||||||
)
|
)
|
||||||
if not self.client_class:
|
if not self.client_class:
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
'Define client_class in view'
|
_('Define client_class in view')
|
||||||
)
|
)
|
||||||
|
|
||||||
code = attrs.get('code')
|
code = attrs.get('code')
|
||||||
|
@ -101,7 +97,7 @@ class SocialLoginSerializer(serializers.Serializer):
|
||||||
access_token = token['access_token']
|
access_token = token['access_token']
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise serializers.ValidationError('Incorrect input. access_token or code is required.')
|
raise serializers.ValidationError(_('Incorrect input. access_token or code is required.'))
|
||||||
|
|
||||||
token = adapter.parse_token({'access_token': access_token})
|
token = adapter.parse_token({'access_token': access_token})
|
||||||
token.app = app
|
token.app = app
|
||||||
|
@ -110,7 +106,7 @@ class SocialLoginSerializer(serializers.Serializer):
|
||||||
login = self.get_social_login(adapter, app, token, access_token)
|
login = self.get_social_login(adapter, app, token, access_token)
|
||||||
complete_social_login(request, login)
|
complete_social_login(request, login)
|
||||||
except HTTPError:
|
except HTTPError:
|
||||||
raise serializers.ValidationError('Incorrect value')
|
raise serializers.ValidationError(_('Incorrect value'))
|
||||||
|
|
||||||
if not login.is_existing:
|
if not login.is_existing:
|
||||||
login.lookup()
|
login.lookup()
|
||||||
|
@ -139,7 +135,7 @@ class RegisterSerializer(serializers.Serializer):
|
||||||
if allauth_settings.UNIQUE_EMAIL:
|
if allauth_settings.UNIQUE_EMAIL:
|
||||||
if email and email_address_exists(email):
|
if email and email_address_exists(email):
|
||||||
raise serializers.ValidationError(
|
raise serializers.ValidationError(
|
||||||
"A user is already registered with this e-mail address.")
|
_("A user is already registered with this e-mail address."))
|
||||||
return email
|
return email
|
||||||
|
|
||||||
def validate_password1(self, password):
|
def validate_password1(self, password):
|
||||||
|
@ -147,7 +143,7 @@ class RegisterSerializer(serializers.Serializer):
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
if data['password1'] != data['password2']:
|
if data['password1'] != data['password2']:
|
||||||
raise serializers.ValidationError("The two password fields didn't match.")
|
raise serializers.ValidationError(_("The two password fields didn't match."))
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def custom_signup(self, request, user):
|
def custom_signup(self, request, user):
|
||||||
|
|
|
@ -17,7 +17,7 @@ urlpatterns = [
|
||||||
# with proper key.
|
# with proper key.
|
||||||
# If you don't want to use API on that step, then just use ConfirmEmailView
|
# If you don't want to use API on that step, then just use ConfirmEmailView
|
||||||
# view from:
|
# view from:
|
||||||
# djang-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py#L190
|
# django-allauth https://github.com/pennersr/django-allauth/blob/master/allauth/account/views.py#L190
|
||||||
url(r'^account-confirm-email/(?P<key>\w+)/$', TemplateView.as_view(),
|
url(r'^account-confirm-email/(?P<key>[-:\w]+)/$', TemplateView.as_view(),
|
||||||
name='account_confirm_email'),
|
name='account_confirm_email'),
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
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.generics import CreateAPIView
|
from rest_framework.generics import CreateAPIView
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.exceptions import MethodNotAllowed
|
|
||||||
|
|
||||||
|
from allauth.account.adapter import get_adapter
|
||||||
from allauth.account.views import ConfirmEmailView
|
from allauth.account.views import ConfirmEmailView
|
||||||
from allauth.account.utils import complete_signup
|
from allauth.account.utils import complete_signup
|
||||||
from allauth.account import app_settings as allauth_settings
|
from allauth.account import app_settings as allauth_settings
|
||||||
|
|
||||||
from rest_auth.app_settings import (TokenSerializer,
|
from rest_auth.app_settings import (TokenSerializer,
|
||||||
|
JWTSerializer,
|
||||||
create_token)
|
create_token)
|
||||||
from rest_auth.registration.serializers import (SocialLoginSerializer,
|
from rest_auth.registration.serializers import (SocialLoginSerializer,
|
||||||
VerifyEmailSerializer)
|
VerifyEmailSerializer)
|
||||||
|
@ -17,17 +21,27 @@ from rest_auth.views import LoginView
|
||||||
from rest_auth.models import TokenModel
|
from rest_auth.models import TokenModel
|
||||||
from .app_settings import RegisterSerializer
|
from .app_settings import RegisterSerializer
|
||||||
|
|
||||||
|
from rest_auth.utils import jwt_encode
|
||||||
|
|
||||||
|
|
||||||
class RegisterView(CreateAPIView):
|
class RegisterView(CreateAPIView):
|
||||||
serializer_class = RegisterSerializer
|
serializer_class = RegisterSerializer
|
||||||
permission_classes = (AllowAny, )
|
permission_classes = (AllowAny, )
|
||||||
token_model = TokenModel
|
token_model = TokenModel
|
||||||
|
throttle_scope = 'register_view'
|
||||||
|
|
||||||
def get_response_data(self, user):
|
def get_response_data(self, user):
|
||||||
if allauth_settings.EMAIL_VERIFICATION == \
|
if allauth_settings.EMAIL_VERIFICATION == \
|
||||||
allauth_settings.EmailVerificationMethod.MANDATORY:
|
allauth_settings.EmailVerificationMethod.MANDATORY:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
if getattr(settings, 'REST_USE_JWT', False):
|
||||||
|
data = {
|
||||||
|
'user': user,
|
||||||
|
'token': self.token
|
||||||
|
}
|
||||||
|
return JWTSerializer(data).data
|
||||||
|
else:
|
||||||
return TokenSerializer(user.auth_token).data
|
return TokenSerializer(user.auth_token).data
|
||||||
|
|
||||||
def create(self, request, *args, **kwargs):
|
def create(self, request, *args, **kwargs):
|
||||||
|
@ -40,7 +54,11 @@ class RegisterView(CreateAPIView):
|
||||||
|
|
||||||
def perform_create(self, serializer):
|
def perform_create(self, serializer):
|
||||||
user = serializer.save(self.request)
|
user = serializer.save(self.request)
|
||||||
|
if getattr(settings, 'REST_USE_JWT', False):
|
||||||
|
self.token = jwt_encode(user)
|
||||||
|
else:
|
||||||
create_token(self.token_model, user, serializer)
|
create_token(self.token_model, user, serializer)
|
||||||
|
|
||||||
complete_signup(self.request._request, user,
|
complete_signup(self.request._request, user,
|
||||||
allauth_settings.EMAIL_VERIFICATION,
|
allauth_settings.EMAIL_VERIFICATION,
|
||||||
None)
|
None)
|
||||||
|
@ -52,16 +70,13 @@ class VerifyEmailView(APIView, ConfirmEmailView):
|
||||||
permission_classes = (AllowAny,)
|
permission_classes = (AllowAny,)
|
||||||
allowed_methods = ('POST', 'OPTIONS', 'HEAD')
|
allowed_methods = ('POST', 'OPTIONS', 'HEAD')
|
||||||
|
|
||||||
def get(self, *args, **kwargs):
|
|
||||||
raise MethodNotAllowed('GET')
|
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
serializer = VerifyEmailSerializer(data=request.data)
|
serializer = VerifyEmailSerializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
self.kwargs['key'] = serializer.validated_data['key']
|
self.kwargs['key'] = serializer.validated_data['key']
|
||||||
confirmation = self.get_object()
|
confirmation = self.get_object()
|
||||||
confirmation.confirm(self.request)
|
confirmation.confirm(self.request)
|
||||||
return Response({'message': 'ok'}, status=status.HTTP_200_OK)
|
return Response({'message': _('ok')}, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class SocialLoginView(LoginView):
|
class SocialLoginView(LoginView):
|
||||||
|
@ -89,3 +104,6 @@ class SocialLoginView(LoginView):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
serializer_class = SocialLoginSerializer
|
serializer_class = SocialLoginSerializer
|
||||||
|
|
||||||
|
def process_login(self):
|
||||||
|
get_adapter(self.request).login(self.request, self.user)
|
||||||
|
|
|
@ -103,7 +103,7 @@ class LoginSerializer(serializers.Serializer):
|
||||||
if app_settings.EMAIL_VERIFICATION == app_settings.EmailVerificationMethod.MANDATORY:
|
if app_settings.EMAIL_VERIFICATION == app_settings.EmailVerificationMethod.MANDATORY:
|
||||||
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.'))
|
||||||
|
|
||||||
attrs['user'] = user
|
attrs['user'] = user
|
||||||
return attrs
|
return attrs
|
||||||
|
@ -130,6 +130,14 @@ class UserDetailsSerializer(serializers.ModelSerializer):
|
||||||
read_only_fields = ('email', )
|
read_only_fields = ('email', )
|
||||||
|
|
||||||
|
|
||||||
|
class JWTSerializer(serializers.Serializer):
|
||||||
|
"""
|
||||||
|
Serializer for JWT authentication.
|
||||||
|
"""
|
||||||
|
token = serializers.CharField()
|
||||||
|
user = UserDetailsSerializer()
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetSerializer(serializers.Serializer):
|
class PasswordResetSerializer(serializers.Serializer):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -149,7 +157,7 @@ class PasswordResetSerializer(serializers.Serializer):
|
||||||
# Create PasswordResetForm with the serializer
|
# Create PasswordResetForm with the serializer
|
||||||
self.reset_form = self.password_reset_form_class(data=self.initial_data)
|
self.reset_form = self.password_reset_form_class(data=self.initial_data)
|
||||||
if not self.reset_form.is_valid():
|
if not self.reset_form.is_valid():
|
||||||
raise serializers.ValidationError(_('Error'))
|
raise serializers.ValidationError(self.reset_form.errors)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
|
73
rest_auth/social_serializers.py
Normal file
73
rest_auth/social_serializers.py
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
from django.conf import settings
|
||||||
|
from django.http import HttpRequest
|
||||||
|
from rest_framework import serializers
|
||||||
|
# Import is needed only if we are using social login, in which
|
||||||
|
# case the allauth.socialaccount will be declared
|
||||||
|
if 'allauth.socialaccount' in settings.INSTALLED_APPS:
|
||||||
|
from allauth.socialaccount.helpers import complete_social_login
|
||||||
|
from allauth.socialaccount.models import SocialToken
|
||||||
|
from allauth.socialaccount.providers.oauth.client import OAuthError
|
||||||
|
|
||||||
|
|
||||||
|
class TwitterLoginSerializer(serializers.Serializer):
|
||||||
|
access_token = serializers.CharField(required=True)
|
||||||
|
token_secret = serializers.CharField(required=True)
|
||||||
|
|
||||||
|
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'
|
||||||
|
)
|
||||||
|
|
||||||
|
adapter_class = getattr(view, 'adapter_class', None)
|
||||||
|
if not adapter_class:
|
||||||
|
raise serializers.ValidationError('Define adapter_class in view')
|
||||||
|
|
||||||
|
adapter = adapter_class(request)
|
||||||
|
app = adapter.get_provider().get_app(request)
|
||||||
|
|
||||||
|
access_token = attrs.get('access_token')
|
||||||
|
token_secret = attrs.get('token_secret')
|
||||||
|
|
||||||
|
request.session['oauth_api.twitter.com_access_token'] = {
|
||||||
|
'oauth_token': access_token,
|
||||||
|
'oauth_token_secret': token_secret,
|
||||||
|
}
|
||||||
|
token = SocialToken(token=access_token, token_secret=token_secret)
|
||||||
|
token.app = app
|
||||||
|
|
||||||
|
try:
|
||||||
|
login = self.get_social_login(adapter, app, token, access_token)
|
||||||
|
complete_social_login(request, login)
|
||||||
|
except OAuthError as e:
|
||||||
|
raise serializers.ValidationError(str(e))
|
||||||
|
|
||||||
|
if not login.is_existing:
|
||||||
|
login.lookup()
|
||||||
|
login.save(request, connect=True)
|
||||||
|
attrs['user'] = login.account.user
|
||||||
|
|
||||||
|
return attrs
|
|
@ -1,79 +1,16 @@
|
||||||
# Moved in Django 1.8 from django to tests/auth_tests/urls.py
|
# Moved in Django 1.8 from django to tests/auth_tests/urls.py
|
||||||
|
|
||||||
from django.conf.urls import include, url
|
from django.conf.urls import url
|
||||||
from django.contrib import admin
|
|
||||||
from django.contrib.auth import views
|
from django.contrib.auth import views
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_required
|
||||||
from django.contrib.auth.forms import AuthenticationForm
|
|
||||||
from django.contrib.auth.urls import urlpatterns
|
from django.contrib.auth.urls import urlpatterns
|
||||||
from django.contrib.messages.api import info
|
|
||||||
from django.http import HttpRequest, HttpResponse
|
|
||||||
from django.shortcuts import render
|
|
||||||
from django.template import RequestContext, Template
|
|
||||||
from django.views.decorators.cache import never_cache
|
|
||||||
|
|
||||||
|
|
||||||
class CustomRequestAuthenticationForm(AuthenticationForm):
|
|
||||||
def __init__(self, request, *args, **kwargs):
|
|
||||||
assert isinstance(request, HttpRequest)
|
|
||||||
super(CustomRequestAuthenticationForm, self).__init__(request, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
@never_cache
|
|
||||||
def remote_user_auth_view(request):
|
|
||||||
"""
|
|
||||||
Dummy view for remote user tests
|
|
||||||
"""
|
|
||||||
t = Template("Username is {{ user }}.")
|
|
||||||
c = RequestContext(request, {})
|
|
||||||
return HttpResponse(t.render(c))
|
|
||||||
|
|
||||||
|
|
||||||
def auth_processor_no_attr_access(request):
|
|
||||||
render(request, 'context_processors/auth_attrs_no_access.html')
|
|
||||||
# *After* rendering, we check whether the session was accessed
|
|
||||||
return render(request,
|
|
||||||
'context_processors/auth_attrs_test_access.html',
|
|
||||||
{'session_accessed': request.session.accessed})
|
|
||||||
|
|
||||||
|
|
||||||
def auth_processor_attr_access(request):
|
|
||||||
render(request, 'context_processors/auth_attrs_access.html')
|
|
||||||
return render(request,
|
|
||||||
'context_processors/auth_attrs_test_access.html',
|
|
||||||
{'session_accessed': request.session.accessed})
|
|
||||||
|
|
||||||
|
|
||||||
def auth_processor_user(request):
|
|
||||||
return render(request, 'context_processors/auth_attrs_user.html')
|
|
||||||
|
|
||||||
|
|
||||||
def auth_processor_perms(request):
|
|
||||||
return render(request, 'context_processors/auth_attrs_perms.html')
|
|
||||||
|
|
||||||
|
|
||||||
def auth_processor_perm_in_perms(request):
|
|
||||||
return render(request, 'context_processors/auth_attrs_perm_in_perms.html')
|
|
||||||
|
|
||||||
|
|
||||||
def auth_processor_messages(request):
|
|
||||||
info(request, "Message 1")
|
|
||||||
return render(request, 'context_processors/auth_attrs_messages.html')
|
|
||||||
|
|
||||||
|
|
||||||
def userpage(request):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def custom_request_auth_login(request):
|
|
||||||
return views.login(request, authentication_form=CustomRequestAuthenticationForm)
|
|
||||||
|
|
||||||
# special urls for auth test cases
|
# special urls for auth test cases
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
url(r'^logout/custom_query/$', views.logout, dict(redirect_field_name='follow')),
|
url(r'^logout/custom_query/$', views.logout, dict(redirect_field_name='follow')),
|
||||||
url(r'^logout/next_page/$', views.logout, dict(next_page='/somewhere/')),
|
url(r'^logout/next_page/$', views.logout, dict(next_page='/somewhere/')),
|
||||||
url(r'^logout/next_page/named/$', views.logout, dict(next_page='password_reset')),
|
url(r'^logout/next_page/named/$', views.logout, dict(next_page='password_reset')),
|
||||||
url(r'^remote_user/$', remote_user_auth_view),
|
|
||||||
url(r'^password_reset_from_email/$', views.password_reset, dict(from_email='staffmember@example.com')),
|
url(r'^password_reset_from_email/$', views.password_reset, dict(from_email='staffmember@example.com')),
|
||||||
url(r'^password_reset/custom_redirect/$', views.password_reset, dict(post_reset_redirect='/custom/')),
|
url(r'^password_reset/custom_redirect/$', views.password_reset, dict(post_reset_redirect='/custom/')),
|
||||||
url(r'^password_reset/custom_redirect/named/$', views.password_reset, dict(post_reset_redirect='password_reset')),
|
url(r'^password_reset/custom_redirect/named/$', views.password_reset, dict(post_reset_redirect='password_reset')),
|
||||||
|
@ -90,16 +27,4 @@ urlpatterns += [
|
||||||
url(r'^admin_password_reset/$', views.password_reset, dict(is_admin_site=True)),
|
url(r'^admin_password_reset/$', views.password_reset, dict(is_admin_site=True)),
|
||||||
url(r'^login_required/$', login_required(views.password_reset)),
|
url(r'^login_required/$', login_required(views.password_reset)),
|
||||||
url(r'^login_required_login_url/$', login_required(views.password_reset, login_url='/somewhere/')),
|
url(r'^login_required_login_url/$', login_required(views.password_reset, login_url='/somewhere/')),
|
||||||
|
|
||||||
url(r'^auth_processor_no_attr_access/$', auth_processor_no_attr_access),
|
|
||||||
url(r'^auth_processor_attr_access/$', auth_processor_attr_access),
|
|
||||||
url(r'^auth_processor_user/$', auth_processor_user),
|
|
||||||
url(r'^auth_processor_perms/$', auth_processor_perms),
|
|
||||||
url(r'^auth_processor_perm_in_perms/$', auth_processor_perm_in_perms),
|
|
||||||
url(r'^auth_processor_messages/$', auth_processor_messages),
|
|
||||||
url(r'^custom_request_auth_login/$', custom_request_auth_login),
|
|
||||||
url(r'^userpage/(.+)/$', userpage, name="userpage"),
|
|
||||||
|
|
||||||
# This line is only required to render the password reset with is_admin=True
|
|
||||||
url(r'^admin/', include(admin.site.urls)),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
django-allauth>=0.19.1
|
django-allauth>=0.19.1
|
||||||
responses>=0.3.0
|
responses>=0.3.0
|
||||||
flake8==2.4.0
|
flake8==2.4.0
|
||||||
|
djangorestframework-jwt>=1.7.2
|
||||||
|
|
|
@ -45,6 +45,27 @@ TEMPLATE_CONTEXT_PROCESSORS = [
|
||||||
"allauth.socialaccount.context_processors.socialaccount",
|
"allauth.socialaccount.context_processors.socialaccount",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# avoid deprecation warnings during tests
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
|
'DIRS': [
|
||||||
|
# insert your TEMPLATE_DIRS here
|
||||||
|
],
|
||||||
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
|
'context_processors': TEMPLATE_CONTEXT_PROCESSORS,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
'DEFAULT_AUTHENTICATION_CLASSES': (
|
||||||
|
'rest_framework.authentication.SessionAuthentication',
|
||||||
|
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
INSTALLED_APPS = [
|
||||||
'django.contrib.admin',
|
'django.contrib.admin',
|
||||||
'django.contrib.auth',
|
'django.contrib.auth',
|
||||||
|
@ -59,14 +80,24 @@ INSTALLED_APPS = [
|
||||||
'allauth.account',
|
'allauth.account',
|
||||||
'allauth.socialaccount',
|
'allauth.socialaccount',
|
||||||
'allauth.socialaccount.providers.facebook',
|
'allauth.socialaccount.providers.facebook',
|
||||||
|
'allauth.socialaccount.providers.twitter',
|
||||||
|
|
||||||
'rest_framework',
|
'rest_framework',
|
||||||
'rest_framework.authtoken',
|
'rest_framework.authtoken',
|
||||||
|
|
||||||
'rest_auth',
|
'rest_auth',
|
||||||
'rest_auth.registration'
|
'rest_auth.registration',
|
||||||
|
|
||||||
|
'rest_framework_jwt'
|
||||||
]
|
]
|
||||||
|
|
||||||
SECRET_KEY = "38dh*skf8sjfhs287dh&^hd8&3hdg*j2&sd"
|
SECRET_KEY = "38dh*skf8sjfhs287dh&^hd8&3hdg*j2&sd"
|
||||||
ACCOUNT_ACTIVATION_DAYS = 1
|
ACCOUNT_ACTIVATION_DAYS = 1
|
||||||
SITE_ID = 1
|
SITE_ID = 1
|
||||||
|
|
||||||
|
AUTHENTICATION_BACKENDS = (
|
||||||
|
# Needed to login by username in Django admin, regardless of `allauth`
|
||||||
|
'django.contrib.auth.backends.ModelBackend',
|
||||||
|
# `allauth` specific authentication methods, such as login by e-mail
|
||||||
|
'allauth.account.auth_backends.AuthenticationBackend',
|
||||||
|
)
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
from django.test import TestCase
|
from django.test import TestCase, override_settings
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.core import mail
|
from django.core import mail
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.test.utils import override_settings
|
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
|
from allauth.account import app_settings as account_app_settings
|
||||||
from .test_base import BaseAPITestCase
|
from .test_base import BaseAPITestCase
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(ROOT_URLCONF="tests.urls")
|
||||||
class APITestCase1(TestCase, BaseAPITestCase):
|
class APITestCase1(TestCase, BaseAPITestCase):
|
||||||
"""
|
"""
|
||||||
Case #1:
|
Case #1:
|
||||||
|
@ -18,7 +18,7 @@ class APITestCase1(TestCase, BaseAPITestCase):
|
||||||
- custom registration: backend defined
|
- custom registration: backend defined
|
||||||
"""
|
"""
|
||||||
|
|
||||||
urls = 'tests.urls'
|
# urls = 'tests.urls'
|
||||||
|
|
||||||
USERNAME = 'person'
|
USERNAME = 'person'
|
||||||
PASS = 'person'
|
PASS = 'person'
|
||||||
|
@ -57,7 +57,36 @@ class APITestCase1(TestCase, BaseAPITestCase):
|
||||||
result['token'] = default_token_generator.make_token(user)
|
result['token'] = default_token_generator.make_token(user)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def test_login(self):
|
@override_settings(ACCOUNT_AUTHENTICATION_METHOD=account_app_settings.AuthenticationMethod.EMAIL)
|
||||||
|
def test_login_failed_email_validation(self):
|
||||||
|
payload = {
|
||||||
|
"email": '',
|
||||||
|
"password": self.PASS
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = self.post(self.login_url, data=payload, status_code=400)
|
||||||
|
self.assertEqual(resp.json['non_field_errors'][0], u'Must include "email" and "password".')
|
||||||
|
|
||||||
|
@override_settings(ACCOUNT_AUTHENTICATION_METHOD=account_app_settings.AuthenticationMethod.USERNAME)
|
||||||
|
def test_login_failed_username_validation(self):
|
||||||
|
payload = {
|
||||||
|
"username": '',
|
||||||
|
"password": self.PASS
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = self.post(self.login_url, data=payload, status_code=400)
|
||||||
|
self.assertEqual(resp.json['non_field_errors'][0], u'Must include "username" and "password".')
|
||||||
|
|
||||||
|
@override_settings(ACCOUNT_AUTHENTICATION_METHOD=account_app_settings.AuthenticationMethod.USERNAME_EMAIL)
|
||||||
|
def test_login_failed_username_email_validation(self):
|
||||||
|
payload = {
|
||||||
|
"password": self.PASS
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = self.post(self.login_url, data=payload, status_code=400)
|
||||||
|
self.assertEqual(resp.json['non_field_errors'][0], u'Must include either "username" or "email" and "password".')
|
||||||
|
|
||||||
|
def test_allauth_login_with_username(self):
|
||||||
payload = {
|
payload = {
|
||||||
"username": self.USERNAME,
|
"username": self.USERNAME,
|
||||||
"password": self.PASS
|
"password": self.PASS
|
||||||
|
@ -91,6 +120,34 @@ class APITestCase1(TestCase, BaseAPITestCase):
|
||||||
# test empty payload
|
# test empty payload
|
||||||
self.post(self.login_url, data={}, status_code=400)
|
self.post(self.login_url, data={}, status_code=400)
|
||||||
|
|
||||||
|
@override_settings(ACCOUNT_AUTHENTICATION_METHOD=account_app_settings.AuthenticationMethod.EMAIL)
|
||||||
|
def test_allauth_login_with_email(self):
|
||||||
|
payload = {
|
||||||
|
"email": self.EMAIL,
|
||||||
|
"password": self.PASS
|
||||||
|
}
|
||||||
|
# there is no users in db so it should throw error (400)
|
||||||
|
self.post(self.login_url, data=payload, status_code=400)
|
||||||
|
|
||||||
|
self.post(self.password_change_url, status_code=403)
|
||||||
|
|
||||||
|
# create user
|
||||||
|
get_user_model().objects.create_user(self.EMAIL, email=self.EMAIL, password=self.PASS)
|
||||||
|
|
||||||
|
self.post(self.login_url, data=payload, status_code=200)
|
||||||
|
|
||||||
|
@override_settings(REST_USE_JWT=True)
|
||||||
|
def test_login_jwt(self):
|
||||||
|
payload = {
|
||||||
|
"username": self.USERNAME,
|
||||||
|
"password": self.PASS
|
||||||
|
}
|
||||||
|
get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
|
||||||
|
|
||||||
|
self.post(self.login_url, data=payload, status_code=200)
|
||||||
|
self.assertEqual('token' in self.response.json.keys(), True)
|
||||||
|
self.token = self.response.json['token']
|
||||||
|
|
||||||
def test_login_by_email(self):
|
def test_login_by_email(self):
|
||||||
# starting test without allauth app
|
# starting test without allauth app
|
||||||
settings.INSTALLED_APPS.remove('allauth')
|
settings.INSTALLED_APPS.remove('allauth')
|
||||||
|
@ -136,6 +193,9 @@ class APITestCase1(TestCase, BaseAPITestCase):
|
||||||
# test empty payload
|
# test empty payload
|
||||||
self.post(self.login_url, data={}, status_code=400)
|
self.post(self.login_url, data={}, status_code=400)
|
||||||
|
|
||||||
|
# bring back allauth
|
||||||
|
settings.INSTALLED_APPS.append('allauth')
|
||||||
|
|
||||||
def test_password_change(self):
|
def test_password_change(self):
|
||||||
login_payload = {
|
login_payload = {
|
||||||
"username": self.USERNAME,
|
"username": self.USERNAME,
|
||||||
|
@ -307,6 +367,21 @@ class APITestCase1(TestCase, BaseAPITestCase):
|
||||||
self.assertEqual(user.last_name, self.response.json['last_name'])
|
self.assertEqual(user.last_name, self.response.json['last_name'])
|
||||||
self.assertEqual(user.email, self.response.json['email'])
|
self.assertEqual(user.email, self.response.json['email'])
|
||||||
|
|
||||||
|
@override_settings(REST_USE_JWT=True)
|
||||||
|
def test_user_details_using_jwt(self):
|
||||||
|
user = get_user_model().objects.create_user(self.USERNAME, self.EMAIL, self.PASS)
|
||||||
|
payload = {
|
||||||
|
"username": self.USERNAME,
|
||||||
|
"password": self.PASS
|
||||||
|
}
|
||||||
|
self.post(self.login_url, data=payload, status_code=200)
|
||||||
|
self.token = self.response.json['token']
|
||||||
|
self.get(self.user_url, status_code=200)
|
||||||
|
|
||||||
|
self.patch(self.user_url, data=self.BASIC_USER_DATA, status_code=200)
|
||||||
|
user = get_user_model().objects.get(pk=user.pk)
|
||||||
|
self.assertEqual(user.email, self.response.json['email'])
|
||||||
|
|
||||||
def test_registration(self):
|
def test_registration(self):
|
||||||
user_count = get_user_model().objects.all().count()
|
user_count = get_user_model().objects.all().count()
|
||||||
|
|
||||||
|
@ -323,6 +398,19 @@ class APITestCase1(TestCase, BaseAPITestCase):
|
||||||
self._login()
|
self._login()
|
||||||
self._logout()
|
self._logout()
|
||||||
|
|
||||||
|
@override_settings(REST_USE_JWT=True)
|
||||||
|
def test_registration_with_jwt(self):
|
||||||
|
user_count = get_user_model().objects.all().count()
|
||||||
|
|
||||||
|
self.post(self.register_url, data={}, status_code=400)
|
||||||
|
|
||||||
|
result = self.post(self.register_url, data=self.REGISTRATION_DATA, status_code=201)
|
||||||
|
self.assertIn('token', result.data)
|
||||||
|
self.assertEqual(get_user_model().objects.all().count(), user_count + 1)
|
||||||
|
|
||||||
|
self._login()
|
||||||
|
self._logout()
|
||||||
|
|
||||||
def test_registration_with_invalid_password(self):
|
def test_registration_with_invalid_password(self):
|
||||||
data = self.REGISTRATION_DATA.copy()
|
data = self.REGISTRATION_DATA.copy()
|
||||||
data['password2'] = 'foobar'
|
data['password2'] = 'foobar'
|
||||||
|
@ -331,7 +419,8 @@ class APITestCase1(TestCase, BaseAPITestCase):
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(
|
||||||
ACCOUNT_EMAIL_VERIFICATION='mandatory',
|
ACCOUNT_EMAIL_VERIFICATION='mandatory',
|
||||||
ACCOUNT_EMAIL_REQUIRED=True
|
ACCOUNT_EMAIL_REQUIRED=True,
|
||||||
|
ACCOUNT_EMAIL_CONFIRMATION_HMAC=False
|
||||||
)
|
)
|
||||||
def test_registration_with_email_verification(self):
|
def test_registration_with_email_verification(self):
|
||||||
user_count = get_user_model().objects.all().count()
|
user_count = get_user_model().objects.all().count()
|
||||||
|
@ -378,3 +467,29 @@ class APITestCase1(TestCase, BaseAPITestCase):
|
||||||
# try to login again
|
# try to login again
|
||||||
self._login()
|
self._login()
|
||||||
self._logout()
|
self._logout()
|
||||||
|
|
||||||
|
@override_settings(ACCOUNT_LOGOUT_ON_GET=True)
|
||||||
|
def test_logout_on_get(self):
|
||||||
|
payload = {
|
||||||
|
"username": self.USERNAME,
|
||||||
|
"password": self.PASS
|
||||||
|
}
|
||||||
|
|
||||||
|
# create user
|
||||||
|
get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
|
||||||
|
|
||||||
|
self.post(self.login_url, data=payload, status_code=200)
|
||||||
|
self.get(self.logout_url, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
@override_settings(ACCOUNT_LOGOUT_ON_GET=False)
|
||||||
|
def test_logout_on_post_only(self):
|
||||||
|
payload = {
|
||||||
|
"username": self.USERNAME,
|
||||||
|
"password": self.PASS
|
||||||
|
}
|
||||||
|
|
||||||
|
# create user
|
||||||
|
get_user_model().objects.create_user(self.USERNAME, '', self.PASS)
|
||||||
|
|
||||||
|
self.post(self.login_url, data=payload, status_code=status.HTTP_200_OK)
|
||||||
|
self.get(self.logout_url, status_code=status.HTTP_405_METHOD_NOT_ALLOWED)
|
||||||
|
|
|
@ -37,17 +37,22 @@ class BaseAPITestCase(object):
|
||||||
|
|
||||||
# check_headers = kwargs.pop('check_headers', True)
|
# check_headers = kwargs.pop('check_headers', True)
|
||||||
if hasattr(self, 'token'):
|
if hasattr(self, 'token'):
|
||||||
|
if getattr(settings, 'REST_USE_JWT', False):
|
||||||
|
kwargs['HTTP_AUTHORIZATION'] = 'JWT %s' % self.token
|
||||||
|
else:
|
||||||
kwargs['HTTP_AUTHORIZATION'] = 'Token %s' % self.token
|
kwargs['HTTP_AUTHORIZATION'] = 'Token %s' % self.token
|
||||||
|
|
||||||
self.response = request_func(*args, **kwargs)
|
self.response = request_func(*args, **kwargs)
|
||||||
is_json = bool(
|
is_json = bool(
|
||||||
[x for x in self.response._headers['content-type'] if 'json' in x])
|
[x for x in self.response._headers['content-type'] if 'json' in x])
|
||||||
|
|
||||||
|
self.response.json = {}
|
||||||
if is_json and self.response.content:
|
if is_json and self.response.content:
|
||||||
self.response.json = json.loads(force_text(self.response.content))
|
self.response.json = json.loads(force_text(self.response.content))
|
||||||
else:
|
|
||||||
self.response.json = {}
|
|
||||||
if status_code:
|
if status_code:
|
||||||
self.assertEqual(self.response.status_code, status_code)
|
self.assertEqual(self.response.status_code, status_code)
|
||||||
|
|
||||||
return self.response
|
return self.response
|
||||||
|
|
||||||
def post(self, *args, **kwargs):
|
def post(self, *args, **kwargs):
|
||||||
|
@ -94,6 +99,9 @@ class BaseAPITestCase(object):
|
||||||
self.user_url = reverse('rest_user_details')
|
self.user_url = reverse('rest_user_details')
|
||||||
self.veirfy_email_url = reverse('rest_verify_email')
|
self.veirfy_email_url = reverse('rest_verify_email')
|
||||||
self.fb_login_url = reverse('fb_login')
|
self.fb_login_url = reverse('fb_login')
|
||||||
|
self.tw_login_url = reverse('tw_login')
|
||||||
|
self.tw_login_no_view_url = reverse('tw_login_no_view')
|
||||||
|
self.tw_login_no_adapter_url = reverse('tw_login_no_adapter')
|
||||||
|
|
||||||
def _login(self):
|
def _login(self):
|
||||||
payload = {
|
payload = {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import json
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
from django.contrib.auth import get_user_model
|
from django.contrib.auth import get_user_model
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
@ -12,10 +14,9 @@ from rest_framework import status
|
||||||
from .test_base import BaseAPITestCase
|
from .test_base import BaseAPITestCase
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(ROOT_URLCONF="tests.urls")
|
||||||
class TestSocialAuth(TestCase, BaseAPITestCase):
|
class TestSocialAuth(TestCase, BaseAPITestCase):
|
||||||
|
|
||||||
urls = 'tests.urls'
|
|
||||||
|
|
||||||
USERNAME = 'person'
|
USERNAME = 'person'
|
||||||
PASS = 'person'
|
PASS = 'person'
|
||||||
EMAIL = "person1@world.com"
|
EMAIL = "person1@world.com"
|
||||||
|
@ -35,9 +36,19 @@ class TestSocialAuth(TestCase, BaseAPITestCase):
|
||||||
client_id='123123123',
|
client_id='123123123',
|
||||||
secret='321321321',
|
secret='321321321',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
twitter_social_app = SocialApp.objects.create(
|
||||||
|
provider='twitter',
|
||||||
|
name='Twitter',
|
||||||
|
client_id='11223344',
|
||||||
|
secret='55667788',
|
||||||
|
)
|
||||||
|
|
||||||
site = Site.objects.get_current()
|
site = Site.objects.get_current()
|
||||||
social_app.sites.add(site)
|
social_app.sites.add(site)
|
||||||
|
twitter_social_app.sites.add(site)
|
||||||
self.graph_api_url = GRAPH_API_URL + '/me'
|
self.graph_api_url = GRAPH_API_URL + '/me'
|
||||||
|
self.twitter_url = 'http://twitter.com/foobarme'
|
||||||
|
|
||||||
@responses.activate
|
@responses.activate
|
||||||
def test_failed_social_auth(self):
|
def test_failed_social_auth(self):
|
||||||
|
@ -58,11 +69,24 @@ class TestSocialAuth(TestCase, BaseAPITestCase):
|
||||||
@responses.activate
|
@responses.activate
|
||||||
def test_social_auth(self):
|
def test_social_auth(self):
|
||||||
# fake response for facebook call
|
# fake response for facebook call
|
||||||
resp_body = '{"id":"123123123123","first_name":"John","gender":"male","last_name":"Smith","link":"https:\\/\\/www.facebook.com\\/john.smith","locale":"en_US","name":"John Smith","timezone":2,"updated_time":"2014-08-13T10:14:38+0000","username":"john.smith","verified":true}' # noqa
|
resp_body = {
|
||||||
|
"id": "123123123123",
|
||||||
|
"first_name": "John",
|
||||||
|
"gender": "male",
|
||||||
|
"last_name": "Smith",
|
||||||
|
"link": "https://www.facebook.com/john.smith",
|
||||||
|
"locale": "en_US",
|
||||||
|
"name": "John Smith",
|
||||||
|
"timezone": 2,
|
||||||
|
"updated_time": "2014-08-13T10:14:38+0000",
|
||||||
|
"username": "john.smith",
|
||||||
|
"verified": True
|
||||||
|
}
|
||||||
|
|
||||||
responses.add(
|
responses.add(
|
||||||
responses.GET,
|
responses.GET,
|
||||||
self.graph_api_url,
|
self.graph_api_url,
|
||||||
body=resp_body,
|
body=json.dumps(resp_body),
|
||||||
status=200,
|
status=200,
|
||||||
content_type='application/json'
|
content_type='application/json'
|
||||||
)
|
)
|
||||||
|
@ -81,18 +105,146 @@ class TestSocialAuth(TestCase, BaseAPITestCase):
|
||||||
self.assertIn('key', self.response.json.keys())
|
self.assertIn('key', self.response.json.keys())
|
||||||
self.assertEqual(get_user_model().objects.all().count(), users_count + 1)
|
self.assertEqual(get_user_model().objects.all().count(), users_count + 1)
|
||||||
|
|
||||||
|
def _twitter_social_auth(self):
|
||||||
|
# fake response for twitter call
|
||||||
|
resp_body = {
|
||||||
|
"id": "123123123123",
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
'https://api.twitter.com/1.1/account/verify_credentials.json',
|
||||||
|
body=json.dumps(resp_body),
|
||||||
|
status=200,
|
||||||
|
content_type='application/json'
|
||||||
|
)
|
||||||
|
|
||||||
|
users_count = get_user_model().objects.all().count()
|
||||||
|
payload = {
|
||||||
|
'access_token': 'abc123',
|
||||||
|
'token_secret': '1111222233334444'
|
||||||
|
}
|
||||||
|
|
||||||
|
self.post(self.tw_login_url, data=payload)
|
||||||
|
|
||||||
|
self.assertIn('key', self.response.json.keys())
|
||||||
|
self.assertEqual(get_user_model().objects.all().count(), users_count + 1)
|
||||||
|
|
||||||
|
# make sure that second request will not create a new user
|
||||||
|
self.post(self.tw_login_url, data=payload, status_code=200)
|
||||||
|
self.assertIn('key', self.response.json.keys())
|
||||||
|
self.assertEqual(get_user_model().objects.all().count(), users_count + 1)
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
@override_settings(SOCIALACCOUNT_AUTO_SIGNUP=True)
|
||||||
|
def test_twitter_social_auth(self):
|
||||||
|
self._twitter_social_auth()
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
@override_settings(SOCIALACCOUNT_AUTO_SIGNUP=False)
|
||||||
|
def test_twitter_social_auth_without_auto_singup(self):
|
||||||
|
self._twitter_social_auth()
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_twitter_social_auth_request_error(self):
|
||||||
|
# fake response for twitter call
|
||||||
|
resp_body = {
|
||||||
|
"id": "123123123123",
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
'https://api.twitter.com/1.1/account/verify_credentials.json',
|
||||||
|
body=json.dumps(resp_body),
|
||||||
|
status=400,
|
||||||
|
content_type='application/json'
|
||||||
|
)
|
||||||
|
|
||||||
|
users_count = get_user_model().objects.all().count()
|
||||||
|
payload = {
|
||||||
|
'access_token': 'abc123',
|
||||||
|
'token_secret': '1111222233334444'
|
||||||
|
}
|
||||||
|
|
||||||
|
self.post(self.tw_login_url, data=payload, status_code=400)
|
||||||
|
self.assertNotIn('key', self.response.json.keys())
|
||||||
|
self.assertEqual(get_user_model().objects.all().count(), users_count)
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_twitter_social_auth_no_view_in_context(self):
|
||||||
|
# fake response for twitter call
|
||||||
|
resp_body = {
|
||||||
|
"id": "123123123123",
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
'https://api.twitter.com/1.1/account/verify_credentials.json',
|
||||||
|
body=json.dumps(resp_body),
|
||||||
|
status=400,
|
||||||
|
content_type='application/json'
|
||||||
|
)
|
||||||
|
|
||||||
|
users_count = get_user_model().objects.all().count()
|
||||||
|
payload = {
|
||||||
|
'access_token': 'abc123',
|
||||||
|
'token_secret': '1111222233334444'
|
||||||
|
}
|
||||||
|
|
||||||
|
self.post(self.tw_login_no_view_url, data=payload, status_code=400)
|
||||||
|
self.assertEqual(get_user_model().objects.all().count(), users_count)
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_twitter_social_auth_no_adapter(self):
|
||||||
|
# fake response for twitter call
|
||||||
|
resp_body = {
|
||||||
|
"id": "123123123123",
|
||||||
|
}
|
||||||
|
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
'https://api.twitter.com/1.1/account/verify_credentials.json',
|
||||||
|
body=json.dumps(resp_body),
|
||||||
|
status=400,
|
||||||
|
content_type='application/json'
|
||||||
|
)
|
||||||
|
|
||||||
|
users_count = get_user_model().objects.all().count()
|
||||||
|
payload = {
|
||||||
|
'access_token': 'abc123',
|
||||||
|
'token_secret': '1111222233334444'
|
||||||
|
}
|
||||||
|
|
||||||
|
self.post(self.tw_login_no_adapter_url, data=payload, status_code=400)
|
||||||
|
self.assertEqual(get_user_model().objects.all().count(), users_count)
|
||||||
|
|
||||||
@responses.activate
|
@responses.activate
|
||||||
@override_settings(
|
@override_settings(
|
||||||
ACCOUNT_EMAIL_VERIFICATION='mandatory',
|
ACCOUNT_EMAIL_VERIFICATION='mandatory',
|
||||||
ACCOUNT_EMAIL_REQUIRED=True,
|
ACCOUNT_EMAIL_REQUIRED=True,
|
||||||
REST_SESSION_LOGIN=False
|
REST_SESSION_LOGIN=False,
|
||||||
|
ACCOUNT_EMAIL_CONFIRMATION_HMAC=False
|
||||||
)
|
)
|
||||||
def test_edge_case(self):
|
def test_edge_case(self):
|
||||||
resp_body = '{"id":"123123123123","first_name":"John","gender":"male","last_name":"Smith","link":"https:\\/\\/www.facebook.com\\/john.smith","locale":"en_US","name":"John Smith","timezone":2,"updated_time":"2014-08-13T10:14:38+0000","username":"john.smith","verified":true,"email":"%s"}' # noqa
|
resp_body = {
|
||||||
|
"id": "123123123123",
|
||||||
|
"first_name": "John",
|
||||||
|
"gender": "male",
|
||||||
|
"last_name": "Smith",
|
||||||
|
"link": "https://www.facebook.com/john.smith",
|
||||||
|
"locale": "en_US",
|
||||||
|
"name": "John Smith",
|
||||||
|
"timezone": 2,
|
||||||
|
"updated_time": "2014-08-13T10:14:38+0000",
|
||||||
|
"username": "john.smith",
|
||||||
|
"verified": True,
|
||||||
|
"email": self.EMAIL
|
||||||
|
}
|
||||||
|
|
||||||
responses.add(
|
responses.add(
|
||||||
responses.GET,
|
responses.GET,
|
||||||
self.graph_api_url,
|
self.graph_api_url,
|
||||||
body=resp_body % self.EMAIL,
|
body=json.dumps(resp_body),
|
||||||
status=200,
|
status=200,
|
||||||
content_type='application/json'
|
content_type='application/json'
|
||||||
)
|
)
|
||||||
|
@ -125,3 +277,28 @@ class TestSocialAuth(TestCase, BaseAPITestCase):
|
||||||
|
|
||||||
self.post(self.fb_login_url, data=payload, status_code=200)
|
self.post(self.fb_login_url, data=payload, status_code=200)
|
||||||
self.assertIn('key', self.response.json.keys())
|
self.assertIn('key', self.response.json.keys())
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
@override_settings(
|
||||||
|
REST_USE_JWT=True
|
||||||
|
)
|
||||||
|
def test_jwt(self):
|
||||||
|
resp_body = '{"id":"123123123123","first_name":"John","gender":"male","last_name":"Smith","link":"https:\\/\\/www.facebook.com\\/john.smith","locale":"en_US","name":"John Smith","timezone":2,"updated_time":"2014-08-13T10:14:38+0000","username":"john.smith","verified":true}' # noqa
|
||||||
|
responses.add(
|
||||||
|
responses.GET,
|
||||||
|
self.graph_api_url,
|
||||||
|
body=resp_body,
|
||||||
|
status=200,
|
||||||
|
content_type='application/json'
|
||||||
|
)
|
||||||
|
|
||||||
|
users_count = get_user_model().objects.all().count()
|
||||||
|
payload = {
|
||||||
|
'access_token': 'abc123'
|
||||||
|
}
|
||||||
|
|
||||||
|
self.post(self.fb_login_url, data=payload, status_code=200)
|
||||||
|
self.assertIn('token', self.response.json.keys())
|
||||||
|
self.assertIn('user', self.response.json.keys())
|
||||||
|
|
||||||
|
self.assertEqual(get_user_model().objects.all().count(), users_count + 1)
|
||||||
|
|
|
@ -3,21 +3,51 @@ from django.views.generic import TemplateView
|
||||||
from . import django_urls
|
from . import django_urls
|
||||||
|
|
||||||
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
|
from allauth.socialaccount.providers.facebook.views import FacebookOAuth2Adapter
|
||||||
|
from allauth.socialaccount.providers.twitter.views import TwitterOAuthAdapter
|
||||||
|
|
||||||
|
from rest_framework.decorators import api_view
|
||||||
|
|
||||||
from rest_auth.urls import urlpatterns
|
from rest_auth.urls import urlpatterns
|
||||||
from rest_auth.registration.views import SocialLoginView
|
from rest_auth.registration.views import SocialLoginView
|
||||||
|
from rest_auth.social_serializers import TwitterLoginSerializer
|
||||||
|
|
||||||
|
|
||||||
class FacebookLogin(SocialLoginView):
|
class FacebookLogin(SocialLoginView):
|
||||||
adapter_class = FacebookOAuth2Adapter
|
adapter_class = FacebookOAuth2Adapter
|
||||||
|
|
||||||
|
|
||||||
|
class TwitterLogin(SocialLoginView):
|
||||||
|
adapter_class = TwitterOAuthAdapter
|
||||||
|
serializer_class = TwitterLoginSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class TwitterLoginSerializerFoo(TwitterLoginSerializer):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@api_view(['POST'])
|
||||||
|
def twitter_login_view(request):
|
||||||
|
serializer = TwitterLoginSerializerFoo(
|
||||||
|
data={'access_token': '11223344', 'token_secret': '55667788'},
|
||||||
|
context={'request': request}
|
||||||
|
)
|
||||||
|
serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
|
|
||||||
|
class TwitterLoginNoAdapter(SocialLoginView):
|
||||||
|
serializer_class = TwitterLoginSerializer
|
||||||
|
|
||||||
|
|
||||||
urlpatterns += [
|
urlpatterns += [
|
||||||
url(r'^rest-registration/', include('rest_auth.registration.urls')),
|
url(r'^rest-registration/', include('rest_auth.registration.urls')),
|
||||||
url(r'^test-admin/', include(django_urls)),
|
url(r'^test-admin/', include(django_urls)),
|
||||||
url(r'^account-email-verification-sent/$', TemplateView.as_view(),
|
url(r'^account-email-verification-sent/$', TemplateView.as_view(),
|
||||||
name='account_email_verification_sent'),
|
name='account_email_verification_sent'),
|
||||||
url(r'^account-confirm-email/(?P<key>\w+)/$', TemplateView.as_view(),
|
url(r'^account-confirm-email/(?P<key>[-:\w]+)/$', TemplateView.as_view(),
|
||||||
name='account_confirm_email'),
|
name='account_confirm_email'),
|
||||||
url(r'^social-login/facebook/$', FacebookLogin.as_view(), name='fb_login'),
|
url(r'^social-login/facebook/$', FacebookLogin.as_view(), name='fb_login'),
|
||||||
|
url(r'^social-login/twitter/$', TwitterLogin.as_view(), name='tw_login'),
|
||||||
|
url(r'^social-login/twitter-no-view/$', twitter_login_view, name='tw_login_no_view'),
|
||||||
|
url(r'^social-login/twitter-no-adapter/$', TwitterLoginNoAdapter.as_view(), name='tw_login_no_adapter'),
|
||||||
url(r'^accounts/', include('allauth.socialaccount.urls'))
|
url(r'^accounts/', include('allauth.socialaccount.urls'))
|
||||||
]
|
]
|
||||||
|
|
|
@ -14,3 +14,16 @@ def import_callable(path_or_callable):
|
||||||
def default_create_token(token_model, user, serializer):
|
def default_create_token(token_model, user, serializer):
|
||||||
token, _ = token_model.objects.get_or_create(user=user)
|
token, _ = token_model.objects.get_or_create(user=user)
|
||||||
return token
|
return token
|
||||||
|
|
||||||
|
|
||||||
|
def jwt_encode(user):
|
||||||
|
try:
|
||||||
|
from rest_framework_jwt.settings import api_settings
|
||||||
|
except ImportError:
|
||||||
|
raise ImportError('rest_framework_jwt needs to be installed')
|
||||||
|
|
||||||
|
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
|
||||||
|
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
|
||||||
|
|
||||||
|
payload = jwt_payload_handler(user)
|
||||||
|
return jwt_encode_handler(payload)
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
from django.contrib.auth import login, logout
|
from django.contrib.auth import (
|
||||||
|
login as django_login,
|
||||||
|
logout as django_logout
|
||||||
|
)
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from rest_framework.views import APIView
|
from rest_framework.views import APIView
|
||||||
|
@ -9,13 +13,17 @@ from rest_framework.generics import GenericAPIView
|
||||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||||
from rest_framework.generics import RetrieveUpdateAPIView
|
from rest_framework.generics import RetrieveUpdateAPIView
|
||||||
|
|
||||||
|
from allauth.account import app_settings as allauth_settings
|
||||||
|
|
||||||
from .app_settings import (
|
from .app_settings import (
|
||||||
TokenSerializer, UserDetailsSerializer, LoginSerializer,
|
TokenSerializer, UserDetailsSerializer, LoginSerializer,
|
||||||
PasswordResetSerializer, PasswordResetConfirmSerializer,
|
PasswordResetSerializer, PasswordResetConfirmSerializer,
|
||||||
PasswordChangeSerializer, create_token
|
PasswordChangeSerializer, JWTSerializer, create_token
|
||||||
)
|
)
|
||||||
from .models import TokenModel
|
from .models import TokenModel
|
||||||
|
|
||||||
|
from .utils import jwt_encode
|
||||||
|
|
||||||
|
|
||||||
class LoginView(GenericAPIView):
|
class LoginView(GenericAPIView):
|
||||||
|
|
||||||
|
@ -31,22 +39,47 @@ class LoginView(GenericAPIView):
|
||||||
permission_classes = (AllowAny,)
|
permission_classes = (AllowAny,)
|
||||||
serializer_class = LoginSerializer
|
serializer_class = LoginSerializer
|
||||||
token_model = TokenModel
|
token_model = TokenModel
|
||||||
|
|
||||||
|
def process_login(self):
|
||||||
|
django_login(self.request, self.user)
|
||||||
|
|
||||||
|
def get_response_serializer(self):
|
||||||
|
if getattr(settings, 'REST_USE_JWT', False):
|
||||||
|
response_serializer = JWTSerializer
|
||||||
|
else:
|
||||||
response_serializer = TokenSerializer
|
response_serializer = TokenSerializer
|
||||||
|
return response_serializer
|
||||||
|
|
||||||
def login(self):
|
def login(self):
|
||||||
self.user = self.serializer.validated_data['user']
|
self.user = self.serializer.validated_data['user']
|
||||||
|
|
||||||
|
if getattr(settings, 'REST_USE_JWT', False):
|
||||||
|
self.token = jwt_encode(self.user)
|
||||||
|
else:
|
||||||
self.token = create_token(self.token_model, self.user, self.serializer)
|
self.token = create_token(self.token_model, self.user, self.serializer)
|
||||||
|
|
||||||
if getattr(settings, 'REST_SESSION_LOGIN', True):
|
if getattr(settings, 'REST_SESSION_LOGIN', True):
|
||||||
login(self.request, self.user)
|
self.process_login()
|
||||||
|
|
||||||
def get_response(self):
|
def get_response(self):
|
||||||
return Response(
|
serializer_class = self.get_response_serializer()
|
||||||
self.response_serializer(self.token).data, status=status.HTTP_200_OK
|
|
||||||
)
|
if getattr(settings, 'REST_USE_JWT', False):
|
||||||
|
data = {
|
||||||
|
'user': self.user,
|
||||||
|
'token': self.token
|
||||||
|
}
|
||||||
|
serializer = serializer_class(instance=data, context={'request': self.request})
|
||||||
|
else:
|
||||||
|
serializer = serializer_class(instance=self.token, context={'request': self.request})
|
||||||
|
|
||||||
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
|
self.request = request
|
||||||
self.serializer = self.get_serializer(data=self.request.data)
|
self.serializer = self.get_serializer(data=self.request.data)
|
||||||
self.serializer.is_valid(raise_exception=True)
|
self.serializer.is_valid(raise_exception=True)
|
||||||
|
|
||||||
self.login()
|
self.login()
|
||||||
return self.get_response()
|
return self.get_response()
|
||||||
|
|
||||||
|
@ -61,20 +94,33 @@ class LogoutView(APIView):
|
||||||
"""
|
"""
|
||||||
permission_classes = (AllowAny,)
|
permission_classes = (AllowAny,)
|
||||||
|
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
if allauth_settings.LOGOUT_ON_GET:
|
||||||
|
response = self.logout(request)
|
||||||
|
else:
|
||||||
|
response = self.http_method_not_allowed(request, *args, **kwargs)
|
||||||
|
except Exception as exc:
|
||||||
|
response = self.handle_exception(exc)
|
||||||
|
|
||||||
|
return self.finalize_response(request, response, *args, **kwargs)
|
||||||
|
|
||||||
def post(self, request):
|
def post(self, request):
|
||||||
|
return self.logout(request)
|
||||||
|
|
||||||
|
def logout(self, request):
|
||||||
try:
|
try:
|
||||||
request.user.auth_token.delete()
|
request.user.auth_token.delete()
|
||||||
except (AttributeError, ObjectDoesNotExist):
|
except (AttributeError, ObjectDoesNotExist):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
logout(request)
|
django_logout(request)
|
||||||
|
|
||||||
return Response({"success": "Successfully logged out."},
|
return Response({"success": _("Successfully logged out.")},
|
||||||
status=status.HTTP_200_OK)
|
status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class UserDetailsView(RetrieveUpdateAPIView):
|
class UserDetailsView(RetrieveUpdateAPIView):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Returns User's details in JSON format.
|
Returns User's details in JSON format.
|
||||||
|
|
||||||
|
@ -111,13 +157,12 @@ class PasswordResetView(GenericAPIView):
|
||||||
serializer.save()
|
serializer.save()
|
||||||
# Return the success message with OK HTTP status
|
# Return the success message with OK HTTP status
|
||||||
return Response(
|
return Response(
|
||||||
{"success": "Password reset e-mail has been sent."},
|
{"success": _("Password reset e-mail has been sent.")},
|
||||||
status=status.HTTP_200_OK
|
status=status.HTTP_200_OK
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class PasswordResetConfirmView(GenericAPIView):
|
class PasswordResetConfirmView(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.
|
||||||
|
|
||||||
|
@ -133,11 +178,10 @@ class PasswordResetConfirmView(GenericAPIView):
|
||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
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 PasswordChangeView(GenericAPIView):
|
class PasswordChangeView(GenericAPIView):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Calls Django Auth SetPasswordForm save method.
|
Calls Django Auth SetPasswordForm save method.
|
||||||
|
|
||||||
|
@ -152,4 +196,4 @@ class PasswordChangeView(GenericAPIView):
|
||||||
serializer = self.get_serializer(data=request.data)
|
serializer = self.get_serializer(data=request.data)
|
||||||
serializer.is_valid(raise_exception=True)
|
serializer.is_valid(raise_exception=True)
|
||||||
serializer.save()
|
serializer.save()
|
||||||
return Response({"success": "New password has been saved."})
|
return Response({"success": _("New password has been saved.")})
|
||||||
|
|
8
setup.py
8
setup.py
|
@ -18,7 +18,7 @@ f.close()
|
||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='django-rest-auth',
|
name='django-rest-auth',
|
||||||
version='0.6.0',
|
version='0.8.1',
|
||||||
author='Sumit Chachra',
|
author='Sumit Chachra',
|
||||||
author_email='chachra@tivix.com',
|
author_email='chachra@tivix.com',
|
||||||
url='http://github.com/Tivix/django-rest-auth',
|
url='http://github.com/Tivix/django-rest-auth',
|
||||||
|
@ -28,16 +28,16 @@ setup(
|
||||||
keywords='django rest auth registration rest-framework django-registration api',
|
keywords='django rest auth registration rest-framework django-registration api',
|
||||||
zip_safe=False,
|
zip_safe=False,
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'Django>=1.7.0',
|
'Django>=1.8.0',
|
||||||
'djangorestframework>=3.1.0',
|
'djangorestframework>=3.1.0',
|
||||||
'six>=1.9.0',
|
'six>=1.9.0',
|
||||||
],
|
],
|
||||||
extras_require={
|
extras_require={
|
||||||
'with_social': ['django-allauth>=0.24.1'],
|
'with_social': ['django-allauth>=0.25.0'],
|
||||||
},
|
},
|
||||||
tests_require=[
|
tests_require=[
|
||||||
'responses>=0.5.0',
|
'responses>=0.5.0',
|
||||||
'django-allauth>=0.24.1',
|
'django-allauth>=0.25.0',
|
||||||
],
|
],
|
||||||
test_suite='runtests.runtests',
|
test_suite='runtests.runtests',
|
||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
|
|
Loading…
Reference in New Issue
Block a user