0

I'm using the SECURITY_TRACKABLE feature for Flask-Security, and in my custom API login request handler I am trying to make sure to commit the datastore changes, as required by the flask-security documentation on login_user().

My login code is inside a blueprint:

from modules.auth.models import User
from flask import Blueprint, request, abort
from flask_restful import Api, Resource
from flask_security import login_required
from flask_security.utils import verify_password, login_user, logout_user 

app = Blueprint(__name__, __name__)
api = Api(app)

class LogResource(Resource):
    """
    Manages user login and logout
    """

    def post(self):
        user = User.query.filter_by(
            email = request.form['email']
        ).first()
        if not user:
            abort(401, "Wrong credentials.")

        if verify_password(request.form['password'], user.password):
            login_user(user)
            app.security.datastore.commit()
            return "Logged in"
        else:
            abort(401, description="Wrong credentials.")

But when the user logs in I got the error: AttributeError: 'Blueprint' object has no attribute 'security', because I'm inside a blueprint and not the app. How can I fix this?

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
andrea56
  • 397
  • 3
  • 21
  • You are referencing `app` here, but the exception says it is a blueprint. How is `app` defined in your code here? – Martijn Pieters Oct 27 '18 at 16:08
  • Or put differently, can you show a *full traceback* that shows what line the exception is thrown on? – Martijn Pieters Oct 27 '18 at 16:08
  • Your link to Flask-Security points to the *Utils* section of the API documentation. That's not really a helpful link, as there is nothing there about your feature. – Martijn Pieters Oct 27 '18 at 16:10
  • `app` is defined inside app.py and i don't know how to access it – andrea56 Oct 27 '18 at 16:11
  • The first element of the Utils section describes how to use the tracking feature with the login function but it's not very clear – andrea56 Oct 27 '18 at 16:13
  • All you need to do is enable the feature, using `SECURITY_TRACKABLE=True`, and provided your user model [supports the required extra fields](https://pythonhosted.org/Flask-Security/models.html#trackable) you are all set. You don't need to provide a login route yourself, that's taken care of for you. – Martijn Pieters Oct 27 '18 at 16:16
  • If you have a different user-login route too, then yes, you need to call `security.datastore.commit()` on the Flask-Security instance. Your error is due to `app` not being what you think it is, not because your route is in a blueprint. – Martijn Pieters Oct 27 '18 at 16:17
  • You need to debug why `app` is a blueprint object and not the module you think it is. Perhaps you used a `from app import *` line at the top, for example. You didn't share that here so we can't correct that error. – Martijn Pieters Oct 27 '18 at 16:18
  • I already did the configuration stated in the documentation and i didn't import nothing from app – andrea56 Oct 27 '18 at 16:23
  • You confused me with *`app` is defined inside app.py and i don't know how to access it*. You haven't actually shown where you create the `Security()` instance. – Martijn Pieters Oct 27 '18 at 16:34

1 Answers1

2

The Security() object is never a direct attribute of the Flask application object. Your error here is that app is a Blueprint object, which is confusing matters more. You usually should not use app for a blueprint object anyway.

You can import the object from the module where you create the Security(...) instance in the first place, or you can access it via the Flask extensions mapping via the current_app reference:

from flask import current_app

security = current_app.extensions['security']  # or reference .datastore, etc.

Next, you generally want to commit the access after the response is complete, as this helps produce a faster result for the end user and lets you record response status too. Use the after_this_request() function to run the commit after the response:

from flask import current_app, after_this_request

def flask_security_datastore_commit(response=None):
    datastore = current_app.extensions['security'].datastore
    datastore.commit()
    return response

and in your view use:

if verify_password(request.form['password'], user.password):
    login_user(user)
    after_this_request(flask_security_datastore_commit)
    return "Logged in"
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • I think this is the solution but after testing i got another error `AttributeError: '_SecurityState' object has no attribute 'commit'` – andrea56 Oct 27 '18 at 16:43
  • @andrea56: mea culpa, not sure why the `.datastore` reference that should have been there was omitted. – Martijn Pieters Oct 27 '18 at 16:49