How to get permission associated with view programmatically - django

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

Related

Django Update Middleware to replace decorator

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)

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 to restrict website users from seeing other user profiles?

I created some users for my website in Django and i want each user to access only his own profile page .
The template for the user profile page is fetched through a CBV Detail View called UserDetailView attached to a URL containing the user's and the page is loading only after authentication .( user logged in). So far so good.
urls.py:
from django.conf.urls import url
from django.urls import path
from basicapp import views
from django.contrib.auth.decorators import login_required
app_name='basicapp'
urlpatterns = [
url(r'^$',views.index,name='index'),
url(r'^user_list/',views.UserView.as_view(),name='user_list'),
url(r'^course_list/',views.CourseView.as_view(),name='course_list'),
url(r'^user_detail/(?P<pk>[-\w]+)/$',views.UserDetailView.as_view(),name='user_detail'),
]
The problem is after I login and get the user detail page : If I manually change the <pk> in the URL I get other user profile pages loaded. I don't want that to happen .
For ex, the URL for the logged in user profile is :
http://127.0.0.1:8000/basicapp/user_detail/1/
With the user already logged in i manually change the URL to :
http://127.0.0.1:8000/basicapp/user_detail/2/
and it works. It should retrict me or show me an error message
I tried using LoginRequiredMixin
views.py:
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.utils.decorators import method_decorator
class UserDetailView(LoginRequiredMixin,DetailView):
context_object_name='user_detail'
model=models.User
template_name='basicapp/user_detail.html'
raise_exception = True # Raise exception when no access instead of redirect
permission_denied_message = "This page dows not exist."
and I also tried usingmethod_decorator :
#method_decorator(login_required)
class UserDetailView(LoginRequiredMixin,DetailView):
context_object_name='user_detail'
model=models.User
template_name='basicapp/user_detail.html'
raise_exception = True # Raise exception when no access instead of redirect
permission_denied_message = "This page dows not exist."
but it doesn't seem to work . I restarted the server.
Any ideas what i am doing wrong?
The LoginRequiredMixin will ensure that you can only see the page if you are logged in, but that does not mean you have to be that user.
However if you can only see your own profile, it does not make much sense to add a primary key in the url anyway, you can just define the url as:
url(r'^user_detail/$', views.UserDetailView.as_view(), name='user_detail'),
In the view you then return the logged in user for the .get_object() method [Django-doc]:
class UserDetailView(LoginRequiredMixin,DetailView):
context_object_name='user_detail'
model=models.User
template_name='basicapp/user_detail.html'
def get_object(self, *args, **kwargs):
return self.request.user
Or you can restrict users by filtering the queryset:
path('^user_detail/<int:pk>/', views.UserDetailView.as_view(), name='user_detail'),
class UserDetailView(LoginRequiredMixin,DetailView):
context_object_name='user_detail'
model=models.User
template_name='basicapp/user_detail.html'
def get_queryset(self, *args, **kwargs):
qs = super().get_queryset(*args, **kwargs)
if not self.request.user.is_superuser:
qs = qs.filter(pk=self.request.user.pk)
return qs

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.

What is the opposite of #login_required decorator for Django views?

If I want to make sure that a view is listed as having public access, is there a decorator equivalent to #public_access which would be the opposite of #login_required and make it clear that the view should be publicly accessible always?
One use case I have in mind is to automatically add "#csrf_exempt" to all public views in addition to making it clear in the code that the view should be publicly accessible.
Unfortunately, there's currently no built-in support for this in Django, leaving you at risk of exposing sensitive info when #login_required is accidentally forgotten.
Here's a solution from one of my projects:
middleware/security.py:
def public(function):
"""
Decorator for public views that do not require authentication
"""
orig_func = function
while isinstance(orig_func, partial): # if partial - use original function for authorization
orig_func = orig_func.func
orig_func.is_public_view = True
return function
def is_public(function):
try: # cache is found
return function.is_public_view
except AttributeError: # cache is not found
result = function.__module__.startswith('django.') and not function.__module__.startswith('django.views.generic') # Avoid modifying admin and other built-in views
try: # try to recreate cache
function.is_public_view = result
except AttributeError:
pass
return result
class NonpublicMiddleware(object):
def process_view_check_logged(self, request, view_func, view_args, view_kwargs):
return
def process_view(self, request, view_func, view_args, view_kwargs):
while isinstance(view_func, partial): # if partial - use original function for authorization
view_func = view_func.func
request.public = is_public(view_func)
if not is_public(view_func):
if request.user.is_authenticated(): # only extended checks are needed
return self.process_view_check_logged(request, view_func, view_args, view_kwargs)
return self.redirect_to_login(request.get_full_path()) # => login page
def redirect_to_login(self, original_target, login_url=settings.LOGIN_URL):
return HttpResponseRedirect("%s?%s=%s" % (login_url, REDIRECT_FIELD_NAME, urlquote(original_target)))
settings.py:
MIDDLEWARE_CLASSES = (
#...
'middleware.security.NonpublicProfilefullMiddleware',
#...
)
and, finally, view code:
from <projname>.middleware import publi
#public
def some_view(request):
#...
# Login required is added automatically
def some_private_view(request):
#...
Also, you may want to look at "Automatically decorating all views of a django project" blog post
As a previous poster mentioned, login not required is the default.
However, sometimes it's useful to block certain views from logged in users -- for instance, it makes no sense for a logged-in user to be able to use the site's signup page. In that case, you could do something like this, based off the existing login_required decorator
from django.contrib.auth.decorators import user_passes_test
from django.conf import settings
LOGGED_IN_HOME = settings.LOGGED_IN_HOME
def login_forbidden(function=None, redirect_field_name=None, redirect_to=LOGGED_IN_HOME):
"""
Decorator for views that checks that the user is NOT logged in, redirecting
to the homepage if necessary.
"""
actual_decorator = user_passes_test(
lambda u: not u.is_authenticated(),
login_url=redirect_to,
redirect_field_name=redirect_field_name
)
if function:
return actual_decorator(function)
return actual_decorator
A bit late, but another simple way to tackle this issue would be to rely on another decorator and add a lambda function:
from django.contrib.auth.decorators import user_passes_test
#user_passes_test(lambda u: u.is_anonymous)
You can use user_passes_test and add a requirement, anonymous_required. This would work like the opposite to login_required and you can use on your register and login page - it is a bit irritating for users to still see the login page, after the are logged in.
This is how you would do it:
from django.contrib.auth.decorators import user_passes_test
#Anonymous required
def anonymous_required(function=None, redirect_url=None):
if not redirect_url:
redirect_url = settings.LOGIN_REDIRECT_URL
actual_decorator = user_passes_test(
lambda u: u.is_anonymous,
login_url=redirect_url
)
if function:
return actual_decorator(function)
return actual_decorator
#Application of the Decorator
#anonymous_required
def register(request):
#Your code goes here
"Login not required" is the default. If you want to annotate that a view should never be login-restricted then you should do so in the docstring.
I use django-decorator-include to use the login_required decorator to secure an entire app, rather than one view at a time. My app's main urls.py looks like this:
path('my_secret_app/', decorator_include(login_required, ('my_secret_app.urls', 'my_secret_app'))),
This works great, except for when one of my apps has one view which needs to be public.
To get around this, I wrote my own login_required and login_not_required. My login_required is based on django's django.contrib.auth.decorators.login_required, but is slightly modified to actually care when a view is marked as not requiring login.
My project is called mysite.
My app is called my_secret_app.
My public view within my_secret_app is called MyPublicView.
My entire solution looks like this:
mysite/lib.py
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.decorators import user_passes_test
# A copy of django.contrib.auth.decorators.login_required that looks for login_not_required attr
def login_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
actual_decorator = user_passes_test(
lambda u: u.is_authenticated,
login_url=login_url,
redirect_field_name=redirect_field_name
)
if function:
login_req = getattr(function, "login_required", True)
if login_req:
return actual_decorator(function)
else:
return function
else:
return actual_decorator
# Decorator to mark a view as not requiring login to access
def login_not_required(f):
f.login_required = False
return f
mysite/urls.py
from .lib import login_required
path('my_secret_app/', decorator_include(login_required, ('my_secret_app.urls', 'my_secret_app'))),
my_secret_app/views.py:
from django.utils.decorators import method_decorator
from mysite.lib import login_not_required
class MyPublicView(View):
#method_decorator(login_not_required)
def dispatch(self, request, *args, **kwargs):
return super().dispatch(request, *args, **kwargs)
def get(self, request):
...
def post(self, request, *args, **kwargs):
...
You should be able to do the same thing no matter if you're subclassing View, ListView, CreateView, UpdateView, TemplateView, or any of the other ones. It should also work if you're using a function as your view, though I haven't tried it:
#login_not_required
def MyPublicView(request):
...
#permission_classes([permissions.AllowAny])