diff --git a/tests/config.defaults.json b/tests/config.defaults.json index 0588ef8..531ed70 100644 --- a/tests/config.defaults.json +++ b/tests/config.defaults.json @@ -25,6 +25,5 @@ "ИКБО-01-21": 30, "ИКБО-02-21": 10 }, - "CLEARABLE_DATABASE": false, - "ALLOW_IP": null + "CLEARABLE_DATABASE": false } diff --git a/webapp/alembic/versions/20240605.00-27.create_allowed_ips.py b/webapp/alembic/versions/20240605.00-27.create_allowed_ips.py new file mode 100644 index 0000000..3eb3437 --- /dev/null +++ b/webapp/alembic/versions/20240605.00-27.create_allowed_ips.py @@ -0,0 +1,29 @@ +"""create_allowed_ips + +Revision ID: c9cc6ddcb46a +Revises: 0ed9e0bfabcb +Create Date: 2024-06-05 00:27:17.497763 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'c9cc6ddcb46a' +down_revision = '0ed9e0bfabcb' +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + 'allowed_ips', + sa.Column("id", sa.Integer, primary_key=True, nullable=False), + sa.Column("ip", sa.String, nullable=False), + sa.Column("label", sa.String, nullable=True), + ) + + +def downgrade(): + op.drop_table('allowed_ips') diff --git a/webapp/config.defaults.json b/webapp/config.defaults.json index c37d381..82c9cd5 100644 --- a/webapp/config.defaults.json +++ b/webapp/config.defaults.json @@ -12,10 +12,7 @@ "HIDE_GROUPS": false, "IMAP_LOGIN": null, "IMAP_PASSWORD": null, - "FINAL_TASKS": { - "0": [0, 1, 2, 3, 4], - "1": [5, 6, 7, 8] - }, + "FINAL_TASKS": null, "ENABLE_LKS_OAUTH": false, "LKS_OAUTH_CLIENT_ID": "kispython.ru", "LKS_OAUTH_CLIENT_SECRET": "CHANGE_ME", @@ -24,6 +21,5 @@ "PLACES_IN_RATING": 8, "PLACES_IN_GROUP": 30, "GROUPS": {}, - "CLEARABLE_DATABASE": true, - "ALLOW_IP": null + "CLEARABLE_DATABASE": true } diff --git a/webapp/dto.py b/webapp/dto.py index a93ca2e..fe0d3d5 100644 --- a/webapp/dto.py +++ b/webapp/dto.py @@ -26,7 +26,6 @@ def __init__(self, config: Config): self.places_in_rating: int = config["PLACES_IN_RATING"] self.places_in_group: int = config["PLACES_IN_GROUP"] self.groups: dict = config["GROUPS"] - self.allow_ip: list[str] = config["ALLOW_IP"] self.hide_groups: bool = config["HIDE_GROUPS"] @property diff --git a/webapp/models.py b/webapp/models.py index 673bff7..24e3d94 100644 --- a/webapp/models.py +++ b/webapp/models.py @@ -139,3 +139,10 @@ class Mailer(Base): __tablename__ = "mailers" id = sa.Column("id", sa.Integer, primary_key=True, nullable=False) domain = sa.Column("domain", sa.String, nullable=False) + + +class AllowedIp(Base): + __tablename__ = "allowed_ips" + id = sa.Column("id", sa.Integer, primary_key=True, nullable=False) + ip = sa.Column("ip", sa.String, nullable=False) + label = sa.Column("label", sa.String, nullable=True) diff --git a/webapp/repositories.py b/webapp/repositories.py index e85f22b..4fe232e 100644 --- a/webapp/repositories.py +++ b/webapp/repositories.py @@ -2,10 +2,11 @@ import uuid from typing import Callable -from sqlalchemy import desc, func, null +from sqlalchemy import desc, func, literal, null from sqlalchemy.orm import Session from webapp.models import ( + AllowedIp, FinalSeed, Group, Mailer, @@ -545,6 +546,38 @@ def create(self, domain: str) -> Mailer: return mailer +class AllowedIpRepository: + def __init__(self, db: DbContextManager): + self.db = db + + def is_allowed(self, ip: str) -> bool: + with self.db.create_session() as session: + total = session.query(AllowedIp).count() + if total == 0: + return True + return session.query(AllowedIp) \ + .filter(literal(ip).contains(AllowedIp.ip)) \ + .count() + + def list_allowed(self): + with self.db.create_session() as session: + return session.query(AllowedIp) \ + .order_by(AllowedIp.id) \ + .all() + + def allow(self, ip: str, label: str): + with self.db.create_session() as session: + aip = AllowedIp(ip=ip, label=label) + session.add(aip) + return aip + + def disallow(self, id: int): + with self.db.create_session() as session: + session.query(AllowedIp) \ + .filter_by(id=id) \ + .delete() + + class AppDatabase: def __init__(self, get_connection: Callable[[], str]): db = DbContextManager(get_connection) @@ -557,3 +590,4 @@ def __init__(self, get_connection: Callable[[], str]): self.seeds = FinalSeedRepository(db) self.students = StudentRepository(db) self.mailers = MailerRepository(db) + self.ips = AllowedIpRepository(db) diff --git a/webapp/templates/teacher/dashboard.jinja b/webapp/templates/teacher/dashboard.jinja index 096d4ba..d0dbc7d 100644 --- a/webapp/templates/teacher/dashboard.jinja +++ b/webapp/templates/teacher/dashboard.jinja @@ -88,5 +88,40 @@ {% endif %} {% endif %} + +
+ Разрешённые IP-адреса +

Оставьте список пустым, чтобы разрешить отправку решений с любого IP-адреса.

+
+ {% for ip in ips %} +
+ +
+
+ +
+
+
+ +
+
+ {% endfor %} +
+
+
+ +
+
+ +
+
+ +
+
+
{% endblock %} \ No newline at end of file diff --git a/webapp/views/student.py b/webapp/views/student.py index 4cef70d..af3ef7a 100644 --- a/webapp/views/student.py +++ b/webapp/views/student.py @@ -162,8 +162,7 @@ def submit_task(student: Student | None, gid: int, vid: int, tid: int): form = StudentMessageForm() valid = form.validate_on_submit() ip = get_real_ip(request) - allow_ip = config.config.allow_ip or "" - if valid and allowed and not status.disabled and allow_ip in ip: + if valid and allowed and not status.disabled and db.ips.is_allowed(ip): sid = student.id if student else None session_id = request.cookies.get("anonymous_identifier") db.messages.submit_task(tid, vid, gid, form.code.data, ip, sid, session_id) diff --git a/webapp/views/teacher.py b/webapp/views/teacher.py index 32c8509..f81af49 100644 --- a/webapp/views/teacher.py +++ b/webapp/views/teacher.py @@ -71,6 +71,7 @@ def dashboard(teacher: Student): glist = db.groups.get_all() vlist = db.variants.get_all() tlist = db.tasks.get_all() + ips = db.ips.list_allowed() return render_template( "teacher/dashboard.jinja", student=teacher, @@ -81,7 +82,8 @@ def dashboard(teacher: Student): groups=groups, glist=glist, vlist=vlist, - tlist=tlist + tlist=tlist, + ips=ips, ) @@ -228,6 +230,22 @@ def reject(teacher: Student, group_id: int, message_id: int): return redirect(f"/teacher/group/{group_id}") +@blueprint.route("/teacher/ips/allow", methods=["GET"]) +@teacher_jwt_required(db.students) +def allow_ip(teacher: Student): + ip = request.args.get('ip') + label = request.args.get('label') + db.ips.allow(ip, label) + return redirect("/teacher") + + +@blueprint.route("/teacher/ips/disallow/", methods=["GET"]) +@teacher_jwt_required(db.students) +def disallow_ip(teacher: Student, id: int): + db.ips.disallow(id) + return redirect("/teacher") + + @blueprint.errorhandler(Exception) def handle_view_errors(e): print(get_exception_info())