How can I test a 403 error? - django

I have a Test Class, that test the access of all page with different user.
Those access are defined by decorator on each of my views.
views.py :
#login_required
def afficher(request):
...
...
#creation_permission_required
def ajouter(request):
...
...
Some of these decorator are defined by me.
decorators.py :
def creation_permission_required(function):
#wraps(function)
#login_required
def decorateur(request, *k, **a):
user = get_object_or_404(User, username__iexact=request.user.username)
if user.is_superuser or user.get_profile().creation:
return function(request, *k, **a)
else:
return HttpResponseRedirect(reverse("non_autorise"))# <--- PROBLEM
return decorateur
return function
When I test them, I use the status_code attribute to verify if the user can access or not the page
test.py :
c = Client()
c.login(username='aucun', password='aucun')
for url in self.url_aucun:
r = c.get(reverse(url['url'], args=url['args']))
self.assertEqual(r.status_code, 200)
for url in self.url_creation:
r = c.get(reverse(url['url'], args=url['args']))
self.assertEqual(r.status_code, 302) # <--- SECOND PROBLEM
When a user doesn't have the right to access a page, the page should return a 403 error (forbidden). How can I do to test 403 instead of 302 ?
EDIT : I tried to use HttpResponseForbidden(reverse("non_autorise")), but couldn't get any content. So then I tried to make my own HttpResponse which is an exact copy of HttpResponseRedirect but with another status_code (403) still didn't get any content...
decorators.py :
class HttpResponseTest(HttpResponse):
def __init__(self, redirect_to):
super(HttpResponseTest, self).__init__()
self['Location'] = iri_to_uri(redirect_to)
self.status_code = 403
def creation_permission_required(function):
#wraps(function)
#login_required
def decorateur(request, *k, **a):
user = get_object_or_404(User, username__iexact=request.user.username)
if user.is_superuser or user.get_profile().creation:
return function(request, *k, **a)
else:
return HttpResponseTest(reverse("non_autorise"))# <--- PROBLEM
return decorateur
return function

If you want a 403 response, you can raise a PermissionDenied exception in your decorator if you are using Django 1.4. Alternatively, you can return a HttpResponseForbidden in your decorator. You will also have to build a custom login_required decorator.

self.assertEqual(r.status_code, 403)

I had this same issue and solved it by instructing the test get() to follow the redirect using follow=True. Using BlueMagma's example it would look something like this:
for url in self.url_creation:
r = c.get(reverse(url['url'], args=url['args']), follow=True)
self.assertEqual(r.status_code, 403) # <--- SECOND PROBLEM NO MORE!!!
Hope this helps someone else!

Related

Test redirect using #user_passes_test decorator

My view file has:
def is_authorised(user):
return user.groups.filter(name='bookkeepers').exists()
#login_required
def unauthorised(request):
context = {'user': request.user}
return render(request, 'order_book/unauthorised.html', context)
#login_required
#user_passes_test(is_authorised,
login_url='/order_book/unauthorised/',
redirect_field_name=None)
def book(request):
return render(request, 'order_book/book.html', {})
I want to write a test asserting that a logged in user who is not authorised does get redirected correctly, so far I have this:
class RestrictedViewsTest(TestCase):
#classmethod
def setUpTestData(cls): # noqa
"""Set up data for the whole TestCase."""
User.objects.create_user(username='JaneDoe',
email='jane.doe#example.com',
password='s3kr3t')
def setUp(self):
auth = self.client.login(username='JaneDoe', password='s3kr3t')
self.assertTrue(auth)
def test_book(self):
response = self.client.get('/order_book/book')
self.assertEqual(response.status_code, 301, response)
self.assertTrue(isinstance(response, HttpResponsePermanentRedirect))
def tearDown(self):
self.client.logout()
This works fine as it is but I cannot fathom where to get the redirected to url. Trying to get response['Location'] gives me '/order_book/book' which is not right.
What am I doing wrong?
You can use the assertRedirects method.
def test_book(self):
response = self.client.get('/order_book/book/')
self.assertRedirects(response, '/order_book/unauthorised/', status_code=302, target_status_code=200)

Django resolve with GET parameters — raises Resolver404()

I need to test if redirecting to a url will result in a 404 error.
Following example in django docs: https://docs.djangoproject.com/en/1.8/ref/urlresolvers/#resolve
I managed to get the following to work. So if the redirect_url raises a 404, we redirect the user to homepage.
redirect_url = '/blog/hello-world'
view, args, kwargs = resolve()
kwargs['request'] = request
try:
view(*args, **kwargs)
except Http404:
self.message_user(...)
return HttpResponseRedirect('/')
However I need to test if the redirect_url with a GET parameter of ?edit also raise 404. As the 'edit' flag is used to return a different queryset. e.g.
if 'edit' in request.GET:
qs = qs.drafts()
else:
qs = qs.public()
And I changed my original code:
redirect_url = '%s?edit' % redirect_url
[...]
However this raise Resolver404.
Full stacktrace here: http://dpaste.com/1DQHH7Q
Now my question is how can I test for HTTP404 error with GET parameters?
The path blog/2015/07/14/sky-limit-only-those-who-arent-afraid-fly/?edit is valid, when I go the url in the browser.. it works as expected.
A dirty solution is to do this:
from django.test.client import Client
client = Client()
resp = client.get(redirect_url)
if not resp.status_code == 404:
return HttpResponseRedirect(redirect_url)
I really don't want to use this.
The resolve() method takes the url without the GET parameters. You could try replacing request.GET.
redirect_url = '/blog/hello-world'
view, args, kwargs = resolve()
request.GET = {'edit': ''}
kwargs['request'] = request
try:
view(*args, **kwargs)
except Http404:
self.message_user(...)
return HttpResponseRedirect('/')
Note that request is the request object for your current view, so be careful be careful if you need the actual request.GET data.

render from another method

I have two urls defined in my urls.py one to go to homepage.html and one to go to unauthorized.html. I created the following in my views.py. My goal was to have a common method called "check_authorized" and to call that from my homepage method and if it met a condition here if 'isFoo' is false to call 'unauthorized' method so that it can redirect to the unauthorized.html. However when I call homepage.html it doesn't redirect to that page instead it stays on homepage.html ( I even removed the if block so that the code path hist the 'unauthorized' method directly). Here is the view.py
def check_authorized(request):
ctx = RequestContext(request)
posixGroups = []
#Have some logic to add groups
ctx['isFoo'] = 'foo' in posixGroups
if not ctx['isFoo']:
unauthorized(request)
return ctx
def unauthorized(request):
ctx = RequestContext(request)
return render_to_response('unauthorized.html', ctx)
def homepage(request):
ctx = check_authorized(request)
return render_to_response('homepage.html', ctx)
In my Urls.py I have in the url patterns
(r'^unauthorized', views.unauthorized),
(r'', views.homepage),
Update:
Adding decorator class
def check_authorized(request, login_url=None):
def is_authorized(u):
user = request.META['REMOTE_USER']
posixGroups = []
#Code to get posix groups
isFoo = 'foo' in posixGroups
if not isFoo:
return False
return True
return user_passes_test(is_authorized, login_url=login_url)
def unauthorized(request):
ctx = RequestContext(request)
return render_to_response('unauthorized.html', ctx)
#check_authorized(login_url='unauthorized')
def homepage(request):
ctx = check_authorized(request)
return render_to_response('homepage.html', ctx)
Error
Exception Type: TypeError
Exception Value:
check_authorized() takes at least 1 argument (1 given)
This is a very good use case for a decorator, like login_required:
from django.contrib.auth.decorators import login_required
#login_required(login_url='/accounts/login/')
def my_view(request):
...
It would redirect the user to the login_url if it is not authenticated.
So, since you have a custom logic that decides whether to redirect the user to login_url or not, you need a custom decorator that would be similar to Django built-in decorators:
from django.contrib.auth.decorators import user_passes_test
def check_authorized(request, login_url=None):
def is_authorized(user):
# Have some logic that returns True/False
...
return user_passes_test(is_authorized, login_url=login_url)
#check_authorized(login_url='unauthorized/')
def homepage(request):
return render_to_response('homepage.html')
Also see:
group_required decorator

Django test client http basic auth for post request

everyone. I am trying to write tests for RESTful API implemented using django-tastypie with http basic auth. So, I have the following code:
def http_auth(username, password):
credentials = base64.encodestring('%s:%s' % (username, password)).strip()
auth_string = 'Basic %s' % credentials
return auth_string
class FileApiTest(TestCase):
fixtures = ['test/fixtures/test_users.json']
def setUp(self):
self.extra = {
'HTTP_AUTHORIZATION': http_auth('testuser', 'qwerty')
}
def test_folder_resource(self):
response = self.client.get('/api/1.0/folder/', **self.extra)
self.assertEqual(response.status_code, 200)
def test_folder_resource_post(self):
response = self.client.post('/api/1.0/folder/', **self.extra)
self.assertNotEqual(response.status_code, 401)
GET request is done well, returning status code 200. But POST request always returns 401. I am sure I am doing something wrong. Any advice?
Check out this question. I've used that code for tests using both GET and POST and it worked. The only difference I can see is that you have used base64.encodestring instead of base64.b64encode.
Otherwise, if that doesn't work, how are you performing the HTTP Authentication? I wrote and use this function decorator:
import base64
from django.http import HttpResponse
from django.contrib.auth import authenticate, login
def http_auth(view, request, realm="", must_be='', *args, **kwargs):
if 'HTTP_AUTHORIZATION' in request.META:
auth = request.META['HTTP_AUTHORIZATION'].split()
if len(auth) == 2:
if auth[0].lower() == "basic":
uname, passwd = base64.b64decode(auth[1]).split(':')
if must_be in ('', uname):
user = authenticate(username=uname, password=passwd)
if user is not None and user.is_active:
login(request, user)
request.user = user
return view(request, *args, **kwargs)
# They mustn't be logged in
response = HttpResponse('Failed')
response.status_code = 401
response['WWW-Authenticate'] = 'Basic realm="%s"' % realm
return response
def http_auth_required(realm="", must_be=''):
""" Decorator that requires HTTP Basic authentication, eg API views. """
def view_decorator(func):
def wrapper(request, *args, **kwargs):
return http_auth(func, request, realm, must_be, *args, **kwargs)
return wrapper
return view_decorator
I've found a reason of my problem. DjangoAuthorization checks permissions with django premissions framework, since I don't use it in my project — all post/put/delete requests from non superuser are unauthorized. My bad.
Anyway, thanks a lot to you, guys, for responses.
On Python 3
#staticmethod
def http_auth(username, password):
"""
Encode Basic Auth username:password.
:param username:
:param password:
:return String:
"""
data = f"{username}:{password}"
credentials = base64.b64encode(data.encode("utf-8")).strip()
auth_string = f'Basic {credentials.decode("utf-8")}'
return auth_string
def post_json(self, url_name: AnyStr, url_kwargs: Dict, data: Dict):
"""
Offers a shortcut alternative to doing this manually each time
"""
header = {'HTTP_AUTHORIZATION': self.http_auth('username', 'password')}
return self.post(
reverse(url_name, kwargs=url_kwargs),
json.dumps(data),
content_type="application/json",
**header
)

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.