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
Related
My question address to the usage of Django permission architecture even when the front is on Vue.js and data is requested/responded through Django REST framework.
I am a little confused about being able to use default permission libraries of Django when the app is combined with Rest Framework and VueJs:
from django.contrib.auth.decorators import login_required, permission_required
#permission_required('pobapp.can_add_instance')
#login_required
def addEmployeeInstance(request):
return render(request, 'pobapp/search.html')
If not, how can I restrict some data and pages for specific users? For example, if I only wanted to let authenticated users to view some specific pages?
If you want to restrict a logged user in pages, first, you should define a Role and a Resource model for User model.
Then, you can define a permission class for controlling this restriction. for example my permission class for this purpose is like this,
class IsAuthRolePermission(permissions.BasePermission):
def has_permission(self, request, view):
if request.user.is_authenticated:
try:
obj = Resource.objects.get(name=view.__class__.__name__,
roles__in=request.user.roles.all())
return True
except Resource.DoesNotExist:
return False
else:
return False
I'm building a job application form. A logged-in user is permitted to apply to the job only once (there's only one job). At the moment, a user is able to directly access the job application (FormView), by typing in its specific URL, and an error message is thrown AFTER the user submits the form. To achieve this, I'm doing a couple of things:
(1) Wrapping the job application view in login_required()
(2) Using the form_valid() method to check whether or not a user has already submitted an application to this specific job via:
def form_valid(self, form):
form = form.save(commit=False)
form.user = self.request.user
if Application.objects.filter(user=user_id):
messages.error(self.request, 'Sorry, our records show that you have already applied to this job.')
return redirect('/')
But I'd rather not permit them to reach the page at all. Instead, I want to check whether or not a user has already applied (during the request), and redirect them away from the form, if they have already applied. I have limited access to logged-in users that pass a test in the past, using something like:
def job_application_view(request):
active_user = request.user
if Application.objects.filter(user=active_user):
return HttpResponse("Some response.")
However, I can't seem to figure out how to access request via the FormView Class-Based View. I'm sure I'm missing something simple. Perhaps another method of FormView I'm missing?
You can still use decorators on class-based views, but they are slightly more difficult to apply than function-based views.
class ApplicationView(FormView):
# ...
#method_decorator(user_passes_test(job_application_view))
def dispatch(self, *args, **kwargs):
return super(ApplicationView, self).dispatch(*args, **kwargs)
Answering specific parts of your post...
I have limited access to logged-in users that pass a test in the past
With class-based views, you need to decorate the url or decorate the dispatch method with any decorators you are interested in applying.
However, I can't seem to figure out how to access request via the FormView Class-Based View. I'm sure I'm missing something simple. Perhaps another method of FormView I'm missing?
You can access the request with self.request
I'd like to develop an app for multiple clients that displays analytics data specific to their account. So far I've set up my views file using the #login_required decorator for restricted pages. Now I need to identify that user to obtain their data.
A bit of research suggests that I could use:
from django.contrib.sessions.models import Session
from django.contrib.auth.models import User
session_key = request.session.session_key()
session = Session.objects.get(session_key=session_key)
uid = session.get_decoded().get('_auth_user_id')
Which obviously returns the user_id, which I can use as a field in the analytics database to identify which user the data belongs to.
Is this the best way to go about doing this, or can anyone suggest a better/more elegant alternative?
Note: I haven't tested the solution I've proposed, it just seems workable in my head.
You can access the logged in user on the request object.
#login_required
def my_view(request):
user = request.user
do_something(user)
...
when you are in a view, you can access the currently logged in user (object) by
def myview(request):
request.user
# or, if you want,
request.user.id
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))
I have some sections on my web site where only logged in users can see their resources.
I also want to make absolutely sure that only that authorized user can modify and delete his/her records. What's the best practice and more secure way of accomplishing this in Django?
Real examples would be truly appreciated.
For my project, I created a Decorator that checked if the right user was logged in:
#decorator.py
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
def same_user_required(func):
def wrapper(request, user):
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('login-view'))
if not user == request.user.username:
return HttpResponseRedirect(reverse('login-view'))
return func(request, user)
return wrapper
You then add it to any views that need checking:
#view_profile.py
from apps.utilities.decorators import same_user_required
#same_user_required
def edit_profile(request, user):
Note that my URL contains the username /profile/edit/<username>, which is where the parameter comes from, in the edit_profile view.
Another way is to use the Django built-in decorator, user_passes_test (see Django Book Chap 14 for an example of its usage. You then just have to write the test, not the decorator boilerplate code.