diff --git a/Watcher/Watcher/accounts/admin.py b/Watcher/Watcher/accounts/admin.py index af4b990..213234c 100644 --- a/Watcher/Watcher/accounts/admin.py +++ b/Watcher/Watcher/accounts/admin.py @@ -1,18 +1,18 @@ from django.contrib import admin - -# Import for Log Entries Snippet from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION from django.utils.html import escape from django.urls import reverse, NoReverseMatch from django.contrib.auth.models import User from django.utils.safestring import mark_safe -from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from .models import APIKey from .api import generate_api_key from django.contrib import messages from django import forms from django.utils import timezone from datetime import timedelta +from knox.models import AuthToken +from django.db.models.signals import post_delete +from django.dispatch import receiver """ Log Entries Snippet @@ -82,7 +82,6 @@ class LogEntryAdmin(admin.ModelAdmin): UserFilter, ActionFilter, 'content_type', - # 'user', ] search_fields = [ @@ -132,130 +131,82 @@ def action_description(self, obj): action_description.short_description = 'Action' - admin.site.register(LogEntry, LogEntryAdmin) -class UserAdmin(BaseUserAdmin): - actions = ['generate_api_key'] - - def generate_api_key(self, request, queryset): - for user in queryset: - raw_key, hashed_key = generate_api_key(user) - if raw_key: - self.message_user(request, f"API Key generated for {user.username}: {raw_key[:10]}...") - else: - self.message_user(request, f"Failed to generate API Key for {user.username}", level='ERROR') - - generate_api_key.short_description = "Generate API Key" - -admin.site.unregister(User) -admin.site.register(User, UserAdmin) - - -class ReadOnlyTextInput(forms.TextInput): - def render(self, name, value, attrs=None, renderer=None): - if value: - truncated_value = value[:5] + '*' * 59 - return f'{truncated_value}' - return super().render(name, value, attrs, renderer) - - class APIKeyForm(forms.ModelForm): EXPIRATION_CHOICES = ( - (1, '1 day'), - (7, '7 days'), - (30, '30 days'), - (60, '60 days'), - (90, '90 days'), - (365, '1 year'), - (730, '2 years'), + (1, '1 day'), (7, '7 days'), (30, '30 days'), (60, '60 days'), (90, '90 days'), (365, '1 year'), (730, '2 years'), ) expiration = forms.ChoiceField(choices=EXPIRATION_CHOICES, label='Expiration', required=True) - + user = forms.ModelChoiceField(queryset=User.objects.all(), label='User', required=True) + class Meta: - model = APIKey - fields = ['user', 'key', 'expiration', 'expiry_at'] + fields = ['user', 'expiration'] def __init__(self, *args, **kwargs): self.request = kwargs.pop('request', None) super().__init__(*args, **kwargs) - instance = kwargs.get('instance') - if instance and instance.pk: - if 'key' in self.fields: - self.fields['key'].widget.attrs['readonly'] = True - self.fields['key'].widget = ReadOnlyTextInput() + + if not self.instance or not self.instance.pk: + self.fields['expiration'].initial = 30 + + else: if 'user' in self.fields: - self.fields['user'].widget.attrs['readonly'] = True - if 'expiry_at' in self.fields: - self.fields['expiry_at'].widget.attrs['readonly'] = True + self.fields['user'].widget = forms.HiddenInput() if 'expiration' in self.fields: - if not self.request.user.is_superuser: - self.fields['expiration'].widget.attrs['disabled'] = True - else: - self.fields['expiration'].widget.attrs['readonly'] = True + self.fields['expiration'].widget = forms.HiddenInput() + + if self.request and not self.request.user.is_superuser: + self.fields['user'].queryset = User.objects.filter(id=self.request.user.id) + self.fields['user'].initial = self.request.user else: - if 'key' in self.fields: - self.fields['key'].widget = forms.HiddenInput() - if 'expiry_at' in self.fields: - self.fields['expiry_at'].widget = forms.HiddenInput() - - if self.request and not self.request.user.is_superuser: - self.fields['user'].queryset = User.objects.filter(id=self.request.user.id) - self.fields['user'].initial = self.request.user - else: - self.fields['user'].queryset = User.objects.all() - - def clean_key(self): - instance = getattr(self, 'instance', None) - if instance and instance.pk: - return instance.key - return self.cleaned_data.get('key', '') - - def clean_expiration(self): - expiration = self.cleaned_data.get('expiration') - if expiration: - try: - expiration = int(expiration) - if expiration not in [choice[0] for choice in self.EXPIRATION_CHOICES]: - raise forms.ValidationError('Invalid expiration value.') - except ValueError: - raise forms.ValidationError('Invalid expiration value.') - return expiration + self.fields['user'].queryset = User.objects.all() def save(self, commit=True): instance = super().save(commit=False) - expiration = self.cleaned_data.get('expiration') - - if expiration: - instance.expiry_at = timezone.now() + timedelta(days=int(expiration)) + expiration_days = int(self.cleaned_data['expiration']) + instance.get_expiry = timezone.now() + timezone.timedelta(days=expiration_days) if commit: instance.save() - return instance - class APIKeyAdmin(admin.ModelAdmin): - list_display = ('user', 'shortened_key', 'created_at', 'expiry_at_display') + list_display = ('get_user', 'get_digest', 'get_created', 'get_expiry') form = APIKeyForm readonly_fields = ('key_details',) - def get_queryset(self, request): - if request.user.is_superuser: - return APIKey.objects.all() - else: - return APIKey.objects.filter(user=request.user) + def get_user(self, obj): + return obj.auth_token.user if obj.auth_token else None + + def get_digest(self, obj): + return obj.auth_token.digest if obj.auth_token else None + + def get_created(self, obj): + return obj.auth_token.created.strftime("%b %d, %Y, %-I:%M %p").replace('AM', 'a.m.').replace('PM', 'p.m.') if obj.auth_token else None + + def get_expiry(self, obj): + return obj.auth_token.expiry.strftime("%b %d, %Y, %-I:%M %p").replace('AM', 'a.m.').replace('PM', 'p.m.') if obj.auth_token else None + + get_user.short_description = 'User' + get_digest.short_description = 'Digest' + get_created.short_description = 'Created' + get_expiry.short_description = 'Expiry' def has_add_permission(self, request): return True + def get_queryset(self, request): + qs = super().get_queryset(request) + if not request.user.is_superuser: + qs = qs.filter(auth_token__user=request.user) + return qs + def get_form(self, request, obj=None, **kwargs): kwargs['form'] = self.form form = super().get_form(request, obj, **kwargs) - if 'key' in form.base_fields: - form.base_fields['key'].widget = ReadOnlyTextInput() - + class CustomAPIKeyForm(form): def __init__(self, *args, **kwargs): kwargs['request'] = request @@ -264,20 +215,12 @@ def __init__(self, *args, **kwargs): return CustomAPIKeyForm def save_model(self, request, obj, form, change): - if not obj.key: - user = request.user - expiration_days = int(form.cleaned_data.get('expiration', 30)) - raw_key, hashed_key = generate_api_key(user, expiration_days) - obj.key = hashed_key - obj.expiry_at = timezone.now() + timedelta(days=expiration_days) - hash_parts = hashed_key.split('$') - obj.key_details = ( - f"algorithm: pbkdf2_sha256 \n " - f"iterations: {hash_parts[1]}\n " - f"salt: {hash_parts[2][:8]}{'*' * (len(hash_parts[2]) - 8)}\n " - f"hash: {hash_parts[3][:8]}{'*' * (len(hash_parts[3]) - 8)}\n\n" - f"Raw API keys are not stored, so there is no way to see this user’s API key." - ) + if not obj.pk: + user = form.cleaned_data['user'] + expiration = form.cleaned_data['expiration'] + raw_key, auth_token = generate_api_key(user, int(expiration)) + obj.auth_token = auth_token + obj.save() copy_button = f'''