Flask admin overrides password when user model is changed
Asked Answered
B

2

6

I am currently diving into a flask project and try to use flask-admin for the first time. Everything is working fine so far, but one thing really bothers me: Whenever I edit my User model the users password gets overwritten. I am following the advice given in the second answer of this question to prevent flask-admin from re-hashing my password. Unfortunately the emptied password field still gets written to the database.

I tried to get the current password from the User-Model which is given as a parameter to the on_model_change method, but somehow the password seems to be already overwritten at that point (or it is not the actual database model I am looking at here - I am a little bit confused here).

Here is what my code looks like:

User-Model

class User(UserMixin, SurrogatePK, Model):
    """A user of the app."""

    __tablename__ = 'users'
    username = Column(db.String(80), unique=True, nullable=False)
    email = Column(db.String(80), unique=True, nullable=False)
    #: The hashed password
    password = Column(db.String(128), nullable=True)
    created_at = Column(db.DateTime, nullable=False,
                        default=datetime.datetime.utcnow)
    first_name = Column(db.String(30), nullable=True)
    last_name = Column(db.String(30), nullable=True)
    active = Column(db.Boolean(), default=False)
    is_admin = Column(db.Boolean(), default=False)

    def __init__(self, username="", email="", password=None, **kwargs):
        """Create instance."""
        db.Model.__init__(self, username=username, email=email, **kwargs)
        if password:
            self.set_password(password)
        else:
            self.password = None

    def __str__(self):
        """String representation of the user. Shows the users email address."""
        return self.email

    def set_password(self, password):
        """Set password"""
        self.password = bcrypt.generate_password_hash(password)

    def check_password(self, value):
        """Check password."""
        return bcrypt.check_password_hash(self.password, value)

    def get_id(self):
        """Return the email address to satisfy Flask-Login's requirements"""
        return self.id

    @property
    def full_name(self):
        """Full user name."""
        return "{0} {1}".format(self.first_name, self.last_name)

    @property
    def is_active(self):
        """Active or non active user (required by flask-login)"""
        return self.active

    @property
    def is_authenticated(self):
        """Return True if the user is authenticated."""
         if isinstance(self, AnonymousUserMixin):
            return False
        else:
            return True

    @property
    def is_anonymous(self):
        """False, as anonymous users aren't supported."""
        return False

Flask-Admin UserView

class UserView(MyModelView):
    """Flask user model view."""
    create_modal = True
    edit_modal = True

    def on_model_change(self, form, User, is_created):
        if form.password.data is not None:
            User.set_password(form.password.data)
        else:
           del form.password

    def on_form_prefill(self, form, id):
        form.password.data = ''                                              

Any help is highly appreciated. Thanks in advance,

oneiro

Brindisi answered 27/8, 2016 at 20:0 Comment(0)
O
12

Might be easier to override the get_edit_form method and delete the password field entirely from the edit form.

class UserView(MyModelView):
    def get_edit_form(self):
        form_class = super(UserView, self).get_edit_form()
        del form_class.password
        return form_class

Another alternative would be to remove the model password field entirely from the form and use a dummy password field that can then be used to populate the model's password. By removing the real password field Flask-Admin will not step on our password data. Example :

class UserView(MyModelView):
    form_excluded_columns = ('password')
    #  Form will now use all the other fields in the model

    #  Add our own password form field - call it password2
    form_extra_fields = {
        'password2': PasswordField('Password')
    }

    # set the form fields to use
    form_columns = (
        'username',
        'email',
        'first_name',
        'last_name',
        'password2',
        'created_at',
        'active',
        'is_admin',
    )

    def on_model_change(self, form, User, is_created):
        if form.password2.data is not None:
            User.set_password(form.password2.data)
Occident answered 28/8, 2016 at 10:29 Comment(2)
Thanks for the response. I though about that, too. But then i would need to create another view for the passwords which also derives from the User-Model which flask-admin somehow wouldn't want me to do. I will definitely try to make your advice work, though. Thanks.Brindisi
Thanks alot. I had to make one tiny change, but now everything seems to work fine. I just added form.password2.data != ' ' to the condition inside on_model_change. This seems to be necessary, because the password2-field don't defaults to None even if it is empty. Thanks again :)Brindisi
T
0

I have faced a similar problem. I needed to generate password's hash when the field of the password had been changed. I did not want to add an additional form for changing the password. On backend I used MongoDB. My solution for flask admin:

class User(db.Document, UserMixin):
    ***
    password = db.StringField(verbose_name='Password')
    roles = db.ListField(db.ReferenceField(Role), default=[] 

    def save(self) -> None:
        if not self.id:
            self.password = hashlib.md5((self.password + Config.SECURITY_PASSWORD_SALT).encode()).hexdigest()
            return super(User, self).save(self)
        else:
            return super(User, self).update(
            ***
            password = self.password,
            )

class UserModelView(ModelView):   
    def on_model_change(self, form, model, is_created):
        user = User.objects(id=model.id)[0]
        if user.password != form.password.data:
            model.password = hashlib.md5((form.password.data + Config.SECURITY_PASSWORD_SALT).encode()).hexdigest()

admin.add_view(UserModelView(User, 'Users'))

For SQL solutions it will be actual as well.

Tisatisane answered 20/3, 2020 at 18:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.