Skip to content
Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Luciano Barletta
/
message-service
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Pipelines
Graphs
Issues
0
Merge Requests
1
Wiki
Network
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Network
Compare
Branches
Tags
Commit f672f0df
authored
2019-10-09 15:04:42 +0000
by
Luciano Barletta
Browse Files
Options
Browse Files
Tag
Download
Email Patches
Plain Diff
improved everything
1 parent
6bf00945
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
192 additions
and
92 deletions
README.md
database.py
deploy.py
enums.py
process.py
services.py
test_flask.py
README.md
View file @
f672f0d
...
...
@@ -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 testear se debe tener una carpeta llamada 'testfolder' donde poder tirar los archivos para testeo.
## Mejoras no implementadas
...
...
database.py
View file @
f672f0d
from
enums
import
Table
,
States
,
Services
,
Datatypes
from
services
import
serviceFactory
import
sqlite3
,
ipdb
,
json
class
DBconnection
:
...
...
@@ -7,12 +8,13 @@ class DBconnection:
# Estructura de las tablas
structure
=
{
Table
.
id
:
"integer PRIMARY KEY"
,
Table
.
path
:
"text"
,
Table
.
serv
:
"text"
,
Table
.
dest
:
"text"
,
Table
.
type
:
"text"
,
Table
.
state
:
"text"
Table
.
id
:
"INTEGER PRIMARY KEY"
,
Table
.
path
:
"TEXT"
,
Table
.
serv
:
"TEXT"
,
Table
.
dest
:
"TEXT"
,
Table
.
type
:
"TEXT"
,
Table
.
info
:
"TEXT"
,
Table
.
state
:
"TEXT"
}
def
__init__
(
self
,
db
):
...
...
@@ -48,11 +50,12 @@ class DBconnection:
return
"La tabla "
+
table
+
" no existe o no está contemplada"
query
=
"INSERT INTO "
+
table
+
"("
values
=
" VALUES("
checked
=
self
.
check
(
insertions
)
if
type
(
checked
)
==
str
:
# error
return
checked
for
column
in
insertions
:
if
not
self
.
check
(
column
,
insertions
[
column
]):
return
"El dato '"
+
insertions
[
column
]
+
"' no es valido"
query
+=
column
+
","
values
+=
"'"
+
insertions
[
column
]
+
"',"
values
+=
"'"
+
str
(
insertions
[
column
])
+
"',"
query
=
query
.
strip
(
","
)
+
")"
values
=
values
.
strip
(
","
)
+
")"
con
=
sqlite3
.
connect
(
self
.
db
)
...
...
@@ -70,11 +73,12 @@ class DBconnection:
if
not
Table
.
validate
(
comparator
[
0
]):
return
"El comparador no es una columna valida"
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
:
if
not
self
.
check
(
column
,
alterations
[
column
]):
return
"El dato '"
+
alterations
[
column
]
+
"' no es valido"
query
+=
column
+
"='"
+
alterations
[
column
]
+
"',"
query
+=
column
+
"='"
+
str
(
alterations
[
column
])
+
"',"
query
=
query
.
strip
(
","
)
con
=
sqlite3
.
connect
(
self
.
db
)
cursor
=
con
.
cursor
()
...
...
@@ -82,22 +86,29 @@ class DBconnection:
con
.
commit
()
con
.
close
()
def
check
(
self
,
column
,
data
):
if
column
==
Table
.
path
:
return
True
if
column
==
Table
.
serv
:
return
Services
.
validate
(
data
)
if
column
==
Table
.
dest
:
return
True
if
column
==
Table
.
type
:
data
=
json
.
loads
(
data
)
def
check
(
self
,
information
):
valid
=
True
for
column
in
information
:
# columna no existe en la tabla
if
not
Table
.
validate
(
column
):
return
"La columna '"
+
column
+
"' no existe"
# chequeos de validez de datos
if
column
==
Table
.
serv
:
valid
=
Services
.
validate
(
information
[
column
])
elif
column
==
Table
.
type
:
data
=
json
.
loads
(
information
[
column
])
for
key
in
data
:
valid
=
valid
and
Datatypes
.
validate
(
data
[
key
])
return
valid
if
column
==
Table
.
state
:
return
States
.
validate
(
data
)
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
def
parseToTable
(
rows
):
...
...
deploy.py
View file @
f672f0d
...
...
@@ -10,6 +10,10 @@ app = Flask(__name__)
# carpeta de los mensajes
msgfolder
=
"msg/"
# id mínimo para una tabla
minid
=
1
# id máximo para una tabla
maxid
=
2
**
63
# tiempo para reintentar mensajhes
retry_timer
=
10
# tiempo para limpieza
...
...
@@ -43,25 +47,28 @@ def data():
key
=
request
.
files
.
get
(
'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
os
.
system
(
"openssl rsautl -decrypt -inkey rsa_key.pri -in "
+
path
+
"rand.key.enc -out "
+
path
+
"rand.key"
)
os
.
remove
(
path
+
"rand.key.enc"
)
for
file
in
request
.
files
:
# si hay llave y no es esta
filepath
=
path
+
file
if
key
and
file
!=
"key"
:
request
.
files
[
file
]
.
save
(
filepath
+
".enc"
)
# 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
.
remove
(
filepath
+
".enc"
)
else
:
request
.
files
[
file
]
.
save
(
path
+
request
.
files
[
file
]
.
filename
)
request
.
files
[
file
]
.
save
(
filepath
)
if
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]
def
newprefix
():
...
...
@@ -86,7 +93,8 @@ def msg():
Table
.
id
:
id
,
Table
.
serv
:
request
.
values
[
'serv'
],
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
)
return
state
...
...
@@ -118,6 +126,7 @@ def clean():
mtime
=
os
.
path
.
getmtime
(
folder
)
# 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
os
.
path
.
exists
(
folder
):
os
.
system
(
"rm -r "
+
folder
)
threading
.
Timer
(
clean_timer
,
clean
)
.
start
()
...
...
enums.py
View file @
f672f0d
...
...
@@ -4,6 +4,7 @@ class Table:
serv
=
"serv"
dest
=
"dest"
type
=
"type"
info
=
"info"
state
=
"state"
@staticmethod
...
...
@@ -14,6 +15,7 @@ class States:
queued
=
"queued"
delivered
=
"delivered"
preprocess
=
"preprocess"
partial
=
"partially delivered"
@staticmethod
def
validate
(
state
):
...
...
process.py
View file @
f672f0d
...
...
@@ -9,38 +9,43 @@ class Process:
self
.
db
=
db
self
.
conn
=
DBconnection
(
db
)
#
stores the
data
def
datastore
(
self
,
path
):
#
Guarda la
data
def
datastore
(
self
,
id
,
path
):
entities
=
{
Table
.
id
:
id
,
Table
.
path
:
path
,
Table
.
state
:
States
.
preprocess
}
id
=
self
.
conn
.
insert
(
"msg"
,
entities
)
return
id
return
self
.
conn
.
insert
(
"msg"
,
entities
)
#
stores the parameter
s
#
Guarda los parámetro
s
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
]):
return
"No existe el servicio '"
+
query
[
Table
.
serv
]
+
"'"
types
=
json
.
loads
(
query
[
Table
.
type
])
filelist
=
os
.
listdir
(
self
.
lookup
(
query
[
Table
.
id
]
)[
Table
.
path
]
)
filelist
=
os
.
listdir
(
row
[
Table
.
path
])
for
file
in
types
:
#
files don't exist
#
el archivo no existe
if
file
not
in
filelist
:
return
"El archivo '"
+
file
+
"' no existe"
# message can't be sent by this service
elif
not
serviceFactory
(
query
[
Table
.
serv
])
.
validate
(
types
[
file
]):
serv
=
serviceFactory
(
query
[
Table
.
serv
])
# el tipo del mensaje no se puede enviar por el servicio
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
=
{
Table
.
dest
:
query
[
Table
.
dest
],
Table
.
serv
:
query
[
Table
.
serv
],
Table
.
type
:
query
[
Table
.
type
],
Table
.
info
:
query
[
Table
.
info
],
Table
.
state
:
States
.
queued
}
error
=
self
.
conn
.
update
(
"msg"
,(
Table
.
id
,
query
[
Table
.
id
]),
entities
)
...
...
@@ -48,22 +53,21 @@ class Process:
return
error
return
States
.
queued
#
tries to send all messages available
#
manda todos los mensajes no enviados
def
send
(
self
):
rows
=
self
.
conn
.
query
(
"SELECT * FROM msg WHERE state = ?"
,(
States
.
queued
,))
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
]):
self
.
conn
.
query
(
"DELETE FROM msg WHERE id = ?"
,(
query
[
Table
.
id
],))
continue
serv
=
serviceFactory
(
query
[
Table
.
serv
])
success
=
serv
.
send
(
query
)
if
success
:
#
save as delivered
#
gurdar como enviado
self
.
conn
.
query
(
"UPDATE msg SET state = ? WHERE id = ?"
,(
States
.
delivered
,
query
[
Table
.
id
]))
# returns the state of a message given its id
# stores the message to history if delivered
# devuelve el estado de un mensaje, lo archiva si está enviado
def
lookup
(
self
,
id
):
rows
=
self
.
conn
.
query
(
"SELECT * FROM msg WHERE id = ?"
,(
id
,))
if
rows
==
[]:
...
...
@@ -82,6 +86,7 @@ class Process:
self
.
conn
.
insert
(
"history"
,
entities
)
return
row
# devuelve todas las carpetas de mensajes
def
paths
(
self
):
rows
=
self
.
conn
.
query
(
"SELECT path FROM msg"
)
paths
=
[]
...
...
services.py
View file @
f672f0d
...
...
@@ -9,16 +9,26 @@ import requests, json, os, smtplib
class
ServiceBase
(
ABC
):
# Método para enviar un mensaje
@abstractmethod
def
send
(
self
,
data
):
pass
# Método para validar un tipo de dato
@abstractmethod
def
validate
(
self
,
serv
):
def
validatetype
(
self
,
type
):
pass
# Método para validar los parámetros
@abstractmethod
def
validateinfo
(
self
,
info
):
pass
class
Wpp1
(
ServiceBase
):
Allowed
=
[
Datatypes
.
text
,
Datatypes
.
image
,
Datatypes
.
document
,
Datatypes
.
link
]
Parameters
=
[
"origin"
]
URL
=
"https://www.waboxapp.com/api/send/"
URLmode
=
{
Datatypes
.
text
:
'chat'
,
...
...
@@ -39,6 +49,8 @@ class Wpp1(ServiceBase):
f
=
open
(
filepath
)
text
=
f
.
read
()
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
})
succ
=
succ
and
result
.
json
()[
'success'
]
else
:
...
...
@@ -47,12 +59,23 @@ class Wpp1(ServiceBase):
succ
=
succ
and
result
.
json
()[
'success'
]
return
succ
def
validate
(
self
,
data
type
):
return
datatype
==
Datatypes
.
text
or
datatype
==
Datatypes
.
image
or
datatype
==
Datatypes
.
document
or
datatype
==
Datatypes
.
link
def
validate
type
(
self
,
type
):
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
):
Allowed
=
[
Datatypes
.
text
,
Datatypes
.
image
,
Datatypes
.
document
,
Datatypes
.
link
]
Parameters
=
[
"origin"
,
"subject"
]
def
__init__
(
self
):
self
.
__username
=
"prueba@anacsoft.com"
self
.
__password
=
"prueba2019"
...
...
@@ -61,51 +84,60 @@ class Mail(ServiceBase):
self
.
s
.
login
(
self
.
__username
,
self
.
__password
)
def
send
(
self
,
data
):
types
=
json
.
loads
(
data
[
Table
.
type
])
info
=
json
.
loads
(
data
[
Table
.
info
])
msg
=
MIMEMultipart
()
msg
[
'From'
]
=
self
.
_Mail__username
msg
[
'To'
]
=
data
[
Table
.
dest
]
msg
[
'Subject'
]
=
"Test"
types
=
json
.
loads
(
data
[
Table
.
type
])
msg
[
'Subject'
]
=
info
[
'subject'
]
for
file
in
types
:
filepath
=
data
[
Table
.
path
]
+
file
MIME
=
self
.
MIMEmode
(
filepath
,
types
[
file
])
MIME
=
self
.
MIMEmode
(
file
,
file
path
,
types
[
file
])
msg
.
attach
(
MIME
)
self
.
s
.
send_message
(
msg
)
return
True
def
MIMEmode
(
self
,
path
,
type
):
def
MIMEmode
(
self
,
name
,
path
,
data
type
):
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
:
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
:
return
True
with
open
(
path
,
"rb"
)
as
f
:
mode
=
MIMEImage
(
f
.
read
())
mode
[
'Content-Disposition'
]
=
'attachment; filename='
+
name
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
:
return
True
if
datatype
==
Datatypes
.
html
:
return
True
with
open
(
path
,
"rb"
)
as
f
:
mode
=
MIMEApplication
(
f
.
read
())
mode
[
'Content-Disposition'
]
=
'attachment; filename='
+
name
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
):
if
serv
==
Services
.
wpp1
:
...
...
test_flask.py
View file @
f672f0d
import
os
,
time
,
requests
,
ipdb
,
json
,
threading
from
database
import
DBconnection
URL
=
"http://192.168.15.75:5000/"
TESTPHONE
=
"5493415959169"
...
...
@@ -19,7 +20,7 @@ def wpp1(phone):
'serv'
:
"wpp1"
,
'dest'
:
phone
,
'type'
:
json
.
dumps
({
'wp
text
'
:
'text'
,
'wp
msg
'
:
'text'
,
'wpimage'
:
'image'
,
'wpmedia'
:
'document'
,
'wplink'
:
'link'
...
...
@@ -44,9 +45,12 @@ def mail(mail):
'serv'
:
"mail"
,
'dest'
:
mail
,
'type'
:
json
.
dumps
({
'mail
text
'
:
'text'
,
'mail
msg
'
:
'text'
,
'mailimage'
:
'image'
,
'maildocument'
:
'document'
}),
'info'
:
json
.
dumps
({
'subject'
:
'Some Subject'
})
})
assert
state
.
text
==
"queued"
,
"'"
+
state
.
text
+
"' no es igual a 'queued'"
...
...
@@ -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"
)
id
=
requests
.
post
(
url
=
URL
+
"data"
,
files
=
{
'plaintext.enc'
:
open
(
FOLDER
+
"plaintext.enc"
,
"rb"
),
'plainimage.enc'
:
open
(
FOLDER
+
"plainimage.enc"
,
"rb"
)
'plaintext'
:
open
(
FOLDER
+
"plaintext.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"
state
=
requests
.
post
(
url
=
URL
+
"msg"
,
params
=
{
...
...
@@ -87,13 +92,46 @@ def encryption(phone):
})
assert
(
state
.
text
==
"delivered"
)
,
"'"
+
state
.
text
+
"' no es igual a 'delivered'"
def
errors
():
# service not valid
# service unable to send type
# trying to set type of unexistent file
# trying to user unexistent table
# ill-formed comparator
# data doesn't pass the checks
def
errors
(
phone
):
# id no existente
error
=
requests
.
post
(
url
=
URL
+
"msg"
,
params
=
{
'id'
:
"-"
,
'serv'
:
"-"
,
'dest'
:
"-"
,
'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
():
if
os
.
path
.
exists
(
"messages.db"
):
...
...
@@ -107,6 +145,7 @@ def main():
threads
.
append
(
threading
.
Thread
(
target
=
wpp1
,
args
=
(
TESTPHONE
,)))
threads
.
append
(
threading
.
Thread
(
target
=
encryption
,
args
=
(
TESTPHONE
,)))
threads
.
append
(
threading
.
Thread
(
target
=
mail
,
args
=
(
TESTMAIL
,)))
threads
.
append
(
threading
.
Thread
(
target
=
errors
,
args
=
(
TESTPHONE
,)))
for
thread
in
threads
:
thread
.
start
()
...
...
Write
Preview
Styling with
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment