I am using the decorator below to authenticate endpoints in my application.
from google.appengine.api import users
from flask import redirect, render_template, request
from google.appengine.ext import ndb
def authenticate_admin(func):
def authenticate_and_call(*args, **kwargs):
user = users.get_current_user()
if user is None:
return redirect(users.create_login_url(request.url))
else:
email = user.email()
register_user_if_required(email, user)
if not users.is_current_user_admin():
return redirect_to_unauthorized(email)
return func(*args, **kwargs)
def redirect_to_unauthorized(email):
return render_template('xxxx/vvvv.html',
email=email,
users=users)
return authenticate_and_call
def register_user_if_required(email, user):
I have the following endpoint which only allows administrators to access it.
@admin_routes.route('/xxxx')
@authenticate_admin
def xxxxx():
return render_template('xxxx/xxxxx.html',
user=user,
logout=users.create_logout_url('/'))
And it works in the sense that only administrator's can access the above endpoint. However when I try to add a new endpoint with the same annotation but a different fancy url I get an error. Here is the code for the endpoint.
@admin_routes.route('/xxxx/bbbbbb')
@authenticate_admin
def abc():
.....
return render_template('xxxx/xxxx/zzzzz.html',
user=user,
breadcrumb=breadcrumb)
And here is the error I get when I run my app.
Traceback (most recent call last):
File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 240, in Handle
handler = _config_handle.add_wsgi_middleware(self._LoadHandler())
File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 299, in _LoadHandler
handler, path, err = LoadObject(self._handler)
File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py", line 85, in LoadObject
obj = __import__(path[0])
File "/Users/vinay/App-Engine/xxxxx/main.py", line 61, in <module>
app.register_blueprint(admin_routes)
File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/app.py", line 62, in wrapper_func
return f(self, *args, **kwargs)
File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/app.py", line 889, in register_blueprint
blueprint.register(self, options, first_registration)
File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/blueprints.py", line 153, in register
deferred(state)
File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/blueprints.py", line 172, in <lambda>
s.add_url_rule(rule, endpoint, view_func, **options))
File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/blueprints.py", line 76, in add_url_rule
view_func, defaults=defaults, **options)
File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/app.py", line 62, in wrapper_func
return f(self, *args, **kwargs)
File "/Users/vinay/App-Engine/xxxxx/server/lib/flask/app.py", line 984, in add_url_rule
'existing endpoint function: %s' % endpoint)
AssertionError: View function mapping is overwriting an existing endpoint function: admin_routes.authenticate_and_call
Traceback (most recent call last):
File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ereporter/ereporter.py", line 240, in emit
record.exc_info)
File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/api/datastore.py", line 2520, in RunInTransactionCustomRetries
return RunInTransactionOptions(options, function, *args, **kwargs)
File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/api/datastore.py", line 2630, in RunInTransactionOptions
ok, result = _DoOneTry(function, args, kwargs)
File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/api/datastore.py", line 2650, in _DoOneTry
result = function(*args, **kwargs)
File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/ext/ereporter/ereporter.py", line 270, in __EmitTx
handler=self.__RelativePath(os.environ['PATH_TRANSLATED']))
File "/Users/vinay/Desktop/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/request_environment.py", line 113, in __getitem__
return self._request.environ[key]
KeyError: 'PATH_TRANSLATED'
Logged from file wsgi.py, line 263
INFO 2015-08-09 03:19:14,731 module.py:812] default: "GET / HTTP/1.1" 500 -
You need to ensure your decorator wrapper has the same name as the wrapped view function, otherwise all your views look like the same endpoint (authenticate_and_call
).
You can do so with the @functool.wraps()
utility:
from functools import wraps
def authenticate_admin(func):
@wraps(func)
def authenticate_and_call(*args, **kwargs):
user = users.get_current_user()
if user is None:
return redirect(users.create_login_url(request.url))
else:
email = user.email()
register_user_if_required(email, user)
if not users.is_current_user_admin():
return redirect_to_unauthorized(email)
return func(*args, **kwargs)
def redirect_to_unauthorized(email):
return render_template('Admin/UnauthorizedAdmin.html',
email=email,
users=users)
return authenticate_and_call
This ensures that metadata such as the function name is copied over from func
to the authenticate_and_call
wrapper. From there on out @Flask.route()
can pick up that name to use as the endpoint name.