From 6c128d05679e8438badd0a06ff6b90aaf5b3f033 Mon Sep 17 00:00:00 2001 From: Mattia Succurro Date: Mon, 11 Nov 2019 19:15:55 +0100 Subject: [PATCH] Adding admin and report endpoints --- CHANGELOG.md | 6 +- README.md | 2 + app/__init__.py | 7 +++ app/admin.py | 66 ++++++++++++++++++++ app/api.py | 5 +- app/models.py | 52 ++++++++++++++- app/reports.py | 42 +++++++++++++ app/templates/admin_base.html | 28 +++++++++ app/templates/admin_home.html | 9 +++ app/templates/admin_report_detail.html | 27 ++++++++ app/templates/admin_reports.html | 16 +++++ app/templates/includes/message.html | 2 +- app/templates/includes/reported_message.html | 15 +++++ app/templates/report_base.html | 27 ++++++++ app/templates/report_done.html | 11 ++++ app/templates/report_message.html | 11 ++++ app/templates/report_user.html | 11 ++++ robots.txt | 4 +- 18 files changed, 335 insertions(+), 6 deletions(-) create mode 100644 app/admin.py create mode 100644 app/reports.py create mode 100644 app/templates/admin_base.html create mode 100644 app/templates/admin_home.html create mode 100644 app/templates/admin_report_detail.html create mode 100644 app/templates/admin_reports.html create mode 100644 app/templates/includes/reported_message.html create mode 100644 app/templates/report_base.html create mode 100644 app/templates/report_done.html create mode 100644 app/templates/report_message.html create mode 100644 app/templates/report_user.html diff --git a/CHANGELOG.md b/CHANGELOG.md index d8c472c..6c78b15 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,12 +2,16 @@ ## 0.8-dev -* Schema changes: moved `full_name` field from table `userprofile` to table `user` for search improvement reasons. +* Added the admin dashboard, accessible from `/admin/` via basic auth. Only users with admin right can access it. Added endpoints `admin.reports` and `admin.reports_detail`. +* Safety is our top priority: added the ability to report someone other's post for everything violating the site's Terms of Service. The current reasons for reporting are: spam, impersonation, pornography, violence, harassment or bullying, hate speech or symbols, self injury, sale or promotion of firearms or drugs, and underage use. +* Schema changes: moved `full_name` field from table `userprofile` to table `user` for search improvement reasons. Added `Report` model. +* Now `profile_search` API endpoint searches by full name too. * Adding `messages_count`, `followers_count` and `following_count` to `profile_info` API endpoint (what I've done to 0.7.1 too). * Adding `create2` API endpoint that accepts media, due to an issue with the `create` endpoint that would make it incompatible. * Adding media URLs to messages in API. * Added `relationships_follow`, `relationships_unfollow`, `username_availability` and `edit_profile` endpoints to API. * Added `url` utility to model `Upload`. +* Changed default `robots.txt`, adding report and admin-related lines. ## 0.7.1-dev diff --git a/README.md b/README.md index 06fc58b..b96fffc 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ To run the app, do "flask run" in the package's parent directory. Based on Tweepee example of [peewee](https://github.com/coleifer/peewee/). +This is the server. For the client, see [coriplusapp](https://github.com/sakuragasaki46/coriplusapp/). + ## Features * Create text statuses, optionally with image diff --git a/app/__init__.py b/app/__init__.py index cbcdd40..83afa06 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -7,6 +7,9 @@ This module also contains very basic web hooks, such as robots.txt. For the website hooks, see `app.website`. For the AJAX hook, see `app.ajax`. +For public API, see `app.api`. +For report pages, see `app.reports`. +For site administration, see `app.admin`. For template filters, see `app.filters`. For the database models, see `app.models`. For other, see `app.utils`. @@ -118,4 +121,8 @@ app.register_blueprint(bp) from .api import bp app.register_blueprint(bp) +from .reports import bp +app.register_blueprint(bp) +from .admin import bp +app.register_blueprint(bp) diff --git a/app/admin.py b/app/admin.py new file mode 100644 index 0000000..b8fb4b9 --- /dev/null +++ b/app/admin.py @@ -0,0 +1,66 @@ +''' +Management of reports and the entire site. + +New in 0.8. +''' + +from flask import Blueprint, redirect, render_template, request, url_for +from .models import User, Message, Report, report_reasons, REPORT_STATUS_ACCEPTED, \ + REPORT_MEDIA_USER, REPORT_MEDIA_MESSAGE +from .utils import pwdhash, object_list +from functools import wraps + +bp = Blueprint('admin', __name__, url_prefix='/admin') + +def check_auth(username, password): + try: + return User.get((User.username == username) & (User.password == pwdhash(password)) + ).is_admin + except User.DoesNotExist: + return False + +def admin_required(f): + @wraps(f) + def wrapped_view(**kwargs): + auth = request.authorization + if not (auth and check_auth(auth.username, auth.password)): + return ('Unauthorized', 401, { + 'WWW-Authenticate': 'Basic realm="Login Required"' + }) + return f(**kwargs) + return wrapped_view + +def review_reports(status, media_type, media_id): + (Report + .update(status=status) + .where((Report.media_type == media_type) & (Report.media_id == media_id)) + .execute()) + if status == REPORT_STATUS_ACCEPTED: + if media_type == REPORT_MEDIA_USER: + user = User[media_id] + user.is_disabled = 2 + user.save() + elif media_type == REPORT_MEDIA_MESSAGE: + Message.delete().where(Message.id == media_id).execute() + +@bp.route('/') +@admin_required +def homepage(): + return render_template('admin_home.html') + +@bp.route('/reports') +@admin_required +def reports(): + return object_list('admin_reports.html', Report.select().order_by(Report.created_date.desc()), 'report_list', report_reasons=dict(report_reasons)) + +@bp.route('/reports/', methods=['GET', 'POST']) +@admin_required +def reports_detail(id): + report = Report[id] + if request.method == 'POST': + if request.form.get('take_down'): + review_reports(REPORT_STATUS_ACCEPTED, report.media_type, report.media_id) + elif request.form.get('discard'): + review_reports(REPORT_STATUS_DECLINED, report.media_type, report.media_id) + return redirect(url_for('admin.reports')) + return render_template('admin_report_detail.html', report=report, report_reasons=dict(report_reasons)) diff --git a/app/api.py b/app/api.py index a11b5eb..0171f0f 100644 --- a/app/api.py +++ b/app/api.py @@ -241,14 +241,15 @@ def relationships_unfollow(self, userid): @validate_access def profile_search(self): data = request.get_json(True) - query = User.select().where(User.username ** ('%' + data['q'] + '%')).limit(20) + query = User.select().where((User.username ** ('%' + data['q'] + '%')) | + (User.full_name ** ('%' + data['q'] + '%'))).limit(20) results = [] for result in query: profile = result.profile results.append({ "id": result.id, "username": result.username, - "full_name": profile.full_name, + "full_name": result.full_name, "followers_count": len(result.followers()) }) return { diff --git a/app/models.py b/app/models.py index e95a793..34d04e3 100644 --- a/app/models.py +++ b/app/models.py @@ -198,11 +198,61 @@ class Notification(BaseModel): detail = TextField() pub_date = DateTimeField() seen = IntegerField(default=0) + +REPORT_MEDIA_USER = 1 +REPORT_MEDIA_MESSAGE = 2 + +REPORT_REASON_SPAM = 1 +REPORT_REASON_IMPERSONATION = 2 +REPORT_REASON_PORN = 3 +REPORT_REASON_VIOLENCE = 4 +REPORT_REASON_HATE = 5 +REPORT_REASON_BULLYING = 6 +REPORT_REASON_SELFINJURY = 7 +REPORT_REASON_FIREARMS = 8 +REPORT_REASON_DRUGS = 9 +REPORT_REASON_UNDERAGE = 10 + +report_reasons = [ + (REPORT_REASON_SPAM, "It's spam"), + (REPORT_REASON_IMPERSONATION, "This profile is pretending to be someone else"), + (REPORT_REASON_PORN, "Nudity or pornography"), + (REPORT_REASON_VIOLENCE, "Violence or dangerous organization"), + (REPORT_REASON_HATE, "Hate speech or symbols"), + (REPORT_REASON_BULLYING, "Harassment or bullying"), + (REPORT_REASON_SELFINJURY, "Self injury"), + (REPORT_REASON_FIREARMS, "Sale or promotion of firearms"), + (REPORT_REASON_DRUGS, "Sale or promotion of drugs"), + (REPORT_REASON_UNDERAGE, "This user is less than 13 years old"), +] + +REPORT_STATUS_DELIVERED = 0 +REPORT_STATUS_ACCEPTED = 1 +REPORT_STATUS_DECLINED = 2 + +# New in 0.8. +class Report(BaseModel): + media_type = IntegerField() + media_id = IntegerField() + sender = ForeignKeyField(User, null=True) + reason = IntegerField() + status = IntegerField(default=REPORT_STATUS_DELIVERED) + created_date = DateTimeField() + + @property + def media(self): + try: + if self.media_type == REPORT_MEDIA_USER: + return User[self.media_id] + elif self.media_type == REPORT_MEDIA_MESSAGE: + return Message[self.media_id] + except DoesNotExist: + return def create_tables(): with database: database.create_tables([ User, UserAdminship, UserProfile, Message, Relationship, - Upload, Notification]) + Upload, Notification, Report]) if not os.path.isdir(UPLOAD_DIRECTORY): os.makedirs(UPLOAD_DIRECTORY) diff --git a/app/reports.py b/app/reports.py new file mode 100644 index 0000000..4c001ba --- /dev/null +++ b/app/reports.py @@ -0,0 +1,42 @@ +''' +Module for user and message reports. + +New in 0.8. +''' + +from flask import Blueprint, redirect, request, render_template, url_for +from .models import Report, REPORT_MEDIA_USER, REPORT_MEDIA_MESSAGE, report_reasons +from .utils import get_current_user +import datetime + +bp = Blueprint('reports', __name__, url_prefix='/report') + +@bp.route('/user/', methods=['GET', 'POST']) +def report_user(userid): + if request.method == "POST": + Report.create( + media_type=REPORT_MEDIA_USER, + media_id=userid, + sender=get_current_user(), + reason=request.form['reason'], + created_date=datetime.datetime.now() + ) + return redirect(url_for('reports.report_done')) + return render_template('report_user.html', report_reasons=report_reasons) + +@bp.route('/message/', methods=['GET', 'POST']) +def report_message(userid): + if request.method == "POST": + Report.create( + media_type=REPORT_MEDIA_MESSAGE, + media_id=userid, + sender=get_current_user(), + reason=request.form['reason'], + created_date=datetime.datetime.now() + ) + return redirect(url_for('reports.report_done')) + return render_template('report_message.html', report_reasons=report_reasons) + +@bp.route('/done', methods=['GET', 'POST']) +def report_done(): + return render_template('report_done.html') diff --git a/app/templates/admin_base.html b/app/templates/admin_base.html new file mode 100644 index 0000000..e59e813 --- /dev/null +++ b/app/templates/admin_base.html @@ -0,0 +1,28 @@ + + + + {{ site_name }} + + + + + +
+ {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} + {% block body %}{% endblock %} +
+ + + + diff --git a/app/templates/admin_home.html b/app/templates/admin_home.html new file mode 100644 index 0000000..d4075d1 --- /dev/null +++ b/app/templates/admin_home.html @@ -0,0 +1,9 @@ +{% extends "admin_base.html" %} + +{% block body %} + +{% endblock %} diff --git a/app/templates/admin_report_detail.html b/app/templates/admin_report_detail.html new file mode 100644 index 0000000..d445d64 --- /dev/null +++ b/app/templates/admin_report_detail.html @@ -0,0 +1,27 @@ +{% extends "admin_base.html" %} + +{% block body %} +

Report detail #{{ report.id }}

+

Type: {{ [None, 'user', 'message'][report.media_type] }}

+

Reason: {{ report_reasons[report.reason] }}

+

Status: {{ ['Unreviewed', 'Accepted', 'Declined'][report.status] }}

+ +

Detail

+ {% if report.media is none %} +

The media is unavailable.

+ {% elif report.media_type == 1 %} +

Showing first 20 messages of the reported user.

+
    + {% for message in report.media.messages %} + {% include "includes/reported_message.html" %} + {% endfor %} +
+ {% elif report.media_type == 2 %} + {% set message = report.media %} + {% include "includes/reported_message.html" %} + {% endif %} +
+ + +
+{% endblock %} diff --git a/app/templates/admin_reports.html b/app/templates/admin_reports.html new file mode 100644 index 0000000..1d022a8 --- /dev/null +++ b/app/templates/admin_reports.html @@ -0,0 +1,16 @@ +{% extends "admin_base.html" %} + +{% block body %} +
    + {% for report in report_list %} +
  • 0 %}class="done"{% endif %}> +

    #{{ report.id }} + (detail)

    +

    Type: {{ [None, 'user', 'message'][report.media_type] }}

    +

    Reason: {{ report_reasons[report.reason] }}

    +

    Status: {{ ['Unreviewed', 'Accepted', 'Declined'][report.status] }}

    +
  • + {% endfor %} +
+ {% include "includes/pagination.html" %} +{% endblock %} diff --git a/app/templates/includes/message.html b/app/templates/includes/message.html index 59369c9..8095ab2 100644 --- a/app/templates/includes/message.html +++ b/app/templates/includes/message.html @@ -22,6 +22,6 @@
  • Edit or change privacy
  • Delete permanently
  • {% else %} - +
  • Report
  • {% endif %} diff --git a/app/templates/includes/reported_message.html b/app/templates/includes/reported_message.html new file mode 100644 index 0000000..aaed6cd --- /dev/null +++ b/app/templates/includes/reported_message.html @@ -0,0 +1,15 @@ +
    +

    Message #{{ message.id }} (detail)

    +

    Author: {{ message.user.username }}

    +

    Text:

    +
    + {{ message.text|enrich }} + {% if message.uploads %} +
    + +
    + {% endif %} +
    +

    Privacy: {{ ['public', 'unlisted', 'friends', 'only me'][message.privacy] }}

    +

    Date: {{ message.pub_date.strftime('%B %-d, %Y %H:%M:%S') }}

    +
    diff --git a/app/templates/report_base.html b/app/templates/report_base.html new file mode 100644 index 0000000..a069cb5 --- /dev/null +++ b/app/templates/report_base.html @@ -0,0 +1,27 @@ + + + + Report – Cori+ + + + + +
    + {% block body %}{% endblock %} +
    +
    + + +
    + + + diff --git a/app/templates/report_done.html b/app/templates/report_done.html new file mode 100644 index 0000000..d696e7c --- /dev/null +++ b/app/templates/report_done.html @@ -0,0 +1,11 @@ +{% extends "report_base.html" %} + +{% block body %} +
    +

    Done

    + +

    Your report has been sent.
    + We'll review the user or message, and, if against our Community + Guidelines, we'll remove it.

    +
    +{% endblock %} diff --git a/app/templates/report_message.html b/app/templates/report_message.html new file mode 100644 index 0000000..c17553e --- /dev/null +++ b/app/templates/report_message.html @@ -0,0 +1,11 @@ +{% extends "report_base.html" %} + +{% block body %} + {% for reason in report_reasons %} +
    +
    +

    {{ reason[1] }}

    +
    +
    + {% endfor %} +{% endblock %} diff --git a/app/templates/report_user.html b/app/templates/report_user.html new file mode 100644 index 0000000..c17553e --- /dev/null +++ b/app/templates/report_user.html @@ -0,0 +1,11 @@ +{% extends "report_base.html" %} + +{% block body %} + {% for reason in report_reasons %} +
    +
    +

    {{ reason[1] }}

    +
    +
    + {% endfor %} +{% endblock %} diff --git a/robots.txt b/robots.txt index 8b13789..2f6dd99 100644 --- a/robots.txt +++ b/robots.txt @@ -1 +1,3 @@ - +User-Agent: * +Disallow: /report/ +Noindex: /admin/