password_reset only for users who are not logged in - django

is it possible, to deny access to django.contrib.auth.views.password_reset for users who are authenticated?
I want to use it, and make it only for not logged in.
I can't figure out how to do that. I have done all things with reset, but all users can enter it:/
UPDATE:
I made it that way:
views.py
def password_remind(request):
from django.contrib.auth.views import password_reset
if request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
return password_reset(request, template_name='remind_password.html', post_reset_redirect=reverse('password_remind_sent'))
def password_remind_sent(request):
from django.contrib.auth.views import password_reset_done
if request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
return password_reset_done(request, template_name='remind_password.html')
def password_remind_set_new(request, uidb36, token):
from django.contrib.auth.views import password_reset_confirm
if request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
return password_reset_confirm(request, template_name='set_new_password.html', post_reset_redirect=reverse('password_remind_changed'), token=token, uidb36=uidb36)
def password_remind_changed(request):
from django.contrib.auth.views import password_reset_complete
if request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
return password_reset_complete(request, template_name='set_new_password.html')
urls.py:
url(r'^remind_password/$', 'password_remind', name='password_remind'),
url(r'^remind_password/sent/$', 'password_remind_sent', name='password_remind_sent'),
url(r'^remind_password/set_new/(?P<uidb36>[0-9A-Za-z]+)-(?P<token>.+)/$', 'password_remind_set_new', name='password_remind_set_new'),
url(r'^remind_password/changed/$', 'password_remind_changed', name='password_remind_changed'),

Sure, you can always wrap the function in your own function with some conditions and point your url to your wrapper function instead. (note to pass in whatever arguments you were passing in before...)
from django.contrib.auth.views import password_reset as real_password_reset
def password_reset(request, *args, **kwargs):
if request.user.is_authenticated():
# separate logic, redirect, whatever you wish.
return direct_to_template(request, "my_password_change.html", {})
return real_password_reset(request, *args, **kwargs)
(r'^password_reset/$', password_reset),

I'd make a decorator function that ensures that request.user is not authenticated and if they are, redirects to an 'access denied' page with a 403 code.
Then, in my urls.py, I'd include url patterns for the view(s) I want to lock down (there are about four, I recall) and wrap the method call in that decorator function.
That way, you don't have to hack contrib.auth's views at all - you just wrap your code around it.
But do you really need to stop users accessing the reset password view?
UPDATE:
Example decorator function (untested code!)
from django.http import HttpResponseForbidden
def unauthenticated_users_only(func):
"""
Decorator that checks whether a user
is logged in. 403s if not.
"""
def dec(request, *args, **kwargs):
if not request.user.is_authenticated():
return HttpResponseForbidden()
return func(request, *args, **kwargs)
dec.__doc__ = func.__doc__
return dec
and then in your main urls.py, import all of the auth urls related to the reset flow and import and apply that function for each of them:
url(r'^password/reset/$', unauthenticated_users_only(auth_views.password_reset), name='auth_password_reset'),

Related

Restrict Admin/ access to anonymous users and not admins django

I have a new app:
newApp
with 3 users
[admin, user1, user2]
and a login page (not from /admin/). Is it possible to restrict/redirect access to django's Admin login? i want to just get into the admin/ path if the user logged IS and admin, That means it should not work if im not logged nor logged as normal user.
You could write a custom view decorator to check if user is admin, and if not raise a Http404, for example:
def user_is_admin(function):
def wrap(request, *args, **kwargs):
if request.user.is_authenticated and request.user.is_admin:
return function(request, *args, **kwargs)
else:
raise Http404
return wrap
user.is_admin would be a custom implementation, just an example. Now you just need to wrap your views
#user_is_admin
def my_view(request):
...
Im adding this answer because the one provided by Pedro couldnt answer the part that i was trying to figure out, not letting anyone access to the /admin/ path.
I used Django-Decorator-Include to mix it with Pedro's custom decorator
and the result on my urls.py is:
from decorator_include import decorator_include
from django.core.exceptions import PermissionDenied
def user_is_admin(function):
def wrap(request, *args, **kwargs):
if request.user.is_authenticated and request.user.is_staff:
return function(request, *args, **kwargs)
else:
raise PermissionDenied
return wrap
urlpatterns = [
.....
path('admin/', decorator_include([user_is_admin], admin.site.urls)),
.....
]

Using Django UserPassesTestMixin with LoginRequiredMixin to go to an authorized URL

I'm trying to write a mixin that will protect views by first checking if someone is logged in and then if they have been onboarded. It seems to work, by blocking views it's attached to, but it the URLjust goes to a 403 forbidden. Any ideas on how to get it to go to the named url?
from django.contrib.auth.mixins import UserPassesTestMixin
from django.http import HttpResponseRedirect
from django.shortcuts import redirect
from django.contrib.auth.mixins import LoginRequiredMixin
class OnboardedMixin(LoginRequiredMixin, UserPassesTestMixin):
"""
a custom mixin that checks to see if the user has been onboarded yet
"""
def test_func(self):
if self.request.user.onboarded and self.request.user.is_active:
return True
def get_login_url(self):
return redirect('onboarding',)
Rather than taking this approach, maybe its best to use a decorator instead. For example:
from django.contrib.auth.decorators import login_required
def my_login_required(function):
def wrapper(obj, request, *args, **kw):
decorated_view_func = login_required(request)
if not decorated_view_func.user.is_authenticated:
return decorated_view_func(request) # restricts without login and sends to signin view
if request.user.onboarded and request.user.is_active:
return function(obj, request, *args, **kw)
return HttpResponseRedirect("/onboarding/")
return wrapper
And use this decorator in desired views:
class SomeView(DetailView):
...
#my_login_requried
def dispatch(self, *args, **kwargs):
return super(SomeView, self).dispatch(*args, **kwargs)

Use LoginRequiredMixin and UserPassesTestMixin at the same time

I want to have a TemplateView Class that uses LoginRequiredMixin and UserPassesTestMixin at the same time. Something like this:
from django.views.generic import TemplateView
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
class FinanceOverview(LoginRequiredMixin, UserPassesTestMixin, TemplateMixin):
login_url = '/login'
redirect_field_name = 'next'
def test_func(self):
return self.request.user.groups.filter(name="FinanceGrp").exists()
def get(self, request, *args, **kwargs):
DO SOMETHING IF USER IS AUTHENTICATED AND ALSO MEMBER OF GROUP FinanceGrp
Basically as you can see above, what I want to achieve is the following:
If user is not authenticated, to redirect user to:
https://website/login?next=financeoverview
However what I can't figure out is how to redirect users who are authenticated but do not belong to group FinanceGrp to another page. For example:
https://website.com/access_denied?previous_page=financeoverview
In my case users are always redirected to /login page when they fail the group test. How can I achieve two mixins used at the same time but at the same time both of them are clashing around variable login_url. Unfortunately UserPassesTestMixin is using the same login_url so it makes this trouble for me.
Thanks in advance
Milos
I think you're better off subclassing AccessMixin and then performing these checks yourself. Something like this:
from django.contrib.auth.mixins import AccessMixin
from django.http import HttpResponseRedirect
class FinanceOverview(AccessMixin, TemplateMixin):
def dispatch(self, request, *args, **kwargs):
if not request.user.is_authenticated:
# This will redirect to the login view
return self.handle_no_permission()
if not self.request.user.groups.filter(name="FinanceGrp").exists():
# Redirect the user to somewhere else - add your URL here
return HttpResponseRedirect(...)
# Checks pass, let http method handlers process the request
return super().dispatch(request, *args, **kwargs)
You should override get_login_url:
class FinanceOverview(LoginRequiredMixin, UserPassesTestMixin, TemplateMixin):
login_url = '/login'
redirect_field_name = 'next'
def test_func(self):
return self.request.user.groups.filter(name="FinanceGrp").exists()
def get_login_url(self):
if self.request.user.is_authenticated:
return URL_FOR_AUTHENTICATED_USERS
return super().get_login_url()
def get(self, request, *args, **kwargs):
DO SOMETHING IF USER IS AUTHENTICATED AND ALSO MEMBER OF GROUP FinanceGrp

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])

How can make Django permission_required decorator not to redirect already logged-in users to login page, but display some message

How can make Django permission_required decorator not to redirect already logged-in users to login page, but display some message like Insufficient permissions?
Thank you.
A quick and dirty solution would be to write your own decorator to do this. Something like this:
decorator_with_arguments = lambda decorator: lambda *args, **kwargs: lambda func: decorator(func, *args, **kwargs)
#decorator_with_arguments
def custom_permission_required(function, perm):
def _function(request, *args, **kwargs):
if request.user.has_perm(perm):
return function(request, *args, **kwargs)
else:
request.user.message_set.create(message = "What are you doing here?!")
# Return a response or redirect to referrer or some page of your choice
return _function
You can then decorate your view thus:
#custom_permission_required('my_perm')
def my_view(request, *args, **kwargs):
#Do stuff
Since django 1.4 permission_required has a raise_exception parameter that you can set to True to have an unauthorized PermissionDenied exception raised
Eg. to give an exemple on a Class Based View:
from django.contrib.auth.decorators import permission_required
...
class MyView(TemplateView):
#method_decorator(permission_required('can_do_something', raise_exception=True))
def dispatch(self, *args, **kwargs):
return super(MyView, self).dispatch(*args, **kwargs)
Ref:permission_required decorator doc
I'm assuming this question requires two pieces
Users that are already logged in do not get redirected to the login page
Users that are not logged in do not get redirected
#Manoj Govindan's answer nor #Stefano's answer will not work. #Lidor's answer will work, but he has fully re-implemented the permission_required function.
Here is a simpler way:
#login_required
#permission_required('hi there', raise_exception=True)
def blah(request):
pass
With this, if the user is not logged in, they will be redirected. If they are but they don't have permissions, they will be down an error.
I had the same problem but learned about raise_exception parameter at the #permission_required decorator!
this parameter is False by default, but once you pass it the True value, it will automatically redirect the without permission user to the 403.html page! (if there was any 403.html page in the root of your project, otherwise shows the server 403 forbidden page!
read more in Django docs here
#permission_required('app_name.view_model', raise_exception=True)
def some_model_view(request):
...
You can write your own decorator to replace django's permission_required decorator:
from django.utils import six
from django.core.exceptions import PermissionDenied
from django.contrib.auth.decorators import user_passes_test
def permission_required(perm, login_url=None, raise_exception=True):
def check_perms(user):
if isinstance(perm, six.string_types):
perms = (perm, )
else:
perms = perm
if user.has_perms(perms):
return True
if raise_exception and user.pk:
raise PermissionDenied
return False
return user_passes_test(check_perms, login_url=login_url)
And use it the same way:
#permission_required('app.permission')
def view_page(request):
# your view function
Logged in users with no permission will get a 403 forbidden error. Those who are not logged in will be redirected to the login page.