How to check USER IP in django project when website loads? - python

I am working on django website. I want make this website country restricted. So I want to check user ip when website is accessed. It doesn't matter what is url or which app is loaded. So if user is not from specific country, I want to show them error message that they cannot access this website.
I am using GeoLitCity database to get user's country. I know how to get user's ip. But I don't know how to do it in initialization step, before anything loads. Can anyone help me with this?

You can create a custom middleware class ValidateUserCountryMiddleware which will check if the country from where the request was made is from a specific country or not. If the request is made from a valid country, then Django will process the request. Otherwise, it will return a HttpResponseForbidden response.
In our custom middleware class, we will define a process_request() method. It should return either None or an HttpResponse object.
From the docs on process_request():
If it returns None, Django will continue processing this request,
executing any other process_request() middleware, then, process_view()
middleware, and finally, the appropriate view. If it returns an
HttpResponse object, Django won’t bother calling any other request,
view or exception middleware, or the appropriate view; it’ll apply
response middleware to that HttpResponse, and return the result.
Here, we will return a HttpResponseForbidden response in case the request did not come from a specified country.
from django.http import HttpResponseForbidden
class ValidateUserCountryMiddleware(object):
"""
This custom middleware class will check the country from where the request
was made using the IP address. If the country is valid, then Django will process
the request. Otherwise, it will return a 403 forbidden response
"""
def process_request(self, request):
# here write the logic to obtain the country from the IP address.
# Then check if its a valid country
if not valid_country: # check if its a valid country
return HttpResponseForbidden('You cannot access the website from this location.') # dont't process the request and return 403 forbidden response
return # return None in case of a valid request
Then add your middleware to the MIDDLEWARE_CLASSES in your settings.py file.
MIDDLEWARE_CLASSES = (
...
# add your custom middleware here
'my_project.middlewares.ValidateUserCountryMiddleware',
)

Related

Django - Changing the url to redirect from a middleware on the fly

I'm building a SSO login meant to be used from links send by emails.
Each link should auto-connect the user (SSO), and be clickable multiple times (they have a TTL, which depends on the email)
It works fine, but I'm concerned about the end-user sharing his url on social networks (basically copy/pasting the url, which contains the SSO token), allowing anyone following the link to be logged in automatically.
My first attempt was to try to remove the GET SSO_TOKEN parameter, from my SSOMiddleware, as follow:
if remove_token_middleware:
request.GET._mutable = True # GET is not mutable by default, we force it
# Remove the token from the url to avoid displaying it to the client (avoids sharing sso token when copy/pasting url)
del request.GET[SSO_TOKEN_PARAM]
request.GET._mutable = False # Restore default mutability
return login(request, user) if service.get("auto_auth") else None
Basically, my thought was that since the SSO_TOKEN is in the request.get object, removing it from it would eventually change the url where the user gets redirected
In my controller, here is how the user gets "redirected" (using render)
return render(request, 'campagne_emprunt/liste_offres_prets.html', locals())
When using render, there is no redirection, and the SSO token is still visible in the URL (in the browser address bar).
Is there a way to somehow tell Django to change the destination url, on the fly?
So in order to redirect, there is a builtin redirect function of Django that you use instead of rendering I think .. give it a shot,
from django.shortcuts import redirect
def redirect_view(request):
response = redirect('/redirect-success/')
return response
and for reference-
https://realpython.com/django-redirects/

How to detect redirection in Django view

I have a view that returns a redirection.
return redirect(reverse('my-view-name'))
Is it a way to detect redirection inside a view method?
I've searched in request object attributes but couldn't find any one that indicates redirection.
A redirection simply means you return a HttpResponseRedirect object [Django-doc] with status code 302 and a header Location that contains the location to which you redirect. There is nothing special about that response, and the target view is not triggered by the redirect response itself.
The browser that receives such response, can then thus act by visiting that link, and then it will thus trigger the view to which you redirected. But a browser does not per se does that (you can usually alter some settings to let your browser (not) follow such redirects).
You can thus for example implement your own middleware that does something with such redirect:
def redirect_processing_middleware(get_response):
def middleware(request):
response = get_response(request)
if 300 <= response.status_code < 400:
# the response is a redirect
# ...
pass
return middleware
An alternative is to add a watchdog on the redirect(..) call, but that can be circumvented, by simply creating a HttpResponse for example, and manually alter that response to transform it into a redirect response.
You can do some static code analyses (automatically or manually). But due to the dynamic nature of Python, it is impossible to know for sure what views will trigger redirections: one can obfuscate the redirect call, by using a proxy function, etc., or dynamically alter/monkey patch code such that redirections are taking place without (explicitly) calling redirect(..).

Relogin after N minutes with django and JWT

Scenario: I want a user to re-login when passing to a security sensible area after N minutes, e.g. when user is about to pay an order, however he logged in 1 hour ago, I would like to be sure it's him. This by using rest_framework_jwt.
Long description:
I've been recently testing django for modern web development (so, backend with rest-api). However, I encountered a problem which I have not yet found a solution.
In rest_framework_jwt you set the authentication class as follows.
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
This will do a great work for the general purpose. However, I want the user to reidentify (re-login) when entering an area with sensible information 10 minutes after login, e.g. sensible information can be a payment area. Thus, I would like to send a parameter to the Authentication class telling that the user is in a sensible area.
What I think as a possible solution but I don't know how to do it yet, is: The rest_framework_jwt creates the variable orig_iat when using the option JWT_ALLOW_REFRESH. I could send a flag to authentication class to tell that current view is a sensitive area or not, if so and the user logged in more than 10 minutes ago, I can send a message to say that the user needs to re-login to continue.
I don't mind forking the rest_framework_jwt project and adapting it for my purposes, however I would like to know how to send the parameter from the view to the authentication class (in this case: rest_framework_jwt.authentication.JSONWebTokenAuthentication).
Also, If there is already something done with rest_framework_jwt for this scenario, I would like to avoid re-inventing the wheel.
Well... So far, what I've done it's to create a decorator for a function view. The code for the decorator is as follows:
from functools import wraps
from rest_framework_jwt.settings import api_settings
from django.utils.translation import ugettext as _
from calendar import timegm
import datetime
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
def recently_authenticated():
def decorator(func):
#wraps(func)
def inner(request, *args, **kwargs):
jwt_payload = jwt_decode_handler(request._auth)
rencent_auth_limit = api_settings.JWT_RECENT_AUTHENTICATION_DELTA
if isinstance(rencent_auth_limit, datetime.timedelta):
rencent_auth_limit = (rencent_auth_limit.days * 24 * 3600 +
rencent_auth_limit.seconds) + jwt_payload["orig_iat"]
timenow = timegm(datetime.datetime.utcnow().utctimetuple())
if timenow>rencent_auth_limit:
return Response({"detail":_("you have to reidentify to enter this area")},
status=401)
return func(request, *args, **kwargs)
return inner
return decorator
The response format is given in the same format as rest_framework_jwt.authentication.JSONWebTokenAuthentication. The constant JWT_RECENT_AUTHENTICATION_DELTA is an ad-hoc parameter inserted in the settings.py of the rest_framework_jwt package (a fork).
Finally, in order to use it, one can add the decorator to any view. For example:
#api_view()
#recently_authenticated()
def index_view(request):
data = User.objects.filter()
return Response(UserSerializer(data, many=True).data)
And when the user has been authenticated a while ago, it will send the message {"detail":"you have to reidentify to enter this area"} with the code 401. This can be evaluated and parsed by the frontend and redirect the user to the login.
Note: The decorator only evaluates the time and the time passed. The verification telling whether or not it's a correct user and a correct token is still performed by rest_framework_jwt.authentication.JSONWebTokenAuthentication.
According to the manual: https://getblimp.github.io/django-rest-framework-jwt/#additional-settings
Disallowing refresh token should do the job. The thing is that you will get only 1 token and you won't be able to refresh it after 1 hour.
JWT_AUTH = {
'JWT_ALLOW_REFRESH': False,
'JWT_REFRESH_EXPIRATION_DELTA': timedelta(hours=1),
}
Other problems should be solved on the frontend side. You should check whether user is trying to get to "sensitive" view. If yes, then check if token is valid. If invalid - redirect to login page. If view is "insensitive" - your choice.
The recommended way to handle this is to separate token freshness from token validation. Most views require a valid token, secure views require a fresh token, one that is not only valid but also has been issued at login and has not been refreshed since.
You can do this by setting a flag on the token to mark it as 'fresh' on login, but unset the flag when refreshing. The flow then becomes:
Client accesses the site without a token -> Deny access
Client authenticates with obtain-token endpoint -> Issue token with fresh=True
Client (or server) refreshes valid token -> Issue token with fresh=False
Client accesses non-secure endpoint with valid token -> Accept token
Client accesses secure endpoint -> Accept token only if fresh=True is set on token.
The only way to obtain a fresh token is to log in again, no refresh allowed.
So you need to be able to:
distinguish between obtaining a new token and refreshing when generating the JWT payload.
inspect the fresh key in the current JWT token in order to create a custom permission.
The first requirement means you'll have to do some view subclassing, as the jwt_payload_handler callback is not given any information to determine what called it.
The easiest way to handle the first requirement is to just subclass the serializers used to produce a fresh or refreshed token, decode the token they produce, inject the applicable fresh key value, then re-encode. Then use the subclassed serializers to create a new set of API views:
from rest_framework_jwt.settings import api_settings
from rest_framework_jwt.serializers import JSONWebTokenSerializer, RefreshJSONWebTokenSerializer
from rest_framework_jwt.views import JSONWebTokenAPIView
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
jwt_decode_handler = api_settings.JWT_DECODE_HANDLER
class FreshJSONWebTokenSerializer(JSONWebTokenSerializer):
"""Add a 'fresh=True' flag to the JWT token when issuing"""
def validate(self, *args, **kwargs):
result = super().validate(*args, **kwargs)
payload = jwt_decode_handler(result['token'])
return {
**result,
'token': jwt_encode_handler({**payload, fresh=True})
}
class NonFreshRefreshJSONWebTokenSerializer(RefreshJSONWebTokenSerializer):
"""Set the 'fresh' flag False on refresh"""
def validate(self, *args, **kwargs):
result = super().validate(*args, **kwargs)
payload = jwt_decode_handler(result['token'])
return {
**result,
'token': jwt_encode_handler({**payload, fresh=False})
}
class ObtainFreshJSONWebToken(JSONWebTokenAPIView):
serializer_class = JSONWebTokenSerializer
class NonFreshRefreshJSONWebToken(JSONWebTokenAPIView):
serializer_class = NonFreshRefreshJSONWebTokenSerializer
obtain_jwt_token = ObtainFreshJSONWebToken.as_view()
refresh_jwt_token = NonFreshRefreshJSONWebToken.as_view()
Then register these two views as API endpoints instead of those provided by the Django REST Framework JWT project, for the obtain and refresh paths.
Next up is the permission; because the JSONWebTokenAuthentication class returns the decoded payload when authenticating the request.auth attribute is set to the payload dictionary, letting us inspect it directly in a custom permission:
class HashFreshTokenPermission(permissions.BasePermission):
message = 'This endpoint requires a fresh token, please obtain a new token.'
def has_permission(self, request, view):
return (
request.user and
request.user.is_authenticated and
request.auth and
isinstance(request.auth, dict) and
request.auth.get('fresh', False)
)
Register this permission with the REST framework:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
'yourmodule.HashFreshTokenPermission',
),
# ...
}
and use this in views that are security sensitive:
class SampleSecureView(APIView):
permission_classes = (
permissions.IsAuthenticated,
HashFreshTokenPermission,
)
# ...

Setting cookies within Django Middleware

I would like to use Custom Django Middleware (Django 1.9) to check whether or not an anonymous user has accepted a site's terms & conditions - I'll show the user a dialog if they've not yet clicked 'Agree'.
I have no need to store this in the database and would prefer to simply use a Cookie. I have the following in settings.py:
MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
...
'myapp.middleware.app_custom_middleware.TermsMiddleware',]
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
I'm then trying something very rudimentary in TermsMiddleware - here I'm deliberately trying to modify the request and the response just to try to get it to work:
class TermsMiddleware(object):
def process_request(self, request):
request.session['myCookieKey'] = 'myCookieValue'
request.session.save()
return
def process_response(self, request, response):
request.session['myCookieKey'] = 'myCookieValue'
request.session.save()
return response
If I inspect the response in the browser, I see that myCookieKey isn't being set, so I think I've misunderstood how this should be written.
Can I manipulate the session within middleware like this, having it take effect in the cookie sent in the response Django was going to send anyway? The documentation makes it sound like I should be able to:
It should return either None or an HttpResponse object. If it returns
None, Django will continue processing this request, executing any
other process_request() middleware, then, process_view() middleware,
and finally, the appropriate view. If it returns an HttpResponse
object, Django won’t bother calling any other request, view or
exception middleware, or the appropriate view; it’ll apply response
middleware to that HttpResponse, and return the result.
I've also seen the note in the documentation to use SessionStore() when outside of a view function. In theory I don't think I should need to do that here because I've got access to request (and response). I tried it anyway, and I still couldn't get it to work.
Help much appreciated!
The thing you've misunderstood is the relationship between the session and the cookies.
You can't set arbitrary cookies by modifying the session. The session is a collection of data, usually stored in the db or a file, and the only cookie is the one that contains the session key for the current user.
You certainly can modify the session in middleware, but wherever you do so, you shouldn't expect to see a cookie being set.
(Separately, you should never call double underscore methods like __setitem__ directly. The session is accessed via a dict-like interface: request.session['foo'] = 'bar'.)

Django logout_then_login with next param

In some cases I want to logout user, redirect it to the login page with next param, so when user will succesfully logged in, Django should redirect user to the some page.
I've found logout_then_login() method but looks like it does not support next after login page param. I've tried to pass 'next' param in extra_content param, but it doesn't work.
How can I do this? Maybe there are some other django methods to do this?
Speaking generally, I just want to make forced logout, make user to re-login and redirect it to the page where it was logget out.
From the docs (Scroll down for this method)
logout_then_login(request[, login_url, current_app, extra_context])
Logs a user out, then redirects to the login page.
URL name: No default URL provided
Optional arguments:
login_url: The URL of the login page to redirect to. Defaults to settings.LOGIN_URL if not supplied.
current_app: A hint indicating which application contains the current view. See the namespaced URL resolution strategy for more information.
extra_context: A dictionary of context data that will be added to the default context data passed to the template.
Here we can see that the login method will accept a next parameter as a get variable.
Adding next to the login_url should do what you want:
from django.conf import settings
...
def myView(request):
login_url = "%s?next=%s" % (settings.LOGIN_URL, request.path)
logout_then_login(request, login_url=login_url)

Categories