Commit f672f0df by Luciano Barletta

improved everything

1 parent 6bf00945
...@@ -129,6 +129,8 @@ Hay un archivo que hace los [tests](/test_flask.py) automáticamente si se corre ...@@ -129,6 +129,8 @@ Hay un archivo que hace los [tests](/test_flask.py) automáticamente si se corre
Para agregar más tests, por cada uno debe existir una función y agregar un thread en main. Para agregar más tests, por cada uno debe existir una función y agregar un thread en main.
Para testear se debe tener una carpeta llamada 'testfolder' donde poder tirar los archivos para testeo.
## Mejoras no implementadas ## Mejoras no implementadas
......
from enums import Table, States, Services, Datatypes from enums import Table, States, Services, Datatypes
from services import serviceFactory
import sqlite3, ipdb, json import sqlite3, ipdb, json
class DBconnection: class DBconnection:
...@@ -7,12 +8,13 @@ class DBconnection: ...@@ -7,12 +8,13 @@ class DBconnection:
# Estructura de las tablas # Estructura de las tablas
structure = { structure = {
Table.id : "integer PRIMARY KEY", Table.id : "INTEGER PRIMARY KEY",
Table.path : "text", Table.path : "TEXT",
Table.serv : "text", Table.serv : "TEXT",
Table.dest : "text", Table.dest : "TEXT",
Table.type : "text", Table.type : "TEXT",
Table.state : "text" Table.info : "TEXT",
Table.state : "TEXT"
} }
def __init__(self,db): def __init__(self,db):
...@@ -48,11 +50,12 @@ class DBconnection: ...@@ -48,11 +50,12 @@ class DBconnection:
return "La tabla " + table + " no existe o no está contemplada" return "La tabla " + table + " no existe o no está contemplada"
query = "INSERT INTO " + table + "(" query = "INSERT INTO " + table + "("
values = " VALUES(" values = " VALUES("
checked = self.check(insertions)
if type(checked) == str: # error
return checked
for column in insertions: for column in insertions:
if not self.check(column,insertions[column]):
return "El dato '" + insertions[column] + "' no es valido"
query += column + "," query += column + ","
values += "'" + insertions[column] + "'," values += "'" + str(insertions[column]) + "',"
query = query.strip(",") + ")" query = query.strip(",") + ")"
values = values.strip(",") + ")" values = values.strip(",") + ")"
con = sqlite3.connect(self.db) con = sqlite3.connect(self.db)
...@@ -70,11 +73,12 @@ class DBconnection: ...@@ -70,11 +73,12 @@ class DBconnection:
if not Table.validate(comparator[0]): if not Table.validate(comparator[0]):
return "El comparador no es una columna valida" return "El comparador no es una columna valida"
query = "UPDATE " + table + " SET " query = "UPDATE " + table + " SET "
where = " WHERE " + comparator[0] + "=" + comparator[1] where = " WHERE " + comparator[0] + "=" + str(comparator[1])
checked = self.check(alterations)
if type(checked) == str: # error
return checked
for column in alterations: for column in alterations:
if not self.check(column,alterations[column]): query += column + "='" + str(alterations[column]) + "',"
return "El dato '" + alterations[column] + "' no es valido"
query += column + "='" + alterations[column] + "',"
query = query.strip(",") query = query.strip(",")
con = sqlite3.connect(self.db) con = sqlite3.connect(self.db)
cursor = con.cursor() cursor = con.cursor()
...@@ -82,22 +86,29 @@ class DBconnection: ...@@ -82,22 +86,29 @@ class DBconnection:
con.commit() con.commit()
con.close() con.close()
def check(self,column,data): def check(self,information):
if column == Table.path: valid = True
return True for column in information:
if column == Table.serv:
return Services.validate(data) # columna no existe en la tabla
if column == Table.dest: if not Table.validate(column):
return True return "La columna '" + column + "' no existe"
if column == Table.type:
data = json.loads(data) # chequeos de validez de datos
valid = True if column == Table.serv:
for key in data: valid = Services.validate(information[column])
valid = valid and Datatypes.validate(data[key]) elif column == Table.type:
return valid data = json.loads(information[column])
if column == Table.state: for key in data:
return States.validate(data) valid = valid and Datatypes.validate(data[key])
return False elif column == Table.info:
return serviceFactory(information[Table.serv]).validateinfo(information[column])
elif column == Table.state:
valid = States.validate(information[column])
if not valid:
return "El dato '" + str(information[column]) + "' no es valido"
return None
@staticmethod @staticmethod
def parseToTable(rows): def parseToTable(rows):
......
...@@ -10,6 +10,10 @@ app = Flask(__name__) ...@@ -10,6 +10,10 @@ app = Flask(__name__)
# carpeta de los mensajes # carpeta de los mensajes
msgfolder = "msg/" msgfolder = "msg/"
# id mínimo para una tabla
minid = 1
# id máximo para una tabla
maxid = 2**63
# tiempo para reintentar mensajhes # tiempo para reintentar mensajhes
retry_timer = 10 retry_timer = 10
# tiempo para limpieza # tiempo para limpieza
...@@ -43,25 +47,28 @@ def data(): ...@@ -43,25 +47,28 @@ def data():
key = request.files.get('key') key = request.files.get('key')
if key: if key:
request.files[file].save(path + "rand.key.enc") request.files['key'].save(path + "rand.key.enc")
# denecripto llave simétrica con mi llave privada y la guardo en la carpeta del emisor # denecripto llave simétrica con mi llave privada y la guardo en la carpeta del emisor
os.system("openssl rsautl -decrypt -inkey rsa_key.pri -in " + path + "rand.key.enc -out " + path + "rand.key") os.system("openssl rsautl -decrypt -inkey rsa_key.pri -in " + path + "rand.key.enc -out " + path + "rand.key")
os.remove(path + "rand.key.enc") os.remove(path + "rand.key.enc")
for file in request.files: for file in request.files:
# si hay llave y no es esta # si hay llave y no es esta
filepath = path + file
if key and file != "key": if key and file != "key":
request.files[file].save(filepath + ".enc") request.files[file].save(filepath + ".enc")
# desencripto archivo con la llave simétrica y lo guardo en la carpeta del emisor # desencripto archivo con la llave simétrica y lo guardo en la carpeta del emisor
os.system("openssl enc -d -aes-256-cbc -in " + filepath + ".enc -out " + filepath + " -pass file:" + path + "rand.key") os.system("openssl enc -d -aes-256-cbc -in " + filepath + ".enc -out " + filepath + " -pass file:" + path + "rand.key")
os.remove(filepath + ".enc") os.remove(filepath + ".enc")
else: else:
request.files[file].save(path + request.files[file].filename) request.files[file].save(filepath)
if key: if key:
os.remove(path + "rand.key") os.remove(path + "rand.key")
return str(process.datastore(path)) return str(process.datastore(
random.randrange(minid,maxid),
path))
# Devuelve un string al azar entre [0-9,a-z,A-Z] # Devuelve un string al azar entre [0-9,a-z,A-Z]
def newprefix(): def newprefix():
...@@ -86,7 +93,8 @@ def msg(): ...@@ -86,7 +93,8 @@ def msg():
Table.id : id, Table.id : id,
Table.serv : request.values['serv'], Table.serv : request.values['serv'],
Table.dest : request.values['dest'], Table.dest : request.values['dest'],
Table.type : request.values['type'] Table.type : request.values['type'],
Table.info : request.values.get('info')
} }
state = process.paramstore(query) state = process.paramstore(query)
return state return state
...@@ -118,7 +126,8 @@ def clean(): ...@@ -118,7 +126,8 @@ def clean():
mtime = os.path.getmtime(folder) mtime = os.path.getmtime(folder)
# Si la carpeta existe por más de X segundos, borrala # Si la carpeta existe por más de X segundos, borrala
if int(now.strftime("%Y%m%d%H%M%S")) - int(time.strftime("%Y%m%d%H%M%S")) > operation_timer: if int(now.strftime("%Y%m%d%H%M%S")) - int(time.strftime("%Y%m%d%H%M%S")) > operation_timer:
os.system("rm -r " + folder) if os.path.exists(folder):
os.system("rm -r " + folder)
threading.Timer(clean_timer, clean).start() threading.Timer(clean_timer, clean).start()
......
...@@ -4,6 +4,7 @@ class Table: ...@@ -4,6 +4,7 @@ class Table:
serv = "serv" serv = "serv"
dest = "dest" dest = "dest"
type = "type" type = "type"
info = "info"
state = "state" state = "state"
@staticmethod @staticmethod
...@@ -14,6 +15,7 @@ class States: ...@@ -14,6 +15,7 @@ class States:
queued = "queued" queued = "queued"
delivered = "delivered" delivered = "delivered"
preprocess = "preprocess" preprocess = "preprocess"
partial = "partially delivered"
@staticmethod @staticmethod
def validate(state): def validate(state):
......
...@@ -9,38 +9,43 @@ class Process: ...@@ -9,38 +9,43 @@ class Process:
self.db = db self.db = db
self.conn = DBconnection(db) self.conn = DBconnection(db)
# stores the data # Guarda la data
def datastore(self,path): def datastore(self,id,path):
entities = { entities = {
Table.id : id,
Table.path : path, Table.path : path,
Table.state : States.preprocess Table.state : States.preprocess
} }
id = self.conn.insert("msg",entities) return self.conn.insert("msg",entities)
return id
# stores the parameters # Guarda los parámetros
def paramstore(self,query): def paramstore(self,query):
# service is wrong row = self.lookup(query[Table.id])
# id no existe
if type(row) == str:
return row
# el servicio no existe
if not Services.validate(query[Table.serv]): if not Services.validate(query[Table.serv]):
return "No existe el servicio '" + query[Table.serv] + "'" return "No existe el servicio '" + query[Table.serv] + "'"
types = json.loads(query[Table.type]) types = json.loads(query[Table.type])
filelist = os.listdir( filelist = os.listdir(row[Table.path])
self.lookup(
query[Table.id]
)[Table.path]
)
for file in types: for file in types:
# files don't exist # el archivo no existe
if file not in filelist: if file not in filelist:
return "El archivo '" + file + "' no existe" return "El archivo '" + file + "' no existe"
# message can't be sent by this service serv = serviceFactory(query[Table.serv])
elif not serviceFactory(query[Table.serv]).validate(types[file]): # el tipo del mensaje no se puede enviar por el servicio
return "El servicio '" + query[Table.serv] + "' no puede enviar el tipo '" + types[file] + "' destinado al archivo '" + file + "'" if not serv.validatetype(types[file]):
return "El servicio '" + query[Table.serv] + "' no puede enviar el tipo '" + types[file] + "' destinado al archivo '" + file + "'"
# el parametro no es valido
if not serv.validateinfo(query[Table.info]):
return "El servicio '" + query[Table.serv] + "' no cuenta con algún parámetro, sus parámetros son " + str(serv.Parameters)
entities = { entities = {
Table.dest : query[Table.dest], Table.dest : query[Table.dest],
Table.serv : query[Table.serv], Table.serv : query[Table.serv],
Table.type : query[Table.type], Table.type : query[Table.type],
Table.info : query[Table.info],
Table.state : States.queued Table.state : States.queued
} }
error = self.conn.update("msg",(Table.id,query[Table.id]),entities) error = self.conn.update("msg",(Table.id,query[Table.id]),entities)
...@@ -48,22 +53,21 @@ class Process: ...@@ -48,22 +53,21 @@ class Process:
return error return error
return States.queued return States.queued
# tries to send all messages available # manda todos los mensajes no enviados
def send(self): def send(self):
rows = self.conn.query("SELECT * FROM msg WHERE state = ?",(States.queued,)) rows = self.conn.query("SELECT * FROM msg WHERE state = ?",(States.queued,))
for query in DBconnection.parseToTable(rows): for query in DBconnection.parseToTable(rows):
# if folder doesn't exist, erase the message request # si no existe la carpeta borrar el mensaje
if not os.path.exists(query[Table.path]): if not os.path.exists(query[Table.path]):
self.conn.query("DELETE FROM msg WHERE id = ?",(query[Table.id],)) self.conn.query("DELETE FROM msg WHERE id = ?",(query[Table.id],))
continue continue
serv = serviceFactory(query[Table.serv]) serv = serviceFactory(query[Table.serv])
success = serv.send(query) success = serv.send(query)
if success: if success:
# save as delivered # gurdar como enviado
self.conn.query("UPDATE msg SET state = ? WHERE id = ?",(States.delivered,query[Table.id])) self.conn.query("UPDATE msg SET state = ? WHERE id = ?",(States.delivered,query[Table.id]))
# returns the state of a message given its id # devuelve el estado de un mensaje, lo archiva si está enviado
# stores the message to history if delivered
def lookup(self,id): def lookup(self,id):
rows = self.conn.query("SELECT * FROM msg WHERE id = ?",(id,)) rows = self.conn.query("SELECT * FROM msg WHERE id = ?",(id,))
if rows == []: if rows == []:
...@@ -82,6 +86,7 @@ class Process: ...@@ -82,6 +86,7 @@ class Process:
self.conn.insert("history",entities) self.conn.insert("history",entities)
return row return row
# devuelve todas las carpetas de mensajes
def paths(self): def paths(self):
rows = self.conn.query("SELECT path FROM msg") rows = self.conn.query("SELECT path FROM msg")
paths = [] paths = []
......
...@@ -9,16 +9,26 @@ import requests, json, os, smtplib ...@@ -9,16 +9,26 @@ import requests, json, os, smtplib
class ServiceBase(ABC): class ServiceBase(ABC):
# Método para enviar un mensaje
@abstractmethod @abstractmethod
def send(self,data): def send(self,data):
pass pass
# Método para validar un tipo de dato
@abstractmethod @abstractmethod
def validate(self,serv): def validatetype(self,type):
pass
# Método para validar los parámetros
@abstractmethod
def validateinfo(self,info):
pass pass
class Wpp1(ServiceBase): class Wpp1(ServiceBase):
Allowed = [Datatypes.text, Datatypes.image, Datatypes.document, Datatypes.link]
Parameters = ["origin"]
URL = "https://www.waboxapp.com/api/send/" URL = "https://www.waboxapp.com/api/send/"
URLmode = { URLmode = {
Datatypes.text : 'chat', Datatypes.text : 'chat',
...@@ -39,6 +49,8 @@ class Wpp1(ServiceBase): ...@@ -39,6 +49,8 @@ class Wpp1(ServiceBase):
f = open(filepath) f = open(filepath)
text = f.read() text = f.read()
f.close() f.close()
if type(text) == bytes:
text = text.decode("utf-8")
result = requests.get(url = Wpp1.URL + Wpp1.URLmode[types[file]],params = {'token':Wpp1.token,'uid':Wpp1.uid,'to':data[Table.dest],'text':text}) result = requests.get(url = Wpp1.URL + Wpp1.URLmode[types[file]],params = {'token':Wpp1.token,'uid':Wpp1.uid,'to':data[Table.dest],'text':text})
succ = succ and result.json()['success'] succ = succ and result.json()['success']
else: else:
...@@ -47,12 +59,23 @@ class Wpp1(ServiceBase): ...@@ -47,12 +59,23 @@ class Wpp1(ServiceBase):
succ = succ and result.json()['success'] succ = succ and result.json()['success']
return succ return succ
def validate(self,datatype): def validatetype(self,type):
return datatype == Datatypes.text or datatype == Datatypes.image or datatype == Datatypes.document or datatype == Datatypes.link return type in Wpp1.Allowed
def validateinfo(self,info):
if info == None:
return True
i = json.loads(info)
for param in i:
if param not in Wpp1.Parameters:
return False
return True
class Mail(ServiceBase): class Mail(ServiceBase):
Allowed = [Datatypes.text, Datatypes.image, Datatypes.document, Datatypes.link]
Parameters = ["origin", "subject"]
def __init__(self): def __init__(self):
self.__username = "prueba@anacsoft.com" self.__username = "prueba@anacsoft.com"
self.__password = "prueba2019" self.__password = "prueba2019"
...@@ -61,51 +84,60 @@ class Mail(ServiceBase): ...@@ -61,51 +84,60 @@ class Mail(ServiceBase):
self.s.login(self.__username,self.__password) self.s.login(self.__username,self.__password)
def send(self,data): def send(self,data):
types = json.loads(data[Table.type])
info = json.loads(data[Table.info])
msg = MIMEMultipart() msg = MIMEMultipart()
msg['From'] = self._Mail__username msg['From'] = self._Mail__username
msg['To'] = data[Table.dest] msg['To'] = data[Table.dest]
msg['Subject'] = "Test" msg['Subject'] = info['subject']
types = json.loads(data[Table.type])
for file in types: for file in types:
filepath = data[Table.path] + file filepath = data[Table.path] + file
MIME = self.MIMEmode(filepath, types[file]) MIME = self.MIMEmode(file, filepath, types[file])
msg.attach(MIME) msg.attach(MIME)
self.s.send_message(msg) self.s.send_message(msg)
return True return True
def MIMEmode(self,path,type): def MIMEmode(self,name,path,datatype):
mode = None mode = None
if type == Datatypes.text:
with open(path) as data:
mode = MIMEText(data.read(),'plain')
if type == Datatypes.html:
with open(path) as data:
mode = MIMEText(data.read(),'html')
if type == Datatypes.image:
with open(path, "rb") as data:
mode = MIMEImage(data.read())
if type == Datatypes.audio:
with open(path, "rb") as data:
mode = MIMEAudio(data.read())
if type == Datatypes.document:
with open(path, "rb") as data:
mode = MIMEApplication(data.read())
return mode
def validate(self,datatype):
if datatype == Datatypes.text: if datatype == Datatypes.text:
return True with open(path) as f:
data = f.read()
if type(data) == bytes:
data = data.decode("utf-8")
mode = MIMEText(data,'plain')
if datatype == Datatypes.html:
with open(path) as f:
data = f.read()
if type(data) == bytes:
data = data.decode("utf-8")
mode = MIMEText(data,'html')
if datatype == Datatypes.image: if datatype == Datatypes.image:
return True with open(path, "rb") as f:
mode = MIMEImage(f.read())
mode['Content-Disposition'] = 'attachment; filename=' + name
if datatype == Datatypes.audio: if datatype == Datatypes.audio:
return True with open(path, "rb") as f:
mode = MIMEAudio(f.read())
mode['Content-Disposition'] = 'attachment; filename=' + name
if datatype == Datatypes.document: if datatype == Datatypes.document:
return True with open(path, "rb") as f:
if datatype == Datatypes.html: mode = MIMEApplication(f.read())
return True mode['Content-Disposition'] = 'attachment; filename=' + name
return False
return mode
def validatetype(self,type):
return type in Mail.Allowed
def validateinfo(self,info):
if info == None:
return False
i = json.loads(info)
for param in i:
if param not in Mail.Parameters:
return False
return True
def serviceFactory(serv): def serviceFactory(serv):
if serv == Services.wpp1: if serv == Services.wpp1:
......
import os, time, requests, ipdb, json, threading import os, time, requests, ipdb, json, threading
from database import DBconnection
URL = "http://192.168.15.75:5000/" URL = "http://192.168.15.75:5000/"
TESTPHONE = "5493415959169" TESTPHONE = "5493415959169"
...@@ -19,7 +20,7 @@ def wpp1(phone): ...@@ -19,7 +20,7 @@ def wpp1(phone):
'serv' : "wpp1", 'serv' : "wpp1",
'dest' : phone, 'dest' : phone,
'type' : json.dumps({ 'type' : json.dumps({
'wptext' : 'text', 'wpmsg' : 'text',
'wpimage' : 'image', 'wpimage' : 'image',
'wpmedia' : 'document', 'wpmedia' : 'document',
'wplink' : 'link' 'wplink' : 'link'
...@@ -44,9 +45,12 @@ def mail(mail): ...@@ -44,9 +45,12 @@ def mail(mail):
'serv' : "mail", 'serv' : "mail",
'dest' : mail, 'dest' : mail,
'type' : json.dumps({ 'type' : json.dumps({
'mailtext' : 'text', 'mailmsg' : 'text',
'mailimage' : 'image', 'mailimage' : 'image',
'maildocument' : 'document' 'maildocument' : 'document'
}),
'info' : json.dumps({
'subject' : 'Some Subject'
}) })
}) })
assert state.text == "queued" , "'" + state.text + "' no es igual a 'queued'" assert state.text == "queued" , "'" + state.text + "' no es igual a 'queued'"
...@@ -67,8 +71,9 @@ def encryption(phone): ...@@ -67,8 +71,9 @@ def encryption(phone):
os.system("openssl rsautl -encrypt -inkey " + FOLDER + "server_rsa_key.pub -pubin -in " + FOLDER + "rand.key -out " + FOLDER + "rand.key.enc") os.system("openssl rsautl -encrypt -inkey " + FOLDER + "server_rsa_key.pub -pubin -in " + FOLDER + "rand.key -out " + FOLDER + "rand.key.enc")
id = requests.post(url = URL + "data", files = { id = requests.post(url = URL + "data", files = {
'plaintext.enc' : open(FOLDER + "plaintext.enc","rb"), 'plaintext' : open(FOLDER + "plaintext.enc","rb"),
'plainimage.enc' : open(FOLDER + "plainimage.enc","rb") 'plainimage' : open(FOLDER + "plainimage.enc","rb"),
'key' : open(FOLDER + "rand.key.enc","rb")
}) })
assert int(id.text) > 0 , id.text + "no es mayor a 0" assert int(id.text) > 0 , id.text + "no es mayor a 0"
state = requests.post(url = URL + "msg", params = { state = requests.post(url = URL + "msg", params = {
...@@ -87,13 +92,46 @@ def encryption(phone): ...@@ -87,13 +92,46 @@ def encryption(phone):
}) })
assert (state.text == "delivered") , "'" + state.text + "' no es igual a 'delivered'" assert (state.text == "delivered") , "'" + state.text + "' no es igual a 'delivered'"
def errors(): def errors(phone):
# service not valid # id no existente
# service unable to send type error = requests.post(url = URL + "msg", params = {
# trying to set type of unexistent file 'id' : "-",
# trying to user unexistent table 'serv' : "-",
# ill-formed comparator 'dest' : "-",
# data doesn't pass the checks 'type' : "-"
})
assert error.text == "El id - no existe"
# servicio no valido
id = requests.post(url = URL + "data", files = {
"errortext" : open(FOLDER + "errortext","rb")
})
error = requests.post(url = URL + "msg", params = {
'id' : id.text,
'serv' : "-",
'dest' : "-",
'type' : "-"
})
assert error.text == "No existe el servicio '-'"
# archivo inexistente
error = requests.post(url = URL + "msg", params = {
'id' : id.text,
'serv' : "wpp1",
'dest' : phone,
'type' : json.dumps({
"-" : "-"
})
})
assert error.text == "El archivo '-' no existe"
# tipo incorrecto
error = requests.post(url = URL + "msg", params = {
'id' : id.text,
'serv' : "wpp1",
'dest' : phone,
'type' : json.dumps({
"errortext" : "-"
})
})
assert error.text == "El servicio 'wpp1' no puede enviar el tipo '-' destinado al archivo 'errortext'"
def main(): def main():
if os.path.exists("messages.db"): if os.path.exists("messages.db"):
...@@ -107,6 +145,7 @@ def main(): ...@@ -107,6 +145,7 @@ def main():
threads.append(threading.Thread(target = wpp1, args = (TESTPHONE,))) threads.append(threading.Thread(target = wpp1, args = (TESTPHONE,)))
threads.append(threading.Thread(target = encryption, args = (TESTPHONE,))) threads.append(threading.Thread(target = encryption, args = (TESTPHONE,)))
threads.append(threading.Thread(target = mail, args = (TESTMAIL,))) threads.append(threading.Thread(target = mail, args = (TESTMAIL,)))
threads.append(threading.Thread(target = errors, args = (TESTPHONE,)))
for thread in threads: for thread in threads:
thread.start() thread.start()
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!