From 7dd0112f098aec2e388ce68bd3fbefeb8c9c0faf Mon Sep 17 00:00:00 2001 From: Dmitry Degtyarev Date: Fri, 20 Aug 2021 15:59:02 +0400 Subject: [PATCH 1/4] add protect deletion edit --- src/adldap/ad_defines.h | 2 + src/adldap/ad_security.cpp | 50 ++++++++++++++++++ src/adldap/ad_security.h | 3 ++ src/admc/CMakeLists.txt | 1 + src/admc/edits/protect_deletion_edit.cpp | 64 ++++++++++++++++++++++++ src/admc/edits/protect_deletion_edit.h | 43 ++++++++++++++++ src/admc/tabs/object_tab.cpp | 3 ++ src/admc/tabs/security_tab.cpp | 2 + 8 files changed, 168 insertions(+) create mode 100644 src/admc/edits/protect_deletion_edit.cpp create mode 100644 src/admc/edits/protect_deletion_edit.h diff --git a/src/adldap/ad_defines.h b/src/adldap/ad_defines.h index 452d5b51..2b581045 100644 --- a/src/adldap/ad_defines.h +++ b/src/adldap/ad_defines.h @@ -244,6 +244,8 @@ enum AcePermission { AcePermission_FullControl, AcePermission_Read, AcePermission_Write, + AcePermission_Delete, + AcePermission_DeleteSubtree, AcePermission_CreateChild, AcePermission_DeleteChild, AcePermission_AllowedToAuthenticate, diff --git a/src/adldap/ad_security.cpp b/src/adldap/ad_security.cpp index b008a2de..ebac2f1a 100644 --- a/src/adldap/ad_security.cpp +++ b/src/adldap/ad_security.cpp @@ -113,6 +113,8 @@ const QHash ace_permission_to_mask_map = { {AcePermission_Read, (SEC_STD_READ_CONTROL | SEC_ADS_LIST | SEC_ADS_READ_PROP)}, // {AcePermission_Write, SEC_ADS_GENERIC_WRITE}, {AcePermission_Write, (SEC_ADS_SELF_WRITE | SEC_ADS_WRITE_PROP)}, + {AcePermission_Delete, SEC_STD_DELETE}, + {AcePermission_DeleteSubtree, SEC_DIR_DELETE_CHILD}, {AcePermission_CreateChild, SEC_ADS_CREATE_CHILD}, {AcePermission_DeleteChild, SEC_ADS_DELETE_CHILD}, {AcePermission_AllowedToAuthenticate, SEC_ADS_CONTROL_ACCESS}, @@ -458,6 +460,14 @@ QByteArray dom_sid_to_bytes(const dom_sid &sid) { return bytes; } +QByteArray dom_sid_string_to_bytes(const QString &string) { + dom_sid sid; + dom_sid_parse(cstr(string), &sid); + const QByteArray bytes = dom_sid_to_bytes(sid); + + return bytes; +} + void ad_security_sort_dacl(security_descriptor *sd) { qsort(sd->dacl->aces, sd->dacl->num_aces, sizeof(security_ace), ace_compare); } @@ -600,3 +610,43 @@ QHash> ad_security_get_state_f return out; } + +bool ad_security_get_protected_against_deletion(const AdObject &object, AdConfig *adconfig) { + QHash> permissions = object.get_security_state(adconfig); + + const QByteArray world_trustee = dom_sid_string_to_bytes(SID_WORLD); + + const PermissionState delete_state = permissions[world_trustee][AcePermission_Delete]; + const PermissionState delete_subtree_state = permissions[world_trustee][AcePermission_DeleteSubtree]; + const bool out = ((delete_state == PermissionState_Denied) && (delete_subtree_state == PermissionState_Denied)); + + return out; +} + +bool ad_security_set_protected_against_deletion(AdInterface &ad, const QString dn, AdConfig *adconfig, const bool enabled) { + const AdObject object = ad.search_object(dn); + const QHash> old_permissions = object.get_security_state(adconfig); + + const QHash> new_permissions = [&]() { + QHash> out; + + const QByteArray world_trustee = dom_sid_string_to_bytes(SID_WORLD); + + const PermissionState state = [&]() { + if (enabled) { + return PermissionState_Denied; + } else { + return PermissionState_None; + } + }(); + out = old_permissions; + out = ad_security_modify(out, world_trustee, AcePermission_Delete, state); + out = ad_security_modify(out, world_trustee, AcePermission_DeleteSubtree, state); + + return out; + }(); + + const bool apply_success = attribute_replace_security_descriptor(&ad, dn, new_permissions); + + return apply_success; +} diff --git a/src/adldap/ad_security.h b/src/adldap/ad_security.h index 0ee36e1f..fe96f1af 100644 --- a/src/adldap/ad_security.h +++ b/src/adldap/ad_security.h @@ -53,6 +53,8 @@ QString ad_security_get_trustee_name(AdInterface &ad, const QByteArray &trustee) bool attribute_replace_security_descriptor(AdInterface *ad, const QString &dn, const QHash> &descriptor_state_arg); QList ad_security_get_trustee_list_from_object(const AdObject &object); QHash> ad_security_get_state_from_sd(security_descriptor *sd, AdConfig *adconfig); +bool ad_security_get_protected_against_deletion(const AdObject &object, AdConfig *config); +bool ad_security_set_protected_against_deletion(AdInterface &ad, const QString dn, AdConfig *config, const bool enabled); // NOTE: have to talloc_free() returned sd security_descriptor *ad_security_get_sd(const AdObject &object); @@ -60,5 +62,6 @@ security_descriptor *ad_security_get_sd(const AdObject &object); void ad_security_sort_dacl(security_descriptor *sd); QByteArray dom_sid_to_bytes(const dom_sid &sid); +QByteArray dom_sid_string_to_bytes(const dom_sid &sid); #endif /* AD_SECURITY_H */ diff --git a/src/admc/CMakeLists.txt b/src/admc/CMakeLists.txt index f4bb50b1..7c8cbd4f 100644 --- a/src/admc/CMakeLists.txt +++ b/src/admc/CMakeLists.txt @@ -144,6 +144,7 @@ set(ADMC_SOURCES edits/delegation_edit.cpp edits/logon_hours_edit.cpp edits/logon_computers_edit.cpp + edits/protect_deletion_edit.cpp console_widget/console_widget.cpp console_widget/scope_proxy_model.cpp diff --git a/src/admc/edits/protect_deletion_edit.cpp b/src/admc/edits/protect_deletion_edit.cpp new file mode 100644 index 00000000..e0a56627 --- /dev/null +++ b/src/admc/edits/protect_deletion_edit.cpp @@ -0,0 +1,64 @@ +/* + * ADMC - AD Management Center + * + * Copyright (C) 2020-2021 BaseALT Ltd. + * Copyright (C) 2020-2021 Dmitry Degtyarev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "edits/protect_deletion_edit.h" + +#include "adldap.h" +#include "utils.h" +#include "globals.h" + +#include +#include + +// Object is protected from deletion if it denies +// permissions for "delete" and "delete subtree" for +// "WORLD"(everyone) trustee + +ProtectDeletionEdit::ProtectDeletionEdit(QList *edits_out, QObject *parent) +: AttributeEdit(edits_out, parent) { + check = new QCheckBox(tr("Protect against deletion")); + + connect( + check, &QCheckBox::stateChanged, + [this]() { + emit edited(); + }); +} + +void ProtectDeletionEdit::load_internal(AdInterface &ad, const AdObject &object) { + const bool enabled = ad_security_get_protected_against_deletion(object, g_adconfig); + + check->setChecked(enabled); +} + +void ProtectDeletionEdit::set_read_only(const bool read_only) { + check->setDisabled(read_only); +} + +void ProtectDeletionEdit::add_to_layout(QFormLayout *layout) { + layout->addRow(check); +} + +bool ProtectDeletionEdit::apply(AdInterface &ad, const QString &dn) const { + const bool enabled = check->isChecked(); + const bool apply_success = ad_security_set_protected_against_deletion(ad, dn, g_adconfig, enabled); + + return apply_success; +} diff --git a/src/admc/edits/protect_deletion_edit.h b/src/admc/edits/protect_deletion_edit.h new file mode 100644 index 00000000..f467ad8c --- /dev/null +++ b/src/admc/edits/protect_deletion_edit.h @@ -0,0 +1,43 @@ +/* + * ADMC - AD Management Center + * + * Copyright (C) 2020-2021 BaseALT Ltd. + * Copyright (C) 2020-2021 Dmitry Degtyarev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef PROTECT_DELETION_EDIT_H +#define PROTECT_DELETION_EDIT_H + +/** + * Checkbox edit that modifies ACL to protect (or not) + * against deletion by denying/allowing deletion. + */ + +#include "edits/attribute_edit.h" + +class QCheckBox; + +class ProtectDeletionEdit final : public AttributeEdit { + Q_OBJECT +public: + ProtectDeletionEdit(QList *edits_out, QObject *parent); + DECL_ATTRIBUTE_EDIT_VIRTUALS(); + +private: + QCheckBox *check; +}; + +#endif /* PROTECT_DELETION_EDIT_H */ diff --git a/src/admc/tabs/object_tab.cpp b/src/admc/tabs/object_tab.cpp index 24cd60ca..47276354 100644 --- a/src/admc/tabs/object_tab.cpp +++ b/src/admc/tabs/object_tab.cpp @@ -23,6 +23,7 @@ #include "adldap.h" #include "edits/datetime_edit.h" #include "edits/string_edit.h" +#include "edits/protect_deletion_edit.h" #include @@ -38,6 +39,8 @@ ObjectTab::ObjectTab() { edits_set_read_only(edits, true); + new ProtectDeletionEdit(&edits, this); + edits_connect_to_tab(edits, this); const auto layout = new QFormLayout(); diff --git a/src/admc/tabs/security_tab.cpp b/src/admc/tabs/security_tab.cpp index eef613a9..395e5b18 100644 --- a/src/admc/tabs/security_tab.cpp +++ b/src/admc/tabs/security_tab.cpp @@ -47,6 +47,8 @@ const QHash ace_permission_to_name_map = { {AcePermission_FullControl, QCoreApplication::translate("Security", "Full control")}, {AcePermission_Read, QCoreApplication::translate("Security", "Read")}, {AcePermission_Write, QCoreApplication::translate("Security", "Write")}, + {AcePermission_Delete, QCoreApplication::translate("Security", "Delete")}, + {AcePermission_DeleteSubtree, QCoreApplication::translate("Security", "Delete subtree")}, {AcePermission_CreateChild, QCoreApplication::translate("Security", "Create child")}, {AcePermission_DeleteChild, QCoreApplication::translate("Security", "Delete child")}, {AcePermission_AllowedToAuthenticate, QCoreApplication::translate("Security", "Allowed to authenticate")}, From e10f6619b87d48c25214307582a7d54f997ef231 Mon Sep 17 00:00:00 2001 From: Dmitry Degtyarev Date: Fri, 20 Aug 2021 16:16:28 +0400 Subject: [PATCH 2/4] test protect deletion edit --- .gear/admc.spec | 1 + tests/CMakeLists.txt | 1 + tests/admc_test_protect_deletion_edit.cpp | 91 +++++++++++++++++++++++ tests/admc_test_protect_deletion_edit.h | 44 +++++++++++ 4 files changed, 137 insertions(+) create mode 100644 tests/admc_test_protect_deletion_edit.cpp create mode 100644 tests/admc_test_protect_deletion_edit.h diff --git a/.gear/admc.spec b/.gear/admc.spec index e850eb41..603394b6 100644 --- a/.gear/admc.spec +++ b/.gear/admc.spec @@ -100,6 +100,7 @@ Tests for ADMC %_bindir/admc_test_delegation_edit %_bindir/admc_test_string_other_edit %_bindir/admc_test_account_option_edit +%_bindir/admc_test_protect_deletion_edit %_bindir/admc_test_gpoptions_edit %_bindir/admc_test_octet_editor %_bindir/admc_test_bool_editor diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fc2725c2..400b4f74 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -59,6 +59,7 @@ set(TEST_TARGETS admc_test_string_other_edit admc_test_account_option_edit admc_test_gpoptions_edit + admc_test_protect_deletion_edit admc_test_octet_editor admc_test_bool_editor admc_test_datetime_editor diff --git a/tests/admc_test_protect_deletion_edit.cpp b/tests/admc_test_protect_deletion_edit.cpp new file mode 100644 index 00000000..a37f65a5 --- /dev/null +++ b/tests/admc_test_protect_deletion_edit.cpp @@ -0,0 +1,91 @@ +/* + * ADMC - AD Management Center + * + * Copyright (C) 2020-2021 BaseALT Ltd. + * Copyright (C) 2020-2021 Dmitry Degtyarev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "admc_test_protect_deletion_edit.h" + +#include "edits/protect_deletion_edit.h" + +#include +#include + +// NOTE: this doesn't really "lock" accounts. Accounts can +// only be locked by the server and lockout time only +// displays the state. Since unlock edit modifies lockout +// time, test it like this. +#define LOCKOUT_LOCKED_VALUE "1" + +void ADMCTestProtectDeletionEdit::init() { + ADMCTest::init(); + + edit = new ProtectDeletionEdit(&edits, parent_widget); + add_attribute_edit(edit); + + checkbox = parent_widget->findChild(); + QVERIFY(checkbox != nullptr); + + dn = test_object_dn(TEST_OU, CLASS_OU); + const bool create_success = ad.object_add(dn, CLASS_OU); + QVERIFY(create_success); +} + +// edited() signal should be emitted when checkbox is toggled +void ADMCTestProtectDeletionEdit::emit_edited_signal() { + bool edited_signal_emitted = false; + connect( + edit, &AttributeEdit::edited, + [&edited_signal_emitted]() { + edited_signal_emitted = true; + }); + + // Check checkbox + checkbox->setChecked(true); + QVERIFY(edited_signal_emitted); + + // Unheck checkbox + edited_signal_emitted = false; + checkbox->setChecked(false); + QVERIFY(edited_signal_emitted); +} + +// Edit should unlock locked user if checkbox is checked +void ADMCTestProtectDeletionEdit::apply() { + const AdObject object = ad.search_object(dn); + edit->load(ad, object); + + // Enable protection against deletion + checkbox->setChecked(true); + const bool apply_success = edit->apply(ad, dn); + QVERIFY(apply_success); + + // Try to delete, should fail + qInfo() << "Error relating to \"Insufficient access\" is part of the test"; + const bool delete_success = ad.object_delete(dn); + QCOMPARE(delete_success, false); + + // Disable protection against deletion + checkbox->setChecked(false); + const bool apply_2_success = edit->apply(ad, dn); + QVERIFY(apply_2_success); + + const bool delete_2_success = ad.object_delete(dn); + QCOMPARE(delete_2_success, true); +} + +QTEST_MAIN(ADMCTestProtectDeletionEdit) diff --git a/tests/admc_test_protect_deletion_edit.h b/tests/admc_test_protect_deletion_edit.h new file mode 100644 index 00000000..76116d4b --- /dev/null +++ b/tests/admc_test_protect_deletion_edit.h @@ -0,0 +1,44 @@ +/* + * ADMC - AD Management Center + * + * Copyright (C) 2020-2021 BaseALT Ltd. + * Copyright (C) 2020-2021 Dmitry Degtyarev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ADMC_TEST_PROTECT_DELETION_EDIT_H +#define ADMC_TEST_PROTECT_DELETION_EDIT_H + +#include "admc_test.h" + +class ProtectDeletionEdit; +class QCheckBox; + +class ADMCTestProtectDeletionEdit : public ADMCTest { + Q_OBJECT + +private slots: + void init() override; + + void emit_edited_signal(); + void apply(); + +private: + ProtectDeletionEdit *edit; + QCheckBox *checkbox; + QString dn; +}; + +#endif /* ADMC_TEST_PROTECT_DELETION_EDIT_H */ From e4e2c479a735d220cf4b786c5f051fb4b52d6d35 Mon Sep 17 00:00:00 2001 From: Dmitry Degtyarev Date: Fri, 20 Aug 2021 16:18:31 +0400 Subject: [PATCH 3/4] add protection edit to ou create dialog --- src/admc/create_object_dialog.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/admc/create_object_dialog.cpp b/src/admc/create_object_dialog.cpp index 4274f3a9..3c6f4c2c 100644 --- a/src/admc/create_object_dialog.cpp +++ b/src/admc/create_object_dialog.cpp @@ -27,6 +27,7 @@ #include "edits/password_edit.h" #include "edits/string_edit.h" #include "edits/upn_edit.h" +#include "edits/protect_deletion_edit.h" #include "globals.h" #include "settings.h" #include "status.h" @@ -188,6 +189,9 @@ CreateObjectDialog::CreateObjectDialog(const QString &parent_dn_arg, const QStri }); } else if (object_class == CLASS_OU) { edits_layout->addRow(tr("Name:"), name_edit); + + auto protect_deletion_edit = new ProtectDeletionEdit(&all_edits, this); + protect_deletion_edit->add_to_layout(edits_layout); } else { qWarning() << "Class" << object_class << "is unsupported by create dialog"; return; From 8eebccae221b8eb487d34999b2c6a7d5f2f3c988 Mon Sep 17 00:00:00 2001 From: Dmitry Degtyarev Date: Fri, 20 Aug 2021 16:25:02 +0400 Subject: [PATCH 4/4] enable delete protection for new ou's by default in create dialog for OU's, can still turn off if needed --- src/admc/create_object_dialog.cpp | 1 + src/admc/edits/protect_deletion_edit.cpp | 4 ++++ src/admc/edits/protect_deletion_edit.h | 2 ++ 3 files changed, 7 insertions(+) diff --git a/src/admc/create_object_dialog.cpp b/src/admc/create_object_dialog.cpp index 3c6f4c2c..086f968d 100644 --- a/src/admc/create_object_dialog.cpp +++ b/src/admc/create_object_dialog.cpp @@ -191,6 +191,7 @@ CreateObjectDialog::CreateObjectDialog(const QString &parent_dn_arg, const QStri edits_layout->addRow(tr("Name:"), name_edit); auto protect_deletion_edit = new ProtectDeletionEdit(&all_edits, this); + protect_deletion_edit->set_enabled(true); protect_deletion_edit->add_to_layout(edits_layout); } else { qWarning() << "Class" << object_class << "is unsupported by create dialog"; diff --git a/src/admc/edits/protect_deletion_edit.cpp b/src/admc/edits/protect_deletion_edit.cpp index e0a56627..2b320f28 100644 --- a/src/admc/edits/protect_deletion_edit.cpp +++ b/src/admc/edits/protect_deletion_edit.cpp @@ -42,6 +42,10 @@ ProtectDeletionEdit::ProtectDeletionEdit(QList *edits_out, QObj }); } +void ProtectDeletionEdit::set_enabled(const bool enabled) { + check->setChecked(enabled); +} + void ProtectDeletionEdit::load_internal(AdInterface &ad, const AdObject &object) { const bool enabled = ad_security_get_protected_against_deletion(object, g_adconfig); diff --git a/src/admc/edits/protect_deletion_edit.h b/src/admc/edits/protect_deletion_edit.h index f467ad8c..622d8560 100644 --- a/src/admc/edits/protect_deletion_edit.h +++ b/src/admc/edits/protect_deletion_edit.h @@ -36,6 +36,8 @@ public: ProtectDeletionEdit(QList *edits_out, QObject *parent); DECL_ATTRIBUTE_EDIT_VIRTUALS(); + void set_enabled(const bool enabled); + private: QCheckBox *check; };