I am using login middleware to create a user login form.
What is the best method to retrieve user id from view, after user successfully login?
Created a middleware.py:
class LoginMiddleware(object):
def process_request(self, request):
if request.path != self.require_login_path and request.user.is_anonymous():
if request.POST:
return login(request)
else:
return HttpResponseRedirect('%s?next=%s' % (self.require_login_path, request.path))
Once a user has logged in, their associated User object is available via request.user in your template, automatically. When you say "user id", I'm not sure if you mean username or the literal value of id in the database. The same method applies for both, though: {{ request.user.username }} or {{ request.user.id }}.
Additionally, as the commenter above noted, there's no reason to create a new authentication middleware unless you need to change something in how the login is processed. Otherwise, leave it alone and use the default. (Which seems to be what you should be doing based on your middleware).
UPDATE: Neglected to mention that in order to access request.user in your template, you must pass in RequestContext. Example render_to_response below:
from django.template import RequestContext
render_to_response('templates/mytemplate.html', {}, context_instance=RequestContext(request))
Related
I am using #login required decorator in my most of views so what I want is to use message in my login page telling user if you want to open that page you have to login first so how I can achieve that I know I cannot achieve that on my views so anyone does that and know how to do please tell me how to achieve that if a user redirected to login because of #login required I want to show message please login to continue
I also looked on some of the same question I am looking for answer which got implemented on all the login required decorator so I don't have to change code everywhere it already got implemented in all of the login required decorator in my whole app
my login view
def my_login(request):
if request.method == 'POST':
form = LoginForm(request.POST)
if form.is_valid():
username = form.cleaned_data["username"]
password = form.cleaned_data["password"]
remember_me = form.cleaned_data['remember_me']
user = authenticate(username=username, password=password)
if user:
login(request, user)
if not remember_me:
request.session.set_expiry(0)
return redirect('accounts:home')
else:
request.session.set_expiry(1209600)
return redirect('accounts:home')
else:
return redirect('accounts:login')
else:
return redirect('accounts:register')
else:
form = LoginForm()
return render(request, "login.html", {'form': form})
Solution 1:
In your view, check the query parameter "next", as if the user is redirected to the login view, it would come with the ?next=/whatever in the URL. You can do
if 'next' in request.GET:
messages.add_message(request, messages.INFO, 'Hello world.')
Solution 2 (not recommended, this makes it confusing to debug for others):
Python being a dynamic language, you can hijack the behaviour of login_required with your version.
in your manage.py and wsgi.py and maybe asgi.py you would need to "monkey patch" the login_required decorator.
from django.contrib.auth import decorators
def my_login_required(...):
# you can look at the implementation in the answer by LvanderRee mentioned in the comments
decorators.login_required = my_login_required
Now, because these files will be the first code to execute before Django even starts, this will replace the built-in login_required with your version.
Based on this Stack Overflow post, using multiple answers, to redirect to another page, either:
Set LOGIN_URL in settings.py. This is the URL that Django will redirect to if a user that's not yet logged in attempts to log in.
or Inline:
from django.contrib.auth.decorators import login_required
#login_required(login_url='/example url you want redirect/') #redirect when user is not logged in
def myview(request):
do something
return something #returns when user is logged in
Of course, you still need to have the login view setup.
If you want to check if the user is authenticated in the HTML, you can use the templating system: (For clarity this is something I just whipped up right now, it's not from the Stack Overflow post mentioned above)
{% if user.is_authenticated %}
// Display something if authenticated maybe
{% else %}
// Display something like "You are not logged in etc"
{% endif %}
A client requires the ability for managers to add users to a company (with a random one time password) where the user must change their password before accessing anything. I am developing the app in Django 2.2
I made a custom user, replacing username with an email address and I added a change_password bool flag to the user. My change_password form/function works properly, but redirecting does not.
urls.py
path('change-password/', views.change_password, name='change-password'),
views.py
class Login(LoginView):
def form_valid(self, form):
# form is valid (= correct password), now check if user requires to set own password
if form.get_user().change_password:
return HttpResponseRedirect(reverse('change-password'))
else:
auth_login(self.request, form.get_user())
return HttpResponseRedirect(self.get_success_url())
def change_password(request):
if request.method == 'POST':
form = PasswordChangeForm(data=request.POST, user=request.user)
if form.is_valid():
form.save()
request.user.change_password = False
request.user.save()
update_session_auth_hash(request, request.user)
return redirect(reverse('user_view'))
else:
return redirect(reverse('change-password'))
else:
form = PasswordChangeForm(user=request.user)
args = {'form': form}
return render(request, 'users/change_password.html', args)
The expected behavior is to redirect to change-password if the change_password flag is True, however, while the app does redirect to change-password, upon Submission the following error is thrown:
NotImplementedError: Django doesn't provide a DB representation for AnonymousUser.
If I add the decorator #login_required to my change_password function this error goes away, however, I am redirected back to the login page with the URL: users/login/?next=/users/change-password/
The problem is that in form_valid method you are calling form.get_user() which authenticates/gets the user and checks for the change_password correctly, but it does not log the user in, meaning that the user making the requests is still anonymous to the system. So while the user gets redirected they are not authenticated, which means that the request.user objects is of type AnonymousUser which does not live in the database hence the Django doesn't provide a DB representation for AnonymousUser error.
And when you use the #login_required decorator the user gets redirected to the login page because it is not a logged in user and the decorator requires the user to be logged in to see the view it is decorating.
The URL that you see users/login/?next=/users/change-password/ is basically how the login_required decorator works and it is doing two things:
1. redirect anonymous user to the login page (the users/login part of the URL)
2. once they have successfully logged in redirect them back from where they came from (?next=/users/change-password/)
My suggestion is that you pass the username of the user that tried to log in but has to change their password to the change_password view and have a form waiting for the user there that asks for the current password, new one and a confirmation of the new password. It is the simplest way to do what you want to do, but you will have to confirm that the users current password is correct again though.
Sorry for the confusing first answer, I didn't read the question right the first time, hopefully this makes more sense :)
In my Django project, I have various users created by Django's built-in authentication system. Each user can create their own instances of the App model. I would like to restrict user access to objects such that users can only view the instances they have created. To do that I have created this view:
#login_required
def appDetail(request, app_id):
try:
app = App.objects.get(pk=app_id)
# Testing if the currently logged in user is
# the same as the user that created the 'app':
if request.user.id == app.user.user.id:
if request.method == 'POST':
form = AppForm(request.POST, instance=app)
if form.is_valid():
edited_app = form.save()
return HttpResponseRedirect('/thanks/')
else:
form = AppForm(instance=app)
# If 'app' does not belong to logged in user, redirect to 'accessdenied' page:
else:
return HttpResponseRedirect('/accessdenied/')
except LeaveApp.DoesNotExist:
raise Http404
return render(request, 'AppDetail.html', {'form':form})
It works, but I'm wondering if there's a more commonly accepted and/or safe way to do this?
This is called row-level permissions and it's a very common problem. See here for all the apps that solve it.
If that particular test is all you need to do, go for a custom solution like yours (though, since it's boilerplate, it's preferable to move it to a decorator). Otherwise, just use an existing app.
I would put the form submission in a different view and write a custom decorator, which you could also use for similar issues.
I would also return a 404 instead of access denied. You might not want to show users that you are protecting something.
There is a decorator called user_passes_test that restricts access to a view based on if the user passes a certain check
from django.contrib.auth.decorators import login_required, user_passes_test
#login_required
#user_passes_test(lambda user: user.username == app.user.user.id)
MyView(request):
...
You can also add in an optional argument for a url to redirect to in the event they fail the check.
Trying to do this from the admin page is also pretty easy, but takes a few extra steps.
Docs Here
Let's say i have a form that does something in database and requires user authentication that has been sent by POST, is it possible inside request someone evil to change the user in order to exploit the system?
The following example creates an item in database but requires a logged in user. Can someone send other user's data in request.user?
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from items_core.models import Item
from items.forms import CreateItemForm
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt
#login_required
def create(request):
errors = None
if request.method == 'POST':
form = CreateItemForm(request.POST)
if form.is_valid():
try:
Item.objects.get(
name = form.cleaned_data['name'],
user = request.user
)
errors = 'Item already exist. Please provide other name.'
except Item.DoesNotExist:
Item.objects.create(
name = form.cleaned_data['name'],
user = request.user
)
return redirect('items:list')
form = CreateItemForm()
else:
form = CreateItemForm()
template = {
'form':form,
'items':Item.objects.filter(user=request.user),
'request':request,
'errors':errors
}
return render(request, 'items/item_create.html', template)
Thanks!
The request.user object is of type SimpleLazyObject which is added by the auth middleware to the requestobject.
SimpleLazyObject(LazyObject): is used to delay the instantiation of the wrapped class
At the time of requesting the actual logged in user, get_user method gets called.
def get_user(request):
if not hasattr(request, '_cached_user'):
request._cached_user = auth.get_user(request)
return request._cached_user
Here, auth.get_user() would inturn validate this way:
backend_path = request.session[BACKEND_SESSION_KEY]
backend = load_backend(backend_path)
user = backend.get_user(user_id) or AnonymousUser()
Hence if the request.user object is tampered with, this validation would fail as the session data validation would fail
user attribute on request i.e request.useris set by AuthenticationMiddleware. process_request for this middleware internally uses get_user() provided by django auth system which is defined in django.contrib.auth.__init__.py.
This get_user() uses django session and django session internally use cookies. Django session use a cookie with key as sessionid.
So, say a malicious user gets hold of the cookie of a legitimate user and sends this cookie to the server, server will think that the request has come from a legitimate user and will login as the legitimate user. But since the request was sent by the malicious user, he now has access to the resources of the legitimate user.
In a mini blog app, I want to create a delete function, so that the owner of the blog can delete his entries (and only his entries).
I guess that the only methods for doing do, is using a form.
Though my the deletion code seems clear and correct, it doesn't work.
My code:
def delete_new(request,id):
u = New.objects.get(pk=id).delete()
if request.method == 'POST':
form = DeleteNewForm(request.POST)
form.u.delete()
form.save()
return render_to_response('news/deleteNew.html', {
'form': form,
},
context_instance=RequestContext(request))
and in the template:
<a href='/news/delete_new/{{object.id}}/'> Delete</a> <br />
Is this a correct approach? I mean, creating a form for this?
also, the only way to take the blog post associated with the deletion link is having an id as a parameter. Is it right? I mean, maybe any user can type another id, in the url, and delete another entry (eventually not one of his)
You need to use a form, or you're vulnerable to CSRF attacks. You're also deleting the model before you've checked whether the request was a GET or a POST.
Create a simple ModelForm:
from django import forms
from .models import New
class DeleteNewForm(forms.ModelForm):
class Meta:
model = New
fields = []
In your views.py in the same Django app:
from django.shortcuts import render, get_object_or_404
from .forms import DeleteNewForm
from .models import New
def delete_new(request, new_id):
new_to_delete = get_object_or_404(New, id=new_id)
#+some code to check if this object belongs to the logged in user
if request.method == 'POST':
form = DeleteNewForm(request.POST, instance=new_to_delete)
if form.is_valid(): # checks CSRF
new_to_delete.delete()
return HttpResponseRedirect("/") # wherever to go after deleting
else:
form = DeleteNewForm(instance=new_to_delete)
template_vars = {'form': form}
return render(request, 'news/deleteNew.html', template_vars)
In general, for deleting objects you should rather use POST (or DELETE) HTTP methods.
If you really want to use HTTP GET for your example, here is what you need to fix:
If you have url pointing to some url like yours: <a href='/news/delete_new/{{object.id}}/'> Delete</a> then you can simply write view that will check if object belongs to logged in user and delete this entry if yes, like in code you have already written:
def delete_new(request,id):
#+some code to check if New belongs to logged in user
u = New.objects.get(pk=id).delete()
To check if New objects belogs to some user you need to create realation between User and New (like created_by = models.ForeignKey(User) in New model).
You can get logged in user this way: request.user
I hope I got your point correctly and my answer helps you somehow.
PS: You can also consider using {% url %} tag instead of writing urls directly in your templates.