I'm just put HTTP authentication for my Flask application and my test is broken. How do I mock request.authentication to make the test pass again?
Here's my code.
server_tests.py
def test_index(self):
res = self.app.get('/')
self.assertTrue('<form' in res.data)
self.assertTrue('action="/upload"' in res.data)
self.assertEquals(200, res.status_code)
server.py
def check_auth(username, password):
"""This function is called to check if a username /
password combination is valid.
"""
return username == 'fusiontv' and password == 'fusiontv'
def authenticate():
"""Sends a 401 response that enables basic auth"""
return Response(
'Could not verify your access level for that URL.\n'
'You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'})
def requires_auth(f):
#wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
return decorated
#app.route("/")
#requires_auth
def index():
return render_template('index.html')
Referring to How do I mock dependencies of the views module of my Flask application in flask-testing?, you can mock it via the import chain.
Assuming server_tests imports application imports server, you probably want something like:
server_tests.py
def setUp(self):
application.server.request.authorization = MagicMock(return_value=True)
Related
I've inherited a custom built web app built on python that currently utilizes our older LDAP instance that we are retiring, and I need to change it to use our MSAD LDAP schema for authorization instead. Anonymous bind is disabled in the MSAD, so I need to add a bind user/pass to connect to the MSAD LDAP instance, but don't know where/how. Below is the excerpt from the custom config.ini file and the authorization.py script
<config.ini>
ldap_server = ldap://ldap3.internaldomain.local
bind_organisation = cn=Company,ou=People
domain_components = dc=internaldomain,dc=local
allowed_groups =
allowed_users =
bind_timeout = 10.0
<authorization.py>
from flask import request, Response, render_template
from ldap_logging import LDAPLoggingClassWrapper
from global_config import config
import ldap
def check_auth(username, password):
try:
ldap_client = LDAPLoggingClassWrapper(ldap.initialize(config.get("LDAP", "ldap_server")))
ldap_client.set_option(ldap.OPT_NETWORK_TIMEOUT, config.getfloat("LDAP", "bind_timeout"), log_id=username)
basedn = "uid=%s,%s,%s" % (username, config.get("LDAP", "bind_organisation"), config.get("LDAP", "domain_components"))
ldap_client.simple_bind_s(basedn,password, log_id=username)
ldap_client.unbind_s(log_id=username)
return True
except (ldap.INVALID_CREDENTIALS, ldap.NO_SUCH_OBJECT) as e:
return False
def authenticate():
"""Sends a 401 response that enables basic auth"""
return Response(
'Authentication failed, you must sign in using your domain credentials to use this app.\n'
'Please contact ITS if you feel your details were correct and that this message is in error.', 401,
{'WWW-Authenticate': 'Basic realm="Please login with your domain credentials"'})
def requires_auth(f):
#wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
try:
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
except Exception as e:
return render_template('server-down.html',
message = e
)
return f(*args, **kwargs)
return decorated
This Flask endpoint is what I am trying hit with Insomnia with a jwt token on a POST:
#app.route('/', methods=['POST'])
#token_required
def json_payloader():
try:
some code to do stuff...
Poking around the internet no matter what I try I always get back a:
{
"message": "Token is missing!"
}
Bearer authentication token:
OR with authentication set to None and just trying headers with the token this also fails:
Any tips try greatly appreciated.
EDIT token_required function
from flask import Flask, request, jsonify
import flask
from flask.helpers import make_response
import jwt
from functools import wraps
def token_required(f):
#wraps(f)
def decorated(*args, **kwargs):
token = request.args.get('token')
if not token:
return jsonify({'message': 'Token is missing!'}), 403
try:
data = jwt.decode(token, app.config['SECRET_KEY'])
except:
return jsonify({'message': 'Token is invalid'}), 403
return f(*args, **kwargs)
return decorated
Unless you're using a different version of flask-jwt (or flask-jwt-extended) then I believe the correct function decorator is #jwt_required()
Seems like you're using JWTs. So, the correct decorator to be used is #jwt_required.
Please see the example from https://flask-jwt-extended.readthedocs.io/en/stable/basic_usage/#basic-usage .
#app.route("/protected", methods=["GET"])
#jwt_required()
def protected():
# Access the identity of the current user with get_jwt_identity
current_user = get_jwt_identity()
return jsonify(logged_in_as=current_user), 200
If you want to create your own implementation, you can retrieve it like
auth_header = request.headers.get("Bearer", "").strip().strip(",")
I am using basic authentication with the decorator in a flask application.
The code looks like:
from flask import Flask, Response, request
from functools import wraps
app = Flask(__name__)
app.config.from_object('settings')
def valid_credentials(username, password):
return username == app.config['USER'] and password == app.config['PASS']
def authenticate(f):
#wraps(f)
def wrapper(*args, **kwargs):
auth = request.authorization
if not auth.username or not auth.password or not valid_credentials(auth.username, auth.password):
return Response('Login!', 401, {'WWW-Authenticate': 'Basic realm="Login!"'})
return f(*args, **kwargs)
return wrapper
#app.route('/')
def index():
return 'Hello, world!'
#app.route('/secure')
#authenticate
def secure():
return 'Secure!'
#app.route('/check')
#authenticate
def check():
secure()
return 'checked'
if __name__ == '__main__':
app.run(debug=True)
But I am not able to call the secure function from check function due to the authentication. Now is it possible to call the function in the current scenario?
The usual method is to keep helper methods separate from views. For example:
def _secure():
return 'Secure!'
#app.route('/secure')
#authenticate
def secure():
return _secure()
You can then reuse the helper method (_secure()) from other places. Since it doesn't have a route associated it is not possible for a visitor to run it without authentication.
It is also a good idea to keep these helper methods in a separate module (such as helpers.py or utils.py).
I have 2 Flask apps (different projects) that work together . One implements some API which uses tokens for auth. The second one consumes the API and makes a web interface for it. Now I have a login function that sends the username and password to the API, and if correct, gets the auth token in return. Once I have the token, I save it to the session of the user and the user should now be considered as logged in/ autheticated. How can I implement the login_required decorator for such a case.
Here is my login function -
def login(self):
response = make_request(BASE_URL + 'login/', clean_data(self.data))
if response.status_code == 200:
session['auth_token'] = response.json().get('auth_token')
return True
return False
How can I make the login_required decorator?
Also I am using Redis to store sessions if that matters.
Have a look at the official flask docs regarding decorators:
https://flask.palletsprojects.com/en/1.1.x/patterns/viewdecorators/ or the python docs https://www.python.org/dev/peps/pep-0318/ as well.
Your decorator should look something like:
from functools import wraps
from flask import abort
import jwt
def authorize(f):
#wraps(f)
def decorated_function(*args, **kws):
if not 'Authorization' in request.headers:
abort(401)
user = None
data = request.headers['Authorization'].encode('ascii','ignore')
token = str.replace(str(data), 'Bearer ','')
try:
user = jwt.decode(token, JWT_SECRET, algorithms=['HS256'])['sub']
except:
abort(401)
return f(user, *args, **kws)
return decorated_function
... and then in your app.py you may have:
#app.route('/api/game', methods=['POST'])
#authorize
def create(user):
data = json.loads(request.data)
....
In this particular case I have used JWT as token and your token can be different respectively the decoding of the token can be your custom implementation, but the basic mechanisms are pretty much as on the example above.
I would place the following decorator function in somewhere common
def validate_api_token(validation_func):
def decorator(f):
#wraps(f)
def decorated_function(*args, **kws):
api_token = request.headers.get('Authorization')
is_valid_api_token = validation_func(api_token)
if is_valid_api_token:
return f(*args, **kws)
return 'Invalid API Token', 401
return decorated_function
return decorator
For small POC flask apps, if you're ok with storing the tokens in a non-versioned file, the following can work:
# tokens are read from a non-versioned `.tokens` file and loaded into a set
api_tokens = load_api_tokens()
def simple_api_token_validation(api_token):
return api_token in api_tokens
#app.route("/v1/my/secret/function", methods=['POST'])
#validate_api_token(simple_api_token_validation)
def my_secret_function():
body = request.get_json()
# ...
Another simple option is to query against a database (e.g. redis):
redis_session = Redis(host=REDIS_HOST, password=REDIS_PASSWORD)
def redis_api_token_validation(api_token):
if not api_token:
return False
api_token_hash = hashlib.sha256(api_token.encode()).hexdigest()
return redis_session.exists(f'api:tokens:{api_token_hash}')
#app.route("/v1/my/secret/function", methods=['POST'])
#validate_api_token(redis_api_token_validation)
def my_secret_function():
body = request.get_json()
# ...
Best IMO as #Velin answered is to use jwt to validate the token
Given that each subsequent request will contain the API token, the decorator should do the following
Accept a generic request. You can use *args and **kargs for that
Extract the token from the header and compare it with the token stored in db (not Redis, but wherever the token generated is stored in the backend)
If authenticated, the *args and **kargs should be passed on to the decorated function
The output of the decorated function should then be returned as is
If the authentication failed, an error message should be returned.
For explanation on decorators, check out this link:
http://thecodeship.com/patterns/guide-to-python-function-decorators/
I am running a Flask web app and using Apache basic authentication(with .htaccess and .htpasswd files) to password protect it. I want to password protect only one webpage in the app. When I password protect the html file for the webpage there is no effect and the webpage is still not password protected. Could this be because it is my python file that is calling the html file using render_template? I'm not sure how to fix this issue.
You need to restrict access to your endpoint. This snippet should get you started down the right path.
from functools import wraps
from flask import request, Response
def check_auth(username, password):
"""This function is called to check if a username /
password combination is valid.
"""
return username == 'admin' and password == 'secret'
def authenticate():
"""Sends a 401 response that enables basic auth"""
return Response(
'Could not verify your access level for that URL.\n'
'You have to login with proper credentials', 401,
{'WWW-Authenticate': 'Basic realm="Login Required"'})
def requires_auth(f):
#wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
return authenticate()
return f(*args, **kwargs)
return decorated
With this, you could decorate any endpoint you want to restrict with #requires_auth.
#app.route('/secret-page')
#requires_auth
def secret_page():
return render_template('secret_page.html')