diff --git a/CHANGELOG.md b/CHANGELOG.md
index d637f88c8..972dc6fe2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,7 @@ v2.4.6 (FUTURE)
## Enhancements
+* [#2479](https://github.com/digitalocean/netbox/issues/2479) - Add user permissions for creating/modifying API tokens
* [#2487](https://github.com/digitalocean/netbox/issues/2487) - Return abbreviated API output when passed `?brief=1`
## Bug Fixes
diff --git a/docs/api/authentication.md b/docs/api/authentication.md
index cb6da3bd1..fa769c08e 100644
--- a/docs/api/authentication.md
+++ b/docs/api/authentication.md
@@ -4,6 +4,9 @@ The NetBox API employs token-based authentication. For convenience, cookie authe
A token is a unique identifier that identifies a user to the API. Each user in NetBox may have one or more tokens which he or she can use to authenticate to the API. To create a token, navigate to the API tokens page at `/user/api-tokens/`.
+!!! note
+ The creation and modification of API tokens can be restricted per user by an administrator. If you don't see an option to create an API token, ask an administrator to grant you access.
+
Each token contains a 160-bit key represented as 40 hexadecimal characters. When creating a token, you'll typically leave the key field blank so that a random key will be automatically generated. However, NetBox allows you to specify a key in case you need to restore a previously deleted token to operation.
By default, a token can be used for all operations available via the API. Deselecting the "write enabled" option will restrict API requests made with the token to read operations (e.g. GET) only.
diff --git a/netbox/templates/users/api_tokens.html b/netbox/templates/users/api_tokens.html
index c0e5c55f9..b152497f5 100644
--- a/netbox/templates/users/api_tokens.html
+++ b/netbox/templates/users/api_tokens.html
@@ -10,8 +10,12 @@
-
Edit
-
Delete
+ {% if perms.users.change_token %}
+
Edit
+ {% endif %}
+ {% if perms.users.delete_token %}
+
Delete
+ {% endif %}
{{ token.key }}
{% if token.is_expired %}
@@ -49,10 +53,16 @@
{% empty %}
You do not have any API tokens.
{% endfor %}
-
-
- Add a token
-
+ {% if perms.users.add_token %}
+
+
+ Add a token
+
+ {% else %}
+
+ You do not have permission to create new API tokens. If needed, ask an administrator to enable token creation for your account or an assigned group.
+
+ {% endif %}
{% endblock %}
diff --git a/netbox/users/migrations/0003_token_permissions.py b/netbox/users/migrations/0003_token_permissions.py
new file mode 100644
index 000000000..a8a1f2a6e
--- /dev/null
+++ b/netbox/users/migrations/0003_token_permissions.py
@@ -0,0 +1,17 @@
+# Generated by Django 2.0.8 on 2018-10-05 14:32
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('users', '0001_api_tokens_squashed_0002_unicode_literals'),
+ ]
+
+ operations = [
+ migrations.AlterModelOptions(
+ name='token',
+ options={},
+ ),
+ ]
diff --git a/netbox/users/models.py b/netbox/users/models.py
index b3698d925..15f4f46f4 100644
--- a/netbox/users/models.py
+++ b/netbox/users/models.py
@@ -43,7 +43,7 @@ class Token(models.Model):
)
class Meta:
- default_permissions = []
+ pass
def __str__(self):
# Only display the last 24 bits of the token to avoid accidental exposure.
diff --git a/netbox/users/views.py b/netbox/users/views.py
index c87fa5c7a..bc8263202 100644
--- a/netbox/users/views.py
+++ b/netbox/users/views.py
@@ -3,8 +3,8 @@ from __future__ import unicode_literals
from django.contrib import messages
from django.contrib.auth import login as auth_login, logout as auth_logout, update_session_auth_hash
from django.contrib.auth.decorators import login_required
-from django.contrib.auth.mixins import LoginRequiredMixin
-from django.http import HttpResponseRedirect
+from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
+from django.http import HttpResponseForbidden, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.decorators import method_decorator
@@ -231,8 +231,12 @@ class TokenEditView(LoginRequiredMixin, View):
def get(self, request, pk=None):
if pk is not None:
+ if not request.user.has_perm('users.change_token'):
+ return HttpResponseForbidden()
token = get_object_or_404(Token.objects.filter(user=request.user), pk=pk)
else:
+ if not request.user.has_perm('users.add_token'):
+ return HttpResponseForbidden()
token = Token(user=request.user)
form = TokenForm(instance=token)
@@ -274,7 +278,8 @@ class TokenEditView(LoginRequiredMixin, View):
})
-class TokenDeleteView(LoginRequiredMixin, View):
+class TokenDeleteView(PermissionRequiredMixin, View):
+ permission_required = 'users.delete_token'
def get(self, request, pk):