mirror of
https://gitlab.com/SIGBUS/nyaa.git
synced 2024-12-22 10:10:00 +00:00
Better bans (#341)
* better bans * put jinja2 template into correct file
This commit is contained in:
parent
6aab5557d6
commit
f8a314df4f
38
migrations/versions/500117641608_add_bans.py
Normal file
38
migrations/versions/500117641608_add_bans.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
"""Add bans table
|
||||
|
||||
Revision ID: 500117641608
|
||||
Revises: b79d2fcafd88
|
||||
Create Date: 2017-08-17 01:44:39.205126
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '500117641608'
|
||||
down_revision = 'b79d2fcafd88'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table('bans',
|
||||
sa.Column('id', sa.Integer(), nullable=False),
|
||||
sa.Column('created_time', sa.DateTime(), nullable=True),
|
||||
sa.Column('admin_id', sa.Integer(), nullable=False),
|
||||
sa.Column('user_id', sa.Integer(), nullable=True),
|
||||
sa.Column('user_ip', sa.Binary(length=16), nullable=True),
|
||||
sa.Column('reason', sa.String(length=2048), nullable=False),
|
||||
sa.ForeignKeyConstraint(['admin_id'], ['users.id'], ),
|
||||
sa.ForeignKeyConstraint(['user_id'], ['users.id'], ),
|
||||
sa.PrimaryKeyConstraint('id'),
|
||||
)
|
||||
op.create_index('user_ip_16', 'bans', ['user_ip'], unique=True, mysql_length=16)
|
||||
op.create_index('user_ip_4', 'bans', ['user_ip'], unique=True, mysql_length=4)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_index('user_ip_4', table_name='bans')
|
||||
op.drop_index('user_ip_16', table_name='bans')
|
||||
op.drop_table('bans')
|
|
@ -148,6 +148,33 @@ class CommentForm(FlaskForm):
|
|||
])
|
||||
|
||||
|
||||
class InlineButtonWidget(object):
|
||||
"""
|
||||
Render a basic ``<button>`` field.
|
||||
"""
|
||||
input_type = 'submit'
|
||||
html_params = staticmethod(html_params)
|
||||
|
||||
def __call__(self, field, label=None, **kwargs):
|
||||
kwargs.setdefault('id', field.id)
|
||||
kwargs.setdefault('type', self.input_type)
|
||||
if not label:
|
||||
label = field.label.text
|
||||
return HTMLString('<button %s>' % self.html_params(name=field.name, **kwargs) + label)
|
||||
|
||||
|
||||
class StringSubmitField(StringField):
|
||||
"""
|
||||
Represents an ``<button type="submit">``. This allows checking if a given
|
||||
submit button has been pressed.
|
||||
"""
|
||||
widget = InlineButtonWidget()
|
||||
|
||||
|
||||
class StringSubmitForm(FlaskForm):
|
||||
submit = StringSubmitField('Submit')
|
||||
|
||||
|
||||
class EditForm(FlaskForm):
|
||||
display_name = StringField('Torrent display name', [
|
||||
Length(min=3, max=255, message='Torrent display name must be at least %(min)d characters '
|
||||
|
@ -185,6 +212,8 @@ class EditForm(FlaskForm):
|
|||
Length(max=10 * 1024, message='Description must be at most %(max)d characters long.')
|
||||
])
|
||||
|
||||
submit = SubmitField('Save Changes')
|
||||
|
||||
|
||||
class DeleteForm(FlaskForm):
|
||||
delete = SubmitField("Delete")
|
||||
|
@ -193,6 +222,23 @@ class DeleteForm(FlaskForm):
|
|||
unban = SubmitField("Unban")
|
||||
|
||||
|
||||
class BanForm(FlaskForm):
|
||||
ban_user = SubmitField("Delete & Ban and Ban User")
|
||||
ban_userip = SubmitField("Delete & Ban and Ban User+IP")
|
||||
unban = SubmitField("Unban")
|
||||
|
||||
_validator = DataRequired()
|
||||
|
||||
def _validate_reason(form, field):
|
||||
if form.ban_user.data or form.ban_userip.data:
|
||||
return BanForm._validator(form, field)
|
||||
|
||||
reason = TextAreaField('Ban Reason', [
|
||||
_validate_reason,
|
||||
Length(max=1024, message='Reason must be at most %(max)d characters long.')
|
||||
])
|
||||
|
||||
|
||||
class UploadForm(FlaskForm):
|
||||
torrent_file = FileField('Torrent file', [
|
||||
FileRequired()
|
||||
|
|
|
@ -484,6 +484,8 @@ class User(db.Model):
|
|||
sukebei_torrents = db.relationship('SukebeiTorrent', back_populates='user', lazy='dynamic')
|
||||
sukebei_comments = db.relationship('SukebeiComment', back_populates='user', lazy='dynamic')
|
||||
|
||||
bans = db.relationship('Ban', uselist=True, foreign_keys='Ban.user_id')
|
||||
|
||||
def __init__(self, username, email, password):
|
||||
self.username = username
|
||||
self.email = email
|
||||
|
@ -521,14 +523,18 @@ class User(db.Model):
|
|||
|
||||
@property
|
||||
def userlevel_str(self):
|
||||
level = ''
|
||||
if self.level == UserLevelType.REGULAR:
|
||||
return 'User'
|
||||
level = 'User'
|
||||
elif self.level == UserLevelType.TRUSTED:
|
||||
return 'Trusted'
|
||||
level = 'Trusted'
|
||||
elif self.level == UserLevelType.MODERATOR:
|
||||
return 'Moderator'
|
||||
level = 'Moderator'
|
||||
elif self.level >= UserLevelType.SUPERADMIN:
|
||||
return 'Administrator'
|
||||
level = 'Administrator'
|
||||
if self.is_banned:
|
||||
level = 'BANNED ' + level
|
||||
return level
|
||||
|
||||
@property
|
||||
def userstatus_str(self):
|
||||
|
@ -541,12 +547,16 @@ class User(db.Model):
|
|||
|
||||
@property
|
||||
def userlevel_color(self):
|
||||
color = ''
|
||||
if self.level == UserLevelType.REGULAR:
|
||||
return 'default'
|
||||
color = 'default'
|
||||
elif self.level == UserLevelType.TRUSTED:
|
||||
return 'success'
|
||||
color = 'success'
|
||||
elif self.level >= UserLevelType.MODERATOR:
|
||||
return 'purple'
|
||||
color = 'purple'
|
||||
if self.is_banned:
|
||||
color += ' strike'
|
||||
return color
|
||||
|
||||
@property
|
||||
def ip_string(self):
|
||||
|
@ -679,6 +689,51 @@ class ReportBase(DeclarativeHelperBase):
|
|||
return cls.query.filter(cls.torrent_id == id, cls.status == 0).delete()
|
||||
|
||||
|
||||
class Ban(db.Model):
|
||||
__tablename__ = 'bans'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
created_time = db.Column(db.DateTime(timezone=False), default=datetime.utcnow)
|
||||
admin_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
|
||||
user_ip = db.Column(db.Binary(length=16), nullable=True)
|
||||
reason = db.Column(db.String(length=2048), nullable=False)
|
||||
|
||||
admin = db.relationship('User', uselist=False, lazy='joined', foreign_keys=[admin_id])
|
||||
user = db.relationship('User', uselist=False, lazy='joined', foreign_keys=[user_id])
|
||||
|
||||
__table_args__ = (
|
||||
Index('user_ip_4', 'user_ip', mysql_length=4, unique=True),
|
||||
Index('user_ip_16', 'user_ip', mysql_length=16, unique=True),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return '<Ban %r>' % self.id
|
||||
|
||||
@property
|
||||
def ip_string(self):
|
||||
if self.user_ip:
|
||||
return str(ip_address(self.user_ip))
|
||||
|
||||
@classmethod
|
||||
def all_bans(cls):
|
||||
return cls.query
|
||||
|
||||
@classmethod
|
||||
def by_id(cls, id):
|
||||
return cls.query.get(id)
|
||||
|
||||
@classmethod
|
||||
def banned(cls, user_id, user_ip):
|
||||
if user_id:
|
||||
if user_ip:
|
||||
return cls.query.filter((cls.user_id == user_id) | (cls.user_ip == user_ip))
|
||||
return cls.query.filter(cls.user_id == user_id)
|
||||
if user_ip:
|
||||
return cls.query.filter(cls.user_ip == user_ip)
|
||||
return None
|
||||
|
||||
|
||||
# Actually declare our site-specific classes
|
||||
|
||||
# Torrent
|
||||
|
|
|
@ -331,6 +331,10 @@ a.text-purple:hover, a.text-purple:active, a.text-purple:focus {
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
.strike {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* Torrent file list */
|
||||
.torrent-file-list ul {
|
||||
padding: 5px 20px 0px 20px;
|
||||
|
|
|
@ -180,10 +180,15 @@ document.addEventListener("DOMContentLoaded", function() {
|
|||
|
||||
// Render markdown from elements with "markdown-text" attribute
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
var markdownTargets = document.querySelectorAll('[markdown-text]');
|
||||
var markdownTargets = document.querySelectorAll('[markdown-text],[markdown-text-inline]');
|
||||
for (var i = 0; i < markdownTargets.length; i++) {
|
||||
var target = markdownTargets[i];
|
||||
var rendered = markdown.render(target.innerHTML);
|
||||
var rendered;
|
||||
if (target.attributes["markdown-text-inline"]) {
|
||||
rendered = markdown.renderInline(target.innerHTML);
|
||||
} else {
|
||||
rendered = markdown.render(target.innerHTML);
|
||||
}
|
||||
target.innerHTML = rendered;
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import os.path
|
||||
import re
|
||||
from base64 import b32encode
|
||||
from datetime import datetime
|
||||
from email.utils import formatdate
|
||||
|
@ -135,3 +136,9 @@ def timesince(dt, default='just now'):
|
|||
return '%d %s ago' % (period, singular if int(period) == 1 else plural)
|
||||
|
||||
return default
|
||||
|
||||
|
||||
@bp.app_template_filter()
|
||||
def regex_replace(s, find, replace):
|
||||
"""A non-optimal implementation of a regex filter"""
|
||||
return re.sub(find, replace, s)
|
||||
|
|
55
nyaa/templates/admin_bans.html
Normal file
55
nyaa/templates/admin_bans.html
Normal file
|
@ -0,0 +1,55 @@
|
|||
{% extends "layout.html" %}
|
||||
{% block title %}Admin Log :: {{ config.SITE_NAME }}{% endblock %}
|
||||
{% block body %}
|
||||
{% from "_formhelpers.html" import render_field %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 75px">#</th>
|
||||
<th>Moderator/Admin</th>
|
||||
<th>User</th>
|
||||
<th>User IP</th>
|
||||
<th style="width: 700px">Reason</th>
|
||||
<th style="width: 175px">Date</th>
|
||||
<th style="width: 75px">Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<form method="POST">
|
||||
{{ form.csrf_token }}
|
||||
{% for ban in bans.items %}
|
||||
<tr>
|
||||
<td>{{ ban.id }}</td>
|
||||
<td>
|
||||
<a href="{{ url_for('users.view_user', user_name=ban.admin.username) }}">{{ ban.admin.username }}</a>
|
||||
</td>
|
||||
{% if ban.user %}
|
||||
<td>
|
||||
<a href="{{ url_for('users.view_user', user_name=ban.user.username) }}">{{ ban.user.username }}</a>
|
||||
</td>
|
||||
{% else %}
|
||||
<td>-</td>
|
||||
{% endif %}
|
||||
{% if g.user.is_superadmin %}
|
||||
<td>{{ ban.ip_string }}</td>
|
||||
{% else %}
|
||||
<td>hidden</td>
|
||||
{% endif %}
|
||||
<td><div markdown-text-inline>{{ ban.reason }}</div></td>
|
||||
<td class="center">{{ ban.created_time }}</td>
|
||||
<td class="center">
|
||||
{{ form.submit(label="Unban", value=ban.id, class="btn btn-danger btn-xs") }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</form>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class=pagination>
|
||||
{% from "bootstrap/pagination.html" import render_pagination %}
|
||||
{{ render_pagination(bans) }}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -19,7 +19,11 @@
|
|||
<td>
|
||||
<a href="{{ url_for('users.view_user', user_name=log.admin.username) }}">{{ log.admin.username }}</a>
|
||||
</td>
|
||||
<td><div markdown-text>{{ log.log }}</div></td>
|
||||
{% if g.user.is_superadmin %}
|
||||
<td><div markdown-text-inline>{{ log.log }}</div></td>
|
||||
{% else %}
|
||||
<td><div markdown-text-inline>{{ log.log | regex_replace("IP\(.*?\)", "IP(hidden)" )}}</div></td>
|
||||
{% endif %}
|
||||
<td>{{ log.created_time }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -80,7 +80,7 @@
|
|||
|
||||
<div class="row">
|
||||
<div class="form-group col-md-6">
|
||||
<input type="submit" value="Save Changes" class="btn btn-primary">
|
||||
{{ form.submit(class="btn btn-primary") }}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
@ -93,10 +93,22 @@
|
|||
<h3 class="panel-title">Danger Zone</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form method="POST" action="{{ url_for('torrents.delete', torrent_id=torrent.id) }}">
|
||||
<form method="POST">
|
||||
{{ delete_form.csrf_token }}
|
||||
|
||||
{% if not torrent.deleted %}
|
||||
<p>
|
||||
{% if torrent.deleted %}
|
||||
This torrent is <strong>deleted</strong>{% if torrent.banned %} and <strong>banned</strong>{% endif %}.<br>
|
||||
{% endif %}
|
||||
{% if torrent.user and torrent.user.is_banned %}
|
||||
The uploader is <strong>banned</strong>.<br>
|
||||
{% endif %}
|
||||
{% if ipbanned %}
|
||||
The uploader is <strong>IP banned</strong>.<br>
|
||||
{% endif %}
|
||||
</p>
|
||||
|
||||
{% if not torrent.deleted %}
|
||||
<p class="lead">
|
||||
Delete torrent.
|
||||
{{ delete_form.delete(class="btn btn-danger pull-right") }}
|
||||
|
@ -113,11 +125,11 @@
|
|||
{{ delete_form.ban(class="btn btn-danger pull-right") }}
|
||||
</p>
|
||||
<p>
|
||||
Soft deletes the torrent and disallows reuploading it.
|
||||
Soft deletes the torrent.<br>
|
||||
Bans it from the tracker and disallows reuploading it.
|
||||
</p>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<p>This torrent is <strong>deleted</strong>{% if torrent.banned %} and <strong>banned</strong>{% endif %}.</p>
|
||||
{% else %} {# if torrent.deleted #}
|
||||
<p class="lead">
|
||||
Undelete{% if torrent.banned %} and unban{% endif %} torrent.
|
||||
{% if torrent.banned %}
|
||||
|
@ -140,19 +152,101 @@
|
|||
Unbans torrent without undeleting it.<br>
|
||||
Allows reuploading this torrent again.
|
||||
</p>
|
||||
{% else%}
|
||||
{% else %}
|
||||
<hr>
|
||||
<p class="lead">
|
||||
Ban torrent.
|
||||
{{ delete_form.ban(value="Ban", class="btn btn-danger pull-right") }}
|
||||
</p>
|
||||
<p>
|
||||
Bans the torrent.<br>
|
||||
Disallows reuploading this torrent.
|
||||
Bans it from the tracker and disallows reuploading it.
|
||||
</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{% if ban_form %}
|
||||
{% if (torrent.user and not torrent.user.is_banned) or not ipbanned %}
|
||||
<hr>
|
||||
<p class="lead">
|
||||
{% if torrent.deleted %}
|
||||
{% if torrent.banned %}
|
||||
Ban uploader.
|
||||
{% else %}
|
||||
Ban torrent and ban uploader.
|
||||
{% endif %}
|
||||
{% else %}
|
||||
Delete and ban torrent and ban uploader.
|
||||
{% endif %}
|
||||
</p>
|
||||
<p>
|
||||
{% if torrent.deleted %}
|
||||
{% if torrent.banned %}
|
||||
Bans the uploader.
|
||||
{% else %}
|
||||
Bans it from the tracker and disallows reuploading it.<br>
|
||||
Additionally bans the uploader.
|
||||
{% endif %}
|
||||
{% else %}
|
||||
Soft deletes the torrent.<br>
|
||||
Bans it from the tracker and disallows reuploading it.<br>
|
||||
Additionally bans the uploader.
|
||||
{% endif %}
|
||||
</p>
|
||||
{{ render_field(ban_form.reason, class_="form-control", placeholder="Specify a ban reason.") }}<br>
|
||||
|
||||
{% if torrent.user %}
|
||||
<div class="pull-left">
|
||||
{% if torrent.user.is_banned %}
|
||||
<button type="button" class="btn btn-danger disabled">Already banned</button>
|
||||
{% else %}
|
||||
{% set text = "Ban User" %}
|
||||
{% if not torrent.banned %}
|
||||
{% set text = "Ban and Ban User" %}
|
||||
{% if not torrent.deleted %}
|
||||
{% set text = "Delete & Ban and Ban User" %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{{ ban_form.ban_user(class="btn btn-danger") }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{% if ipbanned %}
|
||||
<button type="button" class="btn btn-danger disabled">Already IP banned</button>
|
||||
{% else %}
|
||||
{% set text = "Ban User+IP" %}
|
||||
{% if not torrent.banned %}
|
||||
{% set text = "Ban and Ban User+IP" %}
|
||||
{% if not torrent.deleted %}
|
||||
{% set text = "Delete & Ban and Ban User+IP" %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{{ ban_form.ban_userip(value=text, class="btn btn-danger") }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="pull-left">
|
||||
<button type="button" class="btn btn-danger disabled">No user</button>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{% if ipbanned %}
|
||||
<button type="button" class="btn btn-danger disabled">Already IP banned</button>
|
||||
{% else %}
|
||||
{% set text = "Ban IP" %}
|
||||
{% if not torrent.banned %}
|
||||
{% set text = "Ban and Ban IP" %}
|
||||
{% if not torrent.deleted %}
|
||||
{% set text = "Delete & Ban and Ban IP" %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
{{ ban_form.ban_userip(value=text, class="btn btn-danger") }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -97,6 +97,7 @@
|
|||
<ul class="dropdown-menu">
|
||||
<li {% if request.path == url_for('admin.reports') %}class="active"{% endif %}><a href="{{ url_for('admin.reports') }}">Reports</a></li>
|
||||
<li {% if request.path == url_for('admin.log') %}class="active"{% endif %}><a href="{{ url_for('admin.log') }}">Log</a></li>
|
||||
<li {% if request.path == url_for('admin.bans') %}class="active"{% endif %}><a href="{{ url_for('admin.bans') }}">Bans</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
|
|
@ -11,14 +11,15 @@
|
|||
|
||||
{% block body %}
|
||||
{% from "_formhelpers.html" import render_menu_with_button %}
|
||||
{% from "_formhelpers.html" import render_field %}
|
||||
|
||||
{% if g.user and g.user.is_moderator %}
|
||||
<h2>User Information</h2><br>
|
||||
<div class="row" style="margin-bottom: 20px;">
|
||||
<div class="col-sm-2" style="max-width: 150px;">
|
||||
<div class="col-md-2" style="max-width: 150px;">
|
||||
<img class="avatar" src="{{ user.gravatar_url() }}">
|
||||
</div>
|
||||
<div class="col-sm-10">
|
||||
<div class="col-md-4">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>User ID:</dt>
|
||||
<dd>{{ user.id }}</dd>
|
||||
|
@ -35,20 +36,74 @@
|
|||
<dd>{{ user.ip_string }}</dd><br>
|
||||
{%- endif -%}
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
{% if admin_form %}
|
||||
<form method="POST">
|
||||
{{ admin_form.csrf_token }}
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
<div class="form-group">
|
||||
{{ render_menu_with_button(admin_form.user_class) }}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<br>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if ban_form %}
|
||||
<div class="col-md-5">
|
||||
<div class="panel panel-danger">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Danger Zone</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form method="POST">
|
||||
{{ ban_form.csrf_token }}
|
||||
{% if user.is_banned %}
|
||||
This user is <strong>banned</strong>.<br>
|
||||
{% endif %}
|
||||
{% if ipbanned %}
|
||||
This user is <strong>ip banned</strong>.<br>
|
||||
{% endif %}
|
||||
{% if not user.is_banned and not bans %}
|
||||
This user is <strong>not banned</strong>.<br>
|
||||
{% endif %}
|
||||
<br>
|
||||
{% if user.is_banned or bans %}
|
||||
<p>
|
||||
{% for ban in bans %}
|
||||
#{{ ban.id }}
|
||||
by <a href="{{ url_for('users.view_user', user_name=ban.admin.username) }}">{{ ban.admin.username }}</a>
|
||||
for <span markdown-text-inline>{{ ban.reason }}</span>
|
||||
<br>
|
||||
{% endfor %}
|
||||
</p>
|
||||
{{ ban_form.unban(class="btn btn-info") }}
|
||||
{% endif %}
|
||||
|
||||
{% if not user.is_banned or not ipbanned %}
|
||||
{% if user.is_banned or bans %}
|
||||
<hr>
|
||||
{% endif %}
|
||||
{{ render_field(ban_form.reason, class_="form-control", placeholder="Specify a ban reason.") }}<br>
|
||||
<div class="pull-left">
|
||||
{% if not user.is_banned %}
|
||||
{{ ban_form.ban_user(value="Ban User", class="btn btn-danger") }}
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-danger disabled">Already banned</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{% if not ipbanned %}
|
||||
{{ ban_form.ban_userip(value="Ban User+IP", class="btn btn-danger") }}
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-danger disabled">Already IP banned</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<h3>
|
||||
|
|
|
@ -28,12 +28,16 @@ def login():
|
|||
if not user:
|
||||
user = models.User.by_email(username)
|
||||
|
||||
if (not user or password != user.password_hash or
|
||||
user.status == models.UserStatusType.INACTIVE):
|
||||
if not user or password != user.password_hash:
|
||||
flask.flash(flask.Markup(
|
||||
'<strong>Login failed!</strong> Incorrect username or password.'), 'danger')
|
||||
return flask.redirect(flask.url_for('account.login'))
|
||||
|
||||
if user.status != models.UserStatusType.ACTIVE:
|
||||
flask.flash(flask.Markup(
|
||||
'<strong>Login failed!</strong> Account is not activated or banned.'), 'danger')
|
||||
return flask.redirect(flask.url_for('account.login'))
|
||||
|
||||
user.last_login_date = datetime.utcnow()
|
||||
user.last_login_ip = ip_address(flask.request.remote_addr).packed
|
||||
db.session.add(user)
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
from ipaddress import ip_address
|
||||
|
||||
import flask
|
||||
|
||||
from nyaa import forms, models
|
||||
|
@ -20,6 +22,45 @@ def view_adminlog():
|
|||
adminlog=logs)
|
||||
|
||||
|
||||
@bp.route('/bans', endpoint='bans', methods=['GET', 'POST'])
|
||||
def view_adminbans():
|
||||
if not flask.g.user or not flask.g.user.is_moderator:
|
||||
flask.abort(403)
|
||||
|
||||
form = forms.StringSubmitForm()
|
||||
if flask.request.method == 'POST' and form.validate():
|
||||
ban = models.Ban.by_id(form.submit.data)
|
||||
if not ban:
|
||||
flask.abort(404)
|
||||
|
||||
log = 'Unbanned ban #{0}'.format(ban.id)
|
||||
|
||||
if ban.user:
|
||||
log += ' ' + ban.user.username
|
||||
ban.user.status = models.UserStatusType.ACTIVE
|
||||
db.session.add(ban.user)
|
||||
|
||||
if ban.user_ip:
|
||||
log += ' IP({0})'.format(ip_address(ban.user_ip))
|
||||
|
||||
adminlog = models.AdminLog(log=log, admin_id=flask.g.user.id)
|
||||
db.session.add(adminlog)
|
||||
|
||||
db.session.delete(ban)
|
||||
db.session.commit()
|
||||
|
||||
flask.flash('Unbanned ban #{0}'.format(ban.id), 'success')
|
||||
|
||||
page = flask.request.args.get('p', flask.request.args.get('offset', 1, int), int)
|
||||
bans = models.Ban.all_bans() \
|
||||
.order_by(models.Ban.created_time.desc()) \
|
||||
.paginate(page=page, per_page=20)
|
||||
|
||||
return flask.render_template('admin_bans.html',
|
||||
bans=bans,
|
||||
form=form)
|
||||
|
||||
|
||||
@bp.route('/reports', endpoint='reports', methods=['GET', 'POST'])
|
||||
def view_reports():
|
||||
if not flask.g.user or not flask.g.user.is_moderator:
|
||||
|
@ -40,7 +81,7 @@ def view_reports():
|
|||
if not torrent or not report or report.status != 0:
|
||||
flask.abort(404)
|
||||
else:
|
||||
log = "Report #{}: {} [#{}]({}), reported by [{}]({})"
|
||||
log = 'Report #{}: {} [#{}]({}), reported by [{}]({})'
|
||||
if action == 'delete':
|
||||
torrent.deleted = True
|
||||
report.status = 1
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import math
|
||||
import re
|
||||
from datetime import datetime, timedelta
|
||||
from ipaddress import ip_address
|
||||
|
||||
import flask
|
||||
from flask_paginate import Pagination
|
||||
|
@ -28,6 +29,10 @@ def before_request():
|
|||
if not user:
|
||||
return logout()
|
||||
|
||||
# Logout inactive and banned users
|
||||
if user.status != models.UserStatusType.ACTIVE:
|
||||
return logout()
|
||||
|
||||
flask.g.user = user
|
||||
|
||||
if 'timeout' not in flask.session or flask.session['timeout'] < datetime.now():
|
||||
|
@ -35,7 +40,14 @@ def before_request():
|
|||
flask.session.permanent = True
|
||||
flask.session.modified = True
|
||||
|
||||
if flask.g.user.status == models.UserStatusType.BANNED:
|
||||
# Check if user is banned on POST
|
||||
if flask.request.method == 'POST':
|
||||
ip = ip_address(flask.request.remote_addr).packed
|
||||
banned = models.Ban.banned(None, ip).first()
|
||||
if banned:
|
||||
if flask.g.user:
|
||||
return logout()
|
||||
|
||||
return 'You are banned.', 403
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import json
|
||||
import os.path
|
||||
from ipaddress import ip_address
|
||||
from urllib.parse import quote
|
||||
|
||||
import flask
|
||||
|
@ -80,8 +81,9 @@ def view_torrent(torrent_id):
|
|||
def edit_torrent(torrent_id):
|
||||
torrent = models.Torrent.by_id(torrent_id)
|
||||
form = forms.EditForm(flask.request.form)
|
||||
delete_form = forms.DeleteForm()
|
||||
form.category.choices = _create_upload_category_choices()
|
||||
delete_form = forms.DeleteForm()
|
||||
ban_form = None
|
||||
|
||||
editor = flask.g.user
|
||||
|
||||
|
@ -96,7 +98,10 @@ def edit_torrent(torrent_id):
|
|||
if not editor or not (editor is torrent.user or editor.is_moderator):
|
||||
flask.abort(403)
|
||||
|
||||
if flask.request.method == 'POST' and form.validate():
|
||||
if editor and editor.is_moderator and editor.level > torrent.user.level:
|
||||
ban_form = forms.BanForm()
|
||||
|
||||
if flask.request.method == 'POST' and form.submit.data and form.validate():
|
||||
# Form has been sent, edit torrent with data.
|
||||
torrent.main_category_id, torrent.sub_category_id = \
|
||||
form.category.parsed_data.get_category_ids()
|
||||
|
@ -127,9 +132,12 @@ def edit_torrent(torrent_id):
|
|||
|
||||
flask.flash(flask.Markup(
|
||||
'Torrent has been successfully edited! Changes might take a few minutes to show up.'),
|
||||
'info')
|
||||
'success')
|
||||
|
||||
return flask.redirect(url)
|
||||
elif flask.request.method == 'POST' and delete_form.validate() and \
|
||||
(not ban_form or ban_form.validate()):
|
||||
return _delete_torrent(torrent, delete_form, ban_form)
|
||||
else:
|
||||
if flask.request.method != 'POST':
|
||||
# Fill form data only if the POST didn't fail
|
||||
|
@ -146,39 +154,43 @@ def edit_torrent(torrent_id):
|
|||
form.is_trusted.data = torrent.trusted
|
||||
form.is_deleted.data = torrent.deleted
|
||||
|
||||
ipbanned = None
|
||||
if editor.is_moderator:
|
||||
tbanned = models.Ban.banned(None, torrent.uploader_ip).first()
|
||||
ubanned = True
|
||||
if torrent.user:
|
||||
ubanned = models.Ban.banned(None, torrent.user.last_login_ip).first()
|
||||
ipbanned = (tbanned and ubanned)
|
||||
|
||||
return flask.render_template('edit.html',
|
||||
form=form,
|
||||
delete_form=delete_form,
|
||||
torrent=torrent)
|
||||
ban_form=ban_form,
|
||||
torrent=torrent,
|
||||
ipbanned=ipbanned)
|
||||
|
||||
|
||||
@bp.route('/view/<int:torrent_id>/delete', endpoint='delete', methods=['POST'])
|
||||
def delete_torrent(torrent_id):
|
||||
torrent = models.Torrent.by_id(torrent_id)
|
||||
form = forms.DeleteForm(flask.request.form)
|
||||
|
||||
def _delete_torrent(torrent, form, banform):
|
||||
editor = flask.g.user
|
||||
|
||||
if not torrent:
|
||||
flask.abort(404)
|
||||
uploader = torrent.user
|
||||
|
||||
# Only allow admins edit deleted torrents
|
||||
if torrent.deleted and not (editor and editor.is_moderator):
|
||||
flask.abort(404)
|
||||
|
||||
# Only allow torrent owners or admins edit torrents
|
||||
if not editor or not (editor is torrent.user or editor.is_moderator):
|
||||
flask.abort(403)
|
||||
|
||||
action = None
|
||||
url = flask.url_for('main.home')
|
||||
|
||||
ban_torrent = form.ban.data
|
||||
if banform:
|
||||
ban_torrent = ban_torrent or banform.ban_user.data or banform.ban_userip.data
|
||||
|
||||
if form.delete.data and not torrent.deleted:
|
||||
action = 'deleted'
|
||||
torrent.deleted = True
|
||||
db.session.add(torrent)
|
||||
|
||||
elif form.ban.data and not torrent.banned and editor.is_moderator:
|
||||
elif ban_torrent and not torrent.banned and editor.is_moderator:
|
||||
action = 'banned'
|
||||
torrent.banned = True
|
||||
if not torrent.deleted:
|
||||
|
@ -202,20 +214,80 @@ def delete_torrent(torrent_id):
|
|||
backend.tracker_api([torrent.info_hash], 'unban')
|
||||
db.session.add(torrent)
|
||||
|
||||
if not action:
|
||||
if not action and not ban_torrent:
|
||||
flask.flash(flask.Markup('What the fuck are you doing?'), 'danger')
|
||||
return flask.redirect(flask.url_for('torrents.edit', torrent_id=torrent.id))
|
||||
|
||||
if editor.is_moderator:
|
||||
if action and editor.is_moderator:
|
||||
url = flask.url_for('torrents.view', torrent_id=torrent.id)
|
||||
if editor is not torrent.user:
|
||||
if editor is not uploader:
|
||||
log = "Torrent [#{0}]({1}) has been {2}".format(torrent.id, url, action)
|
||||
adminlog = models.AdminLog(log=log, admin_id=editor.id)
|
||||
db.session.add(adminlog)
|
||||
|
||||
if action:
|
||||
db.session.commit()
|
||||
flask.flash(flask.Markup('Torrent has been successfully {0}.'.format(action)), 'success')
|
||||
|
||||
if not banform or not (banform.ban_user.data or banform.ban_userip.data):
|
||||
return flask.redirect(url)
|
||||
|
||||
if banform.ban_userip.data:
|
||||
tbanned = models.Ban.banned(None, torrent.uploader_ip).first()
|
||||
ubanned = True
|
||||
if uploader:
|
||||
ubanned = models.Ban.banned(None, uploader.last_login_ip).first()
|
||||
ipbanned = (tbanned and ubanned)
|
||||
|
||||
if (banform.ban_user.data and (not uploader or uploader.is_banned)) or \
|
||||
(banform.ban_userip.data and ipbanned):
|
||||
flask.flash(flask.Markup('What the fuck are you doing?'), 'danger')
|
||||
return flask.redirect(flask.url_for('torrents.edit', torrent_id=torrent.id))
|
||||
|
||||
flavor = "Nyaa" if app.config['SITE_FLAVOR'] == 'nyaa' else "Sukebei"
|
||||
eurl = flask.url_for('torrents.view', torrent_id=torrent.id, _external=True)
|
||||
reason = "[{0}#{1}]({2}) {3}".format(flavor, torrent.id, eurl, banform.reason.data)
|
||||
ban1 = models.Ban(admin_id=editor.id, reason=reason)
|
||||
ban2 = models.Ban(admin_id=editor.id, reason=reason)
|
||||
db.session.add(ban1)
|
||||
|
||||
if uploader:
|
||||
uploader.status = models.UserStatusType.BANNED
|
||||
db.session.add(uploader)
|
||||
ban1.user_id = uploader.id
|
||||
ban2.user_id = uploader.id
|
||||
|
||||
if banform.ban_userip.data:
|
||||
if not ubanned:
|
||||
ban1.user_ip = ip_address(uploader.last_login_ip)
|
||||
if not tbanned:
|
||||
uploader_ip = ip_address(torrent.uploader_ip)
|
||||
if ban1.user_ip != uploader_ip:
|
||||
ban2.user_ip = uploader_ip
|
||||
db.session.add(ban2)
|
||||
else:
|
||||
ban1.user_ip = ip_address(torrent.uploader_ip)
|
||||
|
||||
uploader_str = "Anonymous"
|
||||
if uploader:
|
||||
uploader_url = flask.url_for('users.view_user', user_name=uploader.username)
|
||||
uploader_str = "[{0}]({1})".format(uploader.username, uploader_url)
|
||||
if ban1.user_ip:
|
||||
uploader_str += " IP({0})".format(ban1.user_ip)
|
||||
ban1.user_ip = ban1.user_ip.packed
|
||||
if ban2.user_ip:
|
||||
uploader_str += " IP({0})".format(ban2.user_ip)
|
||||
ban2.user_ip = ban2.user_ip.packed
|
||||
|
||||
log = "Uploader {0} of torrent [#{1}]({2}) has been banned.".format(
|
||||
uploader_str, torrent.id, flask.url_for('torrents.view', torrent_id=torrent.id), action)
|
||||
adminlog = models.AdminLog(log=log, admin_id=editor.id)
|
||||
db.session.add(adminlog)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
flask.flash(flask.Markup('Torrent has been successfully {0}.'.format(action)), 'info')
|
||||
flask.flash(flask.Markup('Uploader has been successfully banned.'), 'success')
|
||||
|
||||
return flask.redirect(url)
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import math
|
||||
from ipaddress import ip_address
|
||||
|
||||
import flask
|
||||
from flask_paginate import Pagination
|
||||
|
@ -23,14 +24,23 @@ def view_user(user_name):
|
|||
flask.abort(404)
|
||||
|
||||
admin_form = None
|
||||
ban_form = None
|
||||
bans = None
|
||||
ipbanned = None
|
||||
if flask.g.user and flask.g.user.is_moderator and flask.g.user.level > user.level:
|
||||
admin_form = forms.UserForm()
|
||||
default, admin_form.user_class.choices = _create_user_class_choices(user)
|
||||
if flask.request.method == 'GET':
|
||||
admin_form.user_class.data = default
|
||||
|
||||
ban_form = forms.BanForm()
|
||||
if flask.request.method == 'POST':
|
||||
doban = (ban_form.ban_user.data or ban_form.unban.data or ban_form.ban_userip.data)
|
||||
bans = models.Ban.banned(user.id, user.last_login_ip).all()
|
||||
ipbanned = list(filter(lambda b: b.user_ip == user.last_login_ip, bans))
|
||||
|
||||
url = flask.url_for('users.view_user', user_name=user.username)
|
||||
if flask.request.method == 'POST' and admin_form and admin_form.validate():
|
||||
if flask.request.method == 'POST' and admin_form and not doban and admin_form.validate():
|
||||
selection = admin_form.user_class.data
|
||||
log = None
|
||||
if selection == 'regular':
|
||||
|
@ -42,9 +52,6 @@ def view_user(user_name):
|
|||
elif selection == 'moderator':
|
||||
user.level = models.UserLevelType.MODERATOR
|
||||
log = "[{}]({}) changed to moderator user".format(user_name, url)
|
||||
elif selection == 'banned':
|
||||
user.status = models.UserStatusType.BANNED
|
||||
log = "[{}]({}) changed to banned user".format(user_name, url)
|
||||
|
||||
adminlog = models.AdminLog(log=log, admin_id=flask.g.user.id)
|
||||
db.session.add(user)
|
||||
|
@ -53,6 +60,46 @@ def view_user(user_name):
|
|||
|
||||
return flask.redirect(url)
|
||||
|
||||
if flask.request.method == 'POST' and ban_form and doban and ban_form.validate():
|
||||
if (ban_form.ban_user.data and user.is_banned) or \
|
||||
(ban_form.ban_userip.data and ipbanned) or \
|
||||
(ban_form.unban.data and not user.is_banned and not bans):
|
||||
flask.flash(flask.Markup('What the fuck are you doing?'), 'danger')
|
||||
return flask.redirect(url)
|
||||
|
||||
user_str = "[{0}]({1})".format(user.username, url)
|
||||
|
||||
if ban_form.unban.data:
|
||||
action = "unbanned"
|
||||
user.status = models.UserStatusType.ACTIVE
|
||||
db.session.add(user)
|
||||
|
||||
for ban in bans:
|
||||
if ban.user_ip:
|
||||
user_str += " IP({0})".format(ip_address(ban.user_ip))
|
||||
db.session.delete(ban)
|
||||
else:
|
||||
action = "banned"
|
||||
user.status = models.UserStatusType.BANNED
|
||||
db.session.add(user)
|
||||
|
||||
ban = models.Ban(admin_id=flask.g.user.id, user_id=user.id, reason=ban_form.reason.data)
|
||||
db.session.add(ban)
|
||||
|
||||
if ban_form.ban_userip.data:
|
||||
ban.user_ip = ip_address(user.last_login_ip)
|
||||
user_str += " IP({0})".format(ban.user_ip)
|
||||
ban.user_ip = ban.user_ip.packed
|
||||
|
||||
log = "User {0} has been {1}.".format(user_str, action)
|
||||
adminlog = models.AdminLog(log=log, admin_id=flask.g.user.id)
|
||||
db.session.add(adminlog)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
flask.flash(flask.Markup('User has been successfully {0}.'.format(action)), 'success')
|
||||
return flask.redirect(url)
|
||||
|
||||
req_args = flask.request.args
|
||||
|
||||
search_term = chain_get(req_args, 'q', 'term')
|
||||
|
@ -117,7 +164,10 @@ def view_user(user_name):
|
|||
user=user,
|
||||
user_page=True,
|
||||
rss_filter=rss_query_string,
|
||||
admin_form=admin_form)
|
||||
admin_form=admin_form,
|
||||
ban_form=ban_form,
|
||||
bans=bans,
|
||||
ipbanned=ipbanned)
|
||||
# Similar logic as home page
|
||||
else:
|
||||
if use_elastic:
|
||||
|
@ -132,7 +182,10 @@ def view_user(user_name):
|
|||
user=user,
|
||||
user_page=True,
|
||||
rss_filter=rss_query_string,
|
||||
admin_form=admin_form)
|
||||
admin_form=admin_form,
|
||||
ban_form=ban_form,
|
||||
bans=bans,
|
||||
ipbanned=ipbanned)
|
||||
|
||||
|
||||
@bp.route('/user/activate/<payload>')
|
||||
|
@ -164,8 +217,6 @@ def _create_user_class_choices(user):
|
|||
choices.append(('trusted', 'Trusted'))
|
||||
if flask.g.user.is_superadmin:
|
||||
choices.append(('moderator', 'Moderator'))
|
||||
if flask.g.user.is_moderator:
|
||||
choices.append(('banned', 'Banned'))
|
||||
|
||||
if user:
|
||||
if user.is_moderator:
|
||||
|
|
Loading…
Reference in a new issue