Check authentification for each request in Bottle - python

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")

Related

Is it possible to use Flask-Login's authentication as simple HTTP auth for a REST API?

Right now, my webapp has a full auth system implemented through Flask-Login. But say that I have this controller:
#app.route("/search_query")
#login_required
def search_query():
query = request.args.get("query")
return jsonify(search(query))
This works great when you login to the page and make searches against it, if you're authenticated it goes through, otherwise it routes you back to the login page (through an unauthorized_handler function).
#lm.unauthorized_handler
def unauthorized():
return redirect(url_for("login"))
The problem is, I want to be able to use some form of simple HTTP authentication for this without having two separate auth systems so that I can make REST API calls against it. In short, I want to avoid having to use both Flask-Login and HTTPBasicAuth, and just use Flask-Login to achieve simple authentication, is this possible through Flask-Login?
Use the flask-login's request_loader to load from request auth header. You don't need to implement the decorator yourself, and current_user will point to the user loaded.
Example:
login_manager = LoginManager()
#login_manager.request_loader
def load_user_from_header():
auth = request.authorization
if not auth:
return None
user = User.verify_auth(auth.username, auth.password):
if not user:
abort(401)
return user
With that, you can mark a view as login required by:
#app.route('/user/info')
#login_required
def user_info():
return render_template('user_info.html', current_user.name)
If you looking for users to authenticate and make REST API calls they are going to have to send their credentials with every api call.
What you could do is a new decorator that checks for the auth header and authenticate the user.
Please refer to the entire app that I've posted on codeshare for more info:
http://codeshare.io/bByyF
Example using basic auth:
def load_user_from_request(f):
#wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if current_user.is_authenticated:
# allow user through
return f(*args, **kwargs)
if auth and check_auth(auth.username, auth.password):
return f(*args, **kwargs)
else:
return bad_auth()
return decorated
With that code you can now decorate your views to check for the header.
#app.route("/search_query")
#load_user_from_request
def search_query():
query = request.args.get("query")
return jsonify(search(query))
you can curl my example app like this:
curl --user admin#admin.com:secret http://0.0.0.05000/success

Cron job fails accessing url with admin_required decorator

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.

User authentication best practices

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

Testing Flask login and authentication?

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.

Create a Flask public url decorator

I'd like to create a decorator for Flask routes to flag certain routes as public, so I can do things like this:
#public
#app.route('/welcome')
def welcome():
return render_template('/welcome.html')
Elsewhere, here's what I was thinking the decorator and check would look like:
_public_urls = set()
def public(route_function):
# add route_function's url to _public_urls
# _public_urls.add(route_function ...?.url_rule)
def decorator(f):
return f
def requested_url_is_public():
from flask import request
return request.url_rule in _public_urls
Then when a request is made, I have a context function that checks requested_url_is_public.
I'm a bit stumped because I don't know how to get the url rule for a given function in the public decorator.
Perhaps this isn't the best design choice for Flask, but I'd expect there's another simple & elegant way to achieve this.
I've seen this patterns like this before, and would like to mimic it. For example, this is something of a counterpart to Django's login_required decorator.
I'd enjoy reading thoughts on this.
Flask already has a login_required decorator (see view decorators). If you are using public_urls to decide which urls to require authentication for, you are most likely better off using that.
I ended up doing something like this:
def public(endpoint):
"""A decorator for endpoints that flags them as publicly accessible
The endpoint is the Flask endpoint function. This is later tested by the
_is_public function, which is called before every request.
Note that #public must come AFTER route.add i.e.
#app.route('...')
#public
def handler(): ...
"""
#wraps(endpoint)
def public_endpoint(*args, **kwargs):
return endpoint(*args, **kwargs)
public_endpoint._is_public = True
return public_endpoint
and
def _is_public(endpoint):
"""Return true if the given endpoint function is public
Tests whether the #public decorator has been applied to the url.
"""
return getattr(endpoint, '_is_public', False) is True
#blueprint.before_app_request # or #app.before_request
def security_check():
"""Check all incoming requests for a current user.
"""
if current_user.is_logged_in: # need current_user test elsewhere
# we don't need to check if we have a public url if the user is
# logged in
return
try:
if _is_public(current_app.view_functions[request.endpoint]):
# we just go perform the endpoint function if it is public
return
except KeyError:
# There is no endpoint matching the request
abort(404)
# user is not logged in and it's not a public url
logging.info("No current user and %s is not public" % request.path[1:])
# send the user to the welcome page
return redirect(url_for("some_public_page"))

Categories

Resources