Django passthrough API request to hide credentials - python

I need to make a request to b.com/rest/foo to get json data for my application. I want to do it this way to protect the credentials rather than expose them on every page.
I found Consume an API in Django REST, server side, and serve it ,client side, in Angular and the corresponding answer at https://stackoverflow.com/a/65672890/2193381 to be a great starting point.
I created a local url to replicate the data that the external server will return and then tried the following
import requests
from django.http import JsonResponse
from django.contrib.auth.decorators import login_required
from django.views.decorators.cache import never_cache
#never_cache
#login_required
def fakedata(request, item):
return JsonResponse({'item': item})
def getdata(request, item):
url = f"http://localhost:8080/rest/{item}"
username = os.getenv('SITE_USERNAME', None)
password = os.getenv('SITE_PASSWORD', None)
userpass = dict(username=username, password=password)
data = requests.get(
url,
auth=requests.auth.HTTPBasicAuth(**userpass),
)
if data is not None and data.status_code == 200:
try:
return JsonResponse(data.json(), safe=False)
except ValueError:
print("!JSON")
return JsonResponse({})
print("!data")
return JsonResponse({})
urlpatterns = [
path('rest/<str:item>', fakedata),
path('foo/<str:item>', getdata),
]
When I test it with
python manage.py runserver 8080
and call http://localhost:8080/foo/a, I am getting back the html of the login page and not the data from http://localhost:8080/rest/a that I am expecting.
What changes do I need to make to get it to work?

I just went through Django documentation and found this useful and working.
you can first authenticate the user by using the authenticate() method then login with request and call your fakedata() function with passing the request:
from django.contrib.auth import authenticate, login
user = authenticate(request, **userpass)
if user is not None:
login(request, user)
data = fakedata(request, item)
else:
pass

Related

Django refuses request from Python requests

I have a Django view where when a user performs a certain task, an external Python microservice retrieves and then sends some data to the Django view, which is supposed to show it to the user on the template. To send the data i'm using Python requests, the problem is that the Json response is being refused by Django for two reasons: 1) The CSRF Token is not set 2) The view is #login_required
Here is my view:
#login_required
def myTestView(request):
if request.method == 'POST':
received_json_data=json.loads(request.body)
print(received_json_data)
print('received.')
And here is how i send the response:
import requests
req = requests.post('http://127.0.0.1:8000/myTestView/', json={"test": "json-test"})
print('SENT')
With the actual code, i get this error from Django:
Forbidden (CSRF cookie not set.): /myTestView/
[2019-12-24 15:36:08,574] log: WARNING - Forbidden (CSRF cookie not set.): /myTestView/
I know i can use #csrf_exempt, but since the data that i'm sending is personal, i would like it to be as safe as possible, so i need to find a way to send the CSRF Token. The second thing i need to do, is how do i "login" using the request?
I like this question, so I'll try to describe the whole process in detail.
Server side.
First step is to get csrf_token which you'll use in the further post request.
After that you have to authenticate the session.
So let's write a view to serve get for getting csrf_token and post for authenticating session. The same idea for protected view.
After we get authenticated it is possible to access protected view.
import json
from django.contrib.auth import authenticate, login
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseForbidden
from django.middleware.csrf import get_token
#login_required
def myTestView(request):
if request.method == 'POST':
data = request.POST.get('data')
print(json.loads(data))
print('received.')
response = HttpResponse(get_token(request))
return response
def login_view(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return HttpResponse('authenticated')
else:
return HttpResponseForbidden('wrong username or password')
response = HttpResponse(get_token(request))
return response
Client side.
requests.post does not fit for this task because it can't track (or it's really hard) request cookies and headers.
But you can use for this purpose requests.session()
Django is not able to process csrf_token in json, that's why you have to pass json in request data.
import json
import requests
session = requests.session()
token = session.get('http://127.0.0.1:8000/login/')
session.post('http://127.0.0.1:8000/login/',
data={
'username': '<username>',
'password': '<password>',
'csrfmiddlewaretoken': token})
token = session.get('http://127.0.0.1:8000/myTestView/')
data = json.dumps({'test': 'value'})
session.post('http://127.0.0.1:8000/myTestView/',
data={
'csrfmiddlewaretoken': token,
'data': data})
Mind adding urls.py for the views
I checked this code and it is working well. If anyone has an ideas how to improve it I will love to update it.

Session being set but not persisted

Problem
The django.contrib.auth.login function is succeeding, but the session does not seem to persist.
Code
I am creating my own set of functions to use with an AJAX client inside of my Django server.
I have the following code in urls.py:
from django.contrib.auth import authenticate, login
from django.conf.urls import url
from django.http import JsonResponse
import json
def auth_login(request):
username = json.loads(request.body)['username']
password = json.loads(request.body)['password']
user = authenticate(request, username=username, password=password)
if user is not None and user.is_active:
login(request, user)
return JsonResponse({}, status=200)
else:
return JsonResponse({}, status=400)
urlpatterns = [
url(r'auth/login/', auth_login, name='login')
]
Details
As mentioned above, the call to login() succeeds. request.session is being set properly, and I can access if from within auth_login. On subsequent requests, request.session does not exist and django.contrib.auth.get_user returns AnonymousUser.
(Possibly relevant) I am also using the seemingly-popular https://django-tenant-schemas.readthedocs.io/en/latest/. This urls.py file is in an application that is per-tenant, so the user is actually authenticating on tenantname.hostname.com/tenant/auth/login/.
The tenant's django_session table is being set correctly. For each attempted login, I can see session_key, session_data, and expire_data.
The cookie for tenantname.hostname.com is empty after attempted logins.
I'm just out of ideas as to what other things I could try to lead me to a solution.
Question(s)
Any thoughts as to why this session isn't actually saved into the cookie?
OR
Thoughts as to what else I could try that could lead me to a solution?

Login/Logout a user using Django

I am currently working on a project with Django. I am trying to implement the ability of login and logout a user from the application using only python scripts in order to send post request from the client to the server.
I am trying to logout a user from my application but it does not seems to work. In my login function this is the method used to login a user:
# Function for user's login
#csrf_exempt
def loginUser(request):
login_user = authenticate(username=user.customer_username, password=user.customer_password)
if login_user is not None:
if login_user.is_active:
request.session.set_expiry(86400)
login(request, login_user)
print(request.user.is_active)
http_response = HttpResponse()
return http_response
The result of the print is True here which means that the login method is correct if I am not wrong. When I try to logout the user, using this method:
# Function for user's logout
#csrf_exempt
def logoutUser(request):
# Loging out the user
print(request.user.is_active)
logout(request)
http_response = HttpResponse()
return http_response
It does not logout the user and the result of the print is False here, which means that the user is not logged in. If anyone have any idea, how to solve this, it would be very helpful. Thanks.
I managed to find the answer for this problem. Because I was using a python script to send and receive post requests between the server and the client, the current user which was logged in was not saved due to after the request from the client the connection between client and server stoped. So by initialising a global Session object and later use that object to make request it allowed me to persist certain parameters across requests.
This is the code I used in the client side in order to make requests:
import requests
from requests import Session
# Create the Json object for the login
data = {'username': username,
'password': password}
r = requests.Session()
login_response = r.post("http://127.0.0.1:8000/api/login", data=data)
logout_response = r.post("http://127.0.0.1:8000/api/logout")
And this is the code I used in the server side:
# Function for user's login
#csrf_exempt
def loginUser(request):
# Get the username and password from the post request
username = request.POST['username']
password = request.POST['password']
login_user = authenticate(username=username,
password=password)
if login_user is not None:
if login_user.is_active:
login(request, login_user)
http_response = HttpResponse()
return http_response
# Function for user's logout
#csrf_exempt
def logoutUser(request):
# Loging out the user
logout(request)
http_response = HttpResponse()
return http_response
Thanks for the help!!
Commenting for visibility, Kyriakos, this is completely correct.
I also am trying to query Django by logging in and out using Django Auth (django.contrib.auth) but using a custom python script to test the posts of API's when logging in. Without the global request sessions the django.contrib.auth login will always show Anonymous User when attempting to use request.user, and this is the culprit.
import requests
from requests import Session
r = requests.Session() #SETTING GLOBAL SESSION FOR DJANGO TO READ MY SESSION
x = r.post('http://127.0.0.1:8000/newsagency/api/login/', {'username':'jake', 'password':'UOL'})
print("login response: ", x.text)
x = r.post('http://127.0.0.1:8000/newsagency/api/poststory/', {'headline' :'Story 1',
'category' : 'pol',
'region' : 'w',
'details' : 'some details'})
print("post story response: ", x.text)
def LogIn(request):
if request.method=="POST":
loginname = request.POST["name"]
loginemail = request.POST['Email']
loginpassword = request.POST["Password"]
user = authenticate(username=loginname, email=loginemail, password=loginpassword)
#login_required()
def signout(request):
logout(request)
return redirect(index)

Django using one time call function output as global value

While initializing the project, i want to call login function from projects settings for once and use the output token anywhere in project. I want to use same token whether I call again to check if token changed.
def login(creds):
r = requests.post(base_url + '/api/admin/login', json=creds, headers=headers, verify=False)
if r.status_code == 200:
token = r.json()['token']
return token
If i call function in project settings, function starts each time. I don't want to dump token to a file and read each time. Any other way? Thanks.
The best way would be to separate responsibilities for login, authentication and use of token. In practice you'd store token in session. Django provides full support for anonymous sessions, but this could limit your application in cases, when you'd like to store some data associated to that user in the local database.
What I recommend is to setup User model in the local database where you save usernames. Next you create a function for authentication:
import requests
def authentication(username, password):
creds = {'username': username, 'password': password}
headers = 'whatever'
r = requests.post(base_url + '/api/admin/login', json=creds, headers=headers, verify=False)
if r.status_code == 200:
token = r.json()['token']
# Create user in the local database if it does not exist yet
user, created = User.objects.get_or_create(username=username)
return token
else:
error = 'Something went wrong'
return error
Next you create login functionality.
from django.urls import reverse
from django.http import HttpResponseRedirect
from django.contrib.auth import login
from django.shortcuts import render
def login(request):
# Check if user is authenticated by call is_authenticated() and
# additionally check if token is stored in session
if request.user.is_authenticated and request.session.get('token', False):
return HttpResponseRedirect(reverse('user_page'))
if request.method == 'POST':
username = request.POST.get('username')
password = request.POST.get('password')
auth_data = authenticate(username=username, password=password)
if auth_data is not None:
# Save token in session
request.session['user_token'] = auth_data
# Get user and login
# Because you created a user in local database,
# you can login that user in this step
user = User.objects.get(username=username)
login(request, user)
next = request.POST.get('next', '/') if request.POST.get('next') else '/'
return HttpResponseRedirect(next)
else:
# Implement some error handling here
return HttpResponseRedirect(reverse('login'))
else:
return render(request, 'login.html', {})
The next logical step would be to control this users tokens somehow. Decorators come to rescue. You create a decorator that checks token. For example something in this sense:
import functools
from django.contrib.auth import REDIRECT_FIELD_NAME
from urllib.parse import urlparse
from django.shortcuts import resolve_url
from django.contrib.auth.views import redirect_to_login
def token_required(func, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
#functools.wraps(func)
def decorated_function(request, *args, **kwargs):
# Check if token is saved in session
# Redirect to login page if not
if not request.session.get('token', False):
path = request.build_absolute_uri()
resolved_login_url = resolve_url(login_url or settings.LOGIN_URL)
# If the login url is the same scheme and net location then just
# use the path as the "next" url.
login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
current_scheme, current_netloc = urlparse(path)[:2]
if ((not login_scheme or login_scheme == current_scheme) and
(not login_netloc or login_netloc == current_netloc)):
path = request.get_full_path()
return redirect_to_login(
path, resolved_login_url, redirect_field_name)
return func(request, *args, **kwargs)
return decorated_function
You'd need to implement your logic here. This decorator only check if token is stored in session, nothing else. You'd then use this decorator in views as in
from django.contrib.auth import logout
#token_required
#login_required
def user_logout(request):
logout(request)
return HttpResponseRedirect('/')

Django: Basic Auth for one view (avoid middleware)

I need to provide http-basic-auth to one view.
I want to avoid modifying the middleware settings.
Background: This is a view which gets filled in by a remote application.
When you do a basic auth request, you're really adding credentials into the Authorization header. Before transit, these credentials are base64-encoded, so you need to decode them on receipt.
The following code snippet presumes that there's only one valid username and password:
import base64
def my_view(request):
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
token_type, _, credentials = auth_header.partition(' ')
expected = base64.b64encode(b'username:password').decode()
if token_type != 'Basic' or credentials != expected:
return HttpResponse(status=401)
# Your authenticated code here:
...
If you wish to compare to the username and password of a User model, try the following instead:
def my_view(request):
auth_header = request.META.get('HTTP_AUTHORIZATION', '')
token_type, _, credentials = auth_header.partition(' ')
username, password = base64.b64decode(credentials).split(':')
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
return HttpResponse(status=401)
password_valid = user.check_password(password)
if token_type != 'Basic' or not password_valid:
return HttpResponse(status=401)
# Your authenticated code here:
...
Please note that this latter version is not extremely secure. At first glance, I can see that it is vulnerable to timing attacks, for example.
You can try a custom decorator (as seems to be the recommended way here and here) instead of adding new middleware:
my_app/decorators.py:
import base64
from django.http import HttpResponse
from django.contrib.auth import authenticate
from django.conf import settings
def basicauth(view):
def wrap(request, *args, **kwargs):
if 'HTTP_AUTHORIZATION' in request.META:
auth = request.META['HTTP_AUTHORIZATION'].split()
if len(auth) == 2:
if auth[0].lower() == "basic":
uname, passwd = base64.b64decode(auth[1]).decode(
"utf8"
).split(':', 1)
user = authenticate(username=uname, password=passwd)
if user is not None and user.is_active:
request.user = user
return view(request, *args, **kwargs)
response = HttpResponse()
response.status_code = 401
response['WWW-Authenticate'] = 'Basic realm="{}"'.format(
settings.BASIC_AUTH_REALM
)
return response
return wrap
Then use this to decorate your view:
from my_app.decorators import basicauth
#basicauth
def my_view(request):
...
This library could be used: https://github.com/hirokiky/django-basicauth
Basic auth utilities for Django.
The docs show how to use it:
Applying decorator to CBVs
To apply #basic_auth_requried decorator to Class Based Views, use
django.utils.decorators.method_decorator.
Source: https://github.com/hirokiky/django-basicauth#applying-decorator-to-cbvs
For those that already use django-rest-framework (DRF):
DRF has a BasicAuthentication class which, more-or-less, does what is described in the other answers (see source).
This class can also be used in "normal" Django views.
For example:
from rest_framework.authentication import BasicAuthentication
def my_view(request):
# use django-rest-framework's basic authentication to get user
user = None
user_auth_tuple = BasicAuthentication().authenticate(request)
if user_auth_tuple is not None:
user, _ = user_auth_tuple

Categories

Resources