Block token in DRF - django-rest-framework

Hello I have an application where the user access a login in and thereby obtains a token through an endpoint.
I want to block certain users according to their profile (which is linked to a company).
What I did was put my logic here:
class CustomTokenAuthenticationView(ObtainAuthToken):
def post(self, request, *args, **kwargs):
...
I can do it, but I wanted to do something more hard. In this case I wanted to block access to the token as a whole. What would be the best way?

Related

DRF Handling Password Reset Email

I am new to Django Rest Framework and working on a DRF & React Authentication system. Everything is working fine except password reset. The issue I have is when the user requests a password reset a reset URL with the generated token is fired to the registered email. So the user can click the link with the embedded token and go back to the calling (or any other) URL.
http://127.0.0.1:8000/api/auth/password_reset/validate_token/?token=wBBAIfQHzTJVz9bKPLkIgB
The problem is how do I generate the view or serializer to handle the URL with the token on the fly. I just need to understand the full flow I am working with "django-rest-passwordreset1.2.1", (https://pypi.org/project/django-rest-passwordreset/) package so I am generating the reset email and token with the model which as per documentation fires a signal to generate the token and send an email. The endpoints that come with the documentation validate and work fine in postman testing. I am just not sure how to create that functionality in Django. Any help or links to a good tutorial will be appreciated.
Many thanks in advance
models.py
from django.dispatch import receiver
from django.urls import reverse
from django_rest_passwordreset.signals import reset_password_token_created
from django.core.mail import send_mail
#receiver(reset_password_token_created)
def password_reset_token_created(sender, instance, reset_password_token, *args,
**kwargs):
site = 'http://127.0.0.1:3000'
email_plaintext_message = "{}?token={}".format(reverse('password_reset:reset-password-validate'), reset_password_token.key)
reset_url = site + email_plaintext_message
send_mail(
"Password Reset for {title}".format(title="Some website title"),
reset_url,
"noreply#somehost.local",
[reset_password_token.user.email]
)
urls.py
urlpatterns = [
path('api/auth', include('knox.urls')),
path('api/auth/register', RegisterAPI.as_view()),
path('api/auth/login', LoginAPI.as_view()),
path('api/auth/user', UserAPI.as_view()),
path('api/auth/logout', knox_views.LogoutView.as_view(), name="knox_logout"),
path('api/auth/change-password/', ChangePasswordView.as_view(), name='change-password')
path('api/auth/password_reset/', include('django_rest_passwordreset.urls')),
path('admin/', admin.site.urls),
]
The include('django_rest_passwordreset.urls')) provides the following url's:
POST ${API_URL}/ -
request a reset password token by using the email parameter*
POST ${API_URL}/confirm/ -
using a valid token, the users password is set to the provided password
POST ${API_URL}/validate_token/ -
will return a 200 if a given token is valid
where ${API_URL}/ is the url specified in your urls.py (e.g., api/auth/password_reset/ as in the example above)*
OK, let's abstract from libs, and get proper steps to make it manually.
As far as I've got, you have some token token=wBBAIfQHzTJVz9bKPLkIgB that somehow linked with the User instance, am I right?
So we need to write a view that allows us to make auth and get a request with our User and then we can check the token and make a logout.
from django.contrib.auth import logout
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import TokenAuthentication
#api_view["GET"]
#authentication_classes([TokenAuthentication])
#permission_classes([IsAuthenticated])
def logout(request):
if request.query_params.get("token"):
if Token.objects.filter(token=request.query_params["token"]).exists()
logout(request)
# other logic is here
return Response({"status": "logged out"})
return Response({"error": "..."})
Point your urls.py file to use this API view in the needed path.
So my assumption here is that you are using something like TokenAuthentication you can change the decorator's parameter to your actual value. The same thing is for the permission_classes decorator.
The problem was with the front end, I was able to modify the parameters in the model that generated and stored the token by replacing the URL. I changed
email_plaintext_message = "{}?token={}".format(reverse('password_reset:reset-password-validate'), reset_password_token.key)
to the following
email_plaintext_message = "{}?token={}".format('http://127.0.0.1:3000/newpassword' , reset_password_token.key)
Now the return URL is http://127.0.0.1:3000/newpassword?token=f109f93a83040895e3a8b1b8afd
pointing to the correct component in the fornt-end. I then extract the token from the URL and pass it for validation along with the new password in the form hitting the required endpoint.

How to override GET method depending on JWT payload in Django Rest Framework?

First of all, I hope the title of this question is clear enough. The following lines will make things clearer if it is not the case.
I have different users in my database. Some are part of the staff and some are just regular users.
In my API, I have a /users route. My idea is that when a staff member requests it (GET), he will see only the staff users and when a regular user will request it, he will only see the regular users.
Whether or not the user which makes the request is a member of the staff or not is an information which is stored in the JWT token.
Therefore, I used the following code:
class CustomUserList(generics.ListCreateAPIView):
serializer_class = CustomUserSerializer
def get_queryset(self):
token = self.request.META.get('HTTP_AUTHORIZATION')[7:] # [7:] to get rid of 'Bearer '
is_staff = jwt.decode(token, None, None)['is_staff']
print(is_staff)
queryset = CustomUser.objects.filter(is_staff=is_staff)
return queryset
This code works but is there a more direct / logical way to accomplish this ?
It feels a bit off storing what is essentially a server-side permission/setting on the client side (even though some people do use JWT for that). What if a user is promoted to/demoted from staff? You'd have to revoke all such client tokens? Also, what if you need to attach more conditions to these queries?
I think a slightly more flexible approach is to store some kind of a user id in the JWT token, then use a TokenAuthentication class with JWT to establish the identity of the user, attach it to something like request.user and then in your get_queryset method filter by request.user.is_staff. That way, you can attach any context/filters/permissions to the authenticated users on the server side and don't need to rely on the client to present explicit claim(s) to filter out the objects they can access. You'd end up with an extra call to the database to populate request.user, but you might end up needing that anyway.
Authenticating with a JWT token in the header retrieves the user so your view will have access to request.user and all of the user's attributes in every method after the dispatch() method (the first method that is run after .as_view() is triggered when sending a request to an endpoint).
If you're using djangorestframework-simplejwt, the DRF recommended JWT package, all you need to do is this:
class CustomUserList(generics.ListCreateAPIView):
queryset = CustomUser.objects.all()
serializer_class = CustomUserSerializer
def get_queryset(self):
queryset = super().get_queryset()
if self.request.user.is_staff:
queryset = queryset.filter(is_staff=True)
return queryset

What is a safe way to allow REST APIs to be public?

In my website, people can see images given from REST APIs without login. I want to make some of my APIs not to require a user token. So, I had some research and I found the way below.
Since I need to use this way for production, I want to make sure if it is a safe way. The stores that will be given from the API have just store data such as name, urls, description, and images. Any of them are not related to any users.
Is it okay for me to use in production? Just so you know, I use Django REST Framework to serve data to frontend and use React js to show them in frontend side.
from rest_framework.decorators import authentication_classes, permission_classes
#authentication_classes([])
#permission_classes([])
class ListAllStores(APIView):
def get(self, request, format=None):
all_stores = Store.objects.all()
serializer = StoreSerializer(all_stores, many=True)
return Response(data=serializer.data)
You can try something link sending user type on header of each request.
And for the API without token send userType as anonymous or something like that
And for the API with token send userType as customer or something like that.
Create a method which will be called first thing from the each end points and validate the Request header.
If you want to make it more general you can map this end point and type of user as meta data setting, So next time if you have some '/image' end point which was allowed only to the customer user type but now you want it should be allowed to anon also so that can be easily done using that meta data file without changing the code.
You can be explicit by using AllowAny permission class, which will let public access to that endpoint.
from rest_framework import permissions, views
class ListAllStores(views.APIView):
permission_classes = (
permissions.AllowAny,
)
def get(self, request, format=None):
all_stores = Store.objects.all()
serializer = StoreSerializer(all_stores, many=True)
return Response(data=serializer.data)

I want to show progress bar in django template, on the basis of django post process/progress(form is ajax submit). is it possible?

def post(self, request, *args, **kwargs):
if attorney_data_list:
for i in len(attorney_data_list):
if(index%3 == 0):
process_percent = int(100 * float(index) / float(len(case_data_list)+1))
else:
pass
i want this process_percent value in django template on every for loop.is it possible?
No. Standard HTTP connections are request / response based, meaning that you only send the response to the browser once the function completely ran.
To provide the current process from within this function, you would probably need to use websockets, though I am not really familiar with them.
With standard HTTP-requests, you could use the database to store the process_percentage and use another ajax-request to retrieve the process_percentage from the database and send it to the browser.

How to send the authenticated response while authenticating a user via SAML in Rails?

I have been trying to implement SAML in my application, wherein I want to authenticate the user and create the SAML Token(response) and redirect the user to the other website wherein session gets created.
Till now I have been able to get info on init method and consume method, which will be implemented by the other website.
def init
request = OneLogin::RubySaml::Authrequest.new
redirect_to(request.create(saml_settings))
end
def consume
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
response.settings = saml_settings
if response.is_valid? && user = current_account.users.find_by_email(response.name_id)
authorize_success(user)
else
authorize_failure(user)
end
end
Following this Source.
I want to create the method which comes in between init and consume.
Updated:
Like I have this one which is I guess is following SAML 1.1, I wanted to know how can I generate a SAML 2.0 request using the get_settings method in Rails.
def SSOAccount.get_settings
settings = Onelogin::Saml::Settings.new
settings.issuer = "https://example.com/test"
settings.idp_sso_target_url ="https://testexample.com"
settings.idp_cert_fingerprint ="########"
settings.relying_party_identifier = "knsdfnsdf"
settings.assertion_consumer_service_url = "https://www.example.com/consume?http_referer=https://testexample.com"
settings.idp_confirmation_method = "urn:oasis:names:tc:SAML:1.0:cm:bearer"
settings.asserting_party_id = "23424dfsdf"
settings.referer_url = "https://textexample.com"
settings.groups = ["USER"]
settings
end
You can post the data, but do it in a way that resembles a redirect. The problem with a redirect being that the data is usually larger than can be accommodated in a browser acceptable url.
You need to do it this way so that the post comes from the user's browser rather than your server. That is, the post needs to take the user's browser session with it, so that the associated cookies and session data are submitted with the SAML token.
One solution is to use a self submitting form as shown within saml_tools_demo's indentifies#create view.
Have a look at the matching controller action to see how the data are constructed.

Resources