Django permissions checks at urlconf level? - django

I've got a django app that has it's own urlconf included into the main one. Every page in this app is protected by a separate set of perms not granted to normal users. Think employees work view as opposed to users' profiles etc.
I'm using classed based views, so right now I've got the landing view's dispatch() checking perms, but with this method I'm going to have to do that for every view. That just isn't DRY.
So my options as I see them are:
Create a mixin that specifically checks for this permission
manually check using dispatch() in each view
Somehow check at the url level
Is there any way to set a permission requirement on the entire url inclusion? They all currently have login_required() on.

Easy!
from django.contrib.auth.decorators import login_required
urlpatterns = patterns('',
url(r'^foo/', login_required(include('foo.urls'))),
)
Update
You want to check user permissions, not user authentication. Easy too:
from django.contrib.auth.decorators import user_passes_test
urlpatterns = patterns('',
url(r'^foo/', user_passes_test(lambda u: u.has_perm('perm'))(include('foo.urls'))),
)

Related

Custom login URL in django

I am a newbie in django and I was experting different options in Django. I have created a class based view which requires user authentication to view the web page. I am using the inbuilt LoginView.
When the url pattern is specified as follows
url(r'^login/', auth_views.LoginView.as_view(),name='login'),
it is correctly redirected to login page.
But when I give
url(r'^restaurant/login/', auth_views.LoginView.as_view(),name='login'),
I get a 404 when trying to access the page that requires user authentication.
But when I manually go to that url in browser, it works perfectly fine.
Why is that? Shouldn't it both cases work?
It sounds like you need to set LOGIN_URL in your settings:
LOGIN_URL = '/restaurant/login/'
or, it's better to use the URL pattern name, then you don't have to update your settings when you change the login URL
LOGIN_URL = 'login'
Not sure if I'd fully understand your question, just try to give a stupid answer.
Django 2.1.7
use namespace&url name in settings, if you have your own login view just change admin to your url namespace and name your view as 'login'
# settings.py
LOGIN_URL = 'admin:login'
then the login_required decorator will direct you the correct login page.
from django.contrib.auth.decorators import login_required
#login_required()
def month_archive(request, year, month):
production_list = Production.objects.month_archive(year, month)
context = {'production_list': production_list}
return TemplateResponse(request, 'production/production_list.html', context)
If it's a Class Based View, add decorator to urls.py
from django.contrib.auth.decorators import login_required
urlpatterns = [
path('', login_required(views.ProductionList.as_view()), name='production-list'),
path('<int:year>/<int:month>/', views.month_archive, name='production-month'),
]

django-two-factor-auth can't access admin site

I am using django-two-factor-auth for a webapp. I cannot access the admin page.
I know I am entering the correct credentials. When I input incorrect credentials, I get an appropriate error message.
When I input the correct credentials, the page simply reloads with this URL:
http://localhost:8080/account/login/?next=/inveskore/
These are my settings related to two_factor:
LOGIN_URL = 'two_factor:login'
LOGIN_REDIRECT_URL = '/inveskore'
TWO_FACTOR_SMS_GATEWAY = 'two_factor.gateways.twilio.gateway.Twilio'
This is the associated URL path:
path('admin/', admin.site.urls),
According to this, it results from the admin user not having 2FA set.
So, how do you set 2FA for the admin user if you can't access the site?
EDIT:
I took down the 2FA login requirements for the site and then added a phone device. No luck.
I recently ran into this scenario and created this solution based on a comment there:
https://github.com/Bouke/django-two-factor-auth/issues/219#issuecomment-494382380
I subclassed AdminSiteOTPRequired and then specified it as the admin class to use
from django.conf import settings
from django.contrib.auth import REDIRECT_FIELD_NAME
from django.contrib.auth.views import redirect_to_login
from django.http import HttpResponseRedirect
from django.shortcuts import resolve_url
from django.urls import reverse
from django.utils.http import is_safe_url
from two_factor.admin import AdminSiteOTPRequired, AdminSiteOTPRequiredMixin
class AdminSiteOTPRequiredMixinRedirSetup(AdminSiteOTPRequired):
def login(self, request, extra_context=None):
redirect_to = request.POST.get(
REDIRECT_FIELD_NAME, request.GET.get(REDIRECT_FIELD_NAME)
)
# For users not yet verified the AdminSiteOTPRequired.has_permission
# will fail. So use the standard admin has_permission check:
# (is_active and is_staff) and then check for verification.
# Go to index if they pass, otherwise make them setup OTP device.
if request.method == "GET" and super(
AdminSiteOTPRequiredMixin, self
).has_permission(request):
# Already logged-in and verified by OTP
if request.user.is_verified():
# User has permission
index_path = reverse("admin:index", current_app=self.name)
else:
# User has permission but no OTP set:
index_path = reverse("two_factor:setup", current_app=self.name)
return HttpResponseRedirect(index_path)
if not redirect_to or not is_safe_url(
url=redirect_to, allowed_hosts=[request.get_host()]
):
redirect_to = resolve_url(settings.LOGIN_REDIRECT_URL)
return redirect_to_login(redirect_to)
Then in urls.py:
from django.contrib import admin
admin.site.__class__ = AdminSiteOTPRequiredMixinRedirSetup
I cannot comment yet but the answer above by #saschwarz worked like a charm with Django 2.2 and django-two-factor-auth. The only thing missing from his code is the additional import:
from django.http import HttpResponseRedirect
After that I was able to use this line:
admin.site.__class__ = AdminSiteOTPRequiredMixinRedirSetup
to quickly enforce two factor for admin users.
This is really missing from the docs for django-two-factor-auth... I looked at the documentation for quite a while and couldn't find a clear way to generate a qr code. It's only really demonstrated in the demo app, and not in a very modular way.
The solution from saschewarz didn't solve the problem in my app.
So I found another one where standard admin login is shown.
django-two-factor-auth by default patch your admin urls with function 'patch_admin' from two_factor\admin.py so you always will see your standard site login for admin login.
To solve this you can comment out 2 functions in two_factor\admin.py
def patch_admin()
and
def unpatch_admin()
And comment out in two_factor\apps.py
def ready(self)
And to use two factor authentication in admin site add this code in main urls.py (where is path to admin):
from django_otp.admin import OTPAdminSite
admin.site.__class__ = OTPAdminSite

How to override wagtail authentication?

When I attempt to access my wagtail back-office at /cms/, I get redirected to wagtail's login page, /cms/login/.
However, I would like to use my own custom login, which is default for the rest of the site, and sits at /auth/.
My LOGIN_URL is already set to /auth/ in django settings.
EDIT : it's been suggested that this is a generic question of how do you override namespaced url patterns but this is not the case. The urls are not namespaced, and I was looking for wagtail functionality that adressed this specific issue. Fortunately, that functionality does exist.
WAGTAIL_FRONTEND_LOGIN_URL suggested above is specifically intended just for front end users and there is not an equivalent setting for admin users. You could use redirect_to_login like so:
from django.contrib.auth.views import redirect_to_login
from django.urls import reverse
from wagtail.admin import urls as wagtailadmin_urls
def redirect_to_my_auth(request):
return redirect_to_login(reverse('wagtailadmin_home'), login_url='myauth:login')
urlpatterns = [
url(r'^cms/login', redirect_to_my_auth, name='wagtailadmin_login'),
url(r'^cms/', include(wagtailadmin_urls)),
]
The Wagtail setting WAGTAIL_FRONTEND_LOGIN_URL allows you to configure how users login to the Wagtail admin.
From http://docs.wagtail.io/en/v1.10.1/advanced_topics/privacy.html#setting-up-a-login-page:
If the stock Django login view is not suitable - for example, you wish to use an external authentication system, or you are integrating Wagtail into an existing Django site that already has a working login view - you can specify the URL of the login view via the WAGTAIL_FRONTEND_LOGIN_URL setting
To elaborate on Erick M's answer, since this is the working answer:
You do need to set the correct permission (wagtailadmin.access_admin) or set the is_superuser flag in Django's auth_user database table to be able to access the CMS, otherwise you still get a "permission denied" error.
I thought this had to do with my implementation, but it was already working, but failed because of the above reason.

Can the Admin UI be used without the user authenticating in Django 1.3?

I was wondering if there's an easy way to configure the Django Admin UI (eg at http://mysite.com/admin) so that I don't need to authenticate/login?
I've tried tweaking urls.py but couldn't get it to bypass the login screen:
urlpatterns = patterns('',
url(r'^admin/', include(admin.site.urls)),
)
I'd like to go to http://mysite.com/admin and see the list of django objects without having to login.
Thanks.
Django admin framework uses 'is_staff' flag on the 'User' object to verify the permission to use admin site.
So it needs an user to be authenticated to verify his admin related permissions.
If you want to disable this, you have to override the 'index' method of the admin site.
It is available at django.contrib.admin.sites.
def index(self, request, extra_context=None):
"""
Displays the main admin index page, which lists all of the installed
apps that have been registered in this site.
"""

The smartest way to redirect in django without creating view

The task is simple:
If user visits site root then:
if user is authenticated then:
redirect to /dashboard/
else:
redirect to settings.LOGIN_URL
There are many ways to implement that, but I wonder if there is such way in which I do need to use only urls.py.
I found a solution with RedirectView login_required(RedirectView.as_view(url=my_url)), however then I can only write static my_url instead of reverse(), which is not flexible.
You could use reverse_lazy (Django 1.4) in you url configuration, like so:
from django.conf.urls.defaults import url, patterns
from django.core.urlresolvers import reverse_lazy
from django.shortcuts import redirect
urlpatterns = patterns('',
url(r'^/$', lambda request: return redirect(reverse_lazy('url_name')),
)
Another possibility is to define LOGIN_URL using reverse_lazy, so you could continue to use settings.LOGIN_URL in your redirects.
Code is untested, might have a typo somewhere.
You just need to mixin LoginRequired to your view. You can find an example of the mixin here:
http://djangosnippets.org/snippets/2442/
Then where you define that view, you just do:
class RedirectView(LoginRequiredMixin, DetailView):
....
Or whatever Class Based View you're inheriting from. Hope that helps!