Related
I have openapi that defines API with this securityDefinitions:
securityDefinitions:
APIKeyHeader:
type: apiKey
in: header
name: Authorization
security:
- APIKeyHeader: []
When I start the project I get this warning:
WARNING 2022-01-27 13:24:41,001 connexion.operations.secure security_decorator 142 : ... x-apikeyInfoFunc missing
And such error when I try to use API methods:
INFO 2022-01-27 13:56:15,256 connexion.api.security get_authorization_info 131 : ... No auth provided. Aborting with 401.
I found that I need to specify x-apikeyInfoFunc in securityDefinitions. I specified a function that I believe does authentication:
securityDefinitions:
APIKeyHeader:
type: apiKey
in: header
name: Authorization
x-apikeyInfoFunc: util.authentication_decorator.authenticate
security:
- APIKeyHeader: []
The function itself:
def authenticate(arg: Optional[Sequence[str]] = DEFAULT_SCOPE):
""" decorator to handle api key authentication """
def decorator(fun):
""" decorator that gets applied to the function """
def wrapper(*a, **kw):
""" function wrapper """
# pylint: disable=unused-argument
api_key = request.headers.get('Authorization')
if validate_scope(api_key, scopes):
# return fun(*a, **kw)
return fun()
LOGGER.debug('Invalid or missing API key in request')
return {'msg': 'Make sure you supply your API key with sufficient scope in the Authorization header'}, 403
return wrapper
if callable(arg):
scopes = DEFAULT_SCOPE
return decorator(arg)
scopes = arg
return decorator
The function is used as a decorator to authenticate every API method. When I start the project I don't get warning. But I get another error when I actually trying to use one of API method:
ERROR 2022-01-28 13:50:03,330 openapi_helper.app_helper log_exception 1891: Exception on /v1/jira/search_issues_by_tags [GET]
Traceback (most recent call last):
File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 2446, in wsgi_app
response = self.full_dispatch_request()
File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1951, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1820, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python3.6/site-packages/flask/_compat.py", line 39, in reraise
raise value
File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1949, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/local/lib/python3.6/site-packages/flask/app.py", line 1935, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "/usr/local/lib/python3.6/site-packages/connexion/decorators/decorator.py", line 48, in wrapper
response = function(request)
File "/usr/local/lib/python3.6/site-packages/connexion/decorators/security.py", line 322, in wrapper
token_info = get_authorization_info(auth_funcs, request, required_scopes)
File "/usr/local/lib/python3.6/site-packages/connexion/decorators/security.py", line 127, in get_authorization_info
token_info = func(request, required_scopes)
File "/usr/local/lib/python3.6/site-packages/connexion/decorators/security.py", line 284, in wrapper
token_info = apikey_info_func(apikey, required_scopes=required_scopes)
TypeError: authenticate() got an unexpected keyword argument 'required_scopes'
I'm stuck on this point, don't have idea how to proceed. connexion 2.6.0 is used in this case.
According to Connexion docs, the x-apikeyInfoFunc function must have two parameters: apikey and required_scopes.
Example 1
Example 2
I'm using python Flask with WTForm and I'm having serious issues with the populate_obj method:
Traceback (most recent call last):
File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 2088, in __call__
return self.wsgi_app(environ, start_response)
File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 2073, in wsgi_app
response = self.handle_exception(e)
File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 2070, in wsgi_app
response = self.full_dispatch_request()
File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1515, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1513, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/local/lib/python3.9/site-packages/flask/app.py", line 1499, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
File "/app/my_data/main.py", line 29, in submit
form.populate_obj(demande)
File "/usr/local/lib/python3.9/site-packages/wtforms/form.py", line 95, in populate_obj
field.populate_obj(obj, name)
File "/usr/local/lib/python3.9/site-packages/wtforms/fields/core.py", line 877, in populate_obj
raise TypeError('populate_obj: cannot find a value to populate from the provided obj or input data/defaults')
TypeError: populate_obj: cannot find a value to populate from the provided obj or input data/defaults
Here's my code (nothing fancy):
form = DemandeForm(data=request.form)
if form.validate():
form.populate_obj(demande)
flash('Enregistré', 'success')
return redirect(url_for('main.index'))
I could detail all the forms fields, but this would be tedious. My guess is that this as to do with nested forms I'm using:
class DemandeForm(BaseForm):
AdressePrivee = FormField(AdresseForm)
AdresseProfessionnelle = FormField(AdresseForm)
Prestations = FieldList(FormField(PrestationForm), min_entries=1)
So my questions are:
why is the error rising?
how can I have more explicit error, telling me which fields are raising the issue?
how can I solve this?
I'm completely locked right now :(
Thanks for support
EDIT
So I finally managed to confirm that the issue comes from the FormField(AddressField).
Here's my Demande model extract:
AdressePrivee = db.relationship(AdressePrivee, backref='Demande', lazy=False, uselist=False)
And in DemandeForm:
AdressePrivee = FormField(AdresseForm)
Wtform is not happy because demande.AdressePrivee == None, hence self._obj is None in the code and the error rises.
I tried to add default value:
AdressePrivee = FormField(AdresseForm, default=AdressePrivee())
But then I get an error while saving, as AddressePrivee fields are empty while they shouldn't...
Ain't there a missing option, like nullable: True or something? I'm not sure it makes sense that the error rises, nor how I can handle this situation...
Thanks
Here is the relevant source code for the WTF function, this is where the error is triggered:
def populate_obj(self, obj, name):
candidate = getattr(obj, name, None)
if candidate is None:
if self._obj is None:
raise TypeError('populate_obj: cannot find a value to populate from the provided obj or input data/defaults')
candidate = self._obj
setattr(obj, name, candidate)
self.form.populate_obj(candidate)
Source
self._obj is assigned a value in the routine process.
So it seems that there is something wrong with the variable you are passing. In fact I don't understand why you are passing the equivalent of current_user to your form. Look here for an example of usage.
I am trying to access MeisterTask's API with Python and Flask, and no matter what I do, I seem to always get a 302 code in return from the API, although I can get an access token (or so I think). Here is the code I have so far (I tried reducing it, this is the smallest snippet I could get that replicates the error):
from flask import Flask, redirect, url_for, session, request, jsonify
from flask_oauthlib.client import OAuth
app = Flask(__name__)
app.debug = True
app.secret_key = "development"
oauth = OAuth(app)
meistertask = oauth.remote_app(
'meistertask',
consumer_key= "XXXXXX",
consumer_secret= "XXXXXX",
request_token_params={"scope" : "meistertask"},
base_url='https://www.meistertask.com/api',
request_token_url=None,
access_token_method='GET',
access_token_url='https://www.mindmeister.com/login/oauth2/token',
authorize_url='https://www.mindmeister.com/oauth2/authorize'
)
#app.route('/')
def index():
if 'meistertask_token' in session:
me = meistertask.get('user')
return jsonify(me.data)
return redirect(url_for('login'))
#app.route('/login')
def login():
return meistertask.authorize(callback=url_for('authorized', _external=True))
#app.route('/logout')
def logout():
session.pop('meistertask_token', None)
return redirect(url_for('index'))
#app.route('/login/authorized')
def authorized():
resp = meistertask.authorized_response()
print(resp.get('code'))
if resp is None or resp.get('code') is None:
return 'Access denied: reason=%s error=%s resp=%s' % (
request.args['error'],
request.args['error_description'],
resp
)
session['meistertask_token'] = (resp['code'], '')
return "Hello"
#meistertask.tokengetter
def get_meistertask_oauth_token():
return session.get('meistertask_token')
if __name__ == "__main__":
app.run()
And here is the traceback:
flask_oauthlib.client.OAuthException: Invalid response from meistertask
Traceback (most recent call last):
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 2309, in __call__ return self.wsgi_app(environ, start_response)
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 2295, in wsgi_app response = self.handle_exception(e)
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 1741, in handle_exception reraise(exc_type, exc_value, tb)
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\_compat.py", line 35, in reraise raise value
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 2292, in wsgi_app response = self.full_dispatch_request()
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 1815, in full_dispatch_request rv = self.handle_user_exception(e)
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 1718, in handle_user_exception reraise(exc_type, exc_value, tb)
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\_compat.py", line 35, in reraise raise value
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 1813, in full_dispatch_request rv = self.dispatch_request()
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask\app.py", line 1799, in dispatch_request return self.view_functions[rule.endpoint](**req.view_args)
File "~\Documents\MeisterTaskServer\hello.py", line 49, in authorized resp = meistertask.authorized_response()
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask_oauthlib\client.py", line 707, in authorized_response data = self.handle_oauth2_response(args)
File "~\AppData\Local\Programs\Python\Python37-32\lib\site-packages\flask_oauthlib\client.py", line 692, in handle_oauth2_response
Things I have tried
Tried to modify the method to get the access token from GET to POST. The API clearly states that I should use GET, yet every other example I have seen on flask_oauthlib's GitHub uses POST (the examples are 3 years old, but some still work, namely the GitHub one). Either give the same result.
Tried doing it barebones, without any library. The resulting code was thrice as long and also had more problems.
Used Django instead of Flask. Never even managed to get the hello world example going, it was too much work, and also I have discovered the library flask_oauthlib.
Things worth mentioning
I derived this code from this here GitHub example
There is also code there I omitted in order to keep the snippet short, that establishes that the server should use SSL (as per the request from the API that the redirect_uri should use HTTPS)
The app manages to redirect me over at MeisterTask and asks for my permission. Once I grant it, it redirects to "https://127.0.0.1:5000/login/authorized?code=some_token" where I get the traceback. If I look with Chrome's debugging tools to the requests made and what I receive, I see that I get an 302 from the API, but I also get an access token.
I run Windows 10 with Python 3.7.0
So what's the deal? What's the next step here? I've run out of things to try. Thank you for taking the time to solve this!
I'm trying to apply a decorator (Flask-HTTPAuth's login_required) conditionally. If sky_is_blue == True, I want to apply the decorator, if False, to not.
This needs to happen on call, as it could change during the lifetime of the application (actually not so much in practice, but definitely for unit testing purposes, and I'm curious about the cause in any case).
So I wrapped the decorator in a decorator.
Behavior is as expected in the False case (not applying the decorator), but I'm having trouble applying the decorator in the True case. I'm not sure if this is something I've done wrong, or a strange interaction with Flask-HTTPAuth.
The following script demonstrates the problem with two unit tests. test_sky_not_blue passes, but test_sky_blue fails with a stack trace.
from flask import Flask
from flask.ext.httpauth import HTTPBasicAuth
from functools import update_wrapper, wraps
from flask.ext.testing import TestCase
import unittest
app = Flask(__name__)
app.config['TESTING'] = True
sky_is_blue = True
auth = HTTPBasicAuth()
class ConditionalAuth(object):
def __init__(self, decorator):
print("ini with {}".format(decorator.__name__))
self.decorator = decorator
update_wrapper(self, decorator)
def __call__(self, func):
print("__call__: ".format(func.__name__))
#wraps(func)
def wrapped(*args, **kwargs):
print("Wrapped call, function {}".format(func.__name__))
if sky_is_blue:
rv = self.decorator(func(*args, **kwargs))
return rv
else:
rv = func(*args, **kwargs)
return rv
return wrapped
#app.route('/')
#ConditionalAuth(auth.login_required)
def index():
"""
Get a token
"""
return "OK"
class TestSky(TestCase):
def create_app(self):
return app
def test_sky_blue(self):
global sky_is_blue
sky_is_blue = True
response = self.client.get('/')
self.assert200(response)
def test_sky_not_blue(self):
global sky_is_blue
sky_is_blue = False
response = self.client.get('/')
self.assert200(response)
def suite():
return unittest.makeSuite(TestSky)
if __name__ == '__main__':
unittest.main(defaultTest='suite')
The full stack trace I get is:
Traceback (most recent call last):
File "test.py", line 64, in test_sky_blue
response = self.client.get('/')
File "/usr/local/lib/python2.7/site-packages/werkzeug/test.py", line 778, in get
return self.open(*args, **kw)
File "/usr/local/lib/python2.7/site-packages/flask/testing.py", line 108, in open
follow_redirects=follow_redirects)
File "/usr/local/lib/python2.7/site-packages/werkzeug/test.py", line 751, in open
response = self.run_wsgi_app(environ, buffered=buffered)
File "/usr/local/lib/python2.7/site-packages/werkzeug/test.py", line 668, in run_wsgi_app
rv = run_wsgi_app(self.application, environ, buffered=buffered)
File "/usr/local/lib/python2.7/site-packages/werkzeug/test.py", line 871, in run_wsgi_app
app_rv = app(environ, start_response)
File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 1836, in __call__
return self.wsgi_app(environ, start_response)
File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 1820, in wsgi_app
response = self.make_response(self.handle_exception(e))
File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 1403, in handle_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 1817, in wsgi_app
response = self.full_dispatch_request()
File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 1477, in full_dispatch_request
rv = self.handle_user_exception(e)
File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 1381, in handle_user_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 1475, in full_dispatch_request
rv = self.dispatch_request()
File "/usr/local/lib/python2.7/site-packages/flask/app.py", line 1461, in dispatch_request
return self.view_functions[rule.endpoint](**req.view_args)
File "test.py", line 40, in wrapped
rv = self.decorator(func(*args, **kwargs))
File "/usr/local/lib/python2.7/site-packages/flask_httpauth.py", line 48, in login_required
#wraps(f)
File "/usr/local/Cellar/python/2.7.11/Frameworks/Python.framework/Versions/2.7/lib/python2.7/functools.py", line 33, in update_wrapper
setattr(wrapper, attr, getattr(wrapped, attr))
AttributeError: 'str' object has no attribute '__module__'
Tested with Python 2.7.11, Flask-HTTPAuth==2.7.1, Flask==0.10.1, any insights would be greatly appreciated.
It's funny how effective laying a problem out is at helping one solve it.
The problem was the parenthesis in the decorator call:
rv = self.decorator(func(*args, **kwargs))
Changing it to the following fixes it:
rv = self.decorator(func)(*args, **kwargs)
The decorator needs to return a function, but by passing the arguments to func() directly I wasn't giving it a chance to do that.
Breaking it into a separate call would have made this clearer, I think:
decorated_function = self.decorator(func)
return decorated_function(*args, **kwargs))
Conditionally turning authorization 'on/off' seems like it is also provided out of the box using the optional keyword argument to the auth.login_required decorator.
From the API docs:
An optional optional argument can be set to True to allow the route to execute also when authentication is not included with the request, in which case auth.current_user() will be set to None. Example:
#auth.login_required(optional=True)
def private_page():
user = auth.current_user()
return "Hello {}!".format(user.name if user is not None else 'anonymous')
Interesting question. Note that if all you want is to optionally bypass authentication logic, there is a much easier way to do it, without having to use a new decorator. Just incorporate the bypass logic into your verify_password callback:
#auth.verify_password
def verify(username, password):
if not sky_is_blue:
return True # let the request through, no questions asked!
# your authentication logic here
return False # this will trigger a 401 response
Now you can apply the login_required decorator as usual, and authentication will succeed whenever sky_is_blue == False:
#app.route('/')
#auth.login_required
def index():
"""
Get a token
"""
return "OK"
Hope this helps!
Here's a solution if you need to apply a conditional authentication check on all routes without defining the login_required wrapper on all of them. Simply use the before_request hook:
#app.before_request
def conditional_auth_check():
if your_condition:
#auth.login_required
def _check_login():
return None
return _check_login()
login_required doesn't necessarily needs to wrap a route directly.
FYI, had a small requirement of only applying the conditional auth on one route and no auth on other health check routes. This is how I did it by extending #epoc's answer:
#app.before_request
def conditional_auth_check():
"""
Conditional Authentication
1. If called predict
a. with a certain system header, let it pass
b. Else, check basic auth
2. Anything else - let it pass
"""
if request.path == "/predict":
# Check Header Auth here
if request.headers.get("my-header") and os.getenv("system-enabled-var"):
print("Authorizing using headers")
return
# Check Basic Auth here
print("Authorizing using Basic Auth")
#basic_auth.login_required
def _check_login():
return None
return _check_login()
# let all other traffic pass
return
In my case, system-enabled-var can never be enabled by a user and can only be enabled by a certain system.
This bug is driving me nuts. I am trying to create the backend of a website in python using flask. I am getting the following error and traceback:
Traceback (most recent call last):
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1836, in __call__
return self.wsgi_app(environ, start_response)
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1820, in wsgi_app
response = self.make_response(self.handle_exception(e))
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1403, in handle_exception
reraise(exc_type, exc_value, tb)
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1817, in wsgi_app
response = self.full_dispatch_request()
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1478, in full_dispatch_request
response = self.make_response(rv)
File "/usr/local/lib/python2.7/dist-packages/flask/app.py", line 1577, in make_response
rv = self.response_class.force_type(rv, request.environ)
File "/usr/local/lib/python2.7/dist-packages/werkzeug/wrappers.py", line 827, in force_type
response = BaseResponse(*_run_wsgi_app(response, environ))
File "/usr/local/lib/python2.7/dist-packages/werkzeug/test.py", line 855, in run_wsgi_app
app_iter = app(environ, start_response)
TypeError: 'int' object is not callable
Bizarrely, the traceback does not contain any of my files so I can't figure out what's causing it or how to fix it.
Here are the relevant methods that I wrote:
#app.route('/notes/<filenum>', methods=['GET', 'POST'])
def view(filenum):
if request.method == 'GET':
pass
else:
if 'username' in session:
if request.values['fnct'] == "delete":
asdf = database.delNote(filenum,session['username'])
return asdf
return "invalid command"
else:
return "not logged in"
def delNote(self, noteid,username):
try:
if self.getPost(noteid)[7] == self.usernameToID(username):
for file in glob.glob('uploads/' + str(noteid)+"-*"):
os.remove(file)
self._execute('DELETE FROM Posts where postID=?',(noteid,))
return 1
else:
return -2 #autherror
except IndexError:
return -1 #note does not exist
When I call delNote(), it works flawlessly without any errors. When I run view(filenum) with the line that calls delNote() commented out, it works flawlessly with no errors. When I call delNote from view, then I get the above error.
It seems that in spite of the error, the function does complete it's intended task. Using print statements, I figured out the entirety of view() runs before the error, but the error is thrown before the return asdf line is run. Can someone please explain to me what is going on because I'm resisting the growing temptation of throwing my computer out the window.
Thanks in advance
The short answer is, you're returning an integer from your controller and Flask doesn't support that.
Here's the relevant docs:
http://flask.pocoo.org/docs/0.10/api/#flask.Flask.make_response
Controllers must return a Response object or a string (or a few other specialized things). You can't return integers, they don't work. That error is because Flask can't figure out what to do with it, so it's assuming it's a callable, and ints are not callables.
The hacktaculuar way of solving your problem is just return str(asdf), but don't do that. It looks like you're trying to show error codes, so you almost certainly want to use the Flask abort() function instead and return the appropriate HTTP status.
Looking at your view procedure, I see the following:
asdf = database.delNote(filenum,session['username'])
There is no return value from the delNote method, so you're assigning None to asdf. I'm not familiar with Flask, but it's probably doing something with that value that it ultimately can't handle. The error you're getting about an int not being callable makes sense then, as None can be coerced to the integer 0. There's no need to assign your delNote to anything. Try this and see if it makes a difference:
database.delNote(filenum, session['username'])
return "Note deleted."