mirror of
https://gitlab.com/SIGBUS/nyaa.git
synced 2024-12-22 14:19:59 +00:00
DB CHANGE: Add uploader ip address to torrent column and show on torrent view page for superadmins.
Added migration script!: remove sukebei_ lines if your local db does not have those. Show users ip address on user page for superadmins. Rename Admin to Moderator internally. Moderators can now change user level to trusted. Superadmins can make users moderator. Improve changing user level.
This commit is contained in:
parent
152e547ac5
commit
9af778217b
30
migrations/versions/3001f79b7722_add_torrents.uploader_ip.py
Normal file
30
migrations/versions/3001f79b7722_add_torrents.uploader_ip.py
Normal file
|
@ -0,0 +1,30 @@
|
|||
"""Add uploader_ip column to torrents table.
|
||||
|
||||
Revision ID: 3001f79b7722
|
||||
Revises:
|
||||
Create Date: 2017-05-21 18:01:35.472717
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '3001f79b7722'
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.add_column('nyaa_torrents', sa.Column('uploader_ip', sa.Binary(), nullable=True))
|
||||
op.add_column('sukebei_torrents', sa.Column('uploader_ip', sa.Binary(), nullable=True))
|
||||
# ### end Alembic commands ###
|
||||
|
||||
|
||||
def downgrade():
|
||||
# ### commands auto generated by Alembic - please adjust! ###
|
||||
op.drop_column('nyaa_torrents', 'uploader_ip')
|
||||
op.drop_column('sukebei_torrents', 'uploader_ip')
|
||||
# ### end Alembic commands ###
|
|
@ -1,3 +1,4 @@
|
|||
import flask
|
||||
from nyaa import app, db
|
||||
from nyaa import models, forms
|
||||
from nyaa import bencode, utils
|
||||
|
@ -8,6 +9,7 @@ import json
|
|||
from werkzeug import secure_filename
|
||||
from collections import OrderedDict
|
||||
from orderedset import OrderedSet
|
||||
from ipaddress import ip_address
|
||||
|
||||
|
||||
def _replace_utf8_values(dict_or_list):
|
||||
|
@ -53,7 +55,8 @@ def handle_torrent_upload(upload_form, uploading_user=None, fromAPI=False):
|
|||
description=description,
|
||||
encoding=torrent_encoding,
|
||||
filesize=torrent_filesize,
|
||||
user=uploading_user)
|
||||
user=uploading_user,
|
||||
uploader_ip=ip_address(flask.request.remote_addr).packed)
|
||||
|
||||
# Store bencoded info_dict
|
||||
torrent.info = models.TorrentInfo(info_dict=torrent_data.bencoded_info_dict)
|
||||
|
|
|
@ -263,7 +263,7 @@ class UploadForm(FlaskForm):
|
|||
|
||||
|
||||
class UserForm(FlaskForm):
|
||||
user_class = DisabledSelectField('Change User Class')
|
||||
user_class = SelectField('Change User Class')
|
||||
|
||||
def validate_user_class(form, field):
|
||||
if not field.data:
|
||||
|
@ -294,7 +294,8 @@ def _validate_trackers(torrent_dict, tracker_to_check_for=None):
|
|||
for announce in announce_list:
|
||||
_validate_list(announce, 'announce-list item')
|
||||
|
||||
announce_string = _validate_bytes(announce[0], 'announce-list item url', test_decode='utf-8')
|
||||
announce_string = _validate_bytes(
|
||||
announce[0], 'announce-list item url', test_decode='utf-8')
|
||||
if tracker_to_check_for and announce_string.lower() == tracker_to_check_for.lower():
|
||||
tracker_found = True
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ from sqlalchemy import func, ForeignKeyConstraint, Index
|
|||
from sqlalchemy_utils import ChoiceType, EmailType, PasswordType
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
from sqlalchemy_fulltext import FullText
|
||||
from ipaddress import ip_address
|
||||
|
||||
import re
|
||||
import base64
|
||||
|
@ -61,6 +62,7 @@ class Torrent(db.Model):
|
|||
encoding = db.Column(db.String(length=32), nullable=False)
|
||||
flags = db.Column(db.Integer, default=0, nullable=False, index=True)
|
||||
uploader_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True)
|
||||
uploader_ip = db.Column(db.Binary(length=16), default=None, nullable=True)
|
||||
has_torrent = db.Column(db.Boolean, nullable=False, default=False)
|
||||
|
||||
created_time = db.Column(db.DateTime(timezone=False), default=datetime.utcnow, nullable=False)
|
||||
|
@ -138,6 +140,11 @@ class Torrent(db.Model):
|
|||
def magnet_uri(self):
|
||||
return create_magnet(self)
|
||||
|
||||
@property
|
||||
def uploader_ip_string(self):
|
||||
if self.uploader_ip:
|
||||
return str(ip_address(self.uploader_ip))
|
||||
|
||||
@property
|
||||
def anonymous(self):
|
||||
return self.flags & TorrentFlags.ANONYMOUS
|
||||
|
@ -313,7 +320,7 @@ class SubCategory(db.Model):
|
|||
class UserLevelType(IntEnum):
|
||||
REGULAR = 0
|
||||
TRUSTED = 1
|
||||
ADMIN = 2
|
||||
MODERATOR = 2
|
||||
SUPERADMIN = 3
|
||||
|
||||
|
||||
|
@ -362,6 +369,11 @@ class User(db.Model):
|
|||
]
|
||||
return all(checks)
|
||||
|
||||
@property
|
||||
def ip_string(self):
|
||||
if self.last_login_ip:
|
||||
return str(ip_address(self.last_login_ip))
|
||||
|
||||
@classmethod
|
||||
def by_id(cls, id):
|
||||
return cls.query.get(id)
|
||||
|
@ -381,8 +393,8 @@ class User(db.Model):
|
|||
return cls.by_username(username_or_email) or cls.by_email(username_or_email)
|
||||
|
||||
@property
|
||||
def is_admin(self):
|
||||
return self.level >= UserLevelType.ADMIN
|
||||
def is_moderator(self):
|
||||
return self.level >= UserLevelType.MODERATOR
|
||||
|
||||
@property
|
||||
def is_superadmin(self):
|
||||
|
|
|
@ -11,7 +11,7 @@ import config
|
|||
|
||||
import json
|
||||
from datetime import datetime, timedelta
|
||||
import ipaddress
|
||||
from ipaddress import ip_address
|
||||
import os.path
|
||||
import base64
|
||||
from urllib.parse import quote
|
||||
|
@ -135,6 +135,7 @@ def get_category_id_map():
|
|||
|
||||
app.register_blueprint(api_handler.api_blueprint, url_prefix='/api')
|
||||
|
||||
|
||||
def chain_get(source, *args):
|
||||
''' Tries to return values from source by the given keys.
|
||||
Returns None if none match.
|
||||
|
@ -146,6 +147,7 @@ def chain_get(source, *args):
|
|||
return value
|
||||
return None
|
||||
|
||||
|
||||
@app.route('/rss', defaults={'rss': True})
|
||||
@app.route('/', defaults={'rss': False})
|
||||
def home(rss):
|
||||
|
@ -194,7 +196,7 @@ def home(rss):
|
|||
|
||||
if flask.g.user:
|
||||
query_args['logged_in_user'] = flask.g.user
|
||||
if flask.g.user.is_admin: # God mode
|
||||
if flask.g.user.is_moderator: # God mode
|
||||
query_args['admin'] = True
|
||||
|
||||
# If searching, we get results from elastic search
|
||||
|
@ -215,7 +217,8 @@ def home(rss):
|
|||
if render_as_rss:
|
||||
return render_rss('"{}"'.format(search_term), query_results, use_elastic=True, magnet_links=use_magnet_links)
|
||||
else:
|
||||
rss_query_string = _generate_query_string(search_term, category, quality_filter, user_name)
|
||||
rss_query_string = _generate_query_string(
|
||||
search_term, category, quality_filter, user_name)
|
||||
max_results = min(max_search_results, query_results['hits']['total'])
|
||||
# change p= argument to whatever you change page_parameter to or pagination breaks
|
||||
pagination = Pagination(p=query_args['page'], per_page=results_per_page,
|
||||
|
@ -238,7 +241,8 @@ def home(rss):
|
|||
if render_as_rss:
|
||||
return render_rss('Home', query, use_elastic=False, magnet_links=use_magnet_links)
|
||||
else:
|
||||
rss_query_string = _generate_query_string(search_term, category, quality_filter, user_name)
|
||||
rss_query_string = _generate_query_string(
|
||||
search_term, category, quality_filter, user_name)
|
||||
# Use elastic is always false here because we only hit this section
|
||||
# if we're browsing without a search term (which means we default to DB)
|
||||
# or if ES is disabled
|
||||
|
@ -256,22 +260,23 @@ def view_user(user_name):
|
|||
if not user:
|
||||
flask.abort(404)
|
||||
|
||||
if flask.g.user and flask.g.user.id != user.id:
|
||||
admin = flask.g.user.is_admin
|
||||
superadmin = flask.g.user.is_superadmin
|
||||
else:
|
||||
admin = False
|
||||
superadmin = False
|
||||
admin_form = 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
|
||||
|
||||
form = forms.UserForm()
|
||||
form.user_class.choices = _create_user_class_choices()
|
||||
if flask.request.method == 'POST' and form.validate():
|
||||
selection = form.user_class.data
|
||||
if flask.request.method == 'POST' and admin_form and admin_form.validate():
|
||||
selection = admin_form.user_class.data
|
||||
|
||||
if selection == 'regular':
|
||||
user.level = models.UserLevelType.REGULAR
|
||||
elif selection == 'trusted':
|
||||
user.level = models.UserLevelType.TRUSTED
|
||||
elif selection == 'moderator':
|
||||
user.level = models.UserLevelType.MODERATOR
|
||||
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
|
@ -311,7 +316,7 @@ def view_user(user_name):
|
|||
|
||||
if flask.g.user:
|
||||
query_args['logged_in_user'] = flask.g.user
|
||||
if flask.g.user.is_admin: # God mode
|
||||
if flask.g.user.is_moderator: # God mode
|
||||
query_args['admin'] = True
|
||||
|
||||
# Use elastic search for term searching
|
||||
|
@ -344,9 +349,7 @@ def view_user(user_name):
|
|||
user_page=True,
|
||||
rss_filter=rss_query_string,
|
||||
level=user_level,
|
||||
admin=admin,
|
||||
superadmin=superadmin,
|
||||
form=form)
|
||||
admin_form=admin_form)
|
||||
# Similar logic as home page
|
||||
else:
|
||||
if use_elastic:
|
||||
|
@ -362,9 +365,7 @@ def view_user(user_name):
|
|||
user_page=True,
|
||||
rss_filter=rss_query_string,
|
||||
level=user_level,
|
||||
admin=admin,
|
||||
superadmin=superadmin,
|
||||
form=form)
|
||||
admin_form=admin_form)
|
||||
|
||||
|
||||
@app.template_filter('rfc822')
|
||||
|
@ -417,7 +418,7 @@ def login():
|
|||
return flask.redirect(flask.url_for('login'))
|
||||
|
||||
user.last_login_date = datetime.utcnow()
|
||||
user.last_login_ip = ipaddress.ip_address(flask.request.remote_addr).packed
|
||||
user.last_login_ip = ip_address(flask.request.remote_addr).packed
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
|
@ -451,7 +452,7 @@ def register():
|
|||
if flask.request.method == 'POST' and form.validate():
|
||||
user = models.User(username=form.username.data.strip(),
|
||||
email=form.email.data.strip(), password=form.password.data)
|
||||
user.last_login_ip = ipaddress.ip_address(flask.request.remote_addr).packed
|
||||
user.last_login_ip = ip_address(flask.request.remote_addr).packed
|
||||
db.session.add(user)
|
||||
db.session.commit()
|
||||
|
||||
|
@ -479,13 +480,7 @@ def profile():
|
|||
|
||||
form = forms.ProfileForm(flask.request.form)
|
||||
|
||||
level = 'Regular'
|
||||
if flask.g.user.is_admin:
|
||||
level = 'Moderator'
|
||||
if flask.g.user.is_superadmin: # check this second because we can be admin AND superadmin
|
||||
level = 'Administrator'
|
||||
elif flask.g.user.is_trusted:
|
||||
level = 'Trusted'
|
||||
level = ['Regular', 'Trusted', 'Moderator', 'Administrator'][flask.g.user.level]
|
||||
|
||||
if flask.request.method == 'POST' and form.validate():
|
||||
user = flask.g.user
|
||||
|
@ -586,11 +581,11 @@ def view_torrent(torrent_id):
|
|||
flask.abort(404)
|
||||
|
||||
# Only allow admins see deleted torrents
|
||||
if torrent.deleted and not (viewer and viewer.is_admin):
|
||||
if torrent.deleted and not (viewer and viewer.is_moderator):
|
||||
flask.abort(404)
|
||||
|
||||
# Only allow owners and admins to edit torrents
|
||||
can_edit = viewer and (viewer is torrent.user or viewer.is_admin)
|
||||
can_edit = viewer and (viewer is torrent.user or viewer.is_moderator)
|
||||
|
||||
files = None
|
||||
if torrent.filelist:
|
||||
|
@ -614,11 +609,11 @@ def edit_torrent(torrent_id):
|
|||
flask.abort(404)
|
||||
|
||||
# Only allow admins edit deleted torrents
|
||||
if torrent.deleted and not (editor and editor.is_admin):
|
||||
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_admin):
|
||||
if not editor or not (editor is torrent.user or editor.is_moderator):
|
||||
flask.abort(403)
|
||||
|
||||
if flask.request.method == 'POST' and form.validate():
|
||||
|
@ -636,7 +631,7 @@ def edit_torrent(torrent_id):
|
|||
|
||||
if editor.is_trusted:
|
||||
torrent.trusted = form.is_trusted.data
|
||||
if editor.is_admin:
|
||||
if editor.is_moderator:
|
||||
torrent.deleted = form.is_deleted.data
|
||||
|
||||
db.session.commit()
|
||||
|
@ -739,11 +734,22 @@ def send_verification_email(to_address, activ_link):
|
|||
server.quit()
|
||||
|
||||
|
||||
def _create_user_class_choices():
|
||||
def _create_user_class_choices(user):
|
||||
choices = [('regular', 'Regular')]
|
||||
if flask.g.user and flask.g.user.is_superadmin:
|
||||
default = 'regular'
|
||||
if flask.g.user:
|
||||
if flask.g.user.is_moderator:
|
||||
choices.append(('trusted', 'Trusted'))
|
||||
return choices
|
||||
if flask.g.user.is_superadmin:
|
||||
choices.append(('moderator', 'Moderator'))
|
||||
|
||||
if user:
|
||||
if user.is_moderator:
|
||||
default = 'moderator'
|
||||
elif user.is_trusted:
|
||||
default = 'trusted'
|
||||
|
||||
return default, choices
|
||||
|
||||
|
||||
# #################################### STATIC PAGES ####################################
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<div class="col-md-6">
|
||||
<label class="control-label">Torrent flags</label>
|
||||
<div>
|
||||
{% if editor.is_admin %}
|
||||
{% if editor.is_moderator %}
|
||||
<label class="btn btn-primary">
|
||||
{{ form.is_deleted }}
|
||||
Deleted
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{% block body %}
|
||||
{% from "_formhelpers.html" import render_menu_with_button %}
|
||||
|
||||
{% if superadmin %}
|
||||
{% if g.user and g.user.is_moderator %}
|
||||
<h2>User Information</h2><br>
|
||||
<dl class="dl-horizontal">
|
||||
<dt>User ID:</dt>
|
||||
|
@ -13,19 +13,25 @@
|
|||
<dt>Email address:</dt>
|
||||
<dd>{{ user.email }}</dd>
|
||||
<dt>User class:</dt>
|
||||
<dd>{{level}}</dd><br>
|
||||
<dd>{{ level }}</dd>
|
||||
{%- if g.user.is_superadmin -%}
|
||||
<dt>Last login IP:</dt>
|
||||
<dd>{{ user.ip_string }}</dd><br>
|
||||
{%- endif -%}
|
||||
</dl>
|
||||
{% if admin_form %}
|
||||
<form method="POST">
|
||||
{{ form.csrf_token }}
|
||||
{{ admin_form.csrf_token }}
|
||||
|
||||
<div class="form-group row">
|
||||
<div class="col-md-6">
|
||||
{{ render_menu_with_button(form.user_class)}}
|
||||
{{ render_menu_with_button(admin_form.user_class) }}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<br>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<h3>
|
||||
Browsing {{ user.username }}'s torrents
|
||||
|
|
|
@ -28,7 +28,10 @@
|
|||
{%- if not torrent.anonymous and torrent.user -%}
|
||||
<a href="{{ user_url }}">{{ torrent.user.username }}</a>
|
||||
{%- else -%}
|
||||
Anonymous {% if torrent.user and (viewer == torrent.user or viewer.is_admin) %}(<a href="{{ user_url }}">{{ torrent.user.username }}</a>){% endif %}
|
||||
Anonymous {% if torrent.user and (viewer == torrent.user or viewer.is_moderator) %}(<a href="{{ user_url }}">{{ torrent.user.username }}</a>){% endif %}
|
||||
{%- endif -%}
|
||||
{%- if viewer and viewer.is_superadmin and torrent.uploader_ip -%}
|
||||
({{ torrent.uploader_ip_string }})
|
||||
{%- endif -%}
|
||||
</div>
|
||||
|
||||
|
|
Loading…
Reference in a new issue