I am trying to include a middleware in a Django project, but it seems the middleware is not being executed by Django. The idea is to impersonate another user account when having app-administrator privileges.
The MIDDLEWARE section of my settings.py file looks like this:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'mird_project.monitor.middleware.impersonateMiddleware.ImpersonateMiddleware',
]
The middleware class looks like this:
from .models import Usuario
class ImpersonateMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
return response
def process_request(self, request):
us_obj = Usuario.objects.all().filter(id_usuario=request.user.username).first()
tipo = us_obj.tipo.id_tipo
if tipo == "AD" and "__impersonate" in request.GET:
request.session['impersonate_id'] = request.GET["__impersonate"]
elif "__unimpersonate" in request.GET:
del request.session['impersonate_id']
if tipo == "AD" and 'impersonate_id' in request.session:
request.user = Usuario.objects.get(id_usuario=request.session['impersonate_id'])
return None
I inserted an assert False, request inside the process_request method so that it would abort execution with an exception and show me what request contained. It never even got executed, so I assume the middleware never gets executed. It doesn't throw any kind of error and the impersonation mechanism just displays the same administrator user in the site.
Any ideas why the middleware isn't being called?
It looks like you are mixing old and new style middleware APIs. The process_request() method is pre-Django 1.10 and won't get called automatically unless your middleware class uses MiddlewareMixin.
You'll need to call process_request() yourself from the __call__() method:
def __call__(self, request):
self.process_request(request) # Call process_request()
response = self.get_response(request)
return response
Or alternatively you could inherit from MiddlewareMixin so that process_request() is called by Django. However it would only make sense to do that if you need to make the middleware backwards compatible.
Related
I try to add the business attribute to the request object by using my own middleware, it nicely works with rest_framework.authentication.SessionAuthentication and I can use request.business in my views. But when I try to authenticate with JWT method (rest_framework_simplejwt.authentication.JWTAuthentication) when my middleware code is run request.user is set to AnonymouseUser so can't fetch business associated with user? Why did this happen?
# middleware.py
class MyMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
request.business = None
if request.user.is_authenticated and hasattr(request.user, 'business'):
request.business = request.user.business
response = self.get_response(request)
return response
Middlewares:
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware',
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'my_app.middleware.MyMiddleware',
]
rest_framework settings:
REST_FRAMEWORK = {
...
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
'rest_framework.authentication.SessionAuthentication',
],
}
Unfortunately, DEFAULT_AUTHENTICATION_CLASSES are not processed in middleware process, but in the view itself. What is giving you the proper user when using session is the Django's AuthenticationMiddleware and REST Framework is just using this value when the session authentication is enabled.
To solve that, you can do one of the following:
Move adding request.business to the views (for example by adding some class that all your views will inherit from)
Move JWT authentication into Django middlewares (this has a side effect of DRF enforcing CSRF checks by default when user is logged in)
I have a form with a username field on it located at /accounts/choose-username/.
views.py
from django.views.generic import FormView
from .forms import ChooseUsernameForm
from django.contrib.auth import get_user_model
User = get_user_model()
class ChooseUsernameView(FormView):
template_name = 'registration/choose-username.html'
form_class = ChooseUsernameForm
success_url = 'accounts/profile/' # <-------------
def form_valid(self, form):
"""If the form is valid, redirect to the supplied URL."""
print(form.cleaned_data['username'])
print(self.request.user)
user = User.objects.get(email=self.request.user)
user.username = form.cleaned_data['username']
user.save()
return super().form_valid(form)
middleware.py
from django.shortcuts import redirect, reverse
class SimpleMiddleware:
def __init__(self, get_response):
self.get_response = get_response
# One-time configuration and initialization.
def __call__(self, request):
# Code to be executed for each request before
# the view (and later middleware) are called.
response = self.get_response(request)
if request.user.is_authenticated:
if request.user.username is None:
print(request.path)
if not request.path == reverse('choose_username'):
return redirect(reverse('choose_username'))
return response
When I submit the form, my url is accounts/choose-username/accounts/profile/
I want it to be accounts/profile/
MIDDLEWARE settings.py looks like this:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'accounts.middleware.SimpleMiddleware', # <---
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
My Question
Why does my success URL get appended to my accounts/choose-username/ URL? How can I fix it?
As this—accounts/profile/—doesn't start with a slash (/), so it is a relative path. Browsers automatically append the relative path to the current page's url.
You need to use absolute path (add slash in the beginning).
success_url = '/accounts/profile/'
I want to have 2 sessions, one for my application (myapp.com) and one for the admin (myapp.com/admin). With this, I can have access to both in different tabs of my web client without logging in every time I want to use one of them. It is very irritating.
I have created a new session middleware to control that.
import time
from importlib import import_module
from django.conf import settings
from django.contrib.sessions.backends.base import UpdateError
from django.core.exceptions import SuspiciousOperation
from django.utils.cache import patch_vary_headers
from django.utils.deprecation import MiddlewareMixin
from django.utils.http import http_date
class AdminCookieSessionMiddleware(MiddlewareMixin):
def __init__(self, get_response=None):
self.get_response = get_response
engine = import_module(settings.SESSION_ENGINE)
self.SessionStore = engine.SessionStore
def cookie_name(self, request):
parts = request.path.split('/')
if len(parts) > 1 and parts[1].startswith('admin'):
return settings.ADMIN_SESSION_COOKIE_NAME
return settings.SESSION_COOKIE_NAME
def cookie_path(self, request):
parts = request.path.split('/')
if len(parts) > 1 and parts[1].startswith('admin'):
return settings.ADMIN_SESSION_COOKIE_PATH
return settings.SESSION_COOKIE_PATH
def process_request(self, request):
session_key = request.COOKIES.get(self.cookie_name(request))
request.session = self.SessionStore(session_key)
def process_response(self, request, response):
"""
If request.session was modified, or if the configuration is to save the
session every time, save the changes and set a session cookie or delete
the session cookie if the session has been emptied.
"""
try:
accessed = request.session.accessed
modified = request.session.modified
empty = request.session.is_empty()
except AttributeError:
pass
else:
# First check if we need to delete this cookie.
# The session should be deleted only if the session is entirely empty
if settings.SESSION_COOKIE_NAME in request.COOKIES and empty:
response.delete_cookie(
settings.SESSION_COOKIE_NAME,
path=settings.SESSION_COOKIE_PATH,
domain=settings.SESSION_COOKIE_DOMAIN,
)
else:
if accessed:
patch_vary_headers(response, ('Cookie',))
if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
if request.session.get_expire_at_browser_close():
max_age = None
expires = None
else:
max_age = request.session.get_expiry_age()
expires_time = time.time() + max_age
expires = http_date(expires_time)
# Save the session data and refresh the client cookie.
# Skip session save for 500 responses, refs #3881.
if response.status_code != 500:
try:
request.session.save()
except UpdateError:
raise SuspiciousOperation(
"The request's session was deleted before the "
"request completed. The user may have logged "
"out in a concurrent request, for example."
)
response.set_cookie(
self.cookie_name(request),
request.session.session_key, max_age=max_age,
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
path=settings.SESSION_COOKIE_PATH,
secure=settings.SESSION_COOKIE_SECURE or None,
httponly=settings.SESSION_COOKIE_HTTPONLY or None,
)
return response
In my settings file:
SESSION_COOKIE_NAME='usersessionid'
ADMIN_SESSION_COOKIE_NAME='adminsessionid'
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'core.utils.middleware.AdminCookieSessionMiddleware'
]
However, I have still the problem that if logging in one of the apps, i got automatically logged out of the other.
I was tracing the sessionkey and sometimes it is the same for both coockies. is that ok? If not, what should the problem be?
OK, I could fix it, therefore I post the answer so I can help somebody trying to do that as well. I copy the code from the django SessionMiddleware and added a cookie_name function. Inside I created the logic that should apply for my needs.
Remember to replace settings.SESSION_COOKIE_NAME for cookie_name.
import time
from importlib import import_module
from django.conf import settings
from django.contrib.sessions.backends.base import UpdateError
from django.core.exceptions import SuspiciousOperation
from django.utils.cache import patch_vary_headers
from django.utils.deprecation import MiddlewareMixin
from django.utils.http import http_date
class MySessionMiddleware(MiddlewareMixin):
def __init__(self, get_response=None):
self.get_response = get_response
engine = import_module(settings.SESSION_ENGINE)
self.SessionStore = engine.SessionStore
def cookie_name(self, request):
parts = request.path.split('/')
if settings.DEBUG:
if len(parts)>1 and parts[1].startswith('admin'):
return settings.ADMIN_SESSION_COOKIE_NAME
else:
if len(parts)>2 and parts[2].startswith('admin'):
return settings.ADMIN_SESSION_COOKIE_NAME
return settings.SESSION_COOKIE_NAME
def process_request(self, request):
session_key = request.COOKIES.get(self.cookie_name(request))
request.session = self.SessionStore(session_key)
def process_response(self, request, response):
"""
If request.session was modified, or if the configuration is to save the
session every time, save the changes and set a session cookie or delete
the session cookie if the session has been emptied.
"""
try:
accessed = request.session.accessed
modified = request.session.modified
empty = request.session.is_empty()
except AttributeError:
pass
else:
# First check if we need to delete this cookie.
# The session should be deleted only if the session is entirely empty
cookie_name = self.cookie_name(request)
if cookie_name in request.COOKIES and empty:
response.delete_cookie(
cookie_name,
path=settings.SESSION_COOKIE_PATH,
domain=settings.SESSION_COOKIE_DOMAIN,
)
else:
if accessed:
patch_vary_headers(response, ('Cookie',))
if (modified or settings.SESSION_SAVE_EVERY_REQUEST) and not empty:
if request.session.get_expire_at_browser_close():
max_age = None
expires = None
else:
max_age = request.session.get_expiry_age()
expires_time = time.time() + max_age
expires = http_date(expires_time)
# Save the session data and refresh the client cookie.
# Skip session save for 500 responses, refs #3881.
if response.status_code != 500:
try:
request.session.save()
except UpdateError:
raise SuspiciousOperation(
"The request's session was deleted before the "
"request completed. The user may have logged "
"out in a concurrent request, for example."
)
response.set_cookie(
cookie_name,
request.session.session_key, max_age=max_age,
expires=expires, domain=settings.SESSION_COOKIE_DOMAIN,
path=settings.SESSION_COOKIE_PATH,
secure=settings.SESSION_COOKIE_SECURE or None,
httponly=settings.SESSION_COOKIE_HTTPONLY or None,
)
return response
And this is very important: in settings, in the MIDDELWARE list, you must add it but also remove the SessionMiddleware per default. Insert it in the same position where the normal SessionMiddleware was. The idea is to put it before 'django.contrib.auth.middleware.AuthenticationMiddleware', otherwise, you will get an error.
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
#'django.contrib.sessions.middleware.SessionMiddleware',
'core.utils.middleware.MySessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
I'm looking at a solution of implementing a modal pop-up login form to be used site wide in the navigation bar. I'm working with Django 1.11. Now, I'm defining a LoginFormMiddleware class as follows:
middleware.py
from django.http import HttpResponseRedirect
from django.contrib.auth import login
from django.contrib.auth.forms import AuthenticationForm
class LoginFormMiddleware(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
return self.get_response(request)
def process_request(self, request):
from django.contrib.auth.forms import AuthenticationForm
if request.method == 'POST' and request.POST.has_key('base-account') and request.POST['base-account'] == 'Login':
form = AuthenticationForm(data=request.POST, prefix="login")
if form.is_valid():
from django.contrib.auth import login
login(request, form.get_user())
request.method = 'GET'
else:
form = AuthenticationForm(request, prefix="login")
request.login_form = form
This is all included in the standard way in settings.py in MIDDLEWARE = []. I also have 'django.template.context_processors.request' defined in TEMPLATES = [ { ... 'OPTIONS': { 'context_processors': [ *** ] } ] as I should.
Using {{ request.login_form }} in my templates as follows:
<div class="container">
{{ request.login_form }}
</div>
Yet nothing is being rendered - where it should? From browser inspector tools:
Can anyone advise me on what I am missing please?
Full middleware list:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'wagtail.wagtailcore.middleware.SiteMiddleware',
'wagtail.wagtailredirects.middleware.RedirectMiddleware',
'thinkingplace.middleware.LoginFormMiddleware',
]
process_request is from the old-style, pre-Django 1.10 middleware. You are using new-style middleware so Django will not call that method.
You could move that code to the __call__ method before you call get_response.
def __call__(self, request):
# code that sets request.login_form
return self.get_response(request)
However, I’m not sure that using a middleware is the best approach here. I would consider creating a context processor that adds the login form to the template context, then submit login requests to a single login view.
This issue is rather strange. Here's a stack trace:
Environment:
Request Method: GET
Request URL: http://localhost/en/dashboard
Django Version: 1.9.6
Python Version: 2.7.12
Installed Applications:
('django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.admin',
'django_mongoengine',
'django_mongoengine.mongo_auth',
'django_mongoengine.mongo_admin.sites',
'django_mongoengine.mongo_admin',
'django_adyen',
'spiral_api',
'rest_framework',
'rest_framework_mongoengine',
'spiral_admin',
'rest_framework_docs')
Installed Middleware:
('django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'spiral_api.middlewares.BehalfUserMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'spiral_api.middlewares.AuthHeaderMiddleware')
Traceback:
File "/usr/local/lib/python2.7/site-packages/django/core/handlers/base.py" in get_response
235. response = middleware_method(request, response)
File "/code/django_app/spiral_api/middlewares.py" in process_response
11. def process_response(self, request, response):
File "/usr/local/lib/python2.7/site-packages/django/utils/functional.py" in inner
204. self._setup()
File "/usr/local/lib/python2.7/site-packages/django/utils/functional.py" in _setup
351. self._wrapped = self._setupfunc()
File "/code/django_app/spiral_api/middlewares.py" in get_user
18. user = get_user_middleware(request)
Exception Type: TypeError at /en/dashboard
Exception Value: unbound method is_authenticated() must be called with AbstractBaseUser instance as first argument (got nothing instead)
Here's a middleware:
from functools import partial
from django.contrib.auth.middleware import get_user as get_user_middleware
from django.contrib.auth.models import AbstractBaseUser
from django.utils.functional import SimpleLazyObject
from spiral_api.models import SpiralUserProfile
class AuthHeaderMiddleware(object):
def process_response(self, request, response):
response['is_login'] = int(request.user.is_active) if hasattr(request, 'user') else 0
return response
class BehalfUserMiddleware(object):
def get_user(self, request):
user = get_user_middleware(request)
if user.is_authenticated():
profile = SpiralUserProfile.objects.get(user=user)
return profile.behalf or user
else:
return user
def process_request(self, request):
assert hasattr(request, 'session'), (
"The Django authentication middleware requires session middleware "
"to be installed. Edit your MIDDLEWARE_CLASSES setting to insert "
"'django.contrib.sessions.middleware.SessionMiddleware' before "
"'django.contrib.auth.middleware.AuthenticationMiddleware'."
)
request.user = SimpleLazyObject(partial(self.get_user, request))
request.behalf = SimpleLazyObject(lambda: get_user_middleware(request))
Sadly project is not mine. From exception you see it points to get_user_middleware() function. I've tried to add print in there, also as sys.exit(), nothing works.. it seems to be it doesn't even come to that function. Here's how middleware setup looks in Settings:
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
# 'django.contrib.auth.middleware.AuthenticationMiddleware',
'spiral_api.middlewares.BehalfUserMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'spiral_api.middlewares.AuthHeaderMiddleware'
)
Also a model SpiralUserProfile has a field behalf which is reference to model User. I'm completely puzzled why exception shows error in a place where interpreter doesn't even come to? How to solve this issue?
Versions of requirements.txt:
nose==1.3.7
pinocchio==0.4.2
django_nose==1.4.4
bjoern==1.4.3
amqp==1.4.9
anyjson==0.3.3
apiclient==1.0.3
billiard==3.3.0.23
blinker==1.4
build==1.0.2
celery==3.1.23
Django==1.9.6
django-bootstrap3==7.0.1
django-crispy-forms==1.6.0
django-fanout==1.1.1
django-filter==0.13.0
git+https://github.com/MongoEngine/django-mongoengine
django-redis==4.4.3
django-rest-framework-mongoengine==3.3.0
djangorestframework==3.3.3
drfdocs==0.0.9
EasyProcess==0.2.2
fanout==1.2.0
git+http://github.com/google/google-api-python-client/
gunicorn==19.3.0
httplib2==0.9.2
kombu==3.0.35
mongodbforms==0.3
mongoengine==0.10.6
nltk==3.2.1
oauth2==1.9.0.post1
oauth2client==2.1.0
oauthlib==1.1.2
Pillow==3.2.0
psycopg2==2.6
pubcontrol==2.2.7
pyasn1==0.1.9
pyasn1-modules==0.0.8
PyInvoice==0.1.7
PyJWT==1.4.0
pymongo==3.2.2
python-openid==2.2.5
pytz==2016.4
PyVirtualDisplay==0.2
qrcode==5.3
qrplatba==0.3.4
redis==2.10.5
reportlab==3.3.0
requests==2.10.0
requests-oauthlib==0.6.1
rsa==3.4.2
selenium==2.53.2
simplejson==3.8.2
six==1.10.0
uritemplate==0.6
urllib3==1.15.1
xvfbwrapper==0.2.8
zope.dottedname==4.1.0
pyPdf
I am not sure how this ever worked, but I can see why you'd get that error. In django-mongoengine/mongo_auth/models.py I see this:
class BaseUser(object):
is_anonymous = AbstractBaseUser.is_anonymous
is_authenticated = AbstractBaseUser.is_authenticated
class User(BaseUser, document.Document):
...
which seems to be the source of the error.
You can make it work by modifying that library so that the User class implements those methods directly:
class User(document.Document):
...
def is_anonymous(self):
"""
Always returns False. This is a way of comparing User objects to
anonymous users.
"""
return False
def is_authenticated(self):
"""
Always return True. This is a way to tell if the user has been
authenticated in templates.
"""
return True