how can I securely perform Rest requests without authentication? - django

This is more a process logic question than a specific language-framework one.
I am developing a mobile app and want the user to be able to use it without having to login (i.e. try it and offer a plus to the logged users), but I don´t want other persons to make post requests from let´s say Postman or any other platform than the app without having some sort of key, so what would be the approach here?
I am thinking on basic auth with some secret username:password for guests, or some kind of token, but as I am totally new on this I am not sure if it´s the correct approach, I´ve read the authentication and permissions Django Rest Framework tutorial but haven´t found a solution

I am learning Django myself and have gotten to the more advanced topics in the subject. What you could do is create a function in your permissions.py file for this. like so:
from rest_framework import permissions
class specialMobileUserPermissions(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in request.SAFE_METHODS:
return True
if request.user.id == whatever your mobile users id is:
return false
return obj.id == request.user.id # if the user is a subscribed user and they are logged in return true
return false # because we need a way out if none of the above works
So when dealing with permissions classes the permissions.SAFE_PERMISSIONS is a list of permissions that are non-destructive. So the first if statement asks are you a GET, HEAD, or other non data altering method. If so return true.
The second if statement checks the user id of the user that is making the request. And if that user id is equal to the user id you set for the mobile trail user it would return false, denying permissions to whatever this class is used on.
In your viewset you would need to add the permissions_classes variable like below
from . import permissions # your permissions.py file
class FooViewSet(viewsets.ViewSet):
permission_classes = (permissions.specialMobileUserPermissions,)
Unless you need extra functionality, that should be everything you need, all the way down to the imports. I hope I have helped.

Related

How can we specify organization level permissions using Django Rest Framework

I am new to Django authentication and Authorisation , we have a login and signup implemented with ReactJS on the client side and the Django JWT authentication on the server side. Now, I want to take this to next level by adding authorisation aswell by making users from same organizations only must be able to view/read/write the data existing in the application. Every user will be identified using a specific domain name ex: #gmail.com,#xyz.com. Users from one domain must not have any access to the database of the other organizations.
I think we can achieve this with Permissions concept in Django but dont know how we can do it technically. Any ideas are well appreciated.
Thanks
There can be multiple approaches, but I mostly handle this kind of stuff in the views, you can filter out the user's email and then can compare inside the views.
just an example:
#api_view(['GET', 'POST'])
def hello_world(request, id):
user = User.objects.get(id=id)
if request.method == 'POST':
if user.email == re.match(r"... regex here ...", user): # regex
return Response({"message": "Got some data of the organization xyz!", "data": request.data})
else:
return Response({"message": "Not authorized for XYZ operations!", "data": request.data})
This feels simple but understandable approach for me. And most often I follow this.

DRF and Knox Authentication: Allow login for non-staff users

I have created a Login API which authenticates users from django.contrib.auth.models.User. I am using DRF and implementing a token authentication with django-rest-knox and so far so good.
The application I am developing is a bit complicated but I'm gonna use one of our sub-apps as an example. So we have a sub application called jobnet and the goal of this application is to allow people to register an account thru the website and be able to apply for available jobs in our company online.
The application shall have separate login pages for 2 different types of users (i.e. staff users (the company's employees) and those online applicants. The process here is a online applicant will register for an account and that will be marked is_staff=False. Every time he logs in, he shall be redirected to his non-staff dashboard where he can apply for jobs and manage applications.
Once he gets officially hired, his account will be updated to is_staff=True. Now, he can either login via the applicant's login interface, or via the staff's login page. Either way, the system will detect that he is already a staff and will redirect him to the staff's dashboard instead.
I already have a logic (in mind) for redirecting users thru different views depending on their account configuration. My problem now is I have no idea how to allow non-staff users to be able to login in the first place using the authentication tools I am using (Django's User model and knox token authentication). Everytime I try to login a non-staff user, the response says "Invalid credentials..."
I tried defining has_permission(self, request) method inside my LoginAPI class but to no avail.
Here is my Login API source code:
class LoginAPI(generics.GenericAPIView):
serializer_class = LoginSerializer
permission_classes = ()
authentication_classes = (knox.auth.TokenAuthentication,)
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data
allowed_modules = {}
is_staff = False
if user.is_staff:
allowed_modules = set(Permission.objects.filter(group__user=user).values_list('codename', flat=True))
is_staff = True
return Response(
{
"user": UserSerializer(user, context=self.get_serializer_context()).data,
"token": AuthToken.objects.create(user)[1],
"authenticated": True,
"staff": is_staff,
"modules": allowed_modules
}
)
This inquiry is no longer relevant. I have just realized I have unchecked the active property of my test account in the admin portal that's why I was getting an error due to the ValidationError I have raised in my serializer. So so dumb... Nonetheless, code above works just as I want it and everything works fine in my authentication at the moment so far.
Although as per #ArakkalAbu pointed out in the comment above, I will take a look at my LoginAPI view and maybe actually pattern it the way Knox implements it on its LoginView since what I'm doing above is just overriding the post method, creating my custom login logic and just generating token via Knox's Authtoken model.
Thanks!

django-rest-framework authentication: require key parameter in URL?

I am working in Django 1.8 with the excellent django-rest-framework. I have a public RESTful API.
I would now like to start requiring a key GET parameter with this API, and disallowing any requests that do not have this parameter. I will allocate keys to users manually on request.
I have read through the DRF Authentication documentation, but I'm not sure there's anything that meets my use case. I find this strange, since my use case must be very common.
Token-based authentication requires the user to set an HTTP header. My typical API user is not sophisticated (Excel users who will be downloading CSVs), so I don't think I can ask them to do this.
I think Basic-Auth is what I need, but I'd much rather provide a simple URL-based key than a Django username and password (my app has no concept of users right now).
What is the best way to implement this?
Create a table which will contain all the keys that you issue to someone.
Example:
class RestApiKey(models.Model):
api_key = models.CharField(max_length=100)
Next create a custom Permision class which will check for the api Key in the url before forwarding the request to the view like:
from rest_framework import permissions
from yourappname.models import RestApiKey
class OnlyAPIPermission(permissions.BasePermission):
def has_permission(self, request, view):
try:
api_key = request.QUERY_PARAMS.get('apikey', False)
RestApiKey.objects.get(api_key=api_key)
return True
except:
return False
So the request url has to be like http://yourdomain.com/?apikey=sgvwregwrgwg
Next in your views add the permission class:
class YourViewSet(generics.ListAPIView):
permission_classes = (OnlyAPIPermission,)
or if you are using function based views then do like:
#permission_classes((OnlyAPIPermission, ))
def example_view(request, format=None):
. . .

Django Tastypie

I am creating a mobile app where I need to use authentication. How can I achieve the following:
I need to create a user. After creating the user it needs to send Api_client and a secret as a response to the user.
I have a function to perform verification. After creating the user it needs to call the function for mobile verification.
Importantly, how can I stop a user who uses a for loop and starts adding users?
I tried this:
models.signals.post_save.connect(create_api_key, sender=User)
That created an API key but is not sending it as a response when creating the user is successful.
Here's what I understand from your question :
You want any user of your mobile app to register himself,
anonymously, as a user to your Django application.
This request must trigger a Tastypie api_key creation, and then return it.
You want to prevent this request from being spammed.
I don't understand this :
"I have a function for mobile without verification. After creating the user it needs to call the function for mobile verification."
To answer the points I get :
See this SO question regarding user registration with Tastypie How to create or register User using django-tastypie API programmatically?, notably this part :
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
For a complete walkthrough, check this article : http://psjinx.com/programming/2013/06/07/so-you-want-to-create-users-using-djangotastypie/
You're on the right track regarding the api_key creation, except you have to tell the api to actually send it back. You can use the regular way (it requires another request, though) :
i.e make it accessible from UserResource, as described in the article linked above, specifically :
def dehydrate(self, bundle):
bundle.data['key'] = bundle.obj.api_key.key
try:
# Don't return `raw_password` in response.
del bundle.data["raw_password"]
except KeyError:
pass
return bundle
If you want to send it right after a User's registration, don't forget to set "always_return_data" to True and add the api_key to the response.
Spam / loop registration :
You should look into your server's capabilities regarding this matter. For example, assuming you're using Nginx : http://wiki.nginx.org/NginxHttpLimitReqModule
Another option might be to use this : http://django-ratelimit-backend.readthedocs.org/en/latest/
Hope this helps !
Regards,

need an example of doing authorization using django-tastypie

I am relatively new with Django and it's ecosystem. I am writing REST api for our mobile client using django-tastypie. I have gone through almost all the examples on the web about how to use tastypie for creating REST interfaces. but none of them are specific to POSTing the data from client and how would you authorize a client.
I used the from tastypie.authentication.BasicAuthentication as show in the example. It opens a pop up asking username and password and works fine on the browser. But I am not sure, if it will do the same thing on mobile (to be specific, native IOS app). I am not quite getting when a user will make a request to login how this popup will be shown there on his/her mobile device if he or she is not using the browser but the native app.
I am totally lost on this, I would really appreciate your help.
You can check out source and use for example ApiKeyAuthentication.
You just have to POST username and api key to authentificate user.
It looks like usable for ios app.
Here is the part of the checking code.
def is_authenticated(self, request, **kwargs):
"""
Finds the user and checks their API key.
Should return either ``True`` if allowed, ``False`` if not or an
``HttpResponse`` if you need something custom.
"""
from django.contrib.auth.models import User
username = request.GET.get('username') or request.POST.get('username')
api_key = request.GET.get('api_key') or request.POST.get('api_key')
if not username or not api_key:
return self._unauthorized()
try:
user = User.objects.get(username=username)
except (User.DoesNotExist, User.MultipleObjectsReturned):
return self._unauthorized()
request.user = user
return self.get_key(user, api_key)
https://github.com/toastdriven/django-tastypie/blob/master/tastypie/authentication.py#L128
https://github.com/toastdriven/django-tastypie/blob/master/tastypie/authorization.py#L42
Thanks for the help.
I used similar approach mentioned by #Iurii. Here is my solution.
I wrote a class for handling the authentication and override is_authenticated method. and then I can use this class in Meta definition of tastypie resource classes.
from tastypie.authentication import BasicAuthentication
from tastypie.resources import Resource, ModelResource
# class for handling authentication
class MyAuthentication(BasicAuthentication):
def is_authenticated(self, request, **kwargs):
# put here the logic to check username and password from request object
# if the user is authenticated then return True otherwise return False
# tastypie resource class
class MyResource(ModelResource):
class Meta:
authentication = MyAuthentication()
this will ensure a request to access the resource will go through your authentication code.