My question is, how do i write my own custom authentication correctly??
i have tried to follow this:
http://django-tastypie.readthedocs.org/en/latest/authentication.html#implementing-your-own-authentication-authorization
I have implemented basic method,
api.py
def prepareResponce(responceData):
"""Prepares a Json responce with status 200"""
response = JsonResponse(responceData)
return response # {"foo": "bar"}
class CustomBasicAuthentication(BasicAuthentication):
userID = None
userType = None
userAccess = None
userName = None
def is_authenticated(self, request, **kwargs):
if 'admin' in request.user.username:
return prepareResponce({'logged in': 'Admin' })
#return True
return prepareResponce({'not allowed for':userName })
def get_identifier(self, request):
return request.user.username
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
resource_name = 'user'
authentication = CustomBasicAuthentication()
allowed_methods = ['get', 'post']
when i call API providing admin's username and password it's always return the else part. where am i did wrong ?
You missed return and You don't call parent is_authenticated function:
def is_authenticated(self, request, **kwargs):
super(CustomBasicAuthentication, self).is_authenticated(request, **kwargs)
if 'admin' == request.user.username:
return prepareResponce({'logged in': 'Admin' })
return prepareResponce({'not allowed for': self.userName })
Related
I have a custom authentication class following the docs
class ExampleAuthentication(authentication.BaseAuthentication):
def authenticate(self, request):
username = request.META.get('HTTP_X_USERNAME')
if not username:
return None
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
raise exceptions.AuthenticationFailed('No such user')
return (user, None)
and I used it in my APIView:
class profile(APIView):
permission_classes = ()
authentication_classes = (ExampleAuthentication,)
def get(self, request, format=None):
try:
print('user', request.user)
serializer = GetUserSerializer(request.user)
return JsonResponse({'code': 200,'data': serializer.data}, status=200)
except Exception as e:
return JsonResponse({'code': 500,'data': "Server error"}, status=500)
when I try to call it normally from the API through postman I got the following result from the print and it worked normally:
user User(143)
I wrote a test using force_authenticate():
class BaseUserAPITest(APITestCase):
def setUp(self):
# self.factory = APIRequestFactory()
self.user = models.User.objects.get_or_create(
username='test_user_1',
uid='test_user_1',
defaults={'agent_type': 1}
)
def test_details(self):
url = reverse("api.profile")
self.client.force_authenticate(user=self.user)
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
I got server error because the print of request.user return a tuple instead of a User object, this is the print from the test log
user (<User: User(143)>, True)
I tried searching up and seem like there no result or explanation on why this happening
My version:
django==2.2.8
djangorestframework==3.10.2
The problem is not force_authenticate but get_or_create method. It returns tuple. First element of the tuple is object and second one is boolean indicating if object was created or not. To fix change your code in setUp method to this:
def setUp(self):
# self.factory = APIRequestFactory()
self.user, _ = models.User.objects.get_or_create(
username='test_user_1',
uid='test_user_1',
defaults={'agent_type': 1}
)
I'm getting a "Forbidden (CSRF cookie not set.): /user/admin/sign-up" error whenever I test class based views. When I change those views to functional based views with #csrf_exempt on top of the function declaration, it works.
Postman POST Request:
localhost:8000/admin/sign-up
body : {'email' : 'email#gmail.com', 'password' : 123123}
URL path patterns:
...
path(‘/admin/sign-up’, views.AdminSignUpView),
...
Views.py
#csrf_exempt
def token_verification(request,**kwargs):
if request.method == “POST”:
id = kwargs.get(‘id’)
token = kwargs.get(‘token’)
user = User.objects.get(id = id)
redirect_url = ‘/eval/intro’
is_valid = user_activation_token.check_token(user,token)
if is_valid:
user.is_active = True
user.save()
return HttpResponseRedirect(redirect_url,status = 200)
else:
return HttpResponse(status = 403)
class AdminSignInView(View):
#csrf_exempt
def post(self,request):
data = json.loads(request.body)
try:
if User.objects.filter(name = data[‘email’]).exists():
user = User.objects.get(name=data[‘email’])
if bcrypt.checkpw(data[‘password’].encode(‘utf-8’),user.password.encode(‘utf-8’)):
token = jwt.encode({‘email’:data[‘email’]}, SECRET, algorithm = HASH).decode(‘utf-8’)
return JsonResponse({ ‘token’ : token }, status = 200)
return JsonResponse({ ‘message’ : ‘INVALID_USER’ }, status = 401)
return JsonResponse({ ‘message’ : ‘INVALID_USER’ }, status = 401)
except KeyError:
return JsonResponse({ ‘message’ : ‘INVALID_KEYS’ }, status = 400)
class AdminSignUpView(View):
#csrf_exempt
def post(self,request):
try:
data = json.loads(request.body)
if not User.objects.filter(email = data[‘email’]).exists:
password = bcrypt.hashpw(data[‘password’].encode(‘utf-8’),bcrypt.gensalt())
crypted = password.decode(‘utf-8’)
User.objects.create(
name = data[‘name’],
password = bcrypt,
email = data[‘email’],
auth_id = data[‘auth_id’]
)
return HttpResponse(status = 200)
except KeyError:
return JsonResponse({ ‘message’ : ‘INVALID_KEYS’ },status = 4000)
Models.py
class User(models.Model):
name = models.CharField(max_length = 50)
email = models.EmailField(max_length = 200,unique = True, blank = False)
department = models.ForeignKey('Department', on_delete = models.SET_NULL, null = True)
is_active = models.BooleanField(default=False)
question = models.ManyToManyField('eval.Question',through='UserQuestion')
auth = models.ForeignKey('Auth', on_delete = models.SET_NULL, null = True)
class Meta:
db_table = 'users'
token_verification view, which is written in function based, works fine but the last two raised an error. I think the fact that the decorator only goes on top of the function brings up this error, but I'm not sure why #csrf_exempt is necessary for some views.
I have no clue why I'm getting the csrf issue at this time.
I believe the problem is where you're adding the #csrf_exempt.
Django perform the csrf validation before it reaches post
You should check your Django version flowchart to find which method you should override on your View to add the csrf_exempt decorator.
https://docs.djangoproject.com/en/2.2/ref/class-based-views/base/#django.views.generic.base.View.setup
https://docs.djangoproject.com/en/1.8/ref/class-based-views/base/#django.views.generic.base.View
I believe the code bellow should work for django versions < 2.2
#method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super(AdminSignInView, self).dispatch(request, *args, **kwargs)
I wrote some code for custom authentication with this structure:
serializers.py
class LoginSerializer(serializers.Serializer):
first_token = serializers.CharField()
phonenumber = serializers.CharField()
token = serializers.CharField(max_length=255, read_only=True)
views .py
class LoginView(APIView):
serializer_class = LoginSerializer
permission_classes = (AllowAny,)
def post(self, request, format=None):
phonenumber = request.data.get('phonenumber', None)
first_token = request.data.get('first_token', None)
try:
x = User.objects.get(phonenumber=phonenumber)
except x.DoesNotExist:
return Response('user does not exists')
if first_token == x.first_token.token:
user = authenticate(phonenumber=phonenumber)
login_user = login(request, user)
user_info = {
'phonenumber': user.phonenumber,
'username': user.username,
'token': user.token,
'is_admin':user.is_admin,
}
return Response(user_info, status=status.HTTP_200_OK)
urls.py
urlpatterns = [
re_path(r'^login/$', views.LoginView.as_view(), name='login'),
]
so, authentication and login is successful and user logs in. but when i try to go another page testframework doesnt store the authentication. I made a custom authentication already .
auth.py
class PhoneAuthentication(authentication.BaseAuthentication):
authentication_header_prefix = 'Token'
def authenticate(self, request):
request.user = None
auth_header = authentication.get_authorization_header(request).split()
auth_header_prefix = self.authentication_header_prefix.lower()
if not auth_header:
return None
if len(auth_header) == 1:
return None
elif len(auth_header) > 2:
return None
prefix = auth_header[0].decode('utf-8')
token = auth_header[1].decode('utf-8')
if prefix.lower() != auth_header_prefix:
return None
return self._authenticate_credentials(request, token)
def _authenticate_credentials(self, request, token):
try:
payload = jwt.decode(token, settings.SECRET_KEY)
except:
raise exceptions.AuthenticationFailed("invalid authentication . could not decode token")
try:
user = User.objects.get(pk=payload['id'])
except User.DoesNotExist:
raise exceptions.AuthenticationFailed('No such user')
return(user, token)
I think the best way to do it is to subscribe the view ObtainAuthToken that is default of DRF.
Edit
I suspect the whole problem with my UpdateApiView is with the url. No matter how I change it, will return 404.
url(r'verify-phone/(?P<phone_id>^\d+)$', view.VerifyPhone.as_view(), name='verify-phone'),
it returns
{
"detail": "Not found."
}
[18/Apr/2016 01:39:02] "PATCH /api/verify-phone/phone_id=00980 HTTP/1.1" 404 4941
Why?
views.py
class VerifyPhone(generics.UpdateAPIView):
permission_classes = (AllowAny,)
serializer_class = serializers.VerifyPhoneSerializer
allowed_methods = ['PATCH']
lookup_field = 'phone_id'
def get_queryset(self):
phone_id = self.request.query_params.get('phone_id', None)
queryset = User.objects.filter(phone_id=phone_id)
return queryset
def update(self, request, *args, **kwargs):
print('inside update')
print(request.data)
partial = kwargs.pop('partial', False)
instance = self.get_object()
print(instance)
serializer = self.get_serializer(instance, data=request.data, partial=partial)
print(serializer)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
print('done perform update')
return Response(serializer.data)
serializers.py
class VerifyPhoneSerializer(serializers.ModelSerializer):
regex = r'\d+'
verification_code = serializers.RegexField(regex, max_length=7, min_length=7, allow_blank=False)
phone_id = serializers.HyperlinkedRelatedField(view_name='verify-phone', lookup_field='phone_id', read_only=True)
class Meta:
model = User
fields = ('verification_code', 'phone_id')
def validate(self, data):
verification = api.tokens.verify(data['phone_id'], data['verification_code'])
if verification.response.status_code != 200:
raise serializers.ValidationError("Invalid verification code.")
return data
def update(self, instance, validated_data):
instance.phone_number_validated = True
instance.save()
return instance
Second question Is this correct to get phone_id from the views?
phone_id = serializers.HyperlinkedRelatedField(view_name='verify-phone', lookup_field='phone_id', read_only=True)
Looking at your api url def, I think you should call:
/api/verify-phone/00980
instead of
/api/verify-phone/phone_id=00980
I also think something is wrong with the url def itself (the ^ before \d):
url(r'verify-phone/(?P<phone_id>^\d+)$', view.VerifyPhone.as_view(), name='verify-phone')
should be
url(r'verify-phone/(?P<phone_id>\d+)$', view.VerifyPhone.as_view(), name='verify-phone')
or
url(r'verify-phone/(?P<phone_id>\d{5})$', view.VerifyPhone.as_view(), name='verify-phone')
I'm designing a django-tastypie application.
I've some users who are able to post comments. But for now, everybody can delete everything.
How can I solve this problem ?
Okay, I dug in on this a bit and have an answer.
You need to implement a custom Authorization object and use it in your ModelResource.
Below is an example I am using that requires the request user to either be a superuser or owner of the resource.
class UserPickAuthorization(Authorization):
# Checks that the records' owner is either None or the logged in user
def authorize_user(self, bundle):
print 'Authorize User'
if bundle.request.user.is_superuser:
return True
if bundle.request.user == bundle.obj.user:
return True
return False
def user(self, bundle):
print 'User'
return User.objects.get(pk=bundle.request.pk)
def read_list(self, object_list, bundle):
print 'Read List'
return object_list.filter(Q(user = self.user(bundle)) | Q(user = None))
def read_detail(self, object_list, bundle):
print 'Read Detail'
return self.authorize_user(bundle)
def create_list(self, object_list, bundle):
print 'Create List'
return object_list
def create_detail(self, object_list, bundle):
print 'Create Detail'
return self.authorize_user(bundle)
def update_list(self, object_list, bundle):
print 'Update List'
allowed = []
for obj in object_list:
print "User is superuser %s"%(bundle.request.user.is_superuser)
print "User owns obj %s"%(bundle.request.user == bundle.obj.user)
if bundle.request.user.is_superuser or bundle.request.user == bundle.obj.user:
allowed.append(obj)
return allowed
class UserPickResource(ModelResource):
pick = fields.ToOneField(TeamResource, 'pick', full=True)
user = fields.ToOneField(UserResource, 'user', full=True)
league = fields.ToOneField(LeagueResource, 'league', full=True)
class Meta:
queryset = UserPick.objects.all()
resource_name = 'userpick'
authentication = SessionAuthentication()
authorization = UserPickAuthorization()
list_allowed_methods = ['get', 'post','put', 'patch', 'delete']
always_return_data = True
filtering = {
'pick': ALL_WITH_RELATIONS,
'league': ALL_WITH_RELATIONS,
'user': ALL_WITH_RELATIONS,
'week' : ALL
}
I think you can override obj_delete, writing your own method that checks that the object belongs to that user
def obj_delete(self, request=None, **kwargs):
# check that request.user owns object
# go on with the delete