Django: reencode request and call another view - django

I am working on a backend application that exposes a JSON-based REST api. However, I am using some library that has some of its own endpoints and they accept form-endcoded data. I want to extend the actions done by those endpoints and expose my extended versions. At the same time, I want my API to be consistent and I want all my endpoints to use JSON. To be more specific, I use the oauth2_provider library, and I want to logout users when they revoke a token. I am also considering making a logout handle that would require the bearer token in the Authorization header and would both logout the user and revoke the token.
My first approach to this was to write a wrapper view around the oauth2_toolkit revoke token view, loggging the user out in the wrapper view and then calling the actual revoke_token view. However, I have to modify the body of the request, which is immutable.
class Logout(View):
def get(self, request):
if request.user.is_authenticated:
logout(request)
# modify the .body attr of the request or create a new request here
RevokeTokenView.as_view(request)
I couldn't find a way to clone a Django request or modify it. Is there a way to do it?
(For now I am looking into creating a custom oauthlib_backend_class, but it feels as a bit of an overkill)
UPD: the data that is required by the revoke_token view is in the request_body

You can modify the immutable request body like below:
class Logout(View):
def get(self, request):
if request.user.is_authenticated:
logout(request)
# modify or create a new request here
body = request.GET.copy()
body['key'] = 'new_value'
request.GET = body
RevokeTokenView.as_view(request)
UPDATE: If you want to change .body attribute of the request object, according to django's implementation, you should change the ._body attribute which is a private attribute of the request object.

Related

How can I pass in a CSRF token with my API requests if I don't have a user login for my Django site?

Using the Django rest framework, I have an API endpoint I created that I call within my static files that I would like to pass a CSRF token into so that I'm the only one who can access the API.
My Django site does not have users with logins.
I essentially want to do something like this in my API endpoint:
#api_view(['POST'])
def payment(request, *args, **kwargs):
if (("Authorization" not in requests.headers) or (request.headers["Authorization"] != "token")):
return Response({"Error": "Not authorized for access."})
# ...
Do I need to generate a token one time and use that repeatedly?
Or can I generate one every time the script is used?
And how can I access this csrf token in my HTML file?
I'm using class-based views and I assume I would pass it in to get_context_data, but how would I set up the API endpoint to accept this CSRF token?
I would suggest to use TokenAuthentication right from the django-restframework:
https://www.django-rest-framework.org/api-guide/authentication/
You would then pass the token in the header of all your requests
like Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
If you use the decorators from the django-restframework, you can skipp all the custom token validation, as this is done by the django-restframework.
#api_view(['POST'])
#authentication_classes([TokenAuthentication])
#permission_classes([IsAuthenticated])
def payment(request, *args, **kwargs):
# ...

Flask-praetorian, not able to pass auth header to protected endpoint

im using flask-praetorian in order to add security to my app.
I got two routes: one for /login and a protected endpoint called /profile. The Login route works fine, it takes the username and password from the form, im able to pass the information to the guard object and authenticate it to get a new token, but im not been able to pass this token to the request headers for the protected endpoint.
I've tried to use the 'session" to add the header, the 'make_response' method, and the redirect(url_for()), but everytime it gets to the endpoint does it without the correct header causing the error.
Code below:
#user.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if request.method == 'POST':
username = form.username.data
password = form.password.data
user = guard.authenticate(username, password)
token = guard.pack_header_for_user(user)
resp = make_response(profile())
resp.headers['Authorization'] = token['Authorization']
return resp
else:
return render_template('login.html', form=form)
#user.route('/profile')
#auth_required
def profile():
return render_template('profile.html')
(Author of the flask-praetorian package here)
The issue here is that you are just calling the profile() method from your login() method. Calling pack_header_for_user will not actually put the correct auth headers in the current request context. Instead, it just creates a header dict that could be put into a request (but is nicely returned as json). You could jam the token into the current request by tinkering with flask's request context, but it would not be the best way.
The right way to do this is to have your login route include the token in your response payload and then to make another call from your frontend code to the profile endpoint with the token added to the request header.
Really, I think you would be better off going with a different package like flask-security. The flask-praetorian package is intended to be used with pure APIs, and I haven't done any testing or prototyping with standard flask pages.

Django - RawPostDataException: You cannot access body after reading from request's data stream

I'm really stuck at this problem for a couple of days now.
While I understand, what's happening here, I don't really know the best workaround/solution for this.
Problem:
I'm trying to create a user login endpoint using Django and DRF in general.
My login API needs to support a login via password as well as login via OTP.
My LoginView looks like:
def post(self, request, **kwargs):
"""
post
Method to handle user login
:param request:
:param args:
:param kwargs:
:return:
"""
request_data = request.data
login_using_password = request_data.get('login-with-password') is True
login_using_otp = request_data.get('login-with-otp') is True
if request_data is not None:
if all((login_using_password, login_using_otp)):
raise accounts_exceptions.InvalidLoginRequestError()
if login_using_password:
return Response(self._login_with_password(request))
elif login_using_otp:
return Response(self._login_with_otp(request))
raise accounts_exceptions.InvalidLoginRequestError()
return Response(self._login_with_password(request))
Also my _login_with_password looks like:
def _login_with_password(self, request, **kwargs):
"""
_login_with_password
A utility method to handle login with password
:param request:
:return:
"""
return getattr(ObtainJSONWebToken.as_view()(request=request._request, ), 'data')
When I try to login, Django complains saying RawPostDataException You cannot access body after reading from request's data stream
I'm using JWT to authenticate requests. ObtainJSONWebToken is a view provided by DRF-JWT to obtain access tokens to authenticate requests.
What is the workaround/solution for this?
Is there a better way to support such a login requirement?
Thanks in advance!
Resolved this.
There's no concrete way to solve the problem above.
Django disallows access to request.data multiple times.
It could be done only once for the entire request lifetime.
So, this left me with two solutions:
Move my request payload to query params.
Move my request payload to url context.
I ended up using a mix and match of both.
So, basically I used request.query_params and self.context to fetch data from the request and changed my URL and request structure accordingly.

DRF - extend obtain auth token

I have Django Rest Framework with token auth. I have a following url url(r'^api/auth/', views.obtain_auth_token), which returns me token.
What I need to do is perform some db logic, when user performs authorization which is getting the token. I need to query db and do some stuff there.
It seems to me that I have somehow to override default behaviour and add some custom logic to obtain_auth_token.
How can I do that ?
ObtainAuthToken from Rest Framework gets or creates a token for an specific user and then sends it in the Response, all of these behaviour is done in the post method.
The documentation says:
If you need a customized version of the obtain_auth_token view, you can do so by overriding the ObtainAuthToken view class, and using that in your url conf instead.
So you can override the post method, or even create your own APIView to create the Token and add the behaviour you want. In order to do that, change your url:
url(r'^api/auth/', views.custom_obtain_token)
And in views.py:
class CustomObtainToken(APIView):
...
def post(self, request):
<your logic>
<get token n your own way or using DRF way>
return Response({'token': token})
custom_obtain_token = CustomObtainToken.as_view()

How to include user specific access to Django REST API in this example?

My understanding of authentication via an API is that the HTTP request sent by the client needs to include credentials, whether that be just a raw username and password (probably bad practice) or a hashed password, token, etc.
Normally in my Django views, I just use:
request.user.is_authenticated():
If I want my API to be used with an iOS app, this line of code cannot be used because it relies on sessions/cookies?
I would like to edit the following function, to allow it access to a specific user:
api_view(['GET'])
#csrf_exempt
def UserInfoAPI(request):
###if HTTP header includes name and password:###
private_info = Entry.objects.filter(user=request.user)
serializer = EntrySerializer(private_info, many=True)
return Response(serializer.data)
Is there a simple way to manually check for a username/pass in the HTTP header? I don't actually plan to use this in a production environment, but for the sake of understanding, I would like to understand how to have this function verify a username/pass from the http header.
Django REST Framework tries to determine the user that sends the request looking into the Authorization HTTP header. What you should send inside this header depends on the authentication scheme you choose. For example, if you choose BasicAuthentication, your header would be:
Authorization: Basic <"user:password" encoded in base64>
or, if you choose TokenAuthentication:
Authorization: Token <your token>
I would recommend the TokenAuthentication scheme. More schemes are listed in the docs.
To make sure only authenticated users have access to that API's endpoint, use the IsAuthenticated permission. This will check your user's credentials in the request, and if they are not correct, it will raise a HTTP 401 Unauthorized error.
Your Django REST Framework view would look something like this:
from rest_framework import generics
from rest_framework.permissions import IsAuthenticated
class UserInfo(generics.ListAPIView):
model = Entry
serializer_class = EntrySerializer
permission_classes = (IsAuthenticated,)
def get_queryset(self):
queryset = super(UserInfo, self).get_queryset()
user = self.request.user
return queryset.filter(user=user)
As for the code in your iOS app, this post may be helpful.