Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions requirements.api.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
delphi_utils==0.3.15
epiweeks==2.1.2
Flask==2.2.2
Flask-Admin==1.6.1
Flask-Limiter==3.3.0
itsdangerous<2.1
jinja2==3.0.3
Expand Down
16 changes: 16 additions & 0 deletions src/ddl/api_user.sql
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,19 @@ CREATE TABLE IF NOT EXISTS `user_role_link` (
`role_id` int(11) UNSIGNED NOT NULL,
PRIMARY KEY (`user_id`, `role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


-- `registration_responses` table

CREATE TABLE IF NOT EXISTS `registration_responses` (
`email` varchar(320) UNIQUE NOT NULL,
`organization` varchar(120),
`purpose` varchar(320)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- `removal_requests` table

CREATE TABLE IF NOT EXISTS `removal_requests` (
`api_key` varchar(50) UNIQUE NOT NULL,
`comment` varchar(320)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
31 changes: 11 additions & 20 deletions src/maintenance/remove_outdated_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,10 @@
import delphi.operations.secrets as secrets
import mysql.connector
from delphi.epidata.server._config import API_KEY_REGISTRATION_FORM_LINK_LOCAL
from delphi.epidata.server._common import send_email

ApiUserRecord = namedtuple("APIUserRecord", ("api_key", "email", "date_diff"))

SMTP_HOST = "relay.andrew.cmu.edu"
SMTP_PORT = 25

EMAIL_SUBJECT = "Your API Key was deleted."
EMAIL_FROM = "[email protected]"
ALERT_EMAIL_MESSAGE = f"""Hi! \n Your API Key is going to be removed due to inactivity.
To renew it, pelase use it within one month from now."""
DELETED_EMAIL_MESSAGE = f"""Hi! \n Your API Key was removed due to inactivity.
To get new one, please use registration form ({API_KEY_REGISTRATION_FORM_LINK_LOCAL}) or contact us."""


def get_old_keys(cur):
cur.execute(
Expand All @@ -41,25 +32,25 @@ def remove_outdated_key(cur, api_key):
)


def send_notification(to_addr, alert=True):
message = ALERT_EMAIL_MESSAGE if alert else DELETED_EMAIL_MESSAGE
BODY = "\r\n".join((f"FROM: {EMAIL_FROM}", f"TO: {to_addr}", f"Subject: {EMAIL_SUBJECT}", "", message))
smtp_server = SMTP(host=SMTP_HOST, port=SMTP_PORT)
smtp_server.starttls()
smtp_server.sendmail(EMAIL_FROM, to_addr, BODY)


def main():
u, p = secrets.db.epi
cnx = mysql.connector.connect(database="epidata", user=u, password=p, host=secrets.db.host)
cur = cnx.cursor()
outdated_keys_list = get_old_keys(cur)
for item in outdated_keys_list:
if item.date_diff == 5:
send_notification(item.email)
send_email(
to_addr=item.email,
subject="API Key will be expired soon",
message=f"Hi! \n Your API Key: {item.api_key}, is going to be expired due to inactivity.",
)
else:
remove_outdated_key(cur, item.api_key)
send_notification(item.email, alert=False)
send_email(
to_addr=item.email,
subject="Your API Key was expired",
message=f"Hi! \n Your API Key: {item.api_key}, was removed due to inactivity. To get new one, please use registration form ({API_KEY_REGISTRATION_FORM_LINK_LOCAL}) or contact us.""
)
cur.close()
cnx.commit()
cnx.close()
Expand Down
16 changes: 15 additions & 1 deletion src/server/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
from sqlalchemy.engine import Connection, Engine
from werkzeug.exceptions import Unauthorized
from werkzeug.local import LocalProxy
from smtplib import SMTP

from delphi.epidata.common.logger import get_structured_logger
from ._config import SECRET, REVERSE_PROXY_DEPTH
from ._config import SECRET, REVERSE_PROXY_DEPTH, SMTP_HOST, SMTP_PORT, EMAIL_FROM
from ._db import engine
from ._exceptions import DatabaseErrorException, EpiDataException
from ._security import current_user, _is_public_route, resolve_auth_token, update_key_last_time_used, ERROR_MSG_INVALID_KEY
Expand Down Expand Up @@ -210,3 +211,16 @@ def set_compatibility_mode():
sets the compatibility mode for this request
"""
g.compatibility = True


def send_email(to_addr: str, subject: str, message: str):
"""Send email messages
Args:
to_addr (str): Reciever email address
subject (str): Email subject
message (str): Email message
"""
smtp_server = SMTP(host=SMTP_HOST, port=SMTP_PORT)
smtp_server.starttls()
body = "\r\n".join((f"FROM: {EMAIL_FROM}", f"TO: {to_addr}", f"Subject: {subject}", "", message))
smtp_server.sendmail(EMAIL_FROM, to_addr, body)
5 changes: 5 additions & 0 deletions src/server/_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,3 +111,8 @@
# ^ shortcut to "https://docs.google.com/forms/d/e/1FAIpQLSff30tsq4xwPCoUbvaIygLSMs_Mt8eDhHA0rifBoIrjo8J5lw/viewform"
API_KEY_REMOVAL_REQUEST_LINK_LOCAL = "https://api.delphi.cmu.edu/epidata/admin/removal_request"
# ^ redirects to API_KEY_REMOVAL_REQUEST_LINK

# STMP credentials
SMTP_HOST = "relay.andrew.cmu.edu"
SMTP_PORT = 25
EMAIL_FROM = "[email protected]"
4 changes: 3 additions & 1 deletion src/server/_security.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,12 @@ def _get_current_user():


def _is_public_route() -> bool:
public_routes_list = ["lib", "admin", "version"]
public_routes_list = ["lib", "version", "diagnostics"]
for route in public_routes_list:
if request.path.startswith(f"{URL_PREFIX}/{route}"):
return True
if "admin" in request.path:
return True
return False


Expand Down
57 changes: 44 additions & 13 deletions src/server/admin/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from .._db import Session, WriteSession, default_session
from delphi.epidata.common.logger import get_structured_logger

from typing import Set, Optional, List
from datetime import datetime as dtime
from typing import Set, Optional
from datetime import date


Base = declarative_base()
Expand All @@ -20,7 +20,7 @@
)

def _default_date_now():
return dtime.strftime(dtime.now(), "%Y-%m-%d")
return date.today()

class User(Base):
__tablename__ = "api_user"
Expand All @@ -35,16 +35,6 @@ def __init__(self, api_key: str, email: str = None) -> None:
self.api_key = api_key
self.email = email

@property
def as_dict(self):
return {
"id": self.id,
"api_key": self.api_key,
"email": self.email,
"roles": set(role.name for role in self.roles),
"created": self.created,
"last_time_used": self.last_time_used
}

def has_role(self, required_role: str) -> bool:
return required_role in set(role.name for role in self.roles)
Expand Down Expand Up @@ -121,6 +111,9 @@ class UserRole(Base):
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(String(50), unique=True)

def __repr__(self):
return self.name

@staticmethod
@default_session(WriteSession)
def create_role(name: str, session) -> None:
Expand All @@ -142,3 +135,41 @@ def create_role(name: str, session) -> None:
def list_all_roles(session):
roles = session.query(UserRole).all()
return [role.name for role in roles]


class RegistrationResponse(Base):
__tablename__ = "registration_responses"

email = Column(String(320), unique=True, nullable=False, primary_key=True)
organization = Column(String(120), unique=False, nullable=True)
purpose = Column(String(320), unique=False, nullable=True)

def __init__(self, email: str, organization: str = None, purpose: str = None) -> None:
self.email = email
self.organization = organization
self.purpose = purpose

@staticmethod
@default_session(WriteSession)
def add_response(email: str, organization: str, purpose: str, session):
new_response = RegistrationResponse(email, organization, purpose)
session.add(new_response)
session.commit()


class RemovalRequest(Base):
__tablename__ = "removal_requests"

api_key = Column(String(50), unique=True, nullable=False, primary_key=True)
comment = Column(String(320), unique=False, nullable=True)

def __init__(self, api_key: str, comment: str = None) -> None:
self.api_key = api_key
self.comment = comment

@staticmethod
@default_session(WriteSession)
def add_request(api_key: str, comment: str, session):
new_request = RemovalRequest(api_key, comment)
session.add(new_request)
session.commit()
81 changes: 0 additions & 81 deletions src/server/admin/templates/index.html

This file was deleted.

4 changes: 4 additions & 0 deletions src/server/endpoints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
wiki,
signal_dashboard_status,
signal_dashboard_coverage,
registration,
api_key_removal_request
)

endpoints = [
Expand Down Expand Up @@ -66,6 +68,8 @@
wiki,
signal_dashboard_status,
signal_dashboard_coverage,
registration,
api_key_removal_request
]

__all__ = ["endpoints"]
Loading