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
Related
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'm extremely confused as to how to test pages that require a login. I keep getting a 302 instead of a 200 in my response, and in inspecting the response in pdb I am definitely redirecting to a login page. I am using login middleware instead of the decorator, if that's relevant.
class SimplePageLoadsTestCase(TestCase):
def setUp(self):
self.client = Client()
self.user = User.objects.create_user('test_user')
self.client.login(username='test_user', password='test_user')
def test_login(self):
self.assertTrue(self.user.is_authenticated())
def test_index(self):
self.client.login(username='test_user', password='test_user')
response = self.client.get(reverse('index'))
self.assertEqual(response.status_code, 200)
The test_login test passes. I wasn't sure whether or not I needed to re-login the user per test (I think not, since I didn't need in test_login, but I've tried it both ways with the same result. I threw a few print statements in my view, and they do not output, so I know I'm not hitting the view at all, like I suspected.
I can provide the view or middleware if they're relevant.
EDIT: I disabled the middleware and replaced it with the #login_required decorator, and had the same problem.
EDIT AGAIN: Just to check, I took out all login checks, and everything worked (as I expected). So I'm nearly positive that the self.client just doesn't know I've logged in.
It doesn't look like you are creating your user with a password. Without providing a password it is considered a user that cannot be logged in. Providing a password to create_user should fix it
self.client = Client()
self.user = User.objects.create_user('test_user', password='test_user')
self.client.login(username='test_user', password='test_user')
I'm developing a Flask application and using Flask-security for user authentication (which in turn uses Flask-login underneath).
I have a route which requires authentication, /user. I'm trying to write a unit test which tests that, for an authenticated user, this returns the appropriate response.
In my unittest I'm creating a user and logging as that user like so:
from unittest import TestCase
from app import app, db
from models import User
from flask_security.utils import login_user
class UserTest(TestCase):
def setUp(self):
self.app = app
self.client = self.app.test_client()
self._ctx = self.app.test_request_context()
self._ctx.push()
db.create_all()
def tearDown(self):
if self._ctx is not None:
self._ctx.pop()
db.session.remove()
db.drop_all()
def test_user_authentication():
# (the test case is within a test request context)
user = User(active=True)
db.session.add(user)
db.session.commit()
login_user(user)
# current_user here is the user
print(current_user)
# current_user within this request is an anonymous user
r = test_client.get('/user')
Within the test current_user returns the correct user. However, the requested view always returns an AnonymousUser as the current_user.
The /user route is defined as:
class CurrentUser(Resource):
def get(self):
return current_user # returns an AnonymousUser
I'm fairly certain I'm just not fully understanding how testing Flask request contexts work. I've read this Flask Request Context documentation a bunch but am still not understanding how to approach this particular unit test.
The problem is different request contexts.
In your normal Flask application, each request creates a new context which will be reused through the whole chain until creating the final response and sending it back to the browser.
When you create and run Flask tests and execute a request (e.g. self.client.post(...)) the context is discarded after receiving the response. Therefore, the current_user is always an AnonymousUser.
To fix this, we have to tell Flask to reuse the same context for the whole test. You can do that by simply wrapping your code with:
with self.client:
You can read more about this topic in the following wonderful article:
https://realpython.com/blog/python/python-web-applications-with-flask-part-iii/
Example
Before:
def test_that_something_works():
response = self.client.post('login', { username: 'James', password: '007' })
# this will fail, because current_user is an AnonymousUser
assertEquals(current_user.username, 'James')
After:
def test_that_something_works():
with self.client:
response = self.client.post('login', { username: 'James', password: '007' })
# success
assertEquals(current_user.username, 'James')
The problem is that the test_client.get() call causes a new request context to be pushed, so the one you pushed in your the setUp() method of your test case is not the one that the /user handler sees.
I think the approach shown in the Logging In and Out and Test Adding Messages sections of the documentation is the best approach for testing logins. The idea is to send the login request through the application, like a regular client would. This will take care of registering the logged in user in the user session of the test client.
I didn't much like the other solution shown, mainly because you have to keep your password in a unit test file (and I'm using Flask-LDAP-Login, so it's nonobvious to add a dummy user, etc.), so I hacked around it:
In the place where I set up my test app, I added:
#app.route('/auto_login')
def auto_login():
user = ( models.User
.query
.filter_by(username="Test User")
.first() )
login_user(user, remember=True)
return "ok"
However, I am making quite a lot of changes to the test instance of the flask app, like using a different DB, where I construct it, so adding a route doesn't make the code noticeably messier. Obv this route doesn't exist in the real app.
Then I do:
def login(self):
response = self.app.test_client.get("/auto_login")
Anything done after that with test_client should be logged in.
From the docs: https://flask-login.readthedocs.io/en/latest/
It can be convenient to globally turn off authentication when unit testing. To enable this, if the application configuration variable LOGIN_DISABLED is set to True, this decorator will be ignored.
For every request in Bottle I would like to check if the request is eligible through HTTP authentication. My idea is to use a function, which is called at the start of every #route function.
def check_authentificaiton(requests):
auth = request.headers.get('Authorization')
credentials = parse_auth(auth)
if credentials[0] is not 'user' or credentials[1] is not 'password':
raise Exception('Request is not authorized')
This seems a bit redundant, since I want to protect every request, and it could fail if I forget to call it. Is there a better way?
I think you are looking for a decorator which mandates a route to be accessed only if the user is loggedin. Like in the example below, #require_uid is a decorator which you can use around any function where you need user to be logged in. Flask has a login_required decorator.
Using decorators to require sign in with bottle.py
def require_uid(fn):
def check_uid(**kwargs):
cookie_uid = request.get_cookie('cookieName', secret='cookieSignature')
if cookie_uid:
# do stuff with a user object
return fn(**kwargs)
else:
redirect("/loginagain")
return check_uid
#route('/userstuff', method='GET')
#require_uid
#view('app')
def app_userstuff():
# doing things is what i like to do
return dict(foo="bar")
I have some web pages I want people to be logged in to see. I'm using Facebook connect for login, and running the site on a Tornado server.
Right now, I do this:
class Home(BaseHandler):
def get(self):
if not self.current_user:
self.redirect("/")
else:
context = dict(current_user=self.current_user, facebook_app_id=FACEBOOK_APP_ID)
self.render("basic/home.html", **context)
Where BaseHandler is a modified handler that includes a method self.current_user that returns either a user object or None if there's no one logged in.
I was wondering what the best way is to rewrite this as a decorator which I could place on each handler I want to be locked?
Sounds like you haven't found the authenticated decorator
#tornado.web.authenticated
def get(self):
...
If you don't have a current_user it will redirect you to the login_url application setting.