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

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!

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.

User Authentication in Django REST Framework

I have a Django REST backend, and it has a /users endpoint where I can add new users through POST method from frontend.
/users endpoint url:
http://192.168.201.211:8024/users/
In this endpoint I can view all users information and add new user, so I must avoid others entry it except Administrator. I create a superuser admin with password admin123 by python manage.py createsuperuser.
My question is, If I want to do a HTTP POST from frontend(I use Angular) I have to pass the Administrator's user name and password, admin and admin123, along with POST head information. So I let others know the user name and password who check the source code of frontend.
Is there any other way to do this Authentication without exposing Administrator's user name and password to others?
You need to create an API that handles the user creation. This is why we create backends. The user will send the API their credentials and the API will add the user to the database using the admin credentials and post request. The API's code will not be viewable. Depending on your needs, auth0 can be a good solution and save you time on user registration and login. If you make your own sign up and login be sure to hash passwords and make sure they are sent over SSL. A service like auth0 will handle all this for you if you want to focus on other parts of your project.
token auth is may what you need,i use token auth for DRF as backend and angular as frontend
Finally, I find a method to solve this problem.
Here has a very elegant way to do this, rewrite get_queryset function in my UserViewSet:
class UserViewSet(viewsets.ModelViewSet):
# permission_classes = (permissions.IsAdminUser, )
permission_classes = (permissions.AllowAny, ) # <-- change 1
# queryset = User.objects.all() # <-- change 2
serializer_class = UserSerializer
def get_queryset(self):
queryset = User.objects.filter(id=self.request.user.id)
if self.request.user.is_superuser:
queryset = User.objects.all()
return queryset
In change 1, permissions allowed anyone to access, so a new user can do a POST without any authentication.
In change 2, I only return all users when the user is superuser, just like rewrote get_queryset done.
Also need to change urls.py file to add base_name for this url like this:
router.register(r'users', UserViewSet, base_name='user')
ref, https://stackoverflow.com/a/22767325/2803344

how can I securely perform Rest requests without authentication?

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.

django-rest-auth: social login with google

The django-rest-auth documentation discusses Facebook integration, which I am not interested in-- my concern is providing social login via Google. I have tried this for quite some time now and I'm wondering if anyone else has any documentation on how they did this...even just a rough sketch would be helpful. So far, I have not found any results for this search. I am almost there, but cannot get it to work with the Django rest framework (DRF) browsable API.
Here is what I have so far:
I started from the demo project supplied on the django-rest-auth github page and modified the social login template HTML page to only require the 'code' input, not both 'code' AND 'access_token'. When I supply a valid code (obtained by a separate request to google's auth endpoint), this works fine; the browsable API renders the usual web page with the 'key' (my application's API token for the user) in the response. Checking the django admin, everything worked- the user is logged in, email is authenticated, etc. Good so far.
The issue is that starting point of supplying the 'code'- and how I get that code from google in the first place. When I previously (successfully) used the allauth package, I could simply click on a link, which would "invisibly" perform the whole OAuth2 flow (i.e. request the code, use that code to get the access token, and use the access token to get user's google account info).
To recreate that seamless flow (i.e. NOT starting out with the code), I figured I could interrupt the OAuth2 flow and "intercept" the code returned from google, and then POST that code to the rest-auth social login API. To that end, I created a custom allauth.socialaccount.providers.oauth2.views.OAuth2CallbackView by overriding the dispatch method:
class CustomOAuth2CallbackView(OAuth2CallbackView):
def dispatch(self, request):
# gets the code correctly:
code = request.GET['code']
# rp is of type requests.methods.Response
rp = requests.post(<REST-AUTH API ENDPOINT>, data = {'code':code})
return rp
Usually, this method is called when google sends a GET request to the callback uri you initially supply to google's auth endpoint. With this override, I am able to successfully parse the code returned from google in that callback. The POST request works and has the user's key in the resp._content field. However, it ultimately fails to produce the intended view in the DRF browsable API.
Based on diving down in the callstack, I find that rest_framework.views.APIView.dispatch returns an object of type rest_framework.response.Response. However, when the requests.post method used above completes, it returns an instance of type requests.models.Response. As a result, it does not have the proper attributes and fails in the django middleware. For example, it has no acceptable_renderer attribute and no 'get' method (which is used in django.middleware.clickjacking.py). I could, conceivably, add these requirements to the requests.models.Response (rp) instance, but then this hack becomes even more of a kludge.
Thanks for any help you can provide!
https://github.com/st4lk/django-rest-social-auth
class SocialLoginSignup(SocialSessionAuthView):
"""
Mobile user social signup and login api view
args:
provider: name of the social network
access_token: auth token got from the social sites
"""
serializer_class = SocialSignUpSerializer
authentication_classes = (TokenAuthentication,)
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
provider_name = serializer.validated_data['provider']
decorate_request(request, provider_name) # assign the provider class object in request
authed_user = request.user if not request.user.is_anonymous() else None
token = serializer.validated_data['access_token']
if self.oauth_v1() and request.backend.OAUTH_TOKEN_PARAMETER_NAME not in serializer.validated_data:
request_token = parse_qs(request.backend.set_unauthorized_token())
return Response(request_token)
try:
# authentication function get call from assign class object in request
user = request.backend.do_auth(token, user=authed_user)
except social_exceptions.AuthException as e:
raise exceptions.ParseError({'error':str(e)})
except social_exceptions.AuthTokenError as e:
raise exceptions.ParseError({'error': str(e)})
except social_exceptions.AuthAlreadyAssociated as e:
raise exceptions.ParseError({'error': str(e)})
except social_exceptions.AuthFailed as e:
raise exceptions.ParseError({'error':str(e)})
except social_exceptions.AuthUnknownError as e:
raise exceptions.ParseError({'error': str(e)})
except social_exceptions.WrongBackend as e:
raise exceptions.ParseError({'error':str(e)})
except Exception as e:
raise exceptions.ParseError({'error': social_message.INVALID_AUTH_TOKEN})
token, created = Token.objects.get_or_create(user=user)
return Response({'auth_token':token.key})

How to sign-in? Django TastyPie with ApiKeyAuthentication actual authentication Process

I have an Adobe Air mobile application that communicates with Django via TastyPie. To use the app people have to register first. Therefore they have to supply their email and password. Afterwards they will be able to "login". I thought it would be the best idea that after entering a successful username/password combination, the api-key will be sent back to the mobile app where it will be cached, so the user is "logged in".
Please tell me if you think there is a better way for registering and "logging in" users.
Inside Django I have a UserRessource class that I use to register new users when sending data via POST:
class UserResource(ModelResource):
class Meta:
allowed_methods = ['get', 'post']
queryset = User.objects.all()
resource_name = 'auth'
authentication = Authentication()
authorization = Authorization()
fields = ['username', 'email']
def obj_create(self, bundle, request=None, **kwargs):
username, email, password = bundle.data['username'], bundle.data['password'], bundle.data['password'],
try:
bundle.obj = User.objects.create_user(username, email, password)
except IntegrityError:
raise BadRequest('That username already exists')
return bundle
That works very well.
But now I'm struggling with the actual login process. In my opinion it would be best to send username and password via GET (and https) to this ressource and if those are valid, return the users api key. But would that be possible? And is it clean? Usually TastyPie would show all users currently in the DB if you send a GET request to that ressource. But I dont need that data, so I could overwrite that somehow. I already checked http://django-tastypie.readthedocs.org/en/v0.9.9/resources.html but I don't get it to work. Is it even possible to overwrite that behaviour?
So the actual questions are Whats the best way to "sign in" a user using ApiKeyAuthentication?
And Is my approach right and clean or do you have a better method? and Do you have any examples for this case?
Thanks alot in advance!
I'm using BasicAuth so it may be slightly different. But my solution is basicaly an empty resource that requires authentication. If the authentication is a success the service returns response code 200 and the authenticated user, I override obj_get_list and stuff the authenticated user in there. If the credentials are wrong the service returns response code 401.
class LoginResource(ModelResource):
class Meta:
allowed_methods = ['get']
resource_name = 'login'
include_resource_uri = False
object_class = User
authentication = BasicAuthentication()
authorization = DjangoAuthorization()
def obj_get_list(self, bundle, **kwargs):
return [bundle.request.user]
Okay I'll try to explain my point of view on the topic:
First the UserResource example on the tastypie page for me has one significant issue:
The User Objects should not be presented at any time to the single User, they should be able to see they're own "profile" or whatever but never browse and see others. Of course that can be done and with UserResource by clearing the main "list view" of that resource and applying the APIKeyAuth to the individual profiles, but still I don't like the idea of UserResource.
Second in the form when you are developing an API(such as tastypie usage) the APIKey is the actual "password" so what should be send on request is not the username and password but the username and APIKey, which is obtained in other manners(normally an e-mail or some kind of website based UI). Than they are recommended to be send via Authorization Header and not in GET parameters.
Third when we are talking about API there is no such thing as sign-in - at least not in the RESTFULL APIs - it is in some sense connectionless, so you actually going to send the Authorization header with each request.
As to the question yes you can overwrite the data. Look at the hydrate/dehydrate cycle in the Tastypie docs to understand how does it renders the content and if you have more question go ahead and ask.