I am trying to define a decorator to check if the user has admin certain privileges:
def admin_required(function=None, redirect_field_name=REDIRECT_FIELD_NAME, login_url=None):
actual_decorator = user_passes_test(
lambda u: u.is_staff and u.is_authenticated() and not Association.objects.filter(admins=u).exists(),
login_url=login_url,
redirect_field_name=redirect_field_name
)
if function:
return actual_decorator(function)
return actual_decorator
The aim is to use this throught the views. Particularly, I am using it in a class-based view:
class MyCBV(CreateView):
#method_decorator(admin_required)
def dispatch(self, request, *args, **kwargs):
return super(MyCBV, self).dispatch(request, *args, **kwargs)
The problem is that this view is loaded via AJAX, so the redirect doesn't happen. Also, the HTTP status the view returns is success even when the user authentication fails, so the client (JS) has no way of telling when the action really succeeded or not.
I usually have trouble understanding decorators and Django authentication, so my question is: how can I raise an exception (preferably the PermissionDenied exception) when the authentication decoration function fails?
In Django 1.9+, you can use the UserPassesTestMixin instead of a decorator, and set raise_exception to True.
Since you are using Django 1.4, which is insecure and obsolete, you won't be able to do this. There isn't an option to make user_passes_test raise PermissionDenied rather than redirect. You could either try to detect the redirect in your JavaScript, or look at the user_passes_test source code and implement something similar that returns the response you want.
Related
I want to check if the user is authenticated before he/she can access the method, so I wrote a decorator named authorize but only the decorator code is executed and even if the user is authenticated, the method is not called after it.
here are the method and the decorator codes:
#authorize
def post(self, **kw):
# store data in database after authentication done using #authorize
def authorize(f):
def wrapper(*args, **kwargs):
secret_key = config.get('auth_secret_key')
auth_message = config.get('auth_message')
if 'HTTP_TOKEN' not in request.environ:
abort(401, detail='Authentication failed', passthrough='json')
gibberish = request.environ['HTTP_TOKEN']
if triple_des(secret_key).decrypt(gibberish, padmode=2).decode() != auth_message:
abort(401, detail='Authentication failed', passthrough='json')
return wrapper
If user has authentication problem, 401 is raised and request is aborted but if he is authenticated, the post method is not called. BTW, its my first time writing decorator so I might be completely wrong. Thanks for any answers
You need to actually call the function within your wrapper.
f(*args, **kwargs)
According to the documentation Cron jobs should be allowed to access admin protected views. However I get a 302 error if I have the #admin_required decorator on the GET method.
In app.yaml I have defined this:
- url: /generator
script: run.news.app
login: admin
the view:
class GeneratorView(MethodView):
#admin_required
def get(self):
return 'success', 200
urls.py
app.add_url_rule('/generator', 'generator', view_func=GeneratorView.as_view('generator'))
cron job:
cron:
- description: Scrape every 3 hours
url: /generator
schedule: every 3 hours synchronized
decorator:
def admin_required(func):
"""Requires App Engine admin credentials"""
#wraps(func)
def decorated_view(*args, **kwargs):
if users.get_current_user():
if not users.is_current_user_admin():
abort(401) # Unauthorized
return func(*args, **kwargs)
return redirect(users.create_login_url(request.url))
return decorated_view
the funny part is, when I remove the admin_required decorator, the url is still admin-only protected because of login: admin in app.yaml.
However my unit test fails the authorization check because of the missing decorator.
def test_generator_fails_as_normal_user(self):
self.setCurrentUser(u'john#example.com', u'123')
rv = self.client.get('/generator')
self.assertEqual(rv.status_code, 401)
AssertionError: 200 != 401
If I put the decorator back in, the unit test passes and cron job fails. Any suggestions?
The unit test's self.client.get no doubt doesn't go back all the way to app.yaml for routing -- so it's not surprising that, if you remove the app-level check you do in the decorator, it lets non-admin users through.
The real issue however is that the decorator is not finding anybody "logged in" when it's cron that's hitting that URL. This is hinted at (though it surely should be more clearly/explicitly documented!) at https://cloud.google.com/appengine/docs/python/config/cron#Python_app_yaml_Securing_URLs_for_cron :
Note: While cron jobs can use URL paths restricted with login: admin,
they cannot use URL paths restricted with login: required.
This indicates that the serving infrastructure does not validate cron requests by checking the currently logged-in user as it would find none. Rather, it relies on a header in the request:
Requests from the Cron Service will also contain a HTTP header:
X-AppEngine-Cron: true
The X-AppEngine-Cron header is set internally by Google App Engine. If
your request handler finds this header it can trust that the request
is a cron request. If the header is present in an external user
request to your app, it is stripped, except for requests from logged
in administrators of the application, who are allowed to set the
header for testing purposes.
So, your decorator must examine the headers at self.request -- if it finds X-AppEngine-Cron: true, it must let the request through, else it can go on to perform the checks you're doing now.
I'm not quite sure how you should best get at the request's header in your chosen web framework, which you don't mention, but if it was e.g webapp2 then something like:
#wraps(func)
def decorated_view(self, *args, **kwargs):
if self.request.headers.get('X-AppEngine-Cron') == 'true':
return func(self, *args, **kwargs)
# continue here with the other checks you do now
should do the trick.
The flask docs seem to say that you can't decorate your methods like that:
Decorating Views
Since the view class itself is not the view
function that is added to the routing system it does not make much
sense to decorate the class itself. Instead you either have to
decorate the return value of as_view() by hand:
def user_required(f):
"""Checks whether user is logged in or raises error 401."""
def decorator(*args, **kwargs):
if not g.user:
abort(401)
return f(*args, **kwargs)
return decorator
view = user_required(UserAPI.as_view('users'))
app.add_url_rule('/users/', view_func=view)
Starting with Flask 0.8 there is also an alternative way where you can
specify a list of decorators to apply in the class declaration:
class UserAPI(MethodView):
decorators = [user_required]
Due to the implicit self from the caller’s perspective you cannot use
regular view decorators on the individual methods of the view however,
keep this in mind.
I don't understand the reasoning, though.
I have a pretty small, basic site with a handful of views. Pretty much all of the views perform some interaction with an external API.
In each view I instantiate an object of my API class, which obviously is not very DRY. I also have a piece of middleware that adds one or two useful variables to the request object (my middleware runs last in the chain).
I was thinking of instantiating my api class in the middleware, and then passing it to each view as an argument, i.e.
def my_view(request, my_api_class_instance):
and then calling the view from the middleware, something like:
def process_view(self, request, view_func, view_args, view_kwargs):
my_api = api(some, args, here)
response = view_func(request, my_api, *view_args, **view_kwargs)
return response
It seems like a quick and easy way to tidy some code and reduce repetition. Is there anything inherently BAD about this?
If you look at the Django middleware documentation, you'll see;
If process_view returns an HttpResponse object, Django won’t bother calling any other view or exception middleware, or the appropriate view; it’ll apply response middleware to that HttpResponse, and return the result.
So returning an HttpResponse will skip the execution of other middleware classes below this one which in general should be avoided unless your middleware is the last one in settings.MIDDLEWARE_CLASSES list.
However, you still can bind your API object to HttpRequest instance passed on to middleware. It is the same approach to what AuhenticationMiddleware does in order to populate request.user.
def APIMiddleware(object):
def process_request(self, request):
request.api = API(host, port, user, password, extra={"url": request.get_full_path()})
you can just change view_kwargs in middleware
class SomeMiddleware(object):
def process_view(self, request, view_func, view_args, view_kwargs):
view_kwargs['my_api'] = api(some, args, here)
return None
def my_view(request, my_api):
# you can use you api there
def my_view(request, args, my_api)
# you can use you api there
document it's there
middleware returns None, Django will continue processing this request, executing any other process_view() middleware.
but, this only applies to every view function can got the keyword argument 'myapi', otherwise will raise the TypeError.
so the best way isn't pass your api by func arguments, like #ozgur pass your api by request.
You can use a middleware but there are two other possibilities too, both more flexible. The first one is to use a decorator and wrap the view:
#my_api_init_decorator
def my_view(request, my_api):
...
This allows you to explicitly select views, check user authorization or permissions before you init your api...
The second solution is to use class based views and create your own view you inherit from.
I have an app on GAE that checks if an administrator is logged in before it calls any webpage. I have tried various methods to manage the login process.
Q1 - What am I doing wrong with my decorator in example two?
Q2 - Does one normally do this check on the post function too?
Before I used an if statement in each get function. The problem is that I would repeat this if statement over and over in each function.
class IncomePage(webapp2.RequestHandler):
def get(self):
if users.is_current_user_admin():
self.response.write('My Webpage')
else:
self.response.write('Please Login')
Then I tried to make a decorator do that for me. It didn't work so what am I doing wrong.
def check(func):
if users.is_current_user_admin():
return func
else:
response.write('Please Login') ### Doesn't work
class IncomePage(webapp2.RequestHandler):
#check
def get(self):
self.response.write('My Webpage')
That's not a decorator. A decorator needs to return a wrapper function that is called in place of the actual function, and it's the wrapper that needs to do the test and then call the original.
def check(func):
def wrapper(*args, **kwargs):
if users.is_current_user_admin():
return func(*args, **kwargs)
else:
response.write('Please Login')
return wrapper
If all users of the handler must be logged in and be admin, then you can specify the restriction in the app.yaml rather than in your code.
See https://developers.google.com/appengine/docs/python/config/appconfig#Python_app_yaml_Requiring_login_or_administrator_status
And would look something like
- url: /admin/.*
script: somefile.application
login: admin
Be sure to read the docs completely not just skim. It is clear that you have some additional options
auth_fail_action
Describes the action taken when login is present and the user is not
logged in. Has two possible values:
redirect (the default). The user is redirected to the Google sign-in
page, or /_ah/login_required if OpenID authentication is used. The
user is redirected back to the application URL after signing in or
creating an account. unauthorized. The request is rejected with an
HTTP status code of 401 and an error message.
Further down in the document you will see examples.
As an alternative to your own decorator or securing via app.yaml.
webapp2 (which you are using ) has decorators for the handler to do what you require
See https://webapp-improved.appspot.com/api/webapp2_extras/appengine/users.html
Suppose I have view
def foo(request)
I'm also using custom user model as follow:
class MyUser(AbstractUser):
field = models.BooleanField()
I'd like to combine 2 django decorators: login_required and user_passed_test, so that anonymous user should be redirected to a login page and user who is not allowed to see the view (user.field == False) should see a proper message (say, something like 'you're not allowed to see this').
So I tried:
my_decor = user_passes_test(lambda user: user.field == True,
login_url='/bar')
def custom_decor(view_func):
decorated_view_func = login_required(my_decor(view_func), login_url='/login')
return decorated_view_func
And I also have to define view:
def bar(request):
return HttpResponse('you are not allowed to see this context.')
and hardcode it in my urls.
The question is: can I do this without creating an additional view and adding it to urls? Is there a way to make 'user_passed_test' decorator raising an 404 error instead of redirecting to a login view?
There is probably a very simple solution and surely I'm just missing it.
I dont remember very well the decorators syntax, but you can do:
def custom_decor(view_func):
def decorator(request, *args, **kwargs)
if request.user.field is True:
return view_func(request, *arg, **kwargs)
raise Http404
return decorator
so...
#login_required(login_url='/login')
#custom_decor
def foo(request)
# ...