Skip to content

Add a tag to specify the reason why a project was blacklisted. #4962

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 3 additions & 0 deletions warehouse/admin/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ def includeme(config):
config.add_route(
"admin.blacklist.remove", "/admin/blacklist/remove/", domain=warehouse
)
config.add_route(
"admin.blacklist.detail", "/admin/blacklist/{blacklist_id}/", domain=warehouse
)

# Email related Admin pages
config.add_route("admin.emails.list", "/admin/emails/", domain=warehouse)
Expand Down
3 changes: 2 additions & 1 deletion warehouse/admin/templates/admin/blacklist/confirm.html
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
<form action="{{ request.current_route_path() }}" method="POST">
<input name="csrf_token" type="hidden" value="{{ request.session.get_csrf_token() }}">
<input name="project" type="hidden" value="{{ blacklist.project }}">
<input name="comment" type="Hidden" value="{{ blacklist.comment }}">
<input name="comment" type="hidden" value="{{ blacklist.comment }}">
<input name="reason" type="hidden" value="{{ blacklist.reason }}">

<div class="form-group col-sm-8">
<input name="confirm" class="form-control" type="text" placeholder="Enter project name to confirm" autocomplete="off" autocorrect="off" autocapitalize="off">
Expand Down
63 changes: 63 additions & 0 deletions warehouse/admin/templates/admin/blacklist/detail.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
{#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-#}
{% extends "admin/base.html" %}

{% import "admin/utils/pagination.html" as pagination %}

{% block title %}Edit Blacklist{% endblock %}

{% block breadcrumb %}
<li class="active">Blacklist</li>
{% endblock %}

{% block content %}
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">Editing {{ blacklist.name }}</h3>
</div>

<form method="POST" action="{{ request.route_path('admin.blacklist.detail', blacklist_id=blacklist.id) }}">
<input name="csrf_token" type="hidden" value="{{ request.session.get_csrf_token() }}">
<div class="box-body">
<div class="form-group col-sm-6">
<label for="blacklistedProject">Project name</label>
<input name="project" class="form-control" id="blacklistedProject" value="{{ blacklist.name }}" readonly>
</div>
<div class="form-group col-sm-6">
<label for="blacklistedReason">Reason</label>
<select class="form-control" id="blacklistedReason" name="reason"}">
<option value={{ BlacklistReason.not_labeled.name }}
{% if blacklist.reason==BlacklistReason.not_labeled %} selected="selected"{% endif %}
disabled>{{ BlacklistReason.not_labeled.value }}</option>
{% for enum in BlacklistReason %}
{% if enum.name != "not_labeled" %}
<option value="{{ enum.name }}"
{% if blacklist.reason==enum %} selected="selected"{% endif %}>{{ enum.value }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<div class="form-group col-sm-12">
<label for="blacklistedComment">Comment</label>
<textarea name="comment" class="form-control" id="blacklistedComment" rows="3">{{ blacklist.comment }}</textarea>
</div>
</div>
<div class="box-footer">
<div class="pull-right">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
</div>
{% endblock content %}
24 changes: 20 additions & 4 deletions warehouse/admin/templates/admin/blacklist/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,17 @@
<th>Blacklisted by</th>
<th>Blackisted on</th>
<th>Comment</th>
<th>Reason</th>
<th>Actions</th>
</tr>

{% for blacklisted in blacklist %}
<tr>
<td>{{ blacklisted.name }}</td>
<td>
<a href="{{ request.route_path('admin.blacklist.detail', blacklist_id=blacklisted.id) }}">
{{ blacklisted.name }}
</a>
</td>
<td>
{% if blacklisted.blacklisted_by %}
<a href="{{ request.route_path('admin.user.detail', user_id=blacklisted.blacklisted_by.id) }}">
Expand All @@ -58,6 +63,7 @@
</td>
<td>{{ blacklisted.created|format_datetime() }}</td>
<td>{{ blacklisted.comment }}</td>
<td>{{ blacklisted.reason.value }}</td>
<td>
<form method="POST" action="{{ request.route_path('admin.blacklist.remove') }}">
<input name="csrf_token" type="hidden" value="{{ request.session.get_csrf_token() }}">
Expand Down Expand Up @@ -94,12 +100,22 @@ <h3 class="box-title">Blacklist project</h3>

<form method="GET" action="{{ request.route_path('admin.blacklist.add') }}">
<div class="box-body">
<div class="form-group col-sm-4">
<div class="form-group col-sm-6">
<label for="blacklistedProject">Project name</label>
<input name="project" class="form-control" id="blacklistedProject" placeholder="Enter project to blacklist" autocomplete="off" autocorrect="off" autocapitalize="off">
</div>

<div class="form-group col-sm-8">
<div class="form-group col-sm-6">
<label for="blacklistedReason">Reason</label>
<select class="form-control" id="blacklistedReason" name="reason">
<option value="not_labeled" disabled>Not Labeled</option>
{% for enum in BlacklistReason %}
{% if enum.name != "not_labeled" %}
<option value="{{ enum.name }}">{{ enum.value }}</option>
{% endif %}
{% endfor %}
</select>
</div>
<div class="form-group col-sm-12">
<label for="blacklistedComment">Comment</label>
<textarea name="comment" class="form-control" id="blacklistedComment" rows="3" placeholder="Enter comment ..."></textarea>
</div>
Expand Down
52 changes: 48 additions & 4 deletions warehouse/admin/views/blacklist.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from sqlalchemy.orm.exc import NoResultFound

from warehouse.accounts.models import User
from warehouse.packaging.models import Project, Release, File, Role, BlacklistedProject
from warehouse.packaging.models import Project, Release, File, Role, BlacklistedProject, BlacklistReason
from warehouse.utils.http import is_safe_url
from warehouse.utils.paginate import paginate_url_factory
from warehouse.utils.project import remove_project
Expand Down Expand Up @@ -62,7 +62,7 @@ def blacklist(request):
url_maker=paginate_url_factory(request),
)

return {"blacklist": blacklist, "query": q}
return {"blacklist": blacklist, "query": q, "BlacklistReason": BlacklistReason}


@view_config(
Expand All @@ -78,6 +78,7 @@ def confirm_blacklist(request):
raise HTTPBadRequest("Have a project to confirm.")

comment = request.GET.get("comment", "")
reason = request.GET.get("reason", "")

# We need to look up to see if there is an existing project, releases,
# files, roles, etc for what we're attempting to blacklist. If there is we
Expand All @@ -104,7 +105,7 @@ def confirm_blacklist(request):
roles = []

return {
"blacklist": {"project": project_name, "comment": comment},
"blacklist": {"project": project_name, "comment": comment, "reason": reason},
"existing": {
"project": project,
"releases": releases,
Expand All @@ -125,7 +126,13 @@ def add_blacklist(request):
project_name = request.POST.get("project")
if project_name is None:
raise HTTPBadRequest("Have a project to confirm.")

comment = request.POST.get("comment", "")
reason = request.POST.get("reason")

if not reason:
request.session.flash("Include a reason for blacklist request", queue="error")
return HTTPSeeOther(request.current_route_path())

# Verify that the user has confirmed the request to blacklist.
confirm = request.POST.get("confirm")
Expand Down Expand Up @@ -156,7 +163,8 @@ def add_blacklist(request):
# Add our requested blacklist.
request.db.add(
BlacklistedProject(
name=project_name, comment=comment, blacklisted_by=request.user
name=project_name, comment=comment, blacklisted_by=request.user,
reason=reason
)
)

Expand Down Expand Up @@ -208,3 +216,39 @@ def remove_blacklist(request):
redirect_to = request.route_path("admin.blacklist.list")

return HTTPSeeOther(redirect_to)


@view_config(
route_name="admin.blacklist.detail",
renderer="admin/blacklist/detail.html",
permission="admin",
uses_session=True,
require_csrf=True,
require_methods=False,
)
def detail(request):
try:
blacklist = (
request.db.query(BlacklistedProject)
.filter(BlacklistedProject.id == request.matchdict["blacklist_id"])
.one()
)
except NoResultFound:
raise HTTPNotFound

if request.method == "POST":
comment = request.POST.get("comment")
reason = request.POST.get("reason")
blacklist.comment = comment
blacklist.reason = reason
request.db.add(blacklist)
request.session.flash(f"{blacklist.name} updated", queue="success")
redirect_to = request.POST.get("next")
if not redirect_to or not is_safe_url(url=redirect_to, host=request.host):
redirect_to = request.route_path("admin.blacklist.list")
return HTTPSeeOther(redirect_to)

return {
"blacklist": blacklist,
"BlacklistReason": BlacklistReason
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
adding reason enumerated type to database

Revision ID: eae82f0c1992
Revises: e82c3a017d60
Create Date: 2018-10-27 16:19:29.089625
"""

from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql


revision = 'eae82f0c1992'
down_revision = 'e82c3a017d60'

# Note: It is VERY important to ensure that a migration does not lock for a
# long period of time and to ensure that each individual migration does
# not break compatibility with the *previous* version of the code base.
# This is because the migrations will be ran automatically as part of the
# deployment process, but while the previous version of the code is still
# up and running. Thus backwards incompatible changes must be broken up
# over multiple migrations inside of multiple pull requests in order to
# phase them in over multiple deploys.

def upgrade():
black_list_reason = postgresql.ENUM('other', 'not_labeled', 'spam', 'malicious', 'typo_squat', 'dmca',
name='blacklistreason')
black_list_reason.create(op.get_bind())
reason_enum = sa.Enum('other', 'not_labeled', 'spam', 'malicious', 'typo_squat', 'dmca', name='blacklistreason')
op.add_column('blacklist', sa.Column('reason', reason_enum, nullable=True, server_default="not_labeled"))
op.alter_column('blacklist', 'reason', server_default=None)

def downgrade():
op.drop_column('blacklist', 'reason')
op.execute("DROP TYPE blacklistreason;")
11 changes: 10 additions & 1 deletion warehouse/packaging/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -545,8 +545,16 @@ def __table_args__(cls): # noqa
submitted_from = Column(Text)


class BlacklistedProject(db.Model):
class BlacklistReason(enum.Enum):
other = "Other"
not_labeled = "Not Labeled"
spam = "Spam"
malicious = "Malicious"
typo_squat = "Typo Squat"
dmca = "DMCA"


class BlacklistedProject(db.Model):
__tablename__ = "blacklist"
__table_args__ = (
CheckConstraint(
Expand All @@ -566,3 +574,4 @@ class BlacklistedProject(db.Model):
)
blacklisted_by = orm.relationship(User)
comment = Column(Text, nullable=False, server_default="")
reason = Column(Enum(BlacklistReason), nullable=True)