Q: How to store session data before authentication occured - django

I'm using django-all auth with GMail login.
There one of my view that will receive HTTP-POST from a Hotspot login page in other server (actualy it's mikrotik hotspot redirect).
I need to read their posted data AFTER social login.
I read https://stackoverflow.com/a/32250781/5901318
Looks like the safest way is to store the POST data in session, and later my view will get it from request.session
but I don't know how to 'store data safely in request.session before the authentication occurs'.
def my_login_required(function):
#https://stackoverflow.com/a/39256685/5901318
def wrapper(request, *args, **kwargs):
decorated_view_func = login_required(request)
if not decorated_view_func.user.is_authenticated:
if request.method == "POST" :
print('my_login_required POST:',request.POST.__dict__)
print('my_login_required ARGS:',args)
print('my_login_required KWARGS:',kwargs)
print('my_login_required SESSION:',request.session.__dict__)
wrapper.__doc__ = function.__doc__
wrapper.__name__ = function.__name__
return wrapper
##receiver(user_logged_in)
#csrf_exempt
#my_login_required
def hotspotlogin(request,*args,**kwargs):
print('HOTSPOTLOGIN')
I tried to access it using requests :
r = requests.post('http://mysite:8000/radius/hotspotlogin/', json={"NAMA": "BINO"}, headers = {'Content-type': 'application/json', 'Accept': 'text/plain'})
but in django shell I only got :
my_login_required POST: {'_encoding': 'utf-8', '_mutable': False}
my_login_required ARGS: ()
my_login_required KWARGS: {}
my_login_required SESSION: {'storage_path': '/opt/djangos/radius03/mysessions/', 'file_prefix': 'sessionid', '_SessionBase__session_key': None, 'accessed': True, 'modified': False, 'serializer': <class 'django.core.signing.JSONSerializer'>, '_session_cache': {}}
Kindly please give me any clue to do that.
Sincerely
-bino-

Got priceless help from a friend, and here is the solution.
def my_login_required(function):
def wrapper(request, *args, **kwargs):
old_data=dict()
try :
old_data['POST'] = dict(request.POST)
except :
old_data['POST'] = dict()
try :
old_data['GET'] = dict(request.GET)
except :
old_data['GET'] = dict()
old_data['method'] = request.method
decorated_view_func = login_required(request)
if not decorated_view_func.user.is_authenticated: #Only if user not authenticated
request.session['old'] = old_data #put old data in request.session['old']
return decorated_view_func(request) # return redirect to signin
return function(request, *args, **kwargs)
wrapper.__doc__ = function.__doc__
wrapper.__name__ = function.__name__
return wrapper
#my_login_required
def testview(request,*args,**kwargs):
print('SESSION DATA:', request.session.get('old')) #take original post/get data from request.session['old']

Related

Django returns session id but doesn't authenticate user

I have the following code that sends requests to check JWT token, then authorize user and return authorized session with Access token, Refresh Token and Session ID.
#csrf_exempt
def new_login_view(request, *args, **kwargs):
def convert_data(req):
data = {
"email": req.data['username'],
"password": req.data['password'],
}
try:
data["language"] = request.LANGUAGE_CODE
except:
data["language"] = request.POST.get('language', 'en')
return data
if request.user.is_authenticated and not request.META.get('HTTP_X_AVOID_COOKIES'):
return HttpResponseRedirect(request.GET.get(KEY_NEXT, '/'))
if request.method == 'POST':
request_data = convert_data(request)
# request to Accounts API to check if user exists
response = send_service_request(EnumAuthUrls.sign_in.value,
json_data=request_data,
original_response=True)
if isinstance(response, dict):
return JsonResponse(response)
if response.status_code == 200:
tok_ac = response.headers.get(HEADER_ACCESS_KEY)
tok_ref = response.headers.get(HEADER_REFRESH_KEY)
# checking JWT token
user = ApiAuthenticationBackend().authenticate(request, tok_ac)
# creates session
data = login_session(request, response, user)
data['user_id'] = request.user.id
data['account_id'] = request.user.profile.account_id
data['balance'] = request.user.balance
if request.META.get('HTTP_X_AVOID_COOKIES'):
return JsonResponse(data)
response = AuthResponse(
data=data,
ssid=request.session.session_key,
access_token=tok_ac,
refresh_token=tok_ref,
)
return response
else:
return ErrorApiResponse(response.json())
service = urllib.parse.quote_plus(request.build_absolute_uri())
return HttpResponseRedirect(settings.ACCOUNTS_URL + f'login/?service={service}')
Here's the code of login_session fucntion:
def login_session(request: HttpRequest, response: HttpResponse, user):
request.user = user
request.session.create()
base_data = response.json().get(KEY_DATA)
return request.user.serialize(request, base_data, token=True)
And here's the class AuthResponse that is eventually based on HttpResponse:
class AuthResponse(SuccessResponse):
def __init__(self, data={}, ssid='', access_token: str = '', refresh_token: str = '', **kwargs):
super().__init__(data, **kwargs)
if ssid:
logger.debug(f'Setting {settings.SESSION_COOKIE_NAME}: {ssid}')
self.set_cookie(key=settings.SESSION_COOKIE_NAME,
value=ssid)
if access_token:
self.set_cookie(key=settings.ACCESS_KEY_COOKIE_NAME,
value=access_token)
if refresh_token:
self.set_cookie(key=settings.REFRESH_KEY_COOKIE_NAME,
value=refresh_token)
The problem is that looks everything good on the browser side, I get all needed cookies (access token, refresh token and session id) however after trying logging in I get redirected to the main page.
There was problem in the beginning with setting cookies, but then I found out that I should not use SESSION_COOKIE_DOMAIN if it's local. Thus all cookies came up without problem but it didn't resolve situation with authorization.
While setting cookies with self.set_cookie() I tried to use secure=True, samesite='Lax', httponly=True, and all other parameters but it didn't help.
Does anyone knows what else I can try in order to fix it?
Well, I found what was wrong!
There was middleware that supposed to check token from another service. However it was checking old token, instead of new one.
So once I changed it and started to check new token - it was working just fine.
So if there's no other solutions, make sure you have checked middleware or other code where it could affect on whole system.

DRF- Post Request Unitest

I have a post method under View Set. I need to write a unit test case for the method. when I pass param its give None. How should I pass both param and data(payload).
views.py
#action(detail=True, methods=['post'])
def complete_task(self, request, *args, **kwargs):
"""
Method for complete the task
input post request : task_id : str, variable_return:boolean, request data: dict
output Response : gives whether task is completed or not
"""
try:
get_task_id = self.request.query_params.get("task_id")
get_process_variables = request.data
print(get_task_id)
print(get_process_variables)
complete_task = CamundaWriteMixins.complete_task(url=CAMUNDA_URL, task_id=get_task_id,
process_variable_data=get_process_variables)
print("compl", complete_task)
return Response({"task_status": str(complete_task)})
except Exception as error:
return Response(error)
test.py
def test_completed_task(self):
self.client = Client()
url = reverse('complete-task')
data = {"variables": {
"dept_status": {"value": "approved", "type": "String"}}
}
response = self.client.post(url, data=data, params={"task_id": "000c29840512"},
headers={'Content-Type': 'application/json'})
print(response.data)
self.assertTrue(response.data)
I have tried above test case method which is getting request data but I got param None.
Thanks in Advance,.
if you just modify your request a bit and add query param as part of your url then i guess you are good to go.
Example:
response = self.client.post(f'{url}?task_id=000c29840512', data=data,
headers={'Content-Type': 'application/json'})
you can refer the official documentation for the example: https://docs.djangoproject.com/en/4.0/topics/testing/tools/

Django REST Framework - Testing POST request

I'm currently struggling to make this current unit-test pass:
def test_markNotifications(self):
request_url = f'Notifications/mark_notifications/'
view = NotificationsViewSet.as_view(actions={'post': 'mark_notifications'})
request = self.factory.post(request_url)
request.POST = {'id_notifs': "1"}
force_authenticate(request, user=self.user)
response = view(request)
self.assertEqual(response.status_code, 200)
Here's the associated view:
#action(detail=False, methods=['POST'])
def mark_notifications(self, request, pk=None):
"""
Put Notifications as already read.
"""
id_notifs = request.POST.get("id_notifs")
if not id_notifs:
return Response("Missing parameters.", status=400)
id_notifs = str(id_notifs).split(",")
print(id_notifs)
for id in id_notifs:
notif = Notification.objects.filter(pk=id).first()
if not notif:
return Response("No existant notification with the given id.", status=400)
notif.isRead = True
notif.save()
return Response("Notifications have been marked as read.", status=200)
The problem is that even though I'm passing "id_notifs" through the request in test, I'm getting None when I do id_notifs = request.POST.get("id_notifs").
It seems that the id_notifs I'm passing in the POST request are neither in the body and the form-data. In this context, I have no idea on how to access them.
Looking forward some help, thanks.

Django view gives error of Forbidden (CSRF token missing or incorrect.) while using the userdefined decorator for checking the session

I created the userdefined decorator to check the session is active or not. Following is the function defination
def session_required(func):
"""
Decorator to check the session is active or not for logged in user
:param func: Name of function for which you have to check the session is active or not
:return:
"""
def wrap(request, *args, **kwargs):
"""
Wrapper function for the decorator
:param request: request parameter for called URL
:return:
"""
if not request.session.get("admin_id"):
return redirect("/")
func_return = func(request, *args, **kwargs)
return func_return
return wrap
I am using this decorator on the respective function based view. At some places it works absolutely fine but when I do some POST or PUT operation then it gives me error
Forbidden (CSRF token missing or incorrect.):
My function based view is like
#csrf_exempt
#session_required
def mover_profile_handler(request):
"""
mover profile handler function for viewing and editing the details
:param request:
:return:
"""
try:
if request.method == "GET":
login_id = request.session.get("admin_id")
login_info_obj = Login.objects.get(id=login_id)
mover_info_obj = Mover.objects.get(fk_login=login_info_obj)
country_obj = Country.objects.all()
currency_obj = CurrencyType.objects.all()
subscription_detail = SubscriptionMoverDetail.objects.filter(fk_mover=mover_info_obj).order_by("-id")
# Extracting data for showing the subscription package details
current_subscription_detail = {}
subscription_detail_history = []
for index, item in enumerate(subscription_detail):
subscription_master_detail = SubscriptionMaster.objects.get(id=item.fk_subscription_master_id)
subscription_detail_json = {
"plan_name": subscription_master_detail.subscription_plan_name,
"subscription_start_date": item.subscription_date,
"subscription_end_date": item.subscription_end_date,
"amount_paid": item.amount_paid,
"users": subscription_master_detail.customer_permitted_count
}
if index == 0:
current_subscription_detail = subscription_detail_json
else:
subscription_detail_history.append(subscription_detail_json)
return render(request, "mover_profile.html", {
"mover_info_obj": mover_info_obj,
"country_obj": country_obj,
"currency_obj": currency_obj,
"login_info_obj": login_info_obj,
"current_subscription_detail": current_subscription_detail,
"subscription_detail_history": subscription_detail_history
})
elif request.method == "PUT":
request = convert_method_put_to_post(request)
mover_id = request.POST.get("id")
if Mover.objects.filter(id=mover_id).exists():
mover_info_obj = Mover.objects.get(id=mover_id)
mover_info_obj.mover_name = request.POST.get("name")
mover_info_obj.address = request.POST.get("address")
mover_info_obj.phone_no = request.POST.get("phone")
mover_info_obj.mover_size = request.POST.get("size")
mover_info_obj.reg_no = request.POST.get("reg_no")
mover_info_obj.website = request.POST.get("website")
mover_info_obj.fk_country_id = request.POST.get("country")
mover_info_obj.fk_currency_id = request.POST.get("currency")
operational_countries = request.POST.getlist("operational_countries[]")
mover_info_obj.countries_in_operation.set(operational_countries)
mover_info_obj.save()
return HttpResponse("success")
except Exception as e:
error_save(str(traceback.format_exc()))
return redirect('error_handler_500')
I tried with
#csrf_protect #csrf_exempt
in view and also tried {% csrf_token %} in html file
without using #session_required code is working absolutely fine.
So please tell me what is wrong with this stuff!!

Add existing API into django rest framework

Besides the API I created by using the django rest framework, now I want to add the existing commerial API into the backend, how to do this?
The commerial API include several part(API), for example, /prepare, /upload, /merge,/get_result, etc. They all use the "POST" indeed.
Do I need to call the commerial API directly on the frontend? Or I need to integrate the commerial API in to one backend API? Any suggestions are appreciated.Thanks.
For example:
```
class TestView(APIView):
"""
Add all the commerial API here in order
/prepare(POST)
/upload(POST)
/merge(POST)
/get_result(POST)
return Result
"""
```
Depending on your needs, I suggest making the external API calls on backend.
As a good practice, you should seperate your external API calls from your views. As it can be messy as the project gets bigger.
Check the sample code of how I manage external API calls, by seperating them to a different file, for example api_client.py
My default api_client.py looks something like this.
(You need to install "requests" pip package by pip install requests)
import requests
from django.conf import settings
class MyApiClient(object):
def __init__(self):
self.base_url = settings.EXTERNAL_API_1.get('url')
self.auth_url = "{0}login/".format(self.base_url)
self.username = settings.EXTERNAL_API_1.get('username')
self.password = settings.EXTERNAL_API_1.get('password')
self.session = None
self.access_token = None
self.token_type = None
self.token_expires_in = None
def _request(self, url, data=None, method="POST", as_json=True):
if self.session is None:
self.session = requests.Session()
if not self.access_token:
self.authenticate()
r = requests.Request(method, url=url, data=data, headers={
"Accept": "application/json",
"Content-Type": "application/json",
'Authorization': 'Token {0}'.format(
self.access_token)
})
prepared_req = r.prepare()
res = self.session.send(prepared_req, timeout=60)
if as_json:
json_result = res.json()
return json_result
return res
def _get(self, url):
return self._request(url=url, method="GET")
def _post(self, url, data):
return self._request(url=url, data=data, method="POST")
def authenticate(self):
res = requests.post(self.auth_url, json={'username': self.username,
'password': self.password},
headers={"Content-Type": "application/json"})
if res.status_code != 200:
res.raise_for_status()
json_result = res.json()
self.access_token = json_result.get('response', None).get('token',None)
def prepare(self):
something = 'bla bla'
request_url = "{0}prepare".format(self.base_url)
result = self._post(url=request_url, data=something)
return result
And in your views.py
from api_client import MyApiClient
class TestView(APIView):
api_client = MyApiClient()
def post(request, *args, **kwargs):
res1 = api_client.prepare()
res2 = api_client.your_other_method()
res3 = api_client.your_last_method()
return Response(res3)
Edited! Hope this helps now :)