diff --git a/netbox/netbox/api/authentication.py b/netbox/netbox/api/authentication.py index ea66dc5a605..1b7def3a3ed 100644 --- a/netbox/netbox/api/authentication.py +++ b/netbox/netbox/api/authentication.py @@ -1,7 +1,11 @@ +import logging + from django.conf import settings +from django.utils import timezone from rest_framework import authentication, exceptions from rest_framework.permissions import BasePermission, DjangoObjectPermissions, SAFE_METHODS +from netbox.config import get_config from users.models import Token from utilities.request import get_client_ip @@ -40,6 +44,17 @@ def authenticate_credentials(self, key): except model.DoesNotExist: raise exceptions.AuthenticationFailed("Invalid token") + # Update last used, but only once a minute. This reduces the write load on the db + if not token.last_used or (timezone.now() - token.last_used).total_seconds() > 60: + # If maintenance mode is enabled, assume the database is read-only, and disable updating the token's + # last_used time upon authentication. + if get_config().MAINTENANCE_MODE: + logger = logging.getLogger('netbox.auth.login') + logger.warning("Maintenance mode enabled: disabling update of token's last used timestamp") + else: + token.last_used = timezone.now() + token.save() + # Enforce the Token's expiration time, if one has been set. if token.is_expired: raise exceptions.AuthenticationFailed("Token expired") diff --git a/netbox/templates/users/api_tokens.html b/netbox/templates/users/api_tokens.html index 360e65a6798..24b32cc9bd8 100644 --- a/netbox/templates/users/api_tokens.html +++ b/netbox/templates/users/api_tokens.html @@ -34,6 +34,14 @@ Never {% endif %} +
+ Last Used
+ {% if token.last_used %} + {{ token.last_used|annotated_date }} + {% else %} + Never + {% endif %} +
Create/Edit/Delete Operations
{% if token.write_enabled %} diff --git a/netbox/users/admin/__init__.py b/netbox/users/admin/__init__.py index ede26cd1bd2..2db822cfefc 100644 --- a/netbox/users/admin/__init__.py +++ b/netbox/users/admin/__init__.py @@ -58,7 +58,7 @@ def get_inlines(self, request, obj): class TokenAdmin(admin.ModelAdmin): form = forms.TokenAdminForm list_display = [ - 'key', 'user', 'created', 'expires', 'write_enabled', 'description', 'list_allowed_ips' + 'key', 'user', 'created', 'expires', 'last_used', 'write_enabled', 'description', 'list_allowed_ips' ] def list_allowed_ips(self, obj): diff --git a/netbox/users/migrations/0003_token_last_used.py b/netbox/users/migrations/0003_token_last_used.py new file mode 100644 index 00000000000..cc014e59c28 --- /dev/null +++ b/netbox/users/migrations/0003_token_last_used.py @@ -0,0 +1,18 @@ +# Generated by Django 4.0.4 on 2022-06-16 15:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('users', '0002_standardize_id_fields'), + ] + + operations = [ + migrations.AddField( + model_name='token', + name='last_used', + field=models.DateTimeField(blank=True, null=True), + ), + ] diff --git a/netbox/users/models.py b/netbox/users/models.py index 704516c7164..4ee4dce6b4b 100644 --- a/netbox/users/models.py +++ b/netbox/users/models.py @@ -204,6 +204,10 @@ class Token(models.Model): blank=True, null=True ) + last_used = models.DateTimeField( + blank=True, + null=True + ) key = models.CharField( max_length=40, unique=True,