mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-05-01 11:19:32 -05:00
Allow superusers to disable 2fa
This commit is contained in:
parent
52ca8025d4
commit
70aa8d6cab
@ -32,6 +32,20 @@
|
||||
</div>
|
||||
|
||||
<pngx-input-select i18n-title title="Groups" [items]="groups" multiple="true" formControlName="groups"></pngx-input-select>
|
||||
|
||||
@if (object?.is_mfa_enabled && currentUserIsSuperUser) {
|
||||
<label class="form-label" i18n>Two-factor Authentication</label>
|
||||
<pngx-confirm-button
|
||||
label="Disable Two-factor Authentication"
|
||||
i18n-label
|
||||
title="Disable Two-factor Authentication"
|
||||
i18n-title
|
||||
buttonClasses="btn-outline-danger btn-sm"
|
||||
iconName="trash"
|
||||
[disabled]="totpLoading"
|
||||
(confirm)="deactivateTotp()">
|
||||
</pngx-confirm-button>
|
||||
}
|
||||
</div>
|
||||
<div class="col">
|
||||
<pngx-permissions-select i18n-title title="Permissions" formControlName="user_permissions" [error]="error?.user_permissions" [inheritedPermissions]="inheritedPermissions"></pngx-permissions-select>
|
||||
|
@ -5,9 +5,11 @@ import { first } from 'rxjs'
|
||||
import { EditDialogComponent } from 'src/app/components/common/edit-dialog/edit-dialog.component'
|
||||
import { Group } from 'src/app/data/group'
|
||||
import { User } from 'src/app/data/user'
|
||||
import { PermissionsService } from 'src/app/services/permissions.service'
|
||||
import { GroupService } from 'src/app/services/rest/group.service'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
|
||||
@Component({
|
||||
selector: 'pngx-user-edit-dialog',
|
||||
@ -20,12 +22,15 @@ export class UserEditDialogComponent
|
||||
{
|
||||
groups: Group[]
|
||||
passwordIsSet: boolean = false
|
||||
public totpLoading: boolean = false
|
||||
|
||||
constructor(
|
||||
service: UserService,
|
||||
activeModal: NgbActiveModal,
|
||||
groupsService: GroupService,
|
||||
settingsService: SettingsService
|
||||
settingsService: SettingsService,
|
||||
private toastService: ToastService,
|
||||
private permissionsService: PermissionsService
|
||||
) {
|
||||
super(service, activeModal, service, settingsService)
|
||||
|
||||
@ -87,4 +92,30 @@ export class UserEditDialogComponent
|
||||
.length > 0
|
||||
super.save()
|
||||
}
|
||||
|
||||
get currentUserIsSuperUser(): boolean {
|
||||
return this.permissionsService.isSuperUser()
|
||||
}
|
||||
|
||||
deactivateTotp() {
|
||||
this.totpLoading = true
|
||||
;(this.service as UserService)
|
||||
.deactivateTotp(this.object)
|
||||
.pipe(first())
|
||||
.subscribe({
|
||||
next: (result) => {
|
||||
this.totpLoading = false
|
||||
if (result) {
|
||||
this.toastService.showInfo($localize`Totp deactivated`)
|
||||
this.object.is_mfa_enabled = false
|
||||
} else {
|
||||
this.toastService.showError($localize`Totp deactivation failed`)
|
||||
}
|
||||
},
|
||||
error: (e) => {
|
||||
this.totpLoading = false
|
||||
this.toastService.showError($localize`Totp deactivation failed`, e)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -11,4 +11,5 @@ export interface User extends ObjectWithId {
|
||||
groups?: number[] // Group[]
|
||||
user_permissions?: string[]
|
||||
inherited_permissions?: string[]
|
||||
is_mfa_enabled?: boolean
|
||||
}
|
||||
|
@ -56,6 +56,10 @@ export class PermissionsService {
|
||||
return this.currentUser?.is_staff
|
||||
}
|
||||
|
||||
public isSuperUser(): boolean {
|
||||
return this.currentUser?.is_superuser
|
||||
}
|
||||
|
||||
public currentUserOwnsObject(object: ObjectWithPermissions): boolean {
|
||||
return (
|
||||
!object ||
|
||||
|
@ -5,6 +5,7 @@ import { User } from 'src/app/data/user'
|
||||
import { PermissionsService } from '../permissions.service'
|
||||
import { AbstractNameFilterService } from './abstract-name-filter-service'
|
||||
|
||||
const endpoint = 'users'
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
@ -13,7 +14,7 @@ export class UserService extends AbstractNameFilterService<User> {
|
||||
http: HttpClient,
|
||||
private permissionService: PermissionsService
|
||||
) {
|
||||
super(http, 'users')
|
||||
super(http, endpoint)
|
||||
}
|
||||
|
||||
update(o: User): Observable<User> {
|
||||
@ -31,4 +32,11 @@ export class UserService extends AbstractNameFilterService<User> {
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
deactivateTotp(u: User): Observable<boolean> {
|
||||
return this.http.post<boolean>(
|
||||
`${this.getResourceUrl(u.id, 'deactivate_totp')}`,
|
||||
null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -33,6 +33,11 @@ class UserSerializer(serializers.ModelSerializer):
|
||||
required=False,
|
||||
)
|
||||
inherited_permissions = serializers.SerializerMethodField()
|
||||
is_mfa_enabled = serializers.SerializerMethodField()
|
||||
|
||||
def get_is_mfa_enabled(self, user: User):
|
||||
mfa_adapter = get_mfa_adapter()
|
||||
return mfa_adapter.is_mfa_enabled(user)
|
||||
|
||||
class Meta:
|
||||
model = User
|
||||
@ -50,6 +55,7 @@ class UserSerializer(serializers.ModelSerializer):
|
||||
"groups",
|
||||
"user_permissions",
|
||||
"inherited_permissions",
|
||||
"is_mfa_enabled",
|
||||
)
|
||||
|
||||
def get_inherited_permissions(self, obj):
|
||||
|
@ -17,6 +17,7 @@ from django.http import HttpResponseBadRequest
|
||||
from django.views.generic import View
|
||||
from django_filters.rest_framework import DjangoFilterBackend
|
||||
from rest_framework.authtoken.models import Token
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.filters import OrderingFilter
|
||||
from rest_framework.generics import GenericAPIView
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
@ -106,6 +107,24 @@ class UserViewSet(ModelViewSet):
|
||||
filterset_class = UserFilterSet
|
||||
ordering_fields = ("username",)
|
||||
|
||||
@action(detail=True, methods=["post"])
|
||||
def deactivate_totp(self, request, pk=None):
|
||||
request_user = request.user
|
||||
user = User.objects.get(pk=pk)
|
||||
if not request_user.is_superuser and request_user != user:
|
||||
return HttpResponseBadRequest(
|
||||
"You do not have permission to deactivate TOTP for this user",
|
||||
)
|
||||
try:
|
||||
authenticator = Authenticator.objects.filter(
|
||||
user=user,
|
||||
type=Authenticator.Type.TOTP,
|
||||
).first()
|
||||
delete_and_cleanup(request, authenticator)
|
||||
return Response(True)
|
||||
except Authenticator.DoesNotExist:
|
||||
return HttpResponseBadRequest("TOTP not found")
|
||||
|
||||
|
||||
class GroupViewSet(ModelViewSet):
|
||||
model = Group
|
||||
|
Loading…
x
Reference in New Issue
Block a user