Django rest framework with Djoser token authentication token response - django

As i am using a third party package called djoser to handle the token authentication. i want to customise the response. But after trying to change it, the result is not what i wanted.
I just wanted to get the token value of the token instead of having "auth_token:" in front.
Here is the link to djoser: https://github.com/sunscrapers/djoser
Here is my code :
serializer.py
class TokenCreateSerializer(serializers.Serializer):
password = serializers.CharField(
required=False, style={'input_type': 'password'}
)
default_error_messages = {
'invalid_credentials': constants.INVALID_CREDENTIALS_ERROR,
'inactive_account': constants.INACTIVE_ACCOUNT_ERROR,
}
def __init__(self, *args, **kwargs):
super(TokenCreateSerializer, self).__init__(*args, **kwargs)
self.user = None
self.fields[User.USERNAME_FIELD] = serializers.CharField(
required=False
)
def validate(self, attrs):
self.user = authenticate(
username=attrs.get(User.USERNAME_FIELD),
password=attrs.get('password')
)
self._validate_user_exists(self.user)
self._validate_user_is_active(self.user)
return attrs
def _validate_user_exists(self, user):
if not user:
self.fail('invalid_credentials')
def _validate_user_is_active(self, user):
if not user.is_active:
self.fail('inactive_account')
views.py
class TokenCreateView(utils.ActionViewMixin, generics.GenericAPIView):
"""
Use this endpoint to obtain user authentication token.
"""
serializer_class = settings.SERIALIZERS.token_create
permission_classes = [permissions.AllowAny]
def _action(self, serializer):
token = utils.login_user(self.request, serializer.user)
token_serializer_class = settings.SERIALIZERS.token
return Response(
data=token_serializer_class(token).data,
status=status.HTTP_200_OK,
)
my custom views.py
class CustomTokenCreateView(utils.ActionViewMixin, generics.GenericAPIView):
"""
Use this endpoint to obtain user authentication token.
"""
serializer_class = TokenCreateSerializer
permission_classes = [permissions.AllowAny]
def _action(self, serializer):
token = utils.login_user(self.request, serializer.user)
token_serializer_class = settings.SERIALIZERS.token
content = {
'Token': token_serializer_class(token).data,
'promptmsg': 'You have successfully login',
'status': '200'
}
return Response(
data=content,
status=status.HTTP_200_OK,
)
Result from djoser token authentication:
Success :
{
"auth_token": "da57cd11c34cb4332eaa6cc2cac797d0ee95cafb"
}
Result from my edited djoser authentication
Success:
{
"Token": {
"auth_token": "da57cd11c34cb4332eaa6cc2cac797d0ee95cafb"
},
"promptmsg": "You have successfully login",
"status": "200"
}
The result i want
Success:
{
"Token": "da57cd11c34cb4332eaa6cc2cac797d0ee95cafb",
"promptmsg": "You have successfully login",
"status": "200"
}
Is there a way to remove the auth_token tag ? i do not mind if its auth_token but as long as the format is what it is expected

Try this to access the value:
content = {
'Token': token_serializer_class(token).data["auth_token"],
'promptmsg': 'You have successfully login',
'status': '200'
}
That way you are not assigning the entire object.

Related

cannot access the passed file from swagger ui inside django debugger(pdb)

created an api and added swagger to the api with the help of the package
drf-yasg
the current updated version 1.20.0, then added code like this
success_res_data = openapi.Schema(type=openapi.TYPE_OBJECT, properties={'status': openapi.Schema(type=openapi.TYPE_NUMBER, title='200'), 'success': openapi.Schema(type=openapi.TYPE_OBJECT, properties={'message_header': openapi.Schema(type=openapi.TYPE_STRING), 'message': openapi.Schema(type=openapi.TYPE_STRING)})})
error_res_data = openapi.Schema(type=openapi.TYPE_OBJECT, properties={'status': openapi.Schema(type=openapi.TYPE_NUMBER, title='400'), 'error': openapi.Schema(type=openapi.TYPE_OBJECT, properties={'message_header': openapi.Schema(type=openapi.TYPE_STRING), 'message': openapi.Schema(type=openapi.TYPE_STRING)})})
class TestView(APIView):
api_view = ['POST']
authentication_classes = [SessionAuthentication, TokenAuthentication]
invitation_file = openapi.Parameter('invitation_file', openapi.IN_QUERY, type=openapi.TYPE_FILE, required=True)
#swagger_auto_schema(
manual_parameters=[invitation_file], operation_description="description",
responses={200: success_res_data, 400: error_res_data}
)
def post(self, request):
invitation_file = request.data.get('invitation_file', None)
invitation_file = openapi.Parameter('invitation_file', openapi.IN_QUERY, type=openapi.TYPE_FILE, required=True)
#swagger_auto_schema(
manual_parameters=[invitation_file], operation_description="description",
responses={200: success_res_data, 400: error_res_data}
)
def post(self, request):
invitation_file = request.data.get('invitation_file', None)
this invitation_file variable is returning None even if we pass the file from front-end
after a little research and checking the same api in postman, changed the code from whats above to
success_res_data = openapi.Schema(type=openapi.TYPE_OBJECT, properties={'status': openapi.Schema(type=openapi.TYPE_NUMBER, title='200'), 'success': openapi.Schema(type=openapi.TYPE_OBJECT, properties={'message_header': openapi.Schema(type=openapi.TYPE_STRING), 'message': openapi.Schema(type=openapi.TYPE_STRING)})})
error_res_data = openapi.Schema(type=openapi.TYPE_OBJECT, properties={'status': openapi.Schema(type=openapi.TYPE_NUMBER, title='400'), 'error': openapi.Schema(type=openapi.TYPE_OBJECT, properties={'message_header': openapi.Schema(type=openapi.TYPE_STRING), 'message': openapi.Schema(type=openapi.TYPE_STRING)})})
class TestView(APIView):
api_view = ['POST']
authentication_classes = [SessionAuthentication, TokenAuthentication]
invitation_file = openapi.Parameter('invitation_file', openapi.IN_QUERY, type=openapi.TYPE_FILE, required=True)
#swagger_auto_schema(
manual_parameters=[invitation_file],operation_description="API Description", consumes="multipart/form-data",
responses={200: success_res_data, 400: error_res_data}
)
def post(self, request):
invitation_file = request.data.get('invitation_file', None)
invitation_file = openapi.Parameter('invitation_file', openapi.IN_QUERY, type=openapi.TYPE_FILE, required=True)
#swagger_auto_schema(
manual_parameters=[invitation_file], operation_description="description",
responses={200: success_res_data, 400: error_res_data}
)
def post(self, request):
invitation_file = request.data.get('invitation_file', None)
now click on the unlock button on the right of the api and add the "Token auth-token" and click authenticate
now after calling the api and using pdb the value of the passed file is shown for the variable invitation_file

Django and Django rest framework + email

How could i achieve email functionality using drf as backeend and django to hit these apis.What i need how will user be confirm from django while using drf to send activation link.
At first, you need to add the code to send the verification email when you register.
from base64 import urlsafe_b64decode, urlsafe_b64encode
from django.contrib.auth.tokens import default_token_generator
from django.template.loader import render_to_string
from threading import Thread
class EmailRegisterView(APIView):
"""APIs for Email Registeration"""
permission_classes = [AllowAny]
def post(self, request):
"""Signup with Email"""
serializer = EmailRegisterSerializer(data=request.data)
if serializer.is_valid():
...
user.save()
// send verification link
cur_token = default_token_generator.make_token(user)
email = urlsafe_b64encode(str(user.email).encode('utf-8'))
# now send email
mail_subject = 'Email Confirmation'
message = render_to_string('emails/email_verification.html', {
'site_url': settings.SITE_URL,
'token': f'api/users/verify/{email.decode("utf-8")}/{cur_token}',
})
t = Thread(target=send_mail, args=(
mail_subject, message, settings.EMAIL_FROM_USER, to_email))
t.start()
return Response({
"success": True,
"user": MemberSerializer(user).data
}, status.HTTP_200_OK)
And you can add the confirmation view.
urlpatterns = [
...
path('verify/<str:email>/<str:email_token>',
verify_email, name="verify_token"),
...
]
Then the verify_email function verifies the token and redirects.
#api_view(['GET'])
#permission_classes([AllowAny])
def verify_email(request, email, email_token):
"""Verify Email"""
try:
target_link = settings.CLIENT_URL + "/account/result?type=email_verified"
if verify_token(email, email_token):
return redirect(target_link)
else:
return render(
request,
"emails/email_error.html",
{'success': False, 'link': target_link}
)
except BaseException:
pass
Here is the verify_token function.
def verify_token(email, email_token):
"""Return token verification result"""
try:
users = Member.objects.filter(
email=urlsafe_b64decode(email).decode("utf-8"))
for user in users:
valid = default_token_generator.check_token(user, email_token)
if valid:
user.is_verified = True
user.save()
return valid
except BaseException:
pass
return False

How to validate and return access and refresh tokens after user.save()

I'm verifying the user OTP to change password and after change password I'm unable to create access and refresh token using JWT ,
Normally when user get log in I use following method MyTokenObtainPairView which return both access and refresh token with all other stuff to UserSerializerWithToken.
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
data = super().validate(attrs)
serializer = UserSerializerWithToken(self.user).data
for k, v in serializer.items():
data[k] = v
return data
class MyTokenObtainPairView(TokenObtainPairView):
serializer_class = MyTokenObtainPairSerializer
I coppied similar appraoch to return UserSerializerWithToken after set_password and user.save()
UserSerializerWithToken is
class UserSerializerWithToken(UserSerializer):
token = serializers.SerializerMethodField(read_only=True)
class Meta:
model = CustomUser
fields = ['id',
'isAdmin',
'token']
def get_token(self, obj):
token = RefreshToken.for_user(obj)
return str(token.access_token)
and the problematic function is
#api_view(['PUT'])
def reset_password(request):
data = request.data
email = data['email']
otp_to_verify = data['otp']
new_password = data['password']
user = CustomUser.objects.get(email=email)
serializer = UserSerializerWithToken(user, many=False)
if CustomUser.objects.filter(email=email).exists():
if otp_to_verify == user.otp:
if new_password != '':
user.set_password(new_password)
user.save() # here password gets changed
return Response(serializer.data) #
else:
message = {
'detail': 'Password cant be empty'}
return Response(message, status=status.HTTP_400_BAD_REQUEST)
else:
message = {
'detail': 'Something went wrong'}
return Response(message, status=status.HTTP_400_BAD_REQUEST)
I receive the toke but not getting access and refresh toke to use it to login next time. I'm assuming user.save() dosnt create refresh and access token here. Can anybody identify why this is happening and how to fix that
user.save() does not create the tokens
token = RefreshToken.for_user(obj)
return str(token.access_token)
These lines create the token.
In my opinion, you dont need the serializer here.
#api_view(['PUT'])
def reset_password(request):
data = request.data
email = data['email']
otp_to_verify = data['otp']
new_password = data['password']
user = CustomUser.objects.get(email=email)
if CustomUser.objects.filter(email=email).exists():
otp_to_verify == user.otp
if new_password != '':
user.set_password(new_password)
user.save() # here password gets changed
token = RefreshToken.for_user(user)
response = { "refresh_token": str(token),
"access_token": str(token.access_token)
}
return Response(response)
else:
message = {
'detail': 'Password cant be empty'}
return Response(message, status=status.HTTP_400_BAD_REQUEST)
else:
message = {
'detail': 'Something went wrong'}
return Response(message, status=status.HTTP_400_BAD_REQUEST)
When you save user, you can make a post request:
access_url = config('BASE_API_URL') + 'token/'
access_response = requests.post(access_url, data=data)
access_token = access_response.json().get('access')
refresh_token = access_response.json().get('refresh')
Then return,
return Response(
{"access_token": access_token,
"refresh_token" : refresh_token,
"additional_data": messege},
status=status.HTTP_200_OK
)

How can I specify the parameter for POST requests while using APIView with django-rest-swagger

In the latest version on Django REST Swagger (2.1.0) YAML docstrings have been deprecated. I cannot get swagger to show the POST request parameters.
Here is my view
class UserAuthenticationView(APIView):
def post(self, request, *args, **kwargs):
serializer = UserAuthenticationSerializer(data=self.request.data)
if serializer.is_valid():
user = serializer.validated_data['user']
return Response({'token': user.auth_token.key}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_401_UNAUTHORIZED)
Here is my Serializer
class UserAuthenticationSerializer(serializers.Serializer):
username = serializers.CharField()
password = serializers.CharField()
def validate(self, attrs):
username = attrs.get('username')
password = attrs.get('password')
if username and password:
user = authenticate(username=username, password=password)
if user:
if not user.is_active:
msg = 'User account is disabled.'
raise serializers.ValidationError(msg, code='authorization')
else:
msg = 'Unable to log in with provided credentials.'
raise serializers.ValidationError(msg, code='authorization')
else:
msg = 'Must include "username" and "password".'
raise serializers.ValidationError(msg, code='authorization')
attrs['user'] = user
return attrs
This is what I get in my generated
I do not get a form with the fields for the POST data. How do I get that?
django-rest-swagger uses rest_framework.schemas.SchemaGenerator to generate the schema and SchemaGenerator uses get_serializer_fields to get the serializer information of a view. get_serializer_fields checks if a view has a get_serializer method to generate the form. GenericAPIView provides the get_serializer so inheriting from it is enough.
Inherit view from GenericAPIView rather than simple APIView. And add serializer_class attribute with appropriate serializer
from rest_framework.generics import GenericAPIView
class UserAuthenticationView(GenericAPIView):
serializer_class = UserAuthenticationSerializer
def post(self, request, *args, **kwargs):
serializer = UserAuthenticationSerializer(data=self.request.data)
if serializer.is_valid():
user = serializer.validated_data['user']
return Response({'token': user.auth_token.key}, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_401_UNAUTHORIZED)
This is the rest framework get schema code (the part of it):
def get_serializer_fields(self, path, method, view):
"""
Return a list of `coreapi.Field` instances corresponding to any
request body input, as determined by the serializer class.
"""
if method not in ('PUT', 'PATCH', 'POST'):
return []
if not hasattr(view, 'get_serializer'):
return []
serializer = view.get_serializer()
if isinstance(serializer, serializers.ListSerializer):
return [
coreapi.Field(
name='data',
location='body',
required=True,
type='array'
)
]
...
As you can see - it should work if you define the get_serializer method on your view - which returns the UserAuthenticationSerializer.
-- EDIT --
Forget: Happy Coding.
A working example for a custom ViewSet, with django-rest-swagger==2.2.0:
from rest_framework import viewsets
from rest_framework.schemas import AutoSchema
from rest_framework.compat import coreapi, coreschema
from rest_framework.decorators import action
class DeviceViewSchema(AutoSchema):
"""
Schema customizations for DeviceViewSet
"""
def get_manual_fields(self, path, method):
extra_fields = []
if path.endswith('/send_command/'):
extra_fields = [
coreapi.Field(
"command",
required=True,
location="form",
schema=coreschema.String()
),
coreapi.Field(
"params",
required=False,
location="form",
schema=coreschema.String()
),
]
manual_fields = super().get_manual_fields(path, method)
return manual_fields + extra_fields
class DeviceViewSet(viewsets.ViewSet):
lookup_field = 'channel'
lookup_value_regex = '[\w-]+'
schema = DeviceViewSchema()
#action(methods=['post'], detail=True, url_name='send_command')
def send_command(self, request, channel):
"""
Send command to device
Parameters:
- command: string
- params: string (JSON encoded list or dict)
"""
...
The final result is:

User managment django-tastypie (angular)

Okey so I wish to create really simple user managment for my site using django(1.6), tastyie and angularjs. I want to be able to signin/up/out. I have basically implemented the solution from here: How can I login to django using tastypie
my code looks like this:
resources:
from tastypie.resources import ModelResource, ALL, ALL_WITH_RELATIONS
from photod.models import Project, ProjectImage
from tastypie.authorization import DjangoAuthorization,Authorization
from tastypie.authentication import BasicAuthentication,Authentication
from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login, logout
from tastypie import fields
from tastypie.serializers import Serializer
from tastypie.exceptions import BadRequest
from django.db import IntegrityError
from tastypie.http import HttpUnauthorized, HttpForbidden
from django.conf.urls import url
from tastypie.utils import trailing_slash
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'user'
excludes = ['email', 'password', 'is_active', 'is_staff', 'is_superuser']
serializer = Serializer(formats=['json', 'jsonp'])
always_return_data = True
filtering = {
'username': 'exact',
'id': ALL_WITH_RELATIONS,
}
def prepend_urls(self):
return [
url(r"^(?P<resource_name>%s)/login%s$" %
(self._meta.resource_name, trailing_slash()),
self.wrap_view('login'), name="api_login"),
url(r'^(?P<resource_name>%s)/logout%s$' %
(self._meta.resource_name, trailing_slash()),
self.wrap_view('logout'), name='api_logout'),
]
def login(self, request, **kwargs):
self.method_check(request, allowed=['post'])
data = self.deserialize(request, request.body, format=request.META.get('CONTENT_TYPE', 'application/json'))
username = data.get('username', '')
password = data.get('password', '')
user = authenticate(username=username, password=password)
if user:
if user.is_active:
login(request, user)
return self.create_response(request, {
'success': True
})
else:
return self.create_response(request, {
'success': False,
'reason': 'disabled',
}, HttpForbidden )
else:
return self.create_response(request, {
'success': False,
'reason': 'incorrect user information',
}, HttpUnauthorized )
def logout(self, request, **kwargs):
self.method_check(request, allowed=['get'])
if request.user and request.user.is_authenticated():
logout(request)
return self.create_response(request, { 'success': True })
else:
return self.create_response(request, { 'success': False }, HttpUnauthorized)
lass CreateUserResource(ModelResource):
class Meta:
allowed_methods = ['post']
object_class = User
resource_name = 'register'
queryset = User.objects.all()
authentication = Authentication()
authorization = Authorization()
include_resource_uri = False
fields = ['username', 'id']
always_return_data = True
def obj_create(self, bundle, request=None, **kwargs):
username, password = bundle.data['username'], bundle.data['password']
try:
bundle.obj = User.objects.create_user(username, '', password)
except IntegrityError:
raise BadRequest('That username already exists')
return bundle
front end:
photodice.controller('userController', function($scope, $resource, userFactory) {
$scope.userName = '';
$scope.userPw = '';
$scope.Signup = function() {
var userC = $resource("http://xx.xxx.xxx.xx:xxxx/api/v1/register/");
var newUser = new userC();
newUser.username = $scope.userName;
newUser.password = $scope.userPw;
newUser.$save(function (user, headers) {// Success
userFactory.setUser(user.username, user.id);
console.log("$save (signUp) success " + JSON.stringify(user));
$scope.userName = '';//cleanup
$scope.userPw = '';//cleanup
}, function (error) {// failure - TODO: add message
console.log("$save (signUp) failed " + JSON.stringify(error.data.error_message))
});
}
$scope.Signin = function() {
var userResource = $resource('http://xx.xxx.xxx.xx:xxxx/api/v1/user/login/');
user = new userResource();
user.username = $scope.userName;
user.password = $scope.userPw;
user.$save(function () {// Success
userFactory.setUser($scope.userName);
console.log("$save (signIn) success " + JSON.stringify(user));
$scope.userName = '';//cleanup
$scope.userPw = '';//cleanup
}, function (error) {// failure - TODO: add message
console.log("$save (signIn) failed " + JSON.stringify(error.data.error_message));
});
}
$scope.Signout = function() {
console.log("called signout");
var userResource = $resource('http://xx.xxx.xxx.xx:xxxx/api/v1/user/logout/:user', { user:'#user' });
var user = userResource({user:userFactory.getUser()}, function() {
user.$save(function (user, headers) {// Success
userFactory.setUser('');
console.log("$save (signIn) success " + JSON.stringify(user));
}, function (error) {// failure - TODO: add message
console.log("$save (signIn) failed " + JSON.stringify(error.data.error_message));
});
});
}
});
But I have some problems and questions:
Firstly I can sign in with the superuser account, but i only get {"success":true,"$resolved":true} as respons... should i not get some sort of token or id or more data?
Secoundly i can signup new users, but they can NOT sign in as i get: 401 (UNAUTHORIZED)
Edit: upon further investigation i notised that although I can sign up new users, thay do not get any password set... why is this?
1
But I have some problems and questions: Firstly I can sign in with the
superuser account, but i only get {"success":true,"$resolved":true} as
respons... should i not get some sort of token or id or more data?
What you do in login is assigning request with user. You authenticated user here: user = authenticate(username=username, password=password) and assigned that user to request here: login(request, user). So Django will now recognize request.user as that user during your session.
You haven't defined authentication method in your resource therefore is default. It gives access to anonymous users also so don't have to be even authenticated to have access. Once your decide which authentication you want to use then you will think about tokens and stuff.
See this: Authentication in Tastypie
2
Secoundly i can signup new users, but they can NOT sign in as i get:
401 (UNAUTHORIZED)
Your are seeing this most likely because your password or username is incorrect. user = authenticate(username=username, password=password) gives you user is None and your eles block is executed. You can make sure with printing logs in that step.
3
Edit: upon further investigation i notised that although I can sign up
new users, thay do not get any password set... why is this?
I tested the same code and works perfectly. Make sure you don't have typo on frontend side. And print logs with values in obj_create to make sure they aren't empty.
4
To allow session authentication is quite difficult and it is capable for another question. This make it possible to get request.user. (Very insecure but simple)
class PasswordAuthentication(Authentication):
def is_authenticated(self, request, **kwargs):
"""
Allow get not authenticated users but try assign user to request
if possible.
"""
try:
username, password = request.GET.get('username'), request.GET.get('password')
except ValueError:
return True
if not username or not password:
return True
try:
user = User.objects.get(username=username, password=password)
except (User.DoesNotExist, User.MultipleObjectsReturned):
return True
if not self.check_active(user):
return True
request.user = user
return True
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'user'
excludes = ['email', 'password', 'is_active', 'is_staff', 'is_superuser']
serializer = Serializer(formats=['json', 'jsonp'])
authentication = PasswordAuthentication()
always_return_data = True
filtering = {
'username': 'exact',
'id': ALL_WITH_RELATIONS,
}
[...]
def logout(self, request, **kwargs):
self.method_check(request, allowed=['get'])
if request.user and request.user.is_authenticated():
logout(request)
return self.create_response(request, { 'success': True })
else:
return self.create_response(request, { 'success': False }, HttpUnauthorized)
call backend with http://xx.xxx.xxx.xx:xxxx/api/v1/user/logout/4/?username=asdf&password=1234