Enabling digest authentication in Cherrypy server.conf - python

To enable digest auth in Cherrypy they say to use code like this:
from cherrypy.lib import auth_digest
USERS = {'jon': 'secret'}
conf = {
'/protected/area': {
'tools.auth_digest.on': True,
'tools.auth_digest.realm': 'localhost',
'tools.auth_digest.get_ha1': auth_digest.get_ha1_dict_plain(USERS),
'tools.auth_digest.key': 'a565c27146791cfb'
}
}
cherrypy.quickstart(myapp, '/', conf)
And it works pretty well. But I use server.conf file to store all the configs of my app and I want to continue of using this file. So I write there new section:
[/protected/area]
tools.auth_digest.on = True
tools.auth_digest.realm = 'localhost',
tools.auth_digest.get_ha1 = auth_digest.get_ha1_dict_plain({'jon': 'secret'}),
tools.auth_digest.key = 'a565c27146791cfb'
After thjis I've got errr:
ValueError: ('Config error in section: \'/protected/area\', option: \'tools.auth_digest.get_ha1\', value: "auth_digest.get_ha1_dict_plain({\'jon\': \'secret\'}),". Config values must be valid Python.', 'TypeError', ("unrepr could not resolve the name 'auth_digest'",))
I understand the reason but I dont know how to provide "valid Python" with server.conf. Help me, please.

You can make that function call in you application and use the resulting function in the config like:
myapp/__init__.py:
get_ha1 = auth_digest.get_ha1_dict_plain({'jon': 'secret'})
server.conf:
[/protected/area]
tools.auth_digest.on = True
tools.auth_digest.realm = 'localhost'
tools.auth_digest.get_ha1 = myapp.get_ha1
tools.auth_digest.key = 'a565c27146791cfb'
The problem with that is that you're defining credentials in code.
It might worth mention that you can use other functions no just the one that you define your users with plain texts passwords in a dict, you can use an htdigest file using the one from cherrypy.lib.auth_digest.get_ha1_file_htdigest or implement your own ha1 function like the one that the get_ha1_dict_plain returns:
def get_ha1_dict_plain(user_password_dict):
"""Returns a get_ha1 function which obtains a plaintext password from a
dictionary of the form: {username : password}.
If you want a simple dictionary-based authentication scheme, with plaintext
passwords, use get_ha1_dict_plain(my_userpass_dict) as the value for the
get_ha1 argument to digest_auth().
"""
def get_ha1(realm, username):
password = user_password_dict.get(username)
if password:
return md5_hex('%s:%s:%s' % (username, realm, password))
return None
return get_ha1
I implemented one that gets the ha1 from the database, for example using this sqlalchemy model (https://github.com/cyraxjoe/maki/blob/master/maki/db/models.py#L174-L189):
class User(Base):
__tablename__ = 'users'
name = Column(String(32), unique=True, nullable=False)
vname = Column(String(64))
email = Column(String(64), nullable=False)
ha1 = Column(String(32), nullable=False)
active = Column(Boolean, server_default='True')
#validates('ha1')
def validates_ha1(self, key, passwd):
if self.name is None:
raise Exception('Set the name first')
pack = ':'.join([self.name, maki.constants.REALM, passwd])
return hashlib.md5(pack.encode()).hexdigest()
And a get_ha1 function (https://github.com/cyraxjoe/maki/blob/master/maki/db/utils.py#L63):
def get_user_ha1(realm, username):
# realm is not used the stored hash already used it.
user = db.ses.query(db.models.User).filter_by(name=username).scalar()
if user is not None:
return user.ha1
The important part is that a ha1 is only an md5 hash of "user:real:password", you can implement that in a lot of different places.

Related

Flask-Sqlalchemy : How to call a function everytime a new instance of a model is commited?

I want to make a new randomized user id every time a new user is committed.
For this I am using the format : USR-<token>
The token is made by this function :
import string
import secrets
def make_token():
set = string.ascii_letters+string.digits
while True:
token = ''.join(secrets.choice(set) for i in range(6))
if(any(c.islower() for c in token) and
any(c.isupper() for c in token) and
sum(c.isdigit() for c in token)>=3 ):
break
return token
This function will be called in this database model :
class User(db.Model):
usrid = db.Column(db.String(10), primary_key=True,
default="USR-{}".format(make_token()))
usrnm = db.Column(db.String(20), unique=True, nullable = False)
I want the token to be new every time. But right now If I commit two User objects it gives me a Constraint Failed : Unique error. Also, I'm using SQLite for testing, if that makes any difference.
The model is bigger than this but for the question I think only these parts are relevant.
I think you need to pass the make_token function AS the default argument, instead of its return value.
So you could try having your make_token function prepend "USR-" to the string:
# ...
return "USR-" + token
And then pass that function as the default argument:
usrid = db.Column(db.String(10), primary_key=True,
default=make_token)

How do I configure parameters like fuzziness using flask_msearch?

I am trying to configure the flask_msearch ES search. I understand that it is not the most popular library but it is quite easy to start with. This is my first time using elasticsearch but I'd like to know if anyone knows how to configure parameter such as fuzziness using this library?
I can search using the query in the w_search function but I am struggling to find how I can configure it. Thank you in advance.
flask_msearch: https://github.com/honmaple/flask-msearch
my code:
class Post(db.Model):
__tablename__ = 'symptom_database'
__searchable__ = ['symptom']
id = db.Column(db.Integer, primary_key=True)
symptom = db.Column(db.String(100), unique=True)
def __repr__(self):
return '<Post %r>' % self.symptom
# views.py
#app.route("/search", methods = ['GET','POST'])
def w_search():
form = Post()
if request.method == 'POST':
keyword = request.form.get('keyword')
results = Post.query.msearch(keyword,fields=['symptom']).all()
print(results, flush=True)
return ''
return render_template('search.html')
ES fuzziness search use keyword regexp
Such as:
Post.query.msearch("{keyword}*".format(keyword=keyword),fields=['symptom']).all()
# or
Post.query.msearch("symptom:{keyword}*".format(keyword=keyword)).all()

Part of Data get lost on adding to session and commiting flask

The model is
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
artistname = db.Column(db.String(64))
photourl = db.Column(db.String(1000))
contactInfo = db.Column(db.String(20))
description = db.Column(db.String(500))
date = db.Column(db.Date)
def __repr__(self):
return '<User %r>' % (self.photourl)
Here photourl is the url of photos posted.
After form submission.
user = User(artistname = form.artist.data,photourl = "",
description = form.description.data,contactInfo = form.contactinfo.data,date = datetime.datetime.utcnow().date() )
I add all the details without photourl.
Now i make list of all the filenames which is stored in filename variable in below code.And join with * in middle.
filename = "*".join(filename)
print(filename)
The sample output appeared in terminal of printed filename is
mic16.jpg*nepal_earthquake_death6.png
After combining all the filenames. I store it in database by.
user.photourl = filename
print(user)
db.session.add(user)
db.session.commit()
Here printed output of user in terminal is
<User u'mic16.jpg*nepal_earthquake_death6.png'>
which shows that infomation is loaded correctly.
Now when I do db.session.add(user) followed by db.session.commit(). In user table of the database under photourl column only mic16.jpg part is stored and rest of the part is ommited i.e. part before * is stored.
There is no entry in the database.My database if a MYSQL database and using phpmyadmin. I am reading the database by using.
posts = User.query.order_by(User.date.desc()).limit(5).all()
photourls = []
for i in posts:
photourls.append(i.photourl.split('*'))
Required urls are to be in the photourls. But only a single url is present for each post.
I am just out of my mind and don't have clue of whats going on?
Judging by the size of your photourl string, you want to save several image filenames inside a string separated by an asterisk *. A better alternative would be storing the filenames in a JSON array with each filename as a string.
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
artistname = db.Column(db.String(64))
photourls = db.Column(JSON)
You can use getlist to upload several image files at once.
def upload():
uploaded_images = flask.request.files.getlist("file")
The JSON would be stored as shown below.
{
"photourls":["mic16.jpg", "nepal_earthquake_death6.png"]
}

SQL Alchemy overriding ==

I am creating SQLAlchemy class that represents user credentials.
I want to have field password that stores hashed value of password. Therefore I would like to override its behavior the following way:
When assigned credentials.password = value it actually stores hash of the value
When comparing credentials.password == value it actually compares with hash of the value
I have read the following part of SQLAlchemy documentation http://docs.sqlalchemy.org/en/rel_0_7/orm/mapper_config.html#using-descriptors-and-hybrids
And I think I do understand how to solve the issue number 1.
I am however unsure, how to do second point. Is there a way to do it the safe way (without breaking SQLAlchemy)?
Here is the example model:
class Credentials(Base):
__tablename__ = 'credentials'
id = Column(Integer, primary_key=True)
_password = Column('password', String)
#hybrid_property
def password(self):
return self._password
#password.setter(self):
self._password = hash(self._password)
For comparing, since you can't un-hash the password, you would need to create a custom type for the Column class, that over-rides the eq operator:
class MyPasswordType(String):
class comparator_factory(String.Comparator):
def __eq__(self, other):
return self.operate(operators.eq, hash(other))
Have a look at: http://docs.sqlalchemy.org/en/latest/core/types.html#types-operators
And http://docs.sqlalchemy.org/en/latest/core/types.html#sqlalchemy.types.TypeEngine.comparator_factory
To set you just need to pass in the value:
#password.setter
def password(self, value):
self._password = hash(value)

Flask-SQLalchemy update a row's information

How can I update a row's information?
For example I'd like to alter the name column of the row that has the id 5.
Retrieve an object using the tutorial shown in the Flask-SQLAlchemy documentation. Once you have the entity that you want to change, change the entity itself. Then, db.session.commit().
For example:
admin = User.query.filter_by(username='admin').first()
admin.email = 'my_new_email#example.com'
db.session.commit()
user = User.query.get(5)
user.name = 'New Name'
db.session.commit()
Flask-SQLAlchemy is based on SQLAlchemy, so be sure to check out the SQLAlchemy Docs as well.
There is a method update on BaseQuery object in SQLAlchemy, which is returned by filter_by.
num_rows_updated = User.query.filter_by(username='admin').update(dict(email='my_new_email#example.com')))
db.session.commit()
The advantage of using update over changing the entity comes when there are many objects to be updated.
If you want to give add_user permission to all the admins,
rows_changed = User.query.filter_by(role='admin').update(dict(permission='add_user'))
db.session.commit()
Notice that filter_by takes keyword arguments (use only one =) as opposed to filter which takes an expression.
This does not work if you modify a pickled attribute of the model. Pickled attributes should be replaced in order to trigger updates:
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
from pprint import pprint
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqllite:////tmp/users.db'
db = SQLAlchemy(app)
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True)
data = db.Column(db.PickleType())
def __init__(self, name, data):
self.name = name
self.data = data
def __repr__(self):
return '<User %r>' % self.username
db.create_all()
# Create a user.
bob = User('Bob', {})
db.session.add(bob)
db.session.commit()
# Retrieve the row by its name.
bob = User.query.filter_by(name='Bob').first()
pprint(bob.data) # {}
# Modifying data is ignored.
bob.data['foo'] = 123
db.session.commit()
bob = User.query.filter_by(name='Bob').first()
pprint(bob.data) # {}
# Replacing data is respected.
bob.data = {'bar': 321}
db.session.commit()
bob = User.query.filter_by(name='Bob').first()
pprint(bob.data) # {'bar': 321}
# Modifying data is ignored.
bob.data['moo'] = 789
db.session.commit()
bob = User.query.filter_by(name='Bob').first()
pprint(bob.data) # {'bar': 321}
Just assigning the value and committing them will work for all the data types but JSON and Pickled attributes. Since pickled type is explained above I'll note down a slightly different but easy way to update JSONs.
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(80), unique=True)
data = db.Column(db.JSON)
def __init__(self, name, data):
self.name = name
self.data = data
Let's say the model is like above.
user = User("Jon Dove", {"country":"Sri Lanka"})
db.session.add(user)
db.session.flush()
db.session.commit()
This will add the user into the MySQL database with data {"country":"Sri Lanka"}
Modifying data will be ignored. My code that didn't work is as follows.
user = User.query().filter(User.name=='Jon Dove')
data = user.data
data["province"] = "south"
user.data = data
db.session.merge(user)
db.session.flush()
db.session.commit()
Instead of going through the painful work of copying the JSON to a new dict (not assigning it to a new variable as above), which should have worked I found a simple way to do that. There is a way to flag the system that JSONs have changed.
Following is the working code.
from sqlalchemy.orm.attributes import flag_modified
user = User.query().filter(User.name=='Jon Dove')
data = user.data
data["province"] = "south"
user.data = data
flag_modified(user, "data")
db.session.merge(user)
db.session.flush()
db.session.commit()
This worked like a charm.
There is another method proposed along with this method here
Hope I've helped some one.
Models.py define the serializers
def default(o):
if isinstance(o, (date, datetime)):
return o.isoformat()
def get_model_columns(instance,exclude=[]):
columns=instance.__table__.columns.keys()
columns=list(set(columns)-set(exclude))
return columns
class User(db.Model):
__tablename__='user'
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
.......
####
def serializers(self):
cols = get_model_columns(self)
dict_val = {}
for c in cols:
dict_val[c] = getattr(self, c)
return json.loads(json.dumps(dict_val,default=default))
In RestApi, We can update the record dynamically by passing the json data into update query:
class UpdateUserDetails(Resource):
#auth_token_required
def post(self):
json_data = request.get_json()
user_id = current_user.id
try:
instance = User.query.filter(User.id==user_id)
data=instance.update(dict(json_data))
db.session.commit()
updateddata=instance.first()
msg={"msg":"User details updated successfully","data":updateddata.serializers()}
code=200
except Exception as e:
print(e)
msg = {"msg": "Failed to update the userdetails! please contact your administartor."}
code=500
return msg
I was looking for something a little less intrusive then #Ramesh's answer (which was good) but still dynamic. Here is a solution attaching an update method to a db.Model object.
You pass in a dictionary and it will update only the columns that you pass in.
class SampleObject(db.Model):
id = db.Column(db.BigInteger, primary_key=True)
name = db.Column(db.String(128), nullable=False)
notes = db.Column(db.Text, nullable=False)
def update(self, update_dictionary: dict):
for col_name in self.__table__.columns.keys():
if col_name in update_dictionary:
setattr(self, col_name, update_dictionary[col_name])
db.session.add(self)
db.session.commit()
Then in a route you can do
object = SampleObject.query.where(SampleObject.id == id).first()
object.update(update_dictionary=request.get_json())
Update the Columns in flask
admin = User.query.filter_by(username='admin').first()
admin.email = 'my_new_email#example.com'
admin.save()
To use the update method (which updates the entree outside of the session) you have to query the object in steps like this:
query = db.session.query(UserModel)
query = query.filter(UserModel.id == user_id)
query.update(user_dumped)
db.session.commit()

Categories

Resources