Reputation: 3135
Say I have a user model like:
class User(db.Model, UserMixin):
id = db.Column(db.Integer, primary_key=True)
first_name = db.Column(db.String(255))
last_name = db.Column(db.String(255))
email = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
registered_on = db.Column(db.DateTime, nullable=True)
roles = db.relationship('Role', secondary=roles_users,
backref=db.backref('users', lazy='dynamic'))
And an admin view:
class UserView(MyModelView):
form_columns = (
'roles',
'first_name',
'last_name',
'email',
'password',
'registered_on',
)
form_args = dict(
registered_on=dict(default=datetime.now())
)
When I create a new user how to I automatically generate a password hash with something like bcrypt?
Upvotes: 5
Views: 8342
Reputation: 1047
I think by automatic you mean that it should automatically convert a manually-supplied plain-text password into a hash, but I am not certain. I am answering as if this assumption is correct.
We don't expose our password fields in Flask-Admin, but we handle the basic question of how and when passwords get hashed by defining password
as a property with a setter responsible for computing the hash itself and tucking it away in user._password
.
A naive swing at just trying to add password
as a column in the admin view didn't work, but if you use sqlalchemy's hybrid_property
instead of property
, it looks like this works fine with "password"
in your User view's list of form_columns
(as you already have):
# models.py
from sqlalchemy.ext.hybrid import hybrid_property
class User(sql.Model):
# ...
_password = sql.Column(sql.Binary)
_salt = sql.Column(sql.Binary, default=lambda: os.urandom(512))
# ...
@hybrid_property
def password(self):
"""Return the hashed user password."""
return self._password
@password.setter
def password(self, new_pass):
"""Salt/Hash and save the user's new password."""
new_password_hash = compute_new_password_hash(new_pass, self._salt)
self._password = new_password_hash
# admin.py
class UserView(MyModelView):
# ...
form_columns = ("email", "password", ...)
The editing experience isn't exactly polished--you'd probably need to override the field to refine it. Here are some screenshots:
Creating a new user with a pre-set password:
The edit view after creation:
Updating the user's password:
The edit view after saving the new password:
Upvotes: 7
Reputation: 517
You can set a PasswordField
widget for the password
field, and override the process_formdata
method to set de hash.
from flask_security.utils import hash_password from wtforms import PasswordField class AdminPasswordField(PasswordField): def process_formdata(self, valuelist): if valuelist and valuelist[0] != '': self.data = hash_password(valuelist[0]) elif self.data is None: self.data = '' class UserView(MyModelView): form_columns = ( 'roles', 'first_name', 'last_name', 'email', 'password', 'registered_on', ) form_args = dict( registered_on=dict(default=datetime.now()) ) form_overrides = { 'password': AdminPasswordField, }
Upvotes: 2
Reputation: 721
Internally, Flask-Admin
calls WTF
's populate_obj
to set the data from the form.
So we can take advantage of that as shown below:
class CustomPasswordField(PasswordField): # If you don't want hide the password you can use a StringField
def populate_obj(self, obj, name):
setattr(obj, name, hash_password(self.data)) # Password function
Then we can use it in our ModelView
as follows:
class UserView(ModelView):
form_extra_fields = {
'password': CustomPasswordField('Password', validators=[InputRequired()])
}
The password will be hashed the moment the form is saved.
Upvotes: 1
Reputation: 9595
The easiest option I've found is to add an SQLAlchemy attribute event on User.password
and hash the password there if changed. This way, anytime a user password is changed from anywhere, it'll be automatically hashed.
from sqlalchemy import event
from werkzeug.security import generate_password_hash
@event.listens_for(User.password, 'set', retval=True)
def hash_user_password(target, value, oldvalue, initiator):
if value != oldvalue:
return generate_password_hash(value)
return value
Upvotes: 15
Reputation: 3135
You can use Flask-Admin's on_model-change
and give it logic before it commits to the database.
from flask_admin.contrib import sqla
from flask_security import utils
class UserAdmin(sqla.ModelView):
def on_model_change(self, form, model, is_created):
model.password = utils.encrypt_password(model.password)
# as another example
if is_created:
model.registered_on = datetime.now()
Upvotes: 0