Skip to content
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

Allow Users to be moderators and for field to be set via Admin dashboard #5249

Merged
merged 20 commits into from
Jan 24, 2019
Merged
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
10 changes: 7 additions & 3 deletions docs/application.rst
Original file line number Diff line number Diff line change
@@ -42,7 +42,7 @@ PyPI. People and groups who want to run their own package indexes
usually use other tools, like `devpi
<https://pypi.org/project/devpi-server/>`_.

Warehouse serves three main classes of users:
Warehouse serves four main classes of users:

1. *People who are not logged in.* This accounts for the majority of
browser traffic and all API download traffic.
@@ -52,8 +52,12 @@ Warehouse serves three main classes of users:
available to a logged-in user other than to manage projects they
own/maintain. As of March 2018, PyPI had about 270,000 users, and
Test PyPI had about 30,000 users.
3. *PyPI application administrators*, e.g., Ernest W. Durbin III,
Dustin Ingram, and Donald Stufft, who add classifiers, ban
3. *PyPI application moderators*. These users have a subset of the
permissions of *PyPI application administrators* to assist in some
routine administration tasks such as adding new trove classifiers and
adjusting upload limits for distribution packages.
4. *PyPI application administrators*, e.g., Ernest W. Durbin III,
Dustin Ingram, and Donald Stufft, who can ban
spam/malware projects, help users with account recovery, and so
on. There are under ten such admins.

1 change: 1 addition & 0 deletions tests/common/db/accounts.py
Original file line number Diff line number Diff line change
@@ -29,6 +29,7 @@ class Meta:
password = "!"
is_active = True
is_superuser = False
is_moderator = False
date_joined = factory.fuzzy.FuzzyNaiveDateTime(
datetime.datetime(2005, 1, 1), datetime.datetime(2010, 1, 1)
)
12 changes: 9 additions & 3 deletions tests/unit/accounts/test_core.py
Original file line number Diff line number Diff line change
@@ -207,10 +207,16 @@ def test_via_basic_auth_compromised(

class TestAuthenticate:
@pytest.mark.parametrize(
("is_superuser", "expected"), [(False, []), (True, ["group:admins"])]
("is_superuser", "is_moderator", "expected"),
[
(False, False, []),
(True, False, ["group:admins", "group:moderators"]),
(False, True, ["group:moderators"]),
(True, True, ["group:admins", "group:moderators"]),
],
)
def test_with_user(self, is_superuser, expected):
user = pretend.stub(is_superuser=is_superuser)
def test_with_user(self, is_superuser, is_moderator, expected):
user = pretend.stub(is_superuser=is_superuser, is_moderator=is_moderator)
service = pretend.stub(get_user=pretend.call_recorder(lambda userid: user))
request = pretend.stub(find_service=lambda iface, context: service)

2 changes: 2 additions & 0 deletions tests/unit/packaging/test_models.py
Original file line number Diff line number Diff line change
@@ -123,6 +123,7 @@ def test_acl(self, db_session):

assert acls == [
(Allow, "group:admins", "admin"),
(Allow, "group:moderators", "moderator"),
(Allow, str(owner1.user.id), ["manage:project", "upload"]),
(Allow, str(owner2.user.id), ["manage:project", "upload"]),
(Allow, str(maintainer1.user.id), ["upload"]),
@@ -291,6 +292,7 @@ def test_acl(self, db_session):

assert acls == [
(Allow, "group:admins", "admin"),
(Allow, "group:moderators", "moderator"),
(Allow, str(owner1.user.id), ["manage:project", "upload"]),
(Allow, str(owner2.user.id), ["manage:project", "upload"]),
(Allow, str(maintainer1.user.id), ["upload"]),
2 changes: 2 additions & 0 deletions warehouse/accounts/__init__.py
Original file line number Diff line number Diff line change
@@ -98,6 +98,8 @@ def _authenticate(userid, request):

if user.is_superuser:
principals.append("group:admins")
if user.is_moderator or user.is_superuser:
principals.append("group:moderators")

return principals

1 change: 1 addition & 0 deletions warehouse/accounts/models.py
Original file line number Diff line number Diff line change
@@ -72,6 +72,7 @@ class User(SitemapMixin, db.Model):
password_date = Column(DateTime, nullable=True, server_default=sql.func.now())
is_active = Column(Boolean, nullable=False, server_default=sql.false())
is_superuser = Column(Boolean, nullable=False, server_default=sql.false())
is_moderator = Column(Boolean, nullable=False, server_default=sql.false())
date_joined = Column(DateTime, server_default=sql.func.now())
last_login = Column(DateTime, nullable=False, server_default=sql.func.now())
disabled_for = Column(
8 changes: 4 additions & 4 deletions warehouse/admin/templates/admin/blacklist/list.html
Original file line number Diff line number Diff line change
@@ -63,7 +63,7 @@
<input name="csrf_token" type="hidden" value="{{ request.session.get_csrf_token() }}">
<input name="blacklist_id" type="hidden" value="{{ blacklisted.id }}">
<input name="next" type="hidden" value="{{ request.current_route_path() }}">
<button type="submit" class="btn btn-link">
<button type="submit" class="btn btn-link" title="{{ "Submitting requires superuser privileges" if not request.has_permission('admin') }}" {{ "disabled" if not request.has_permission('admin') }}>
<i class="fa fa-times"></i>
</button>
</form>
@@ -96,19 +96,19 @@ <h3 class="box-title">Blacklist project</h3>
<div class="box-body">
<div class="form-group col-sm-4">
<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">
<input name="project" class="form-control" id="blacklistedProject" placeholder="Enter project to blacklist" {{ "disabled" if not request.has_permission('admin') }} autocomplete="off" autocorrect="off" autocapitalize="off">
</div>

<div class="form-group col-sm-8">
<label for="blacklistedComment">Comment</label>
<textarea name="comment" class="form-control" id="blacklistedComment" rows="3" placeholder="Enter comment ..."></textarea>
<textarea name="comment" class="form-control" id="blacklistedComment" rows="3" placeholder="Enter comment ..." {{ "disabled" if not request.has_permission('admin') }}></textarea>
</div>
</div>


<div class="box-footer">
<div class="pull-right">
<button type="submit" class="btn btn-primary">Submit</button>
<button type="submit" class="btn btn-primary" title="{{ "Submitting requires superuser privileges" if not request.has_permission('admin') }}" {{ "disabled" if not request.has_permission('admin') }}>Submit</button>
</div>
</div>
</form>
6 changes: 3 additions & 3 deletions warehouse/admin/templates/admin/flags/index.html
Original file line number Diff line number Diff line change
@@ -43,10 +43,10 @@ <h3 class="box-title">Edit Flags</h3>
<input name="csrf_token" type="hidden" value="{{ csrf_token }}">
<input name="id" type="hidden" value="{{ flag.id }}">
<td><code>{{ flag.id }}</code></td>
<td><input name="description" size="50" value="{{ flag.description }}"></td>
<td><input name="description" size="50" value="{{ flag.description }}" {{ "disabled" if not request.has_permission('admin') }}></td>
<td>{{ flag.notify }}</td>
<td><input name="enabled" type="checkbox" {{ 'checked' if flag.enabled else '' }}></td>
<td><input type="submit" value="Save"></td>
<td><input name="enabled" type="checkbox" {{ "disabled" if not request.has_permission('admin') }} {{ 'checked' if flag.enabled else '' }}></td>
<td><input type="submit" title="{{ "Flag changes require superuser privileges" if not request.has_permission('admin') }}" value="Save" {{ "disabled" if not request.has_permission('admin') }}></td>
</form>
</tr>
{% endfor %}
4 changes: 2 additions & 2 deletions warehouse/admin/templates/admin/projects/delete.html
Original file line number Diff line number Diff line change
@@ -35,13 +35,13 @@ <h3 class="box-title">Delete Project</h3>
<label for="confirm_project_name">
Are you sure you want to delete <strong>{{ project_name }}</strong>?
</label>
<input name="confirm_project_name" class="form-control" type="text" placeholder="Enter project name to confirm" autocomplete="off" autocorrect="off" autocapitalize="off">
<input name="confirm_project_name" class="form-control" type="text" placeholder="Enter project name to confirm" {{ "disabled" if not request.has_permission('admin') }} autocomplete="off" autocorrect="off" autocapitalize="off">
</div>

</div>
<div class="box-footer">
<div class="pull-right">
<button type="submit" class="btn btn-primary">Confirm</button>
<button type="submit" class="btn btn-primary" title="{{ "Deleting requires superuser privileges" if not request.has_permission('admin') }}" {{ "disabled" if not request.has_permission('admin') }}>Confirm</button>
</div>
</div>
</div>
12 changes: 6 additions & 6 deletions warehouse/admin/templates/admin/projects/detail.html
Original file line number Diff line number Diff line change
@@ -77,7 +77,7 @@ <h4>Maintainers:</h4>
<td><a href="{{ request.route_path('admin.user.detail', user_id=role.user.id) }}">{{ role.user.username }}</a></td>
<td>{{ role.role_name }}</td>
<td>
<button type="button" class="btn-danger btn-sm" data-toggle="modal" data-target="#deleteRoleModal-{{ role.id }}">
<button type="button" class="btn-danger btn-sm" data-toggle="modal" data-target="#deleteRoleModal-{{ role.id }}" {{ "disabled" if not request.has_permission('admin') }}>
<i class="fa fa-trash"></i>
</button>
<div class="modal fade" id="deleteRoleModal-{{ role.id }}" tabindex="-1" role="dialog">
@@ -116,10 +116,10 @@ <h4 class="modal-title" id="exampleModalLabel">Remove role for {{ role.user.user
<form method="POST" action="{{ request.route_path('admin.project.add_role', project_name=project.name) }}">
<input name="csrf_token" type="hidden" value="{{ request.session.get_csrf_token() }}">
<td>
<input name="username" class="form-control" placeholder="Username" required>
<input name="username" class="form-control" placeholder="Username" required {{ "disabled" if not request.has_permission('admin') }}>
</td>
<td>
<select class="form-control" name="role_name" required>
<select class="form-control" name="role_name" required {{ "disabled" if not request.has_permission('admin') }}>
<option disabled selected>Select a role</option>
{% for role_name in ['Maintainer', 'Owner'] %}
<option value="{{ role_name }}">
@@ -129,7 +129,7 @@ <h4 class="modal-title" id="exampleModalLabel">Remove role for {{ role.user.user
</select>
</td>
<td>
<button type="submit" class="btn btn-primary btn-sm">
<button type="submit" class="btn btn-primary btn-sm" {{ "disabled" if not request.has_permission('admin') }}>
<i class="fa fa-plus"></i>
</button>
</td>
@@ -248,13 +248,13 @@ <h3 class="box-title">Blacklist project</h3>
<div class="box-body">
<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>
<textarea name="comment" class="form-control" id="blacklistedComment" rows="3" placeholder="Enter comment ..." {{ "disabled" if not request.has_permission('admin') }}></textarea>
</div>
</div>

<div class="box-footer">
<div class="pull-right">
<button type="submit" class="btn btn-primary">Submit</button>
<button type="submit" class="btn btn-primary" title="{{ "Submitting requires superuser privileges" if not request.has_permission('admin') }}" {{ "disabled" if not request.has_permission('admin') }}>Submit</button>
</div>
</div>
</form>
2 changes: 1 addition & 1 deletion warehouse/admin/templates/admin/squats/index.html
Original file line number Diff line number Diff line change
@@ -61,7 +61,7 @@ <h3 class="box-title">List Squats</h3>
<form method="POST" action="{{ request.route_path('admin.squats.review') }}">
<input type="hidden" name="id" value="{{ squat.id }}">
<input name="csrf_token" type="hidden" value="{{ request.session.get_csrf_token() }}">
<input type="submit" value="Mark as reviewed">
<input type="submit" value="Mark as reviewed" title="{{ "Marking requires superuser privileges" if not request.has_permission('admin') }}" {{ "disabled" if not request.has_permission('admin') }}>
</form>
</td>
</tr>
9 changes: 5 additions & 4 deletions warehouse/admin/templates/admin/users/detail.html
Original file line number Diff line number Diff line change
@@ -20,7 +20,7 @@
<label for="{{ input_id }}" class="col-sm-2 control-label">{{ label }}</label>

<div class="col-sm-10">
{{ field(id=input_id, class=class, placeholder=placeholder)}}
{{ field(id=input_id, class=class, placeholder=placeholder, disabled=(not request.has_permission('admin')))}}

{% if field.errors %}
<span class="help-block">
@@ -68,7 +68,7 @@ <h2 class="box-title">Actions</h2>
<div class="box-body">
<ul class="list-unstyled">
<li>
<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#nukeModal">
<button type="button" class="btn btn-danger" data-toggle="modal" data-target="#nukeModal" {{ "disabled" if not request.has_permission('admin') }}>
<i class="icon fa fa-bomb"></i> Nuke user
</button>
<div class="modal fade" id="nukeModal" tabindex="-1" role="dialog">
@@ -143,6 +143,7 @@ <h3 class="box-title">Permissions</h3>
<div class="box-body">
{{ render_field("Active", form.is_active, "is-active") }}
{{ render_field("Superuser", form.is_superuser, "is-superuser")}}
{{ render_field("Moderator", form.is_moderator, "is-moderator")}}
</div>
</div>

@@ -155,14 +156,14 @@ <h3 class="box-title">Emails</h3>
{% for field in form.emails.entries %}
{{ render_field("Email", field.email, "email-" ~ loop.index0, class="form-control", placeholder="Email")}}
{{ render_field("Primary", field.primary, "email-primary-" ~ loop.index0)}}
{{ render_field("Verified", field.verified, "email-verified-" ~ loop.index0)}}
{{ render_field("Verified", field.verified, "email-verified-" ~ loop.index0) }}
{% endfor %}
</div>
</div>

<div class="form-group">
<div class="col-sm-offset-10 col-sm-2">
<button type="submit" class="btn btn-danger">Submit</button>
<button type="submit" class="btn btn-danger" title="{{ "Submitting requires superuser privileges" if not request.has_permission('admin') }}" {{ "disabled" if not request.has_permission('admin') }}>Submit</button>
</div>
</div>

2 changes: 2 additions & 0 deletions warehouse/admin/templates/admin/users/list.html
Original file line number Diff line number Diff line change
@@ -44,6 +44,7 @@
<th>Name</th>
<th>Email</th>
<th>Admin</th>
<th>Moderator</th>
<th>Active</th>
</tr>

@@ -56,6 +57,7 @@
<td>{{ user.name }}</td>
<td>{{ user.email }}</td>
<td>{% if user.is_superuser %}<i class="fa fa-check text-green"></i>{% endif %}</td>
<td>{% if user.is_moderator %}<i class="fa fa-check text-green"></i>{% endif %}</td>
<td>{% if user.is_active %}<i class="fa fa-check text-green"></i>{% endif %}</td>
</tr>
{% endfor %}
5 changes: 3 additions & 2 deletions warehouse/admin/views/blacklist.py
Original file line number Diff line number Diff line change
@@ -29,7 +29,8 @@
@view_config(
route_name="admin.blacklist.list",
renderer="admin/blacklist/list.html",
permission="admin",
permission="moderator",
request_method="GET",
uses_session=True,
)
def blacklist(request):
@@ -68,7 +69,7 @@ def blacklist(request):
@view_config(
route_name="admin.blacklist.add",
renderer="admin/blacklist/confirm.html",
permission="admin",
permission="moderator",
request_method="GET",
uses_session=True,
)
7 changes: 4 additions & 3 deletions warehouse/admin/views/classifiers.py
Original file line number Diff line number Diff line change
@@ -19,7 +19,8 @@
@view_config(
route_name="admin.classifiers",
renderer="admin/classifiers/index.html",
permission="admin",
permission="moderator",
request_method="GET",
uses_session=True,
)
def get_classifiers(request):
@@ -30,7 +31,7 @@ def get_classifiers(request):

@view_defaults(
route_name="admin.classifiers.add",
permission="admin",
permission="moderator",
request_method="POST",
uses_session=True,
require_methods=False,
@@ -87,7 +88,7 @@ def add_child_classifier(self):

@view_config(
route_name="admin.classifiers.deprecate",
permission="admin",
permission="moderator",
request_method="POST",
uses_session=True,
require_methods=False,
2 changes: 1 addition & 1 deletion warehouse/admin/views/core.py
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@ def forbidden(exc, request):
@view_config(
route_name="admin.dashboard",
renderer="admin/dashboard.html",
permission="admin",
permission="moderator",
uses_session=True,
)
def dashboard(request):
6 changes: 4 additions & 2 deletions warehouse/admin/views/emails.py
Original file line number Diff line number Diff line change
@@ -25,7 +25,8 @@
@view_config(
route_name="admin.emails.list",
renderer="admin/emails/list.html",
permission="admin",
permission="moderator",
request_method="GET",
uses_session=True,
)
def email_list(request):
@@ -62,7 +63,8 @@ def email_list(request):
@view_config(
route_name="admin.emails.detail",
renderer="admin/emails/detail.html",
permission="admin",
permission="moderator",
request_method="GET",
uses_session=True,
)
def email_detail(request):
3 changes: 2 additions & 1 deletion warehouse/admin/views/flags.py
Original file line number Diff line number Diff line change
@@ -19,7 +19,8 @@
@view_config(
route_name="admin.flags",
renderer="admin/flags/index.html",
permission="admin",
permission="moderator",
request_method="GET",
uses_session=True,
)
def get_flags(request):
2 changes: 1 addition & 1 deletion warehouse/admin/views/journals.py
Original file line number Diff line number Diff line change
@@ -25,7 +25,7 @@
@view_config(
route_name="admin.journals.list",
renderer="admin/journals/list.html",
permission="admin",
permission="moderator",
uses_session=True,
)
def journals_list(request):
Loading