Skip to content
Snippets Groups Projects
Commit b2722b01 authored by James Graham's avatar James Graham
Browse files

Allow user tokens to be revoked by their owner and clean token AJAX

parent 454461d4
No related branches found
No related tags found
No related merge requests found
......@@ -66,47 +66,51 @@
<tr>
<td>API Key</td>
<td>
<span id="spanApiToken">
{% if application.proxy_user.auth_token %}
<script type="application/javascript">
function revokeToken() {
function getToken() {
$.ajax({
dataType: "json",
url: "{% url 'applications:token' pk=application.pk %}",
method: "DELETE",
headers: {
"X-CSRFToken": Cookies.get("csrftoken")
},
data: null,
success: function (data) {
$('#spanApiToken').text("");
$('#spanApiToken').text(data.data.token.key);
document.getElementById("spanApiTokenPresent").style.display = "inline";
document.getElementById("spanApiTokenAbsent").style.display = "none";
}
});
}
</script>
{{ application.proxy_user.auth_token }}
<button onclick="revokeToken();" class="btn btn-danger" role="button">Revoke API Token</button>
{% else %}
<script type="application/javascript">
function getToken() {
function revokeToken() {
$.ajax({
dataType: "json",
url: "{% url 'applications:token' pk=application.pk %}",
method: "DELETE",
headers: {
"X-CSRFToken": Cookies.get("csrftoken")
},
data: null,
success: function (data) {
$('#spanApiToken').text(data.data.token.key);
$('#spanApiToken').text("");
document.getElementById("spanApiTokenPresent").style.display = "none";
document.getElementById("spanApiTokenAbsent").style.display = "inline";
}
});
}
</script>
<button onclick="getToken();" class="btn btn-default" role="button">Generate an API Token</button>
{% endif %}
<span id="spanApiTokenPresent" {% if not application.proxy_user.auth_token %}style="display: none;"{% endif %}>
<span id="spanApiToken">
{{ application.proxy_user.auth_token }}
</span>
<button onclick="revokeToken();" class="btn btn-danger" role="button">Revoke API Token</button>
</span>
<span id="spanApiTokenAbsent" {% if application.proxy_user.auth_token %}style="display: none;"{% endif %}>
<button onclick="getToken();" class="btn btn-default" role="button">Generate API Token</button>
</span>
</td>
</tr>
{% endif %}
......
......@@ -26,11 +26,7 @@ urlpatterns = [
name='application.delete'),
path('<int:pk>/token',
views.ApplicationGetTokenView.as_view(),
name='token'),
path('<int:pk>/token',
views.ApplicationGetTokenView.as_view(),
views.ApplicationManageTokenView.as_view(),
name='token'),
path('<int:pk>/manage-access',
......
"""
Views to manage and access :class:`Application`s.
"""
from django.contrib.auth.mixins import PermissionRequiredMixin
from django.http import JsonResponse
from django.urls import reverse_lazy
......@@ -90,9 +94,9 @@ class ApplicationManageAccessView(OwnerPermissionMixin, ManageAccessView):
context_object_name = 'application'
class ApplicationGetTokenView(OwnerPermissionMixin, DetailView):
class ApplicationManageTokenView(OwnerPermissionMixin, DetailView):
"""
Get an API Token for an application.
Manage an API Token for an application.
"""
model = models.Application
......@@ -116,8 +120,6 @@ class ApplicationGetTokenView(OwnerPermissionMixin, DetailView):
def delete(self, request, *args, **kwargs):
"""
Revoke an API Token for the requested :class:`Application`.
:return: JSON containing Token key
"""
self.object = self.get_object()
self.object.proxy_user.revoke_auth_token()
......
"""
Mixins for views to require certain permissions or ownership.
"""
from django.contrib.auth.mixins import UserPassesTestMixin, PermissionRequiredMixin
class SelfOrAdminPermissionMixin(UserPassesTestMixin):
"""
Mixin to require that a user is the linked object or an admin.
To be used e.g. for edit permission on user profiles
"""
def test_func(self) -> bool:
user = self.get_object()
return user == self.request.user or self.request.user.is_superuser
class OwnerPermissionMixin(PermissionRequiredMixin):
"""
Mixin to require that a user has the relevant global permission and is the owner of the relevant object.
......
{% extends "base.html" %}
{% load bootstrap4 %}
{% block extra_head %}
<script src="https://cdn.jsdelivr.net/npm/js-cookie@2.2.0/src/js.cookie.min.js"></script>
{% endblock %}
{% block content %}
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
......@@ -22,32 +26,58 @@
</thead>
<tbody>
{% if user == request.user or request.user.is_superuser %}
<tr>
<td>API Token</td>
<td>
<span id="spanApiToken">
{% if user == request.user and user.auth_token %}
{{ user.auth_token }}
{% else %}
<script type="application/javascript">
function getToken() {
$.ajax({
dataType: "json",
url: "{% url 'profiles:token' %}",
url: "{% url 'profiles:token' pk=user.pk %}",
data: null,
success: function (data) {
$('#spanApiToken').text(data.data.token.key);
document.getElementById("spanApiTokenPresent").style.display = "inline";
document.getElementById("spanApiTokenAbsent").style.display = "none";
}
});
}
function revokeToken() {
$.ajax({
dataType: "json",
url: "{% url 'profiles:token' pk=user.pk %}",
method: "DELETE",
headers: {
"X-CSRFToken": Cookies.get("csrftoken")
},
data: null,
success: function (data) {
$('#spanApiToken').text("");
document.getElementById("spanApiTokenPresent").style.display = "none";
document.getElementById("spanApiTokenAbsent").style.display = "inline";
}
});
}
</script>
<button onclick="getToken();" class="btn btn-default" role="button">Generate an API Token</button>
{% endif %}
<span id="spanApiTokenPresent" {% if not user.auth_token %}style="display: none;"{% endif %}>
<span id="spanApiToken">
{{ user.auth_token }}
</span>
<button onclick="revokeToken();" class="btn btn-danger" role="button">Revoke API Token</button>
</span>
<span id="spanApiTokenAbsent" {% if user.auth_token %}style="display: none;"{% endif %}>
<button onclick="getToken();" class="btn btn-default" role="button">Generate API Token</button>
</span>
</td>
</tr>
{% endif %}
</tbody>
</table>
......
......@@ -44,7 +44,7 @@ class UserTest(TestCase):
# User should not already have token
token = Token.objects.get(user=user)
response = client.get(reverse('profiles:token'))
response = client.get(reverse('profiles:token', kwargs={'pk': user.pk}))
self.assertEqual(200, response.status_code)
......
......@@ -19,7 +19,7 @@ urlpatterns = [
views.UserUriView.as_view(),
name='uri'),
path('token',
views.UserGetTokenView.as_view(),
path('<int:pk>/token',
views.UserManageTokenView.as_view(),
name='token'),
]
"""
Views to manage user profiles.
"""
from django.contrib.auth import get_user_model
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import JsonResponse
from django.views.generic import TemplateView
from django.views.generic.detail import DetailView
......@@ -8,6 +11,7 @@ from rest_framework.authtoken.models import Token
from applications.models import Application
from datasources.models import DataSource
from profiles.permissions import SelfOrAdminPermissionMixin
class IndexView(TemplateView):
......@@ -61,16 +65,15 @@ class UserInactiveView(TemplateView):
template_name = 'profiles/user/inactive.html'
class UserGetTokenView(LoginRequiredMixin, DetailView):
class UserManageTokenView(SelfOrAdminPermissionMixin, DetailView):
"""
Get an API Token for the currently authenticated user.
Manage an API Token for the requested user.
"""
def get_object(self, queryset=None):
return self.request.user
model = get_user_model()
def render_to_response(self, context, **response_kwargs):
"""
Get an existing API Token or create a new one for the currently authenticated user.
Get an existing API Token or create a new one for the requested user.
:return: JSON containing Token key
"""
......@@ -84,3 +87,17 @@ class UserGetTokenView(LoginRequiredMixin, DetailView):
}
}
})
def delete(self, request, *args, **kwargs):
"""
Revoke an API Token for the requested user.
"""
self.object = self.get_object()
self.object.revoke_auth_token()
return JsonResponse({
'status': 'success',
'data': {
'token': None,
}
})
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment