Skip to content

Commit

Permalink
Allow WebAuthn devices to be managed in admin
Browse files Browse the repository at this point in the history
This fixes #1698. Note that we cannot add WebAuthn devices for the user, so
that part is not and will never be implemented.

Setting is_webauthn_enabled to False when removing all WebAuthn devices is
moved to a signal so that it also works when deleting credentials in the admin.
  • Loading branch information
quantum5 committed Jun 5, 2021
1 parent 36ea904 commit c363a38
Show file tree
Hide file tree
Showing 4 changed files with 27 additions and 6 deletions.
12 changes: 11 additions & 1 deletion judge/admin/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from reversion.admin import VersionAdmin

from django_ace import AceWidget
from judge.models import Profile
from judge.models import Profile, WebAuthnCredential
from judge.utils.views import NoBatchDeleteMixin
from judge.widgets import AdminMartorWidget, AdminSelect2Widget

Expand Down Expand Up @@ -44,6 +44,15 @@ def queryset(self, request, queryset):
return queryset.filter(timezone=self.value())


class WebAuthnInline(admin.TabularInline):
model = WebAuthnCredential
readonly_fields = ('cred_id', 'public_key', 'counter')
extra = 0

def has_add_permission(self, request):
return False


class ProfileAdmin(NoBatchDeleteMixin, VersionAdmin):
fields = ('user', 'display_rank', 'about', 'organizations', 'timezone', 'language', 'ace_theme',
'math_engine', 'last_access', 'ip', 'mute', 'is_unlisted', 'notes', 'is_totp_enabled', 'user_script',
Expand All @@ -58,6 +67,7 @@ class ProfileAdmin(NoBatchDeleteMixin, VersionAdmin):
actions_on_top = True
actions_on_bottom = True
form = ProfileForm
inlines = [WebAuthnInline]

def get_queryset(self, request):
return super(ProfileAdmin, self).get_queryset(request).select_related('user')
Expand Down
7 changes: 7 additions & 0 deletions judge/models/profile.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,13 @@ def webauthn_user(self):
rp_id=settings.WEBAUTHN_RP_ID,
)

def __str__(self):
return f'WebAuthn credential: {self.name}'

class Meta:
verbose_name = _('WebAuthn credential')
verbose_name_plural = _('WebAuthn credentials')


class OrganizationRequest(models.Model):
user = models.ForeignKey(Profile, verbose_name=_('user'), related_name='requests', on_delete=models.CASCADE)
Expand Down
10 changes: 9 additions & 1 deletion judge/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

from .caching import finished_submission
from .models import BlogPost, Comment, Contest, ContestSubmission, EFFECTIVE_MATH_ENGINES, Judge, Language, License, \
MiscConfig, Organization, Problem, Profile, Submission
MiscConfig, Organization, Problem, Profile, Submission, WebAuthnCredential


def get_pdf_path(basename):
Expand Down Expand Up @@ -56,6 +56,14 @@ def profile_update(sender, instance, **kwargs):
for org_id in instance.organizations.values_list('id', flat=True)])


@receiver(post_delete, sender=WebAuthnCredential)
def webauthn_delete(sender, instance, **kwargs):
profile = instance.user
if profile.webauthn_credentials.count() == 0:
profile.is_webauthn_enabled = False
profile.save(update_fields=['is_webauthn_enabled'])


@receiver(post_save, sender=Contest)
def contest_update(sender, instance, **kwargs):
if hasattr(instance, '_updating_stats_only'):
Expand Down
4 changes: 0 additions & 4 deletions judge/views/two_factor.py
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,6 @@ def post(self, request, *args, **kwargs):
return HttpResponseBadRequest(_('Staff may not disable 2FA'))
credential.delete()

if count <= 1:
request.profile.is_webauthn_enabled = False
request.profile.save(update_fields=['is_webauthn_enabled'])

return HttpResponse()


Expand Down

0 comments on commit c363a38

Please sign in to comment.