Skip to content

Commit

Permalink
allow sysadmins to be managed via the web frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
chris48s committed Jul 31, 2020
1 parent 0e1f588 commit 259ffa1
Show file tree
Hide file tree
Showing 4 changed files with 172 additions and 8 deletions.
76 changes: 71 additions & 5 deletions ckan/templates/admin/index.html
Original file line number Diff line number Diff line change
@@ -1,11 +1,77 @@
{% extends "admin/base.html" %}

{% block primary_content_inner %}
<ul class="user-list">
{% for user in sysadmins %}
<li>{{ h.linked_user(user) }}</li>
{% endfor %}
</ul>
<h2>Current Sysadmins</h2>

<table class="table table-header table-hover table-bordered">
<thead>
<tr>
<th>User</th>
<th>&nbsp;</th>
</tr>
</thead>
<tbody>
{% for user in sysadmins %}
<tr>
<td>{{ h.linked_user(user.name) }}</td>
<td>
<div class="btn-group pull-right">
<form method="POST" action="{% url_for 'user.sysadmin' %}">
<input type="hidden" value="{{ user.id }}" name="id" />
<input type="hidden" value="0" name="status" />
<button
type="submit"
class="btn btn-danger btn-sm"
title="Revoke Sysadmin permission"
>
<i class="fa fa-times"></i>
</button>
</form>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>

<hr />

<h2>Promote user to Sysadmin</h2>

<form method="POST" action="{% url_for 'user.sysadmin' %}">
<div class="row">
<div class="col-md-6">

<div class="form-group">
<select name="id" id="promote-userid" required="required" style="width: 100%">
<option value=""></option>
{% for user in all_users %}
<option value="{{ user.id }}">{{ user.display_name }}</option>
{% endfor %}
</select>
<input type="hidden" value="1" name="status" />
</div>

<div class="form-actions">
<button
type="submit"
class="btn btn-primary"
title="Promote user to Sysadmin"
>Promote</button>
</div>

</div>
</div>
</form>

<script>
document.addEventListener("DOMContentLoaded", function(event) {
$('#promote-userid')
.select2({
placeholder: 'Click or start typing a user name',
});
});
</script>
{% endblock %}

{% block secondary_content %}
Expand Down
60 changes: 60 additions & 0 deletions ckan/tests/controllers/test_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -842,6 +842,66 @@ def test_request_reset_but_mailer_not_configured(

assert "Error sending the email" in response

def test_sysadmin_not_authorized(self, app):
user = factories.User()
env = {'REMOTE_USER': user['name'].encode('ascii')}
app.post(
url_for("user.sysadmin"),
data={'id': user['id'], 'status': '1'},
extra_environ=env,
status=403
)

def test_sysadmin_invalid_user(self, app):
user = factories.Sysadmin()
env = {'REMOTE_USER': user['name'].encode('ascii')}
app.post(
url_for("user.sysadmin"),
data={'id': 'fred', 'status': '1'},
extra_environ=env,
status=404
)

def test_sysadmin_promote_success(self, app):
admin = factories.Sysadmin()
env = {'REMOTE_USER': admin['name'].encode('ascii')}

# create a normal user
user = factories.User(fullname='Alice')

# promote them
resp = app.post(
url_for("user.sysadmin"),
data={'id': user['id'], 'status': '1'},
extra_environ=env,
status=200
)
assert 'Promoted Alice to sysadmin' in resp.body

# now they are a sysadmin
userobj = model.User.get(user['id'])
assert userobj.sysadmin

def test_sysadmin_revoke_success(self, app):
admin = factories.Sysadmin()
env = {'REMOTE_USER': admin['name'].encode('ascii')}

# create another sysadmin
user = factories.Sysadmin(fullname='Bob')

# revoke their status
resp = app.post(
url_for("user.sysadmin"),
data={'id': user['id'], 'status': '0'},
extra_environ=env,
status=200
)
assert 'Revoked sysadmin permission from Bob' in resp.body

# now they are not a sysadmin any more
userobj = model.User.get(user['id'])
assert not userobj.sysadmin


@pytest.mark.usefixtures("clean_db", "with_request_context")
class TestUserImage(object):
Expand Down
12 changes: 9 additions & 3 deletions ckan/views/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,12 @@ def before_request():


def index():
data = dict(sysadmins=[a.name for a in _get_sysadmins()])
return base.render(u'admin/index.html', extra_vars=data)
context = {u'user': g.user}
users = logic.get_action(u'user_list')(context, {})
return base.render(u'admin/index.html', {
u'sysadmins': [u for u in users if u[u'sysadmin']],
u'all_users': [u for u in users if not u[u'sysadmin']],
})


class ResetConfigView(MethodView):
Expand Down Expand Up @@ -200,7 +204,9 @@ def post(self):
return h.redirect_to(u'admin.trash')


admin.add_url_rule(u'/', view_func=index, strict_slashes=False)
admin.add_url_rule(
u'/', view_func=index, methods=['GET'], strict_slashes=False
)
admin.add_url_rule(u'/reset_config',
view_func=ResetConfigView.as_view(str(u'reset_config')))
admin.add_url_rule(u'/config', view_func=ConfigView.as_view(str(u'config')))
Expand Down
32 changes: 32 additions & 0 deletions ckan/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -815,6 +815,37 @@ def followers(id):
return base.render(u'user/followers.html', extra_vars)


def sysadmin():
id_ = request.form.get(u'id')
status = asbool(request.form.get(u'status'))

try:
context = {
u'model': model,
u'session': model.Session,
u'user': g.user,
u'auth_user_obj': g.userobj,
}
data_dict = {u'id': id_, u'sysadmin': status}
user = logic.get_action(u'user_patch')(context, data_dict)
except logic.NotAuthorized:
return base.abort(403, u'Not authorized to promote user to sysadmin')
except logic.NotFound:
return base.abort(404, u'User not found')

if status:
h.flash_success(
u'Promoted {} to sysadmin'.format(user[u'display_name'])
)
else:
h.flash_success(
u'Revoked sysadmin permission from {}'.format(
user[u'display_name']
)
)
return h.redirect_to(u'admin.index')


user.add_url_rule(u'/', view_func=index, strict_slashes=False)
user.add_url_rule(u'/me', view_func=me)

Expand Down Expand Up @@ -858,3 +889,4 @@ def followers(id):
u'/<id>/api-tokens/<jti>/revoke', view_func=api_token_revoke,
methods=(u'POST',)
)
user.add_url_rule(rule=u'/sysadmin', view_func=sysadmin, methods=['POST'])

0 comments on commit 259ffa1

Please sign in to comment.