From ceb82674176dcf7e6cdbb3a1dec0aa1f64f7de00 Mon Sep 17 00:00:00 2001 From: Philipp Lackinger Date: Fri, 18 Dec 2020 23:08:27 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Mail=20Framework=20Working?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/mail.py | 47 ++++++++++++++++++++++++---------- docker/Dockerfile | 20 +++++++++++++++ docker/docker-compose.yml | 12 +++++++++ functions/user.py | 5 ++-- main.py | 54 ++++++++++++++++++++++++++++++++++++--- models/mail.py | 22 ++++++++++++++++ requirements.txt | 5 +++- 7 files changed, 144 insertions(+), 21 deletions(-) create mode 100644 docker/Dockerfile create mode 100644 docker/docker-compose.yml create mode 100644 models/mail.py diff --git a/assets/mail.py b/assets/mail.py index 6fc85c6..c028749 100644 --- a/assets/mail.py +++ b/assets/mail.py @@ -1,18 +1,37 @@ import smtplib -from socket import gaierror +import ssl +from email.mime.multipart import MIMEMultipart +from email.mime.text import MIMEText -def sendMail(to: str, subject: str, message: str): +def sendMail(to: str, subject: str, message: str, html: bool = False): import env as e - try: - with smtplib.SMTP(e.mail_host, e.mail_port) as server: - server.login(e.mail_username, e.mail_password) - server.sendmail(from_addr=e.mail_sender, to_addrs=to, msg=message) - except (gaierror, ConnectionRefusedError): - return "failed to connect to the server" - except smtplib.SMTPServerDisconnected: - return "Failed to connect to the server with the given credentials" - except smtplib.SMTPException as e: - return "SMTP error occured: " + str(e) - server.close() - return True + + context = ssl.create_default_context() + + mail = MIMEMultipart("alternative") + mail["Subject"] = subject + mail["From"] = e.mail_sender + mail["To"] = to + + if html: + msg = MIMEText(message, "html") + else: + msg = MIMEText(message, "plain") + + mail.attach(msg) + + with smtplib.SMTP_SSL(e.mail_host, e.mail_port, context=context) as server: + + server.login( + user=e.mail_sender, + password=e.mail_password + ) + + server.sendmail( + from_addr=e.mail_sender, + to_addrs=to, + msg=mail.as_string() + ) + + return True diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..d485e85 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,20 @@ +FROM python:3.8.1-slim-buster + +ENV WORKDIR=/usr/src/app +ENV USER=app +ENV APP_HOME=/home/app/web +ENV PYTHONDONTWRITEBYTECODE=1 PYTHONUNBUFFERED=1 + +WORKDIR $WORKDIR + +RUN pip install --upgrade pip +COPY ./requirements.txt $WORKDIR/requirements.txt +RUN pip install -r requirements.txt + +RUN adduser --system --group $USER +RUN mkdir $APP_HOME +WORKDIR $APP_HOME + +WORKDIR $APP_HOME +RUN chown -R $USER:$USER $APP_HOME +USER $USER \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..7db916a --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,12 @@ +version: '3.8' + +services: + web: + build: + context: ./services/api + command: gunicorn main:app --bind 0.0.0.0:5000 -k uvicorn.workers.UvicornWorker + expose: + - 5000 + labels: + - "traefik.enable=true" + - "traefik.http.routers.fastapi.rule=Host(`fastapi.localhost`)" \ No newline at end of file diff --git a/functions/user.py b/functions/user.py index b502b41..462e036 100644 --- a/functions/user.py +++ b/functions/user.py @@ -11,10 +11,10 @@ def get_user(ID=None, EMAIL=None): if ID is not None: cursor.execute("SELECT * FROM USERDATA WHERE ID = %s", (ID,)) - return fetch_data(cursor.fetchall()[0]) + return fetch_data(cursor.get_row()) if EMAIL is not None: cursor.execute("SELECT * FROM USERDATA WHERE EMAIL = %s", (EMAIL,)) - return fetch_data(cursor.fetchall()[0]) + return fetch_data(cursor.get_row()) else: raise ValueError('USER not Valid') @@ -35,6 +35,7 @@ def push_data(u: User): ACTIVE = %s WHERE ID = %s """ + PARAM = ( u.EMAIL, u.PASSWORD_HASH, diff --git a/main.py b/main.py index 3bec477..7a4db49 100644 --- a/main.py +++ b/main.py @@ -7,12 +7,14 @@ from pydantic import ValidationError from starlette.responses import JSONResponse +from assets.database import openDBConnection from functions.hashing import get_current_active_user, authenticate_user, get_password_hash from functions.sessionkey import create_access_token from functions.user import create_user, get_user, is_teacher, get_all_users, remove_passwordhash_obj from models.token import Token from models.user import User, preUser from models.apikey import ApiKey +from models.mail import Mail app = FastAPI() @@ -50,7 +52,7 @@ async def read_user_by_id(id: int, current_user: User = Depends(get_current_acti return get_user(ID=id) else: return JSONResponse(status_code=status.HTTP_403_FORBIDDEN, - content="Not sufficent Permissions to view other users") + content="Not sufficient Permissions to view other users") @app.get("/users", description="returns all users (for admin dashboard)") @@ -62,7 +64,7 @@ async def return_all_users(current_user: User = Depends(get_current_active_user) return JSONResponse(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, content=e.errors()) else: return JSONResponse(status_code=status.HTTP_403_FORBIDDEN, - content="Not sufficent Permissions to view other users") + content="Not sufficient Permissions to view other users") # Put @@ -76,7 +78,7 @@ async def admin_create_user(user: User, current_user: User = Depends(get_current else: return JSONResponse( status_code=status.HTTP_403_FORBIDDEN, - content="Not sufficent Permissions to create other users" + content="Not sufficient Permissions to create other users" ) @@ -90,6 +92,21 @@ async def form_create_user(api_key: str, user: preUser): content=e.errors() ) + db = openDBConnection() + cursor = db.cursor() + + SQL = "SELECT EMAIL FROM USERDATA WHERE EMAIL = %s" + PAR = (user.EMAIL,) + + cursor.execute(SQL, PAR) + data = cursor.fetchone() + + if data is None: + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail="User is already registered" + ) + PERMISSION_LEVEL = 0 if is_teacher(user.EMAIL): PERMISSION_LEVEL = 1 @@ -118,5 +135,34 @@ async def form_create_user(api_key: str, user: preUser): create_user(account) return JSONResponse( status_code=status.HTTP_200_OK, - content="User succesfully created" + content="User successfully created" ) + + +# Mail API + +@app.post("/sendmail") +async def api_send_mail(apikey: ApiKey, mail: Mail): + try: + if apikey.PERMISSION >= 2: + if mail.send(): + return JSONResponse( + status_code=status.HTTP_200_OK, + content="E-Mail was sent successfully" + ) + else: + return HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail="Internal Server Error with Processing the Mail" + ) + else: + return HTTPException( + status_code=status.HTTP_403_FORBIDDEN, + detail="You are not allowed to send Mails" + ) + except ValidationError as e: + return JSONResponse( + status_code=status.HTTP_403_FORBIDDEN, + content=e.errors() + ) + diff --git a/models/mail.py b/models/mail.py new file mode 100644 index 0000000..7f287c1 --- /dev/null +++ b/models/mail.py @@ -0,0 +1,22 @@ +from pydantic import BaseModel, validator +from typing import Optional +from assets.mail import sendMail +import re + + +class Mail(BaseModel): + to: str + subject: str + message: str + html: Optional[bool] = False + + @validator('to') + def is_valid_email(cls, email): + regex = r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$" + if re.search(regex, email): + return email + else: + raise ValueError('EMAIL not Valid') + + def send(self): + return sendMail(to=self.to, subject=self.subject, message=self.message, html=self.html) diff --git a/requirements.txt b/requirements.txt index 1db57e8..20da120 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,7 @@ pydantic~=1.6.1 fastapi~=0.61.1 mysql-connector-python~=8.0.21 passlib~=1.7.2 -uvicorn~=0.11.8 \ No newline at end of file +bcrypt~=3.2.0 +uvicorn~=0.12.2 +python-jose~=3.2.0 +python-multipart~=0.0.5 \ No newline at end of file