Django Update Middleware to replace decorator - django

I have the following Decorator which works fine when applied to different views with: #otp_required(login_url='login') on my site:
Decorator
from django.contrib.auth.decorators import user_passes_test
from django_otp import user_has_device
from django_otp.conf import settings
def otp_required(view=None, redirect_field_name='next', login_url=None, if_configured=False):
"""
Similar to :func:`~django.contrib.auth.decorators.login_required`, but
requires the user to be :term:`verified`. By default, this redirects users
to :setting:`OTP_LOGIN_URL`.
:param if_configured: If ``True``, an authenticated user with no confirmed
OTP devices will be allowed. Default is ``False``.
:type if_configured: bool
"""
if login_url is None:
login_url = settings.OTP_LOGIN_URL
def test(user):
return user.is_verified() or (if_configured and user.is_authenticated and not user_has_device(user))
decorator = user_passes_test(test, login_url=login_url, redirect_field_name=redirect_field_name)
return decorator if (view is None) else decorator(view)
However, I’d like to convert this into a Middleware as I want to avoid having to apply a decorator to every view on my site, but not managed to get working.
I tried to amend the following Middleware which I currently have in place which is just for authorised users and has been working but as per above decorator I want this Middleware extended to also have OTP required as well:
Middleware
from django.utils.deprecation import MiddlewareMixin
from django.urls import resolve, reverse
from django.http import HttpResponseRedirect
from wfi_workflow import settings
from django_otp import user_has_device
from django_otp.decorators import otp_required
from django_otp.middleware import is_verified
class LoginRequiredMiddleware(MiddlewareMixin):
"""
Middleware that requires a user to be authenticated to view any page other
than LOGIN_URL. Exemptions to this requirement can optionally be specified
in settings by setting a tuple of routes to ignore
"""
##otp_required(login_url='login')
def process_request(self, request):
assert hasattr(request, 'user'), """
The Login Required middleware needs to be after AuthenticationMiddleware.
Also make sure to include the template context_processor:
'django.contrib.account.context_processors.account'."""
if not request.user.is_verified() and not request.path.startswith('/admin/') and not request.path.startswith('/account/' ):
current_route_name = resolve(request.path_info).url_name
if not current_route_name in settings.AUTH_EXEMPT_ROUTES:
return HttpResponseRedirect(reverse(settings.LOGIN_URL))
Help is much appreciated.

The fact that you return a HttpResponseRedirect will not work: Django's MiddlewareMixin will simply call the function to (optionally) alter the request, but it will never take the return into account.
What you can do is define middleware in a decorator-like structure, and return the HttpResponseRedirect in case the user should be authenticated with:
from django.urls import resolve, reverse
from django.http import HttpResponseRedirect
from wfi_workflow import settings
def OTPRequiredMiddleware(get_response):
"""
Middleware that requires a user to be authenticated to view any page other
than LOGIN_URL. Exemptions to this requirement can optionally be specified
in settings by setting a tuple of routes to ignore
"""
def middleware(request):
from django_otp import user_has_device
if not user.is_verified() and not (if_configured and user.is_authenticated and not user_has_device(user)):
return HttpResponseRedirect(settings.OTP_LOGIN_URL)
return get_response(request)

Related

How to restrict non staff users from accessing django-admin

My django-admin page is located at
http://127.0.0.1:8000/admin/
Suppose there are 3 users in my website.
Superuser
Staff
End user
If anyone of these three users tries to access this link http://127.0.0.1:8000/admin/, 1st and 2nd can access it.
And End User is not able to access it. Till this, it is fine.
What I want is to do is to show Error 404 to End User. He/She should never know that this link is for admin page. Moreover, if anyone is not logged in, they should also see same 404 Page.
I have referred this link but it takes me to another default page.
PFA for the same
PS: I am using Signin With Google, so it should not redirect me there.
I am trying this since 3 continous days, so any help is highly appreciated. Thanks in advance.
Django-Version: 3. 0. 5
You first need to make a custom decorator that would give a 404 if the user is not a staff:
from django.http import Http404
from functools import wraps
def staff_required(func):
#wraps(func)
def wrapper(request, *args, **kwargs):
if request.user.is_staff:
return func(request, *args, **kwargs)
raise Http404()
return wrapper
Next we will use this decorator as described in your linked question,:
from django.contrib import admin
admin.site.login = staff_required(admin.site.login)
urlpatterns = [
path('admin/', admin.site.urls),
]
Edit: Above method was a bit hacky it would show the login pages url to the user even if it gives a 404 error. It would be better to make a custom admin site class and use it. The admin site has a method admin_view which decorates the admin views which we shall override. We shall do this in the file admin.py in the projects main app (let's say the project is named myproject)
from functools import update_wrapper
from django.contrib import admin
from django.http import (
Http404, HttpResponseRedirect,
)
from django.urls import reverse
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
class MyAdminSite(admin.AdminSite):
def admin_view(self, view, cacheable=False):
def inner(request, *args, **kwargs):
if not self.has_permission(request):
if request.path == reverse('admin:logout', current_app=self.name):
index_path = reverse('admin:index', current_app=self.name)
return HttpResponseRedirect(index_path)
raise Http404()
return view(request, *args, **kwargs)
if not cacheable:
inner = never_cache(inner)
# We add csrf_protect here so this function can be used as a utility
# function for any view, without having to repeat 'csrf_protect'.
if not getattr(view, 'csrf_exempt', False):
inner = csrf_protect(inner)
return update_wrapper(inner, view)
Now we need to replace the default admin site with our custom admin site. To do this we will follow Overriding the default admin site [Django docs]:
In the projects main app's apps.py file (assuming project to be named myproject):
from django.contrib.admin.apps import AdminConfig
class MyAdminConfig(AdminConfig):
default_site = 'myproject.admin.MyAdminSite'
Now in settings.py we will replace 'django.contrib.admin' with our own config class:
INSTALLED_APPS = [
...
'myproject.apps.MyAdminConfig', # replaces 'django.contrib.admin'
...
]
A simpler way is to create your own middleware that whenever the path starts with /admin/ and the user is logged in but doesn't have is_staff set to True (both staff and superusers have it), then raise the 404.
from django.http import Http404
class NoDjangoAdminForEndUserMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.path.startswith("/admin/"):
if request.user.is_authenticated and not request.user.is_staff:
raise Http404()
response = self.get_response(request)
return response
Then, to activate it, add it to the MIDDLEWARE list in your Django settings.
You'll still see the URL with http://127.0.0.1:8000/admin/, despite seeing a 404 page. If you want to redirect the user to a different page, instead of raising the Http404(), then return an HttpResponseRedirect().

how can i overide django allauth signup success_url

I'm working on a project using allauth and i'm using customer user model and i wan the newly registered user to be redirected to a different page (say profile form page) which will be totally different from the login_redirect_url, I have tried it this way
any idea how i can make this work pls?
from django.shortcuts import get_object_or_404, redirect, render
from allauth.account.views import LogoutView
from django.urls import reverse_lazy
from allauth.account.views import SignupView
from django.views.generic import TemplateView
from .models import CustomUser
class Signup(SignupView):
success_url = reverse_lazy('business:company_profile')
def get_success_url(self):
return self.success_url
I am not sure there is way to override SignUp redirection since when you sign up in the application, you also sign in, which will use the login_redirect_url.
If you overrode login_redirect_url (documentation) you can update your logic and redirect the user to his profile if some fields are missing/empty?
def get_login_redirect_url(self, request):
if not request.user.your_custom_field:
path = "/accounts/{username}/"
return path.format(username=request.user.username)
else
return "/"
You could also implement another logic by adding a bool is_first_visit on your CustomerUser model (with default=True) and set it to False after his first visit.
Is the code that you proposed not working? What errors does it produce?
On first glance, the view that you've proposed should work. You would just have to make sure it's being used in "urls.py".

How to get permission associated with view programmatically

In Django I have a function that provides a list of all URL Patterns for my system. I am attempting to create a search feature that shows users links to a list of urls that they have permissions to view, but I cannot seem to figure out how to grab the associated permission from the view function.
How can I do this?
Here is my code so far:
def get_url_patterns():
from django.apps import apps
list_of_all_urls = list()
for name, app in apps.app_configs.items():
mod_to_import = f'apps.{name}.urls'
try:
urls = getattr(importlib.import_module(mod_to_import), "urlpatterns")
list_of_all_urls.extend(urls)
except ImportError as ex:
# is an app without urls
pass
for thing in list_of_all_urls:
# print(type(thing))
# print(type(thing.callback.__name__))
print(thing.callback.__dict__)
return list_of_all_urls
Hey there are many mays to do it
Use already build decorator
from django.contrib.auth.decorators import login_required,user_passes_test
User it as
#user_passes_test(lambda u: Model.objects.get(condition) in queryset)
You can make your own decorator
from django.contrib.auth.decorators import login_required, user_passes_test
user_login_required = user_passes_test(lambda user: user.is_active, login_url='/')
def active_user_required(view_func):
decorated_view_func = login_required(user_login_required(view_func))
return decorated_view_func
#active_user_required
def index(request):
return render(request, 'index.html')
List item

DefaultAccountAdapter and DefaultSocialAccountAdapter?

I tried the following to specific different signup flows for users who sign up via social accounts (facebook) and those that sign up via traditional login.
from django.conf import settings
from allauth.account.adapter import DefaultAccountAdapter
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
class NormalAdapter(DefaultSocialAccountAdapter):
def get_login_redirect_url(self, request):
if request.user.last_login == request.user.date_joined:
return 'survey/'
else:
return '/results/'
class CorporateAdapter(DefaultAccountAdapter):
def get_login_redirect_url(self, request):
if request.user.last_login == request.user.date_joined:
return 'corporate/survey/'
else:
return 'corporate/results/'
But even if you log in with facebook, it calls DefaultAccountAdapter's get_login_redirect_url instead of DefaultSocialAccountAdapter's.
Point the all auth plugin to the correct adapter classes! Add the following settings to your django app:
# project/settings.py:
ACCOUNT_ADAPTER = 'python.path.to.your.CorporateAdapter'
SOCIALACCOUNT_ADAPTER ='python.path.to.your.NormalAdapter'
Then save and reload the app (if you are on apache, just restart apache)

Django: "How to get the slug from url() before passing it to MyClass.as_view()?" or "How to check if registration is open?"

I want to check if a user is allowed to register for a specific event. I thought in order to save code I could do it like the login_required decorator right between the url() and MyClass.as_view(). But how do I get that slug? Or is this solution totally wrong? (I unfortunatelly can't use the user_passes_test because I don't want to test someting on the user but on the url.)
So I tryed this:
views.py
from registration.models import Event
from django.utils import timezone
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
def reg_is_open(event_slug):
"""
Return True if registration is open.
"""
event = get_object_or_404(Event, slug=event_slug)
if event.open_date <= timezone.now() and event.cut_off >= timezone.now():
return True
def allow_view(cls, **initkwargs):
"""
Check weather registration is open and user is logged in.
Returns to registration start page if registration is closed.
"""
slug = initkwargs.get('event') # Does not work!
if not reg_is_open(slug):
return HttpResponseRedirect(reverse('registration:event_index', args=slug))
return login_required(cls.as_view(**initkwargs))
# Also works when I remove **initkwargs. That means that what I'm looking for just passes...
urls.py
from django.conf.urls import patterns, url, include
from registration import views
event_patterns = patterns('',
url(r'^person/$', views.allow_view(views.PersonList), name='person_list'),
# instead of
# url(r'^person/$', login_required(views.PersonList.as_view()), name='person_list'),
# ...
urlpatterns = patterns('',
url(r'^(?P<event>[-a-zA-Z0-9_]+)/$', views.EventDetails.as_view(), name='event_index'),
url(r'^(?P<event>[-a-zA-Z0-9_]+)/', include(event_patterns)),
# ...
If I understand correctly, your view is a DetailView of the event, correct? In that case, use something along the lines of:
class EventDetailView(DetailView):
model = Event
def dispatch(self, request, *args, **kwargs):
if self.request.user not in self.object.registered_users.all():
return HttpResponseForbidden()
else:
return super(EventDetailView, self).dispatch(request, *args, **kwargs)
This assumes that you have a M2M key between User and Event models, called registered_users on the Event side; change the code to fit your situation.