From aa1ad6652543f85730aec108b6a8f8c2c4d4b838 Mon Sep 17 00:00:00 2001 From: Antonio de la Rosa Date: Sun, 29 Sep 2024 18:26:51 +0200 Subject: [PATCH] More fixes for modernize codebase --- paramecio/citoplasma/sendmail.py | 53 ++++++- .../cromosoma/extrafields/passwordfield.py | 44 +++++- paramecio/modules/admin/index.py | 137 ++---------------- setup.py | 2 +- 4 files changed, 109 insertions(+), 127 deletions(-) diff --git a/paramecio/citoplasma/sendmail.py b/paramecio/citoplasma/sendmail.py index 84f7374..588e7ae 100644 --- a/paramecio/citoplasma/sendmail.py +++ b/paramecio/citoplasma/sendmail.py @@ -1,4 +1,23 @@ #!/usr/bin/env python3 + +""" +Parameciofm is a series of wrappers for bottle.py, mako and others and construct a simple headless cms. + +Copyright (C) 2024 Antonio de la Rosa Caballero + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +""" import os import smtplib import mimetypes @@ -13,6 +32,8 @@ import ssl as ssl_module import sys class SendMail: + """Class for send email + """ port=587 @@ -25,6 +46,20 @@ class SendMail: #ssl=True def __init__(self, ssl=True): + """Class for send email + + Class for send email using standard python library + + Attributes: + port (int): The port used for send email, by default 587 + host (str): The hostname of mail server used for send the email + username (str): The username for login in mail server + password (str): The password for login in mail server + smtp (smtplib.SMTP): The python SMTP object used for send emails + txt_error: (str): If error, is saved in this attribute + + """ + self.smtp=None #smtplib.SMTP(host=self.host, port=self.port) self.txt_error='' @@ -79,9 +114,9 @@ class SendMail: return False - except smtplib.SMTPAuthenticationError: + except smtplib.SMTPAuthenticationError as eauth: - self.txt_error='Error: cannot login. Wrong username or password' + self.txt_error='Error: cannot login. Wrong username or password '+eauth.__str__() return False @@ -94,6 +129,18 @@ class SendMail: return True def send(self, from_address, to_address: list, subject, message, content_type='plain', attachments=[]): + """ Method that send email + + With this method you can send email to multiple address, html, and add attachments to email + + Args: + from_address (str): The adress used for send the email + to_address (list): A list of emails where the email will be sended. + subject (str): The subject of the email + message (str): The content of the message + content_type (str): The type of mail content, can be plain or html. + attachments (list): A list with a serie of file paths for attach to the email. + """ if self.smtp==None: if not self.connect(): @@ -174,12 +221,14 @@ class SendMail: return True def quit(self): + """Function used when you need quit connection for any reason""" if self.smtp!=None: self.smtp.quit() self.smtp=None def __del__(self): + """Method for clean the connection when the object is closed""" if self.smtp!=None: diff --git a/paramecio/cromosoma/extrafields/passwordfield.py b/paramecio/cromosoma/extrafields/passwordfield.py index 9c4cce0..bd4ae06 100644 --- a/paramecio/cromosoma/extrafields/passwordfield.py +++ b/paramecio/cromosoma/extrafields/passwordfield.py @@ -1,9 +1,38 @@ +#!/usr/bin/python3 + +""" +Parameciofm is a series of wrappers for bottle.py, mako and others and construct a simple headless cms. + +Copyright (C) 2024 Antonio de la Rosa Caballero + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU Affero General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU Affero General Public License for more details. + +You should have received a copy of the GNU Affero General Public License +along with this program. If not, see . +""" + from paramecio.cromosoma.corefields import PhangoField from paramecio.cromosoma.coreforms import PasswordForm from hmac import compare_digest as compare_hash -import crypt +from hmac import compare_digest as compare_hash +#try: +# import crypt +#except: +# pass + +#import bcrypt +from argon2 import PasswordHasher class PasswordField(PhangoField): + """Field for check and save passwords""" def __init__(self, name, size=1024, required=False): @@ -43,7 +72,10 @@ class PasswordField(PhangoField): #salt=crypt.mksalt(crypt.METHOD_SHA512) if self.encrypt_password: - value=crypt.crypt(value) + #value=crypt.crypt(value) + ph=PasswordHasher() + final_value=ph.hash(value) + return final_value """ else: @@ -57,8 +89,14 @@ class PasswordField(PhangoField): @staticmethod def verify( password, h): + """Static method used for verify a password save using PasswordField""" #return bcrypt_sha256.verify(password, h) - return compare_hash(h, crypt.crypt(password, h)) + #return compare_hash(h, crypt.crypt(password, h)) + ph=PasswordHasher() + try: + return ph.verify(h, password) + except: + return False # Old function bcrypt diff --git a/paramecio/modules/admin/index.py b/paramecio/modules/admin/index.py index 7f1e713..0fae535 100644 --- a/paramecio/modules/admin/index.py +++ b/paramecio/modules/admin/index.py @@ -24,6 +24,8 @@ from paramecio.wsgiapp import app import copy from paramecio.i18n import admin +admin_app=Bottle() + #from citoplasma.login import LoginClass # Check login @@ -70,11 +72,11 @@ if hasattr(config, 'admin_templates_index'): num_template+=1 -@app.get('/'+config.admin_folder) -@app.get('/'+config.admin_folder+'/') -@app.post('/'+config.admin_folder+'/') -@app.get('/'+config.admin_folder+'//') -@app.post('/'+config.admin_folder+'//') +@app.get('/admin') +@admin_app.get('/') +@admin_app.post('/') +@admin_app.get('//') +@admin_app.post('//') def home(module='', submodule='', t=t): # A simple boolean used for show or not the code of admin module in standard template @@ -248,121 +250,12 @@ def home(module='', submodule='', t=t): return "" -@app.post('/'+config.admin_folder+'/login') +@admin_app.post('/login') def login(): return login_model(UserAdmin) - - """ - connection=WebModel.connection() - - user_admin=UserAdmin(connection) - - getpostfiles=GetPostFiles() - - getpostfiles.obtain_post() - - getpostfiles.post['username']=getpostfiles.post.get('username', '') - getpostfiles.post['password']=getpostfiles.post.get('password', '') - - username=user_admin.fields['username'].check(getpostfiles.post['username']) - - password=getpostfiles.post['password'].strip() - - user_admin.conditions=['WHERE username=%s', [username]] - - arr_user=user_admin.select_a_row_where(['id', 'password', 'privileges', 'lang', 'num_tries', 'email']) - - if arr_user==False: - - s=get_session() - - s['csrf_token']=create_key_encrypt() - - s.save() - connection.close() - return {'error': 1, 'csrf_token': s['csrf_token']} - else: - - num_tries=int(arr_user['num_tries']) - - if arr_user['num_tries']<3: - - if user_admin.fields['password'].verify(password, arr_user['password']): - - s=get_session() - - s['id']=arr_user['id'] - s['login']=1 - s['privileges']=arr_user['privileges'] - s['lang']=arr_user['lang'] - s['email']=arr_user['email'] - - if s['lang']=='': - s['lang']=I18n.default_lang - - remember_login=getpostfiles.post.get('remember_login', '0') - - if remember_login=='1': - - timestamp=time()+315360000 - - random_text=create_key_encrypt() - - #Update user with autologin token - - user_admin.check_user=False - - user_admin.conditions=['WHERE username=%s', [username]] - - user_admin.valid_fields=['token_login'] - - user_admin.reset_require() - - if user_admin.update({'token_login': random_text}): - - response.set_cookie('remember_login', random_text, path=config.session_opts['session.path'], expires=timestamp, secret=key_encrypt) - #else: - #print(user_admin.query_error) - s.save() - - connection.close() - - return {'error': 0} - else: - - user_admin.check_user=False - - user_admin.conditions=['WHERE username=%s', [username]] - - user_admin.valid_fields=['num_tries'] - - user_admin.reset_require() - - user_admin.update({'num_tries': arr_user['num_tries']+1}) - - s=get_session() - - s['csrf_token']=create_key_encrypt() - - s.save() - - connection.close() - - return {'error': 1, 'csrf_token': s['csrf_token']} - else: - s=get_session() - - s['csrf_token']=create_key_encrypt() - - s.save() - - connection.close() - - return {'error': 1, 'csrf_token': s['csrf_token']} - """ -@app.post('/'+config.admin_folder+'/register') +@admin_app.post('/register') def register(): getpostfiles=GetPostFiles() @@ -423,7 +316,7 @@ def register(): return {'error': 1} -@app.get('/'+config.admin_folder+'/logout') +@admin_app.get('/logout') def logout(): s=get_session() @@ -446,7 +339,7 @@ def logout(): redirect(make_url(config.admin_folder)) -@app.get('/'+config.admin_folder+'/recovery_password') +@admin_app.get('/recovery_password') def recovery_password(): t=PTemplate(env) @@ -465,7 +358,7 @@ def recovery_password(): connection.close() return t.load_template('admin/recovery.phtml', forms=forms) -@app.post('/'+config.admin_folder+'/recovery_password') +@admin_app.post('/recovery_password') def send_password(): connection=WebModel.connection() @@ -522,13 +415,13 @@ def send_password(): return {'email': '', 'error': 0} -@app.get('/'+config.admin_folder+'/check_token') +@admin_app.get('/check_token') def check_token(): t=PTemplate(env) return t.load_template('admin/check_token.phtml') -@app.post('/'+config.admin_folder+'/check_token') +@admin_app.post('/check_token') def check_code_token(): t=PTemplate(env) @@ -583,3 +476,5 @@ def check_code_token(): s.save() return {'token': 'Error: token is not valid', 'error': 1, 'csrf_token': s['csrf_token']} + +app.mount('/'+config.admin_folder+'/', admin_app) diff --git a/setup.py b/setup.py index 95f1847..299cf2f 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ setup(name='paramecio', url='https://git.cuchulu.com/paramecio/parameciofm/', packages=['paramecio', 'paramecio.i18n', 'paramecio.settings'], include_package_data=True, - install_requires=['bottle', 'mako', 'pymysql', 'sqlalchemy', 'oslo.concurrency', 'itsdangerous', 'colorama','cherrypy', 'arrow'], + install_requires=['bottle', 'mako', 'pymysql', 'sqlalchemy', 'oslo.concurrency', 'itsdangerous', 'colorama','cherrypy', 'arrow', 'argon2-cffi', 'pillow'], entry_points={'console_scripts': [ 'paramecio = paramecio.console:start', 'parameciodb = paramecio.cromosoma.dbamin.start'