diff --git a/.gear/admc.spec b/.gear/admc.spec index c9e25026..683b263b 100644 --- a/.gear/admc.spec +++ b/.gear/admc.spec @@ -1,7 +1,7 @@ %define _unpackaged_files_terminate_build 1 Name: admc -Version: 0.8.3 +Version: 0.9.0 Release: alt1 Summary: AD editor @@ -65,6 +65,7 @@ Tests for ADMC %_libdir/libadldap.so %_man1dir/admc* %_datadir/applications/admc.desktop +%_iconsdir/hicolor/scalable/apps/admc.svg %files test %_libdir/libadmctest.so @@ -75,9 +76,9 @@ Tests for ADMC %_bindir/admc_test_country_edit %_bindir/admc_test_gplink %_bindir/admc_test_ad_interface +%_bindir/admc_test_ad_security %_bindir/admc_test_select_base_widget %_bindir/admc_test_filter_widget -%_bindir/admc_test_security_tab %_bindir/admc_test_attributes_tab %_bindir/admc_test_member_of_tab %_bindir/admc_test_members_tab @@ -105,6 +106,8 @@ Tests for ADMC %_bindir/admc_test_find_object_dialog %_bindir/admc_test_rename_object_dialog %_bindir/admc_test_create_object_dialog +%_bindir/admc_test_select_classes_widget +%_bindir/admc_test_sam_name_edit %changelog * Thu Mar 31 2022 Dmitry Degtyarev 0.8.3-alt1 @@ -167,4 +170,3 @@ Tests for ADMC * Thu May 21 2020 Igor Chudov 0.1.0-alt1 - Initial build - diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 37fbe581..14c97446 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,137 @@ admc CHANGELOG +----------------------------------------------------------------------- + VERSION 0.9.0 +----------------------------------------------------------------------- +- Account Tab: Improved editor for "Logon Hours". +- Account Tab: Fixed "Logon Computers" editor + incorrectly adding empty entry when being opened. +- Account Tab: Fixed incorrect flag value for + "Reversible encryption" account option. +- Attributes Tab: Added special display format for some + bitfield attributes, so that both the value and names + of bitfields are displayed. Includes + "userAccountControl", "sAMAccountType" and + "primaryGroupId". +- Account Tab: Added missing "DES key" account option. +- Account Tab: Removed "Trusted for delegation" option + because it should be in "Delegation" tab only. +- Account Tab: Fixed some options using incorrect flags. +- Action menu: Added creation of more object types: + "Contact", "inetOrgPerson" and "Shared Folder". +- Action menu: Fixed "New" sub-menu in action menu so + that it contains only those object types that can be + children of target object. (#343) +- Action menu: Removed "Rename" action from computer + objects because computer objects must be renamed from + the machine itself in system configuration. +- Console: Added object display limit option. Applies + to main window and find dialog. (#319) +- Address Tab: Changed order of country list so that + the country of current locale is always first, for + ease of use. +- Console: Implemented opening action menu by right + clicking on empty space in results pane. In this + case, currently selected scope item is treated as + target object for the menu. (#334) +- Console: Fixed action menu being empty on startup + until user selected another console item. (#340) +- Console: Fixed being able to move an object onto it's + current parent via action menu or drag and drop. This + caused visual glitches. (#336, #338) +- Console: Improved default sort setting for results + view (for all view types - details, icons, list). +- Create dialog: Fix incorrect field labels. +- Logon Hours: Improved usability. +- Logon Hours: Added legend that explains how dialog + works. +- Security Tab: Fixed handling of inherited rights. + Inherited rights will now correctly display as grayed + out checkboxes and will not be editable. (#327) +- Security Tab: Fixed all objects having the same kinds + of rights. Security tab will now correctly show + rights that are appropriate for the object type. + (#328) +- Security Tab: Added verification of ACL order. When + switching to security tab, order will be verified and + if it's incorrect, a warning dialog will open + offering to fix it. (#339) +- Toolbar: Added keyboard shortcuts to all actions. +- Propeties: Added LAPS(Local Administration Password + Solution) tab for computer objects. This tab is + accessible only if LAPS is enabled in the domain. (#312) +- Properties: Improved how properties dialog responds + to failing to load a user due to connection error or + absence of object. +- Properties: Disabled "Profile" tab, not usable + because not implemented in backend. +- Properties: Disabled "Logon Computers" feature, not + usable because not implemented in backend. +- Policies: Fixed permission check incorrectly + complaining about policies made by RSAT. (#333) +- Policies: Implemented "Edit" action for policies. + Installation of "gpui" package is required for + correct functioning. +- Queries: Removed "Cut" and "Copy" actions from Query + root, because they don't apply to it. (#350) +- Queries: Remove ability to recursively cut and paste + query folders into themselves. +- Queries: Fixed program freezing when copying and + pasting a query folder into itself. +- Queries: Fixed default name for query folders when + making them. +- Queries: Fixed "Copy" and "Paste" of query folder + into same parent. +- Queries: Remove ability to drag and drop query + folders onto themselves. +- Find dialog: Added class filtering by all classes, + implemented as a separate checkbox. This filtering + will accept all classes, not just the ones in the + list. (#341) +- Managed by tab: Fixed tab not applying. +- Managed by tab: Fixed some fields being editable when + they shouldn't be. +- Menubar: Fixed checkboxes for view types (Icons, + List, Details) not being exclusive. (#329) +- Menubar: Fixed search logging, now correctly works + when browsing the object tree, in find dialog and + other UI elements. +- Menubar: Added keyboard shortcuts to all actions, + except for the "Action" menu. +- Menubar: Added keyboard navigation for all sub-menu's + except "Action" menu. +- Misc: Fixed class filters not working correctly until + class selection dialog was opened. Occured in find + dialog, query creation/editing and others. (#322) +- Misc: Added "Operations Masters" dialog for viewing + and editing FSMO roles. Opened from + Menubar->File->Operations Masters. (#313) +- Misc: Changed AD error dialogs to be modal. +- Misc: Removed "Reset" button from rename dialogs. +- Misc: Fixed group icons not displaying on some DE's. (#330) +- Misc: Added app icon which is displayed next to in + app lists and on the title bar. +- Misc: Empty password input is now not allowed. +- Misc: Fixed incorrect value for length limit of the + field "Logon name (pre-Windows 2000)". +- Misc: Improved handling of illegal characters in + dialogs that create objects and those that rename + objects. +- Misc: Added automatic trimming of leading and + trailing spaces in most fields for inputting string + values. +- Misc: Fixed creation of computer objects so that + attributes are set correctly. +- Misc: Added "Show password" checkbox to all password + fields, which toggles password visibility when + entering. +- Misc: Fixed crash that sometimes happened when + loading objects from server. +- Misc: Fixed incorrect "Street" field for OU objects. + Was using the wrong attribute. +- Misc: Improved translation. + ----------------------------------------------------------------------- VERSION 0.8.3 ----------------------------------------------------------------------- @@ -107,11 +238,14 @@ CHANGELOG connection options. ----------------------------------------------------------------------- +<<<<<<< HEAD VERSION 0.7.1 ----------------------------------------------------------------------- - Misc: Fix stack smashing on 32 bit systems. (#294) ----------------------------------------------------------------------- +======= +>>>>>>> release/0-9-0-test VERSION 0.7.0 ----------------------------------------------------------------------- - Manual: Fixed manual not opening when app is installed diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a7fb3a3..fdc79d3c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,7 +54,7 @@ add_subdirectory(src) if(NOT ADMC_BUILD_DEB) add_subdirectory(tests) endif(NOT ADMC_BUILD_DEB) -add_subdirectory(doc) +add_subdirectory(share) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/CHANGELOG.txt ${CMAKE_CURRENT_BINARY_DIR} COPYONLY) diff --git a/doc/CMakeLists.txt b/share/CMakeLists.txt similarity index 84% rename from doc/CMakeLists.txt rename to share/CMakeLists.txt index 36c52c6d..a953a06e 100644 --- a/doc/CMakeLists.txt +++ b/share/CMakeLists.txt @@ -1,5 +1,5 @@ -install(FILES ${PROJECT_SOURCE_DIR}/doc/admc.1 +install(FILES ${PROJECT_SOURCE_DIR}/share/admc.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1/ PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ) diff --git a/doc/admc.1 b/share/admc.1 similarity index 100% rename from doc/admc.1 rename to share/admc.1 diff --git a/doc/admc.desktop b/share/admc.desktop similarity index 99% rename from doc/admc.desktop rename to share/admc.desktop index 7ab6acd3..5a61775b 100644 --- a/doc/admc.desktop +++ b/share/admc.desktop @@ -57,7 +57,7 @@ Comment[tr]=Active Directory yönetimi için bir araç Comment[uk]=Інструмент для адміністрування Active Directory Comment[zh_CN]=用于 Active Directory 管理的工具 Comment[zh_TW]=用於 Active Directory 管理的工具 -Icon= +Icon=admc Exec=admc Terminal=false Type=Application diff --git a/src/adldap/CMakeLists.txt b/src/adldap/CMakeLists.txt index 61c44350..e183de6b 100644 --- a/src/adldap/CMakeLists.txt +++ b/src/adldap/CMakeLists.txt @@ -12,6 +12,13 @@ pkg_check_modules(Smbclient REQUIRED IMPORTED_TARGET smbclient) pkg_check_modules(Krb5 REQUIRED IMPORTED_TARGET krb5) pkg_check_modules(Glib REQUIRED IMPORTED_TARGET glib-2.0) pkg_check_modules(Uuid REQUIRED IMPORTED_TARGET uuid) +pkg_check_modules(Sasl REQUIRED IMPORTED_TARGET libsasl2) + +pkg_check_modules(Ndr REQUIRED IMPORTED_TARGET ndr) +if(Ndr_VERSION VERSION_GREATER "1.0.1") + set(NDR_VERSION_GREATER_THAN_1_0_1 ON) +endif() +configure_file("adldap_config.h.in" "adldap_config.h") pkg_check_modules(Ndr REQUIRED IMPORTED_TARGET ndr) if(Ndr_VERSION VERSION_GREATER "1.0.1") @@ -52,10 +59,10 @@ target_link_libraries(adldap Qt5::Core PkgConfig::Uuid PkgConfig::Smbclient - PkgConfig::Smbclient PkgConfig::Krb5 Ldap::Ldap PkgConfig::NdrStandard + PkgConfig::Sasl resolv ) diff --git a/src/adldap/ad_config.cpp b/src/adldap/ad_config.cpp index b4c5a33e..40c80811 100644 --- a/src/adldap/ad_config.cpp +++ b/src/adldap/ad_config.cpp @@ -25,6 +25,9 @@ #include "ad_interface.h" #include "ad_object.h" #include "ad_utils.h" +#include "ad_security.h" + +#include "samba/ndr_security.h" #include #include @@ -51,6 +54,7 @@ #define ATTRIBUTE_SYSTEM_FLAGS "systemFlags" #define ATTRIBUTE_LINK_ID "linkID" #define ATTRIBUTE_SYSTEM_AUXILIARY_CLASS "systemAuxiliaryClass" +#define ATTRIBUTE_SUB_CLASS_OF "subClassOf" #define CLASS_ATTRIBUTE_SCHEMA "attributeSchema" #define CLASS_CLASS_SCHEMA "classSchema" @@ -71,7 +75,6 @@ AdConfig::~AdConfig() { void AdConfig::load(AdInterface &ad, const QLocale &locale) { d->domain = get_default_domain_from_krb5(); - d->domain_head = domain_to_domain_dn(d->domain); d->filter_containers.clear(); d->columns.clear(); @@ -82,6 +85,12 @@ void AdConfig::load(AdInterface &ad, const QLocale &locale) { d->attribute_schemas.clear(); d->class_schemas.clear(); + const AdObject rootDSE_object = ad.search_object(ROOT_DSE); + d->domain_dn = rootDSE_object.get_string(ATTRIBUTE_ROOT_DOMAIN_NAMING_CONTEXT); + d->schema_dn = rootDSE_object.get_string(ATTRIBUTE_SCHEMA_NAMING_CONTEXT); + d->configuration_dn = rootDSE_object.get_string(ATTRIBUTE_CONFIGURATION_NAMING_CONTEXT); + d->supported_control_list = rootDSE_object.get_strings(ATTRIBUTE_SUPPORTED_CONTROL); + const QString locale_dir = [this, locale]() { const QString locale_code = [locale]() { if (locale.language() == QLocale::Russian) { @@ -108,6 +117,7 @@ void AdConfig::load(AdInterface &ad, const QLocale &locale) { ATTRIBUTE_RANGE_UPPER, ATTRIBUTE_LINK_ID, ATTRIBUTE_SYSTEM_FLAGS, + ATTRIBUTE_SCHEMA_ID_GUID, }; const QHash results = ad.search(schema_dn(), SearchScope_Children, filter, attributes); @@ -115,6 +125,9 @@ void AdConfig::load(AdInterface &ad, const QLocale &locale) { for (const AdObject &object : results.values()) { const QString attribute = object.get_string(ATTRIBUTE_LDAP_DISPLAY_NAME); d->attribute_schemas[attribute] = object; + + const QByteArray guid = object.get_value(ATTRIBUTE_SCHEMA_ID_GUID); + d->guid_to_attribute_map[guid] = attribute; } } @@ -132,6 +145,8 @@ void AdConfig::load(AdInterface &ad, const QLocale &locale) { ATTRIBUTE_SYSTEM_MUST_CONTAIN, ATTRIBUTE_AUXILIARY_CLASS, ATTRIBUTE_SYSTEM_AUXILIARY_CLASS, + ATTRIBUTE_SCHEMA_ID_GUID, + ATTRIBUTE_SUB_CLASS_OF, }; const QHash results = ad.search(schema_dn(), SearchScope_Children, filter, attributes); @@ -139,6 +154,12 @@ void AdConfig::load(AdInterface &ad, const QLocale &locale) { for (const AdObject &object : results.values()) { const QString object_class = object.get_string(ATTRIBUTE_LDAP_DISPLAY_NAME); d->class_schemas[object_class] = object; + + const QByteArray guid = object.get_value(ATTRIBUTE_SCHEMA_ID_GUID); + d->guid_to_class_map[guid] = object_class; + + const QString sub_class_of = object.get_string(ATTRIBUTE_SUB_CLASS_OF); + d->sub_class_of_map[object_class] = sub_class_of; } } @@ -277,14 +298,16 @@ void AdConfig::load(AdInterface &ad, const QLocale &locale) { return out; }(); - d->right_to_guid_map = [&]() { - QHash out; - + // Extended rights + { const QString filter = filter_CONDITION(Condition_Equals, ATTRIBUTE_OBJECT_CLASS, CLASS_CONTROL_ACCESS_RIGHT); const QList attributes = { ATTRIBUTE_CN, + ATTRIBUTE_DISPLAY_NAME, ATTRIBUTE_RIGHTS_GUID, + ATTRIBUTE_APPLIES_TO, + ATTRIBUTE_VALID_ACCESSES, }; const QString search_base = extended_rights_dn(); @@ -293,39 +316,65 @@ void AdConfig::load(AdInterface &ad, const QLocale &locale) { for (const AdObject &object : search_results.values()) { const QString cn = object.get_string(ATTRIBUTE_CN); - const QString guid = object.get_string(ATTRIBUTE_RIGHTS_GUID); + const QString guid_string = object.get_string(ATTRIBUTE_RIGHTS_GUID); + const QByteArray guid = guid_string_to_bytes(guid_string); + const QByteArray display_name = object.get_value(ATTRIBUTE_DISPLAY_NAME); + const QList applies_to = [this, object]() { + QList out; + + const QList class_guid_string_list = object.get_strings(ATTRIBUTE_APPLIES_TO); + for (const QString &class_guid_string : class_guid_string_list) { + const QByteArray class_guid = guid_string_to_bytes(class_guid_string); + const QString object_class = guid_to_class(class_guid); - out[cn] = guid; + out.append(object_class); + } + + return out; + }(); + const int valid_accesses = object.get_int(ATTRIBUTE_VALID_ACCESSES); + + d->right_to_guid_map[cn] = guid; + d->right_guid_to_cn_map[guid] = cn; + d->rights_guid_to_name_map[guid] = display_name; + d->rights_name_to_guid_map[cn] = guid; + d->rights_applies_to_map[guid] = applies_to; + d->extended_rights_list.append(cn); + d->rights_valid_accesses_map[cn] = valid_accesses; } - - return out; - }(); + } } QString AdConfig::domain() const { return d->domain; } -QString AdConfig::domain_head() const { - return d->domain_head; +QString AdConfig::domain_dn() const { + return d->domain_dn; } QString AdConfig::configuration_dn() const { - return QString("CN=Configuration,%1").arg(domain_head()); + return d->configuration_dn; } QString AdConfig::schema_dn() const { - return QString("CN=Schema,%1").arg(configuration_dn()); + return d->schema_dn; } QString AdConfig::partitions_dn() const { - return QString("CN=Partitions,CN=Configuration,%1").arg(domain_head()); + return QString("CN=Partitions,%1").arg(configuration_dn()); } QString AdConfig::extended_rights_dn() const { return QString("CN=Extended-Rights,%1").arg(configuration_dn()); } +bool AdConfig::control_is_supported(const QString &control_oid) const { + const bool supported = d->supported_control_list.contains(control_oid); + + return supported; +} + QString AdConfig::get_attribute_display_name(const Attribute &attribute, const ObjectClass &objectClass) const { if (d->attribute_display_names.contains(objectClass) && d->attribute_display_names[objectClass].contains(attribute)) { const QString display_name = d->attribute_display_names[objectClass][attribute]; @@ -393,6 +442,34 @@ QList AdConfig::get_possible_superiors(const QList &object return out; } +ObjectClass AdConfig::get_parent_class(const ObjectClass &object_class) const { + const ObjectClass out = d->sub_class_of_map.value(object_class); + + return out; +} + +QList AdConfig::get_inherit_chain(const ObjectClass &object_class) const { + QList out; + + ObjectClass current_class = object_class; + + while (true) { + out.append(current_class); + + const QString parent_class = get_parent_class(current_class); + + const bool chain_ended = (parent_class == current_class); + + if (chain_ended) { + break; + } else { + current_class = parent_class; + } + } + + return out; +} + QList AdConfig::get_optional_attributes(const QList &object_classes) const { const QList all_classes = d->add_auxiliary_classes(object_classes); @@ -538,11 +615,151 @@ bool AdConfig::get_attribute_is_backlink(const QString &attribute) const { bool AdConfig::get_attribute_is_constructed(const QString &attribute) const { const int system_flags = d->attribute_schemas[attribute].get_int(ATTRIBUTE_SYSTEM_FLAGS); - return bit_is_set(system_flags, FLAG_ATTR_IS_CONSTRUCTED); + return bitmask_is_set(system_flags, FLAG_ATTR_IS_CONSTRUCTED); } -QString AdConfig::get_right_guid(const QString &right_cn) const { - const QString out = d->right_to_guid_map.value(right_cn, QString()); +QByteArray AdConfig::get_right_guid(const QString &right_cn) const { + const QByteArray out = d->right_to_guid_map.value(right_cn, QByteArray()); + return out; +} + + +// NOTE: technically, Active Directory provides +// translations for right names but it's not +// accessible, so have to translate these ourselves. On +// Windows, you would use the localizationDisplayId +// retrieved from schema to get translation from +// dssec.dll. And we don't have dssec.dll, nor do we +// have the ability to interact with it! +QString AdConfig::get_right_name(const QByteArray &right_guid, const QLocale::Language language) const { + const QHash cn_to_map_russian = { + {"DS-Replication-Get-Changes", QCoreApplication::translate("AdConfig", "DS Replication Get Changes")}, + {"DS-Replication-Get-Changes-All", QCoreApplication::translate("AdConfig", "DS Replication Get Changes All")}, + {"Email-Information", QCoreApplication::translate("AdConfig", "Phone and Mail Options")}, + {"DS-Bypass-Quota", QCoreApplication::translate("AdConfig", "Bypass the quota restrictions during creation.")}, + {"Receive-As", QCoreApplication::translate("AdConfig", "Receive As")}, + {"Unexpire-Password", QCoreApplication::translate("AdConfig", "Unexpire Password")}, + {"Do-Garbage-Collection", QCoreApplication::translate("AdConfig", "Do Garbage Collection")}, + {"Allowed-To-Authenticate", QCoreApplication::translate("AdConfig", "Allowed To Authenticate")}, + {"Change-PDC", QCoreApplication::translate("AdConfig", "Change PDC")}, + {"Reanimate-Tombstones", QCoreApplication::translate("AdConfig", "Reanimate Tombstones")}, + {"msmq-Peek-Dead-Letter", QCoreApplication::translate("AdConfig", "msmq Peek Dead Letter")}, + {"Certificate-AutoEnrollment", QCoreApplication::translate("AdConfig", "AutoEnrollment")}, + {"DS-Install-Replica", QCoreApplication::translate("AdConfig", "DS Install Replica")}, + {"Domain-Password", QCoreApplication::translate("AdConfig", "Domain Password & Lockout Policies")}, + {"Generate-RSoP-Logging", QCoreApplication::translate("AdConfig", "Generate RSoP Logging")}, + {"Run-Protect-Admin-Groups-Task", QCoreApplication::translate("AdConfig", "Run Protect Admin Groups Task")}, + {"Self-Membership", QCoreApplication::translate("AdConfig", "Self Membership")}, + {"DS-Clone-Domain-Controller", QCoreApplication::translate("AdConfig", "Allow a DC to create a clone of itself")}, + {"Domain-Other-Parameters", QCoreApplication::translate("AdConfig", "Other Domain Parameters (for use by SAM)")}, + {"SAM-Enumerate-Entire-Domain", QCoreApplication::translate("AdConfig", "SAM Enumerate Entire Domain")}, + {"DS-Write-Partition-Secrets", QCoreApplication::translate("AdConfig", "Write secret attributes of objects in a Partition")}, + {"Send-As", QCoreApplication::translate("AdConfig", "Send As")}, + {"DS-Replication-Manage-Topology", QCoreApplication::translate("AdConfig", "DS Replication Manage Topology")}, + {"DS-Set-Owner", QCoreApplication::translate("AdConfig", "Set Owner of an object during creation.")}, + {"Generate-RSoP-Planning", QCoreApplication::translate("AdConfig", "Generate RSoP Planning")}, + {"Certificate-Enrollment", QCoreApplication::translate("AdConfig", "Certificate Enrollment")}, + {"Web-Information", QCoreApplication::translate("AdConfig", "Web Information")}, + {"Create-Inbound-Forest-Trust", QCoreApplication::translate("AdConfig", "Create Inbound Forest Trust")}, + {"Migrate-SID-History", QCoreApplication::translate("AdConfig", "Migrate SID History")}, + {"Update-Password-Not-Required-Bit", QCoreApplication::translate("AdConfig", "Update Password Not Required Bit")}, + {"MS-TS-GatewayAccess", QCoreApplication::translate("AdConfig", "MS-TS-GatewayAccess")}, + {"Validated-MS-DS-Additional-DNS-Host-Name", QCoreApplication::translate("AdConfig", "Validated write to MS DS Additional DNS Host Name")}, + {"msmq-Receive", QCoreApplication::translate("AdConfig", "msmq Receive")}, + {"Validated-DNS-Host-Name", QCoreApplication::translate("AdConfig", "Validated DNS Host Name")}, + {"Send-To", QCoreApplication::translate("AdConfig", "Send To")}, + {"DS-Replication-Get-Changes-In-Filtered-Set", QCoreApplication::translate("AdConfig", "DS Replication Get Changes In Filtered Set")}, + {"Read-Only-Replication-Secret-Synchronization", QCoreApplication::translate("AdConfig", "Read Only Replication Secret Synchronization")}, + {"Validated-MS-DS-Behavior-Version", QCoreApplication::translate("AdConfig", "Validated write to MS DS behavior version")}, + {"msmq-Open-Connector", QCoreApplication::translate("AdConfig", "msmq Open Connector")}, + {"Terminal-Server-License-Server", QCoreApplication::translate("AdConfig", "Terminal Server License Server")}, + {"Change-Schema-Master", QCoreApplication::translate("AdConfig", "Change Schema Master")}, + {"Recalculate-Hierarchy", QCoreApplication::translate("AdConfig", "Recalculate Hierarchy")}, + {"DS-Check-Stale-Phantoms", QCoreApplication::translate("AdConfig", "DS Check Stale Phantoms")}, + {"msmq-Receive-computer-Journal", QCoreApplication::translate("AdConfig", "msmq Receive computer Journal")}, + {"User-Force-Change-Password", QCoreApplication::translate("AdConfig", "User Force Change Password")}, + {"Domain-Administer-Server", QCoreApplication::translate("AdConfig", "Domain Administer Server")}, + {"DS-Replication-Synchronize", QCoreApplication::translate("AdConfig", "DS Replication Synchronize")}, + {"Personal-Information", QCoreApplication::translate("AdConfig", "Personal Information")}, + {"msmq-Peek", QCoreApplication::translate("AdConfig", "msmq Peek")}, + {"General-Information", QCoreApplication::translate("AdConfig", "General Information")}, + {"Membership", QCoreApplication::translate("AdConfig", "Group Membership")}, + {"Add-GUID", QCoreApplication::translate("AdConfig", "Add GUID")}, + {"RAS-Information", QCoreApplication::translate("AdConfig", "Remote Access Information")}, + {"DS-Execute-Intentions-Script", QCoreApplication::translate("AdConfig", "DS Execute Intentions Script")}, + {"Allocate-Rids", QCoreApplication::translate("AdConfig", "Allocate Rids")}, + {"Update-Schema-Cache", QCoreApplication::translate("AdConfig", "Update Schema Cache")}, + {"Apply-Group-Policy", QCoreApplication::translate("AdConfig", "Apply Group Policy")}, + {"User-Account-Restrictions", QCoreApplication::translate("AdConfig", "Account Restrictions")}, + {"Validated-SPN", QCoreApplication::translate("AdConfig", "Validated SPN")}, + {"DS-Read-Partition-Secrets", QCoreApplication::translate("AdConfig", "Read secret attributes of objects in a Partition")}, + {"User-Logon", QCoreApplication::translate("AdConfig", "Logon Information")}, + {"DS-Query-Self-Quota", QCoreApplication::translate("AdConfig", "DS Query Self Quota")}, + {"Change-Infrastructure-Master", QCoreApplication::translate("AdConfig", "Change Infrastructure Master")}, + {"Open-Address-Book", QCoreApplication::translate("AdConfig", "Open Address Book")}, + {"User-Change-Password", QCoreApplication::translate("AdConfig", "User Change Password")}, + {"msmq-Peek-computer-Journal", QCoreApplication::translate("AdConfig", "msmq Peek computer Journal")}, + {"Change-Domain-Master", QCoreApplication::translate("AdConfig", "Change Domain Master")}, + {"msmq-Send", QCoreApplication::translate("AdConfig", "msmq Send")}, + {"Change-Rid-Master", QCoreApplication::translate("AdConfig", "Change Rid Master")}, + {"Recalculate-Security-Inheritance", QCoreApplication::translate("AdConfig", "Recalculate Security Inheritance")}, + {"Refresh-Group-Cache", QCoreApplication::translate("AdConfig", "Refresh Group Cache")}, + {"Manage-Optional-Features", QCoreApplication::translate("AdConfig", "Manage Optional Features")}, + {"Reload-SSL-Certificate", QCoreApplication::translate("AdConfig", "Reload SSL Certificate")}, + {"Enable-Per-User-Reversibly-Encrypted-Password", QCoreApplication::translate("AdConfig", "Enable Per User Reversibly Encrypted Password")}, + {"DS-Replication-Monitor-Topology", QCoreApplication::translate("AdConfig", "DS Replication Monitor Topology")}, + {"Public-Information", QCoreApplication::translate("AdConfig", "Public Information")}, + {"Private-Information", QCoreApplication::translate("AdConfig", "Private Information")}, + {"msmq-Receive-Dead-Letter", QCoreApplication::translate("AdConfig", "msmq Receive Dead Letter")}, + {"msmq-Receive-journal", QCoreApplication::translate("AdConfig", "msmq Receive journal")}, + {"DNS-Host-Name-Attributes", QCoreApplication::translate("AdConfig", "DNS Host Name Attributes")}, + }; + + const QString right_cn = d->right_guid_to_cn_map[right_guid]; + if (language == QLocale::Russian && cn_to_map_russian.contains(right_cn)) { + const QString out = cn_to_map_russian[right_cn]; + + return out; + } + + const QString out = d->rights_guid_to_name_map.value(right_guid, QCoreApplication::translate("AdConfig", "")); + return out; +} + +QList AdConfig::get_extended_rights_list(const QList &class_list) const { + QList out; + + for (const QString &rights : d->extended_rights_list) { + const bool applies_to = rights_applies_to_class(rights, class_list); + if (applies_to) { + out.append(rights); + } + } + + return out; +} + +int AdConfig::get_rights_valid_accesses(const QString &rights_cn) const { + // NOTE: awkward exception. Can't write group + // membership because target attribute is + // constructed. For some reason valid accesses for + // membership right does allow writing. + if (rights_cn == "Membership") { + return SEC_ADS_READ_PROP; + } + + const int out = d->rights_valid_accesses_map.value(rights_cn, 0); + + return out; +} + +QString AdConfig::guid_to_attribute(const QByteArray &guid) const { + const QString out = d->guid_to_attribute_map.value(guid, ""); + return out; +} + +QString AdConfig::guid_to_class(const QByteArray &guid) const { + const QString out = d->guid_to_class_map.value(guid, ""); return out; } @@ -558,6 +775,14 @@ QList AdConfig::get_noncontainer_classes() { return out; } +bool AdConfig::rights_applies_to_class(const QString &rights_cn, const QList &class_list) const { + const QByteArray rights_guid = d->rights_name_to_guid_map[rights_cn]; + const QSet applies_to = d->rights_applies_to_map[rights_guid].toSet(); + const bool applies = applies_to.intersects(class_list.toSet()); + + return applies; +} + QList AdConfigPrivate::add_auxiliary_classes(const QList &object_classes) const { QList out; diff --git a/src/adldap/ad_config.h b/src/adldap/ad_config.h index 51b8e42c..c1798d0e 100644 --- a/src/adldap/ad_config.h +++ b/src/adldap/ad_config.h @@ -29,11 +29,14 @@ #include "ad_defines.h" +#include + class AdConfigPrivate; class AdInterface; class QLocale; class QString; class QLineEdit; +class QByteArray; template class QList; @@ -50,11 +53,12 @@ public: void load(AdInterface &ad, const QLocale &locale); QString domain() const; - QString domain_head() const; + QString domain_dn() const; QString configuration_dn() const; QString schema_dn() const; QString partitions_dn() const; QString extended_rights_dn() const; + bool control_is_supported(const QString &control_oid) const; QString get_attribute_display_name(const Attribute &attribute, const ObjectClass &objectClass) const; @@ -67,6 +71,10 @@ public: QList get_filter_containers() const; QList get_possible_superiors(const QList &object_classes) const; + ObjectClass get_parent_class(const ObjectClass &object_class) const; + // Returns all ancestors of given class and the + // given class itself + QList get_inherit_chain(const ObjectClass &object_class) const; QList get_optional_attributes(const QList &object_classes) const; QList get_mandatory_attributes(const QList &object_classes) const; @@ -81,12 +89,26 @@ public: bool get_attribute_is_backlink(const Attribute &attribute) const; bool get_attribute_is_constructed(const Attribute &attribute) const; + // Limit's edit's max valid input length based on + // the upper range defined for attribute in schema void limit_edit(QLineEdit *edit, const QString &attribute); - QString get_right_guid(const QString &right_cn) const; + QByteArray get_right_guid(const QString &right_cn) const; + QString get_right_name(const QByteArray &right_guid, const QLocale::Language language) const; + int get_rights_valid_accesses(const QString &rights_cn) const; + + // Returns extended rights that apply to given + // classes + QList get_extended_rights_list(const QList &class_list) const; + + QString guid_to_attribute(const QByteArray &guid) const; + + QString guid_to_class(const QByteArray &guid) const; QList get_noncontainer_classes(); + bool rights_applies_to_class(const QString &rights_cn, const QList &class_list) const; + private: AdConfigPrivate *d; }; diff --git a/src/adldap/ad_config_p.h b/src/adldap/ad_config_p.h index e3046b6d..b9b70fd1 100644 --- a/src/adldap/ad_config_p.h +++ b/src/adldap/ad_config_p.h @@ -38,7 +38,9 @@ public: AdConfigPrivate(); QString domain; - QString domain_head; + QString domain_dn; + QString configuration_dn; + QString schema_dn; QList filter_containers; @@ -54,7 +56,20 @@ public: QList add_auxiliary_classes(const QList &object_classes) const; - QHash right_to_guid_map; + QHash right_to_guid_map; + QHash right_guid_to_cn_map; + QHash rights_guid_to_name_map; + QHash rights_name_to_guid_map; + QHash> rights_applies_to_map; + QList extended_rights_list; + QHash rights_valid_accesses_map; + + QHash guid_to_attribute_map; + QHash guid_to_class_map; + + QList supported_control_list; + + QHash sub_class_of_map; }; #endif /* AD_CONFIG_P_H */ diff --git a/src/adldap/ad_defines.h b/src/adldap/ad_defines.h index b9235b46..9bd5ba72 100644 --- a/src/adldap/ad_defines.h +++ b/src/adldap/ad_defines.h @@ -60,6 +60,7 @@ enum LargeIntegerSubtype { enum AccountOption { AccountOption_Disabled, AccountOption_CantChangePassword, + AccountOption_AllowReversibleEncryption, AccountOption_PasswordExpired, AccountOption_DontExpirePassword, AccountOption_UseDesKey, @@ -89,6 +90,8 @@ enum SystemFlagsBit { SystemFlagsBit_CannotDelete = 0x80000000 }; +#define ROOT_DSE "" + #define ATTRIBUTE_CN "cn" #define ATTRIBUTE_USER_ACCOUNT_CONTROL "userAccountControl" #define ATTRIBUTE_LOCKOUT_TIME "lockoutTime" @@ -97,6 +100,7 @@ enum SystemFlagsBit { #define ATTRIBUTE_NAME "name" #define ATTRIBUTE_INITIALS "initials" #define ATTRIBUTE_SAM_ACCOUNT_NAME "sAMAccountName" +#define ATTRIBUTE_SAM_ACCOUNT_TYPE "sAMAccountType" #define ATTRIBUTE_DISPLAY_NAME "displayName" #define ATTRIBUTE_DESCRIPTION "description" #define ATTRIBUTE_USER_PRINCIPAL_NAME "userPrincipalName" @@ -114,6 +118,7 @@ enum SystemFlagsBit { #define ATTRIBUTE_POSTAL_CODE "postalCode" #define ATTRIBUTE_STATE "st" #define ATTRIBUTE_STREET "streetAddress" +#define ATTRIBUTE_STREET_OU "street" #define ATTRIBUTE_DN "distinguishedName" #define ATTRIBUTE_OBJECT_CLASS "objectClass" #define ATTRIBUTE_WHEN_CREATED "whenCreated" @@ -158,7 +163,8 @@ enum SystemFlagsBit { #define ATTRIBUTE_PROFILE_PATH "profilePath" #define ATTRIBUTE_SCRIPT_PATH "scriptPath" #define ATTRIBUTE_HOME_DIRECTORY "homeDirectory" - +#define ATTRIBUTE_LAPS_PASSWORD "ms-Mcs-AdmPwd" +#define ATTRIBUTE_LAPS_EXPIRATION "ms-Mcs-AdmPwdExpirationTime" #define ATTRIBUTE_HOME_PHONE "homePhone" #define ATTRIBUTE_OTHER_HOME_PHONE "otherHomePhone" #define ATTRIBUTE_PAGER "pager" @@ -179,6 +185,18 @@ enum SystemFlagsBit { #define ATTRIBUTE_LOGON_HOURS "logonHours" #define ATTRIBUTE_USER_WORKSTATIONS "userWorkstations" #define ATTRIBUTE_VERSION_NUMBER "versionNumber" +#define ATTRIBUTE_SUPPORTED_CONTROL "supportedControl" +#define ATTRIBUTE_DS_SERVICE_NAME "dsServiceName" +#define ATTRIBUTE_SCHEMA_NAMING_CONTEXT "schemaNamingContext" +#define ATTRIBUTE_CONFIGURATION_NAMING_CONTEXT "configurationNamingContext" +#define ATTRIBUTE_ROOT_DOMAIN_NAMING_CONTEXT "rootDomainNamingContext" +#define ATTRIBUTE_FSMO_ROLE_OWNER "fSMORoleOwner" +#define ATTRIBUTE_SERVER_NAME "serverName" +#define ATTRIBUTE_SCHEMA_ID_GUID "schemaIDGUID" +#define ATTRIBUTE_APPLIES_TO "appliesTo" +#define ATTRIBUTE_VALID_ACCESSES "validAccesses" +#define ATTRIBUTE_UNC_NAME "uNCName" +#define ATTRIBUTE_KEYWORDS "keywords" #define CLASS_GROUP "group" #define CLASS_USER "user" @@ -234,49 +252,53 @@ const long long MILLIS_TO_100_NANOS = 10000LL; #define SACL_SECURITY_INFORMATION 0x08 #define DACL_SECURITY_INFORMATION 0x10 -enum PermissionState { - PermissionState_None, - PermissionState_Allowed, - PermissionState_Denied, -}; +#define SAM_NAME_BAD_CHARS "@\"[]:;|=+*?<>/\\," +#define UPN_BAD_CHARS "#,+\"\\<>" +// NOTE: names technically can contain these chars but +// we choose to be more strict about it +#define NAME_BAD_CHARS ",\\#+<>;\"=" -enum AcePermission { - AcePermission_FullControl, - AcePermission_Read, - AcePermission_Write, - AcePermission_Delete, - AcePermission_DeleteSubtree, - AcePermission_CreateChild, - AcePermission_DeleteChild, - AcePermission_AllowedToAuthenticate, - AcePermission_ChangePassword, - AcePermission_ReceiveAs, - AcePermission_ResetPassword, - AcePermission_SendAs, - AcePermission_ReadAccountRestrictions, - AcePermission_WriteAccountRestrictions, - AcePermission_ReadGeneralInfo, - AcePermission_WriteGeneralInfo, - AcePermission_ReadGroupMembership, - AcePermission_ReadLogonInfo, - AcePermission_WriteLogonInfo, - AcePermission_ReadPersonalInfo, - AcePermission_WritePersonalInfo, - AcePermission_ReadPhoneAndMailOptions, - AcePermission_WritePhoneAndMailOptions, - AcePermission_ReadPrivateInfo, - AcePermission_WritePrivateInfo, - AcePermission_ReadPublicInfo, - AcePermission_WritePublicInfo, - AcePermission_ReadRemoteAccessInfo, - AcePermission_WriteRemoteAccessInfo, - AcePermission_ReadTerminalServerLicenseServer, - AcePermission_WriteTerminalServerLicenseServer, - AcePermission_ReadWebInfo, - AcePermission_WriteWebInfo, +#define UAC_SCRIPT 0x00000001 +#define UAC_ACCOUNTDISABLE 0x00000002 +#define UAC_HOMEDIR_REQUIRED 0x00000008 +#define UAC_LOCKOUT 0x00000010 +#define UAC_PASSWD_NOTREQD 0x00000020 +#define UAC_PASSWD_CANT_CHANGE 0x00000040 +#define UAC_ENCRYPTED_TEXT_PASSWORD_ALLOWED 0x00000080 +#define UAC_TEMP_DUPLICATE_ACCOUNT 0x00000100 +#define UAC_NORMAL_ACCOUNT 0x00000200 +#define UAC_INTERDOMAIN_TRUST_ACCOUNT 0x00000800 +#define UAC_WORKSTATION_TRUST_ACCOUNT 0x00001000 +#define UAC_SERVER_TRUST_ACCOUNT 0x00002000 +#define UAC_DONT_EXPIRE_PASSWORD 0x00010000 +#define UAC_MNS_LOGON_ACCOUNT 0x00020000 +#define UAC_SMARTCARD_REQUIRED 0x00040000 +#define UAC_TRUSTED_FOR_DELEGATION 0x00080000 +#define UAC_NOT_DELEGATED 0x00100000 +#define UAC_USE_DES_KEY_ONLY 0x00200000 +#define UAC_DONT_REQUIRE_PREAUTH 0x00400000 +#define UAC_ERROR_PASSWORD_EXPIRED 0x00800000 +#define UAC_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION 0x01000000 +#define UAC_PARTIAL_SECRETS_ACCOUNT 0x04000000 +#define UAC_USER_USE_AES_KEYS 0x08000000 - AcePermission_COUNT, -}; +#define SAM_DOMAIN_OBJECT 0x0 +#define SAM_GROUP_OBJECT 0x10000000 +#define SAM_NON_SECURITY_GROUP_OBJECT 0x10000001 +#define SAM_ALIAS_OBJECT 0x20000000 +#define SAM_NON_SECURITY_ALIAS_OBJECT 0x20000001 +#define SAM_USER_OBJECT 0x30000000 +#define SAM_NORMAL_USER_ACCOUNT 0x30000000 +#define SAM_MACHINE_ACCOUNT 0x30000001 +#define SAM_TRUST_ACCOUNT 0x30000002 +#define SAM_APP_BASIC_GROUP 0x40000000 +#define SAM_APP_QUERY_GROUP 0x40000001 +#define SAM_ACCOUNT_TYPE_MAX 0x7fffffff + +// NOTE: this is according to Microsoft, haven't found +// any better source +#define SAM_NAME_MAX_LENGTH 20 +#define SAM_NAME_COMPUTER_MAX_LENGTH 16 enum SearchScope { SearchScope_Object, diff --git a/src/adldap/ad_display.cpp b/src/adldap/ad_display.cpp index 25548f2e..56703b92 100644 --- a/src/adldap/ad_display.cpp +++ b/src/adldap/ad_display.cpp @@ -42,6 +42,9 @@ QString datetime_display_value(const QString &attribute, const QByteArray &bytes QString timespan_display_value(const QByteArray &bytes); QString octet_display_value(const QByteArray &bytes); QString guid_to_display_value(const QByteArray &bytes); +QString uac_to_display_value(const QByteArray &bytes); +QString samaccounttype_to_display_value(const QByteArray &bytes); +QString primarygrouptype_to_display_value(const QByteArray &bytes); QString attribute_display_value(const QString &attribute, const QByteArray &value, const AdConfig *adconfig) { if (adconfig == nullptr) { @@ -51,6 +54,17 @@ QString attribute_display_value(const QString &attribute, const QByteArray &valu const AttributeType type = adconfig->get_attribute_type(attribute); switch (type) { + case AttributeType_Integer: { + if (attribute == ATTRIBUTE_USER_ACCOUNT_CONTROL) { + return uac_to_display_value(value); + } else if (attribute == ATTRIBUTE_SAM_ACCOUNT_TYPE) { + return samaccounttype_to_display_value(value); + } else if (attribute == ATTRIBUTE_PRIMARY_GROUP_ID) { + return primarygrouptype_to_display_value(value); + } else { + return QString(value); + } + } case AttributeType_LargeInteger: { const LargeIntegerSubtype subtype = adconfig->get_attribute_large_integer_subtype(attribute); @@ -250,3 +264,158 @@ QString octet_display_value(const QByteArray &bytes) { return QString(out); } + +QString uac_to_display_value(const QByteArray &bytes) { + bool uac_toInt_ok; + const int uac = bytes.toInt(&uac_toInt_ok); + + if (!uac_toInt_ok) { + return QCoreApplication::translate("attribute_display", ""); + } + + // Create string of the form "X | Y | Z", where X, + // Y, Z are names of masks that are set in given UAC + const QString masks_string = [&]() { + // NOTE: using separate list instead of map's + // keys() because keys() is unordered and we need + // order so that display string is consistent + const QList mask_list = { + UAC_SCRIPT, + UAC_ACCOUNTDISABLE, + UAC_HOMEDIR_REQUIRED, + UAC_LOCKOUT, + UAC_PASSWD_NOTREQD, + UAC_PASSWD_CANT_CHANGE, + UAC_ENCRYPTED_TEXT_PASSWORD_ALLOWED, + UAC_TEMP_DUPLICATE_ACCOUNT, + UAC_NORMAL_ACCOUNT, + UAC_INTERDOMAIN_TRUST_ACCOUNT, + UAC_WORKSTATION_TRUST_ACCOUNT, + UAC_SERVER_TRUST_ACCOUNT, + UAC_DONT_EXPIRE_PASSWORD, + UAC_MNS_LOGON_ACCOUNT, + UAC_SMARTCARD_REQUIRED, + UAC_TRUSTED_FOR_DELEGATION, + UAC_NOT_DELEGATED, + UAC_USE_DES_KEY_ONLY, + UAC_DONT_REQUIRE_PREAUTH, + UAC_ERROR_PASSWORD_EXPIRED, + UAC_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, + UAC_PARTIAL_SECRETS_ACCOUNT, + UAC_USER_USE_AES_KEYS, + }; + + const QHash mask_name_map = { + {UAC_SCRIPT, "SCRIPT"}, + {UAC_ACCOUNTDISABLE, "ACCOUNTDISABLE"}, + {UAC_HOMEDIR_REQUIRED, "HOMEDIR_REQUIRED"}, + {UAC_LOCKOUT, "LOCKOUT"}, + {UAC_PASSWD_NOTREQD, "PASSWD_NOTREQD"}, + {UAC_PASSWD_CANT_CHANGE, "PASSWD_CANT_CHANGE"}, + {UAC_ENCRYPTED_TEXT_PASSWORD_ALLOWED, "ENCRYPTED_TEXT_PASSWORD_ALLOWED"}, + {UAC_TEMP_DUPLICATE_ACCOUNT, "TEMP_DUPLICATE_ACCOUNT"}, + {UAC_NORMAL_ACCOUNT, "NORMAL_ACCOUNT"}, + {UAC_INTERDOMAIN_TRUST_ACCOUNT, "INTERDOMAIN_TRUST_ACCOUNT"}, + {UAC_WORKSTATION_TRUST_ACCOUNT, "WORKSTATION_TRUST_ACCOUNT"}, + {UAC_SERVER_TRUST_ACCOUNT, "SERVER_TRUST_ACCOUNT"}, + {UAC_DONT_EXPIRE_PASSWORD, "DONT_EXPIRE_PASSWORD"}, + {UAC_MNS_LOGON_ACCOUNT, "MNS_LOGON_ACCOUNT"}, + {UAC_SMARTCARD_REQUIRED, "SMARTCARD_REQUIRED"}, + {UAC_TRUSTED_FOR_DELEGATION, "TRUSTED_FOR_DELEGATION"}, + {UAC_NOT_DELEGATED, "NOT_DELEGATED"}, + {UAC_USE_DES_KEY_ONLY, "USE_DES_KEY_ONLY"}, + {UAC_DONT_REQUIRE_PREAUTH, "DONT_REQUIRE_PREAUTH"}, + {UAC_ERROR_PASSWORD_EXPIRED, "ERROR_PASSWORD_EXPIRED"}, + {UAC_TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION, "TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION"}, + {UAC_PARTIAL_SECRETS_ACCOUNT, "PARTIAL_SECRETS_ACCOUNT"}, + {UAC_USER_USE_AES_KEYS, "USER_USE_AES_KEYS"}, + }; + + const QList set_mask_name_list = [&]() { + QList out_list; + + for (const int mask : mask_list) { + const bool mask_is_set = bitmask_is_set(uac, mask); + + if (mask_is_set) { + const QString mask_name = mask_name_map[mask]; + out_list.append(mask_name); + } + } + + return out_list; + }(); + + const QString out_string = set_mask_name_list.join(" | "); + + return out_string; + }(); + + const QString out = QString("%1 = ( %2 )").arg(QString(bytes), masks_string); + + return out; +} + +QString samaccounttype_to_display_value(const QByteArray &bytes) { + bool toInt_ok; + const int value_int = bytes.toInt(&toInt_ok); + + if (!toInt_ok) { + return QCoreApplication::translate("attribute_display", ""); + } + + const QHash mask_name_map = { + {SAM_DOMAIN_OBJECT, "DOMAIN_OBJECT"}, + {SAM_GROUP_OBJECT, "GROUP_OBJECT"}, + {SAM_NON_SECURITY_GROUP_OBJECT, "NON_SECURITY_GROUP_OBJECT"}, + {SAM_ALIAS_OBJECT, "ALIAS_OBJECT"}, + {SAM_NON_SECURITY_ALIAS_OBJECT, "NON_SECURITY_ALIAS_OBJECT"}, + {SAM_USER_OBJECT, "USER_OBJECT"}, + {SAM_NORMAL_USER_ACCOUNT, "NORMAL_USER_ACCOUNT"}, + {SAM_MACHINE_ACCOUNT, "MACHINE_ACCOUNT"}, + {SAM_TRUST_ACCOUNT, "TRUST_ACCOUNT"}, + {SAM_APP_BASIC_GROUP, "APP_BASIC_GROUP"}, + {SAM_APP_QUERY_GROUP, "APP_QUERY_GROUP"}, + {SAM_ACCOUNT_TYPE_MAX, "ACCOUNT_TYPE_MAX"}, + }; + + const QString mask_name = mask_name_map.value(value_int, ""); + + const QString out = QString("%1 = ( %2 )").arg(QString(bytes), mask_name); + + return out; +} + +QString primarygrouptype_to_display_value(const QByteArray &bytes) { + bool toInt_ok; + const int value_int = bytes.toInt(&toInt_ok); + + if (!toInt_ok) { + return QCoreApplication::translate("attribute_display", ""); + } + + // NOTE: builtin group rid's are not included + // because they can't be primary + const QHash mask_name_map = { + {DOMAIN_RID_ADMINS, "GROUP_RID_ADMINS"}, + {DOMAIN_RID_USERS, "GROUP_RID_USERS"}, + {DOMAIN_RID_GUESTS, "GROUP_RID_GUESTS"}, + {DOMAIN_RID_DOMAIN_MEMBERS, "GROUP_RID_DOMAIN_MEMBERS"}, + {DOMAIN_RID_DCS, "GROUP_RID_DCS"}, + {DOMAIN_RID_CERT_ADMINS, "GROUP_RID_CERT_ADMINS"}, + {DOMAIN_RID_SCHEMA_ADMINS, "GROUP_RID_SCHEMA_ADMINS"}, + {DOMAIN_RID_ENTERPRISE_ADMINS, "GROUP_RID_ENTERPRISE_ADMINS"}, + {DOMAIN_RID_POLICY_ADMINS, "GROUP_RID_POLICY_ADMINS"}, + {DOMAIN_RID_READONLY_DCS, "GROUP_RID_READONLY_DCS"}, + {DOMAIN_RID_RAS_SERVERS, "GROUP_RID_RAS_SERVERS"}, + }; + + if (mask_name_map.contains(value_int)) { + const QString mask_name = mask_name_map[value_int]; + const QString out = QString("%1 = ( %2 )").arg(QString(bytes), mask_name); + + return out; + } else { + return QString::number(value_int); + } +} diff --git a/src/adldap/ad_interface.cpp b/src/adldap/ad_interface.cpp index 7fcfed0e..5e800bb4 100644 --- a/src/adldap/ad_interface.cpp +++ b/src/adldap/ad_interface.cpp @@ -83,6 +83,7 @@ enum AceMaskFormat { QList query_server_for_hosts(const char *dname); int sasl_interact_gssapi(LDAP *ld, unsigned flags, void *indefaults, void *in); QString get_gpt_sd_string(const AdObject &gpc_object, const AceMaskFormat format); +int create_sd_control(bool get_sacl, int iscritical, LDAPControl **ctrlp); AdConfig *AdInterfacePrivate::adconfig = nullptr; bool AdInterfacePrivate::s_log_searches = false; @@ -114,12 +115,10 @@ AdInterface::AdInterface() { d->domain = get_default_domain_from_krb5(); if (d->domain.isEmpty()) { - d->error_message(connect_error_context, tr("Failed to get a domain.")); + d->error_message(connect_error_context, tr("Failed to get a domain. Check that you have initialized kerberos credentials (kinit).")); return; } - d->domain_head = domain_to_domain_dn(d->domain); - // // Connect via LDAP // @@ -327,10 +326,6 @@ void AdInterface::set_cert_strategy(const CertStrategy strategy) { AdInterfacePrivate::s_cert_strat = strategy; } -QString AdInterface::get_dc() { - return AdInterfacePrivate::s_dc; -} - AdInterfacePrivate::AdInterfacePrivate(AdInterface *q_arg) { q = q_arg; } @@ -374,52 +369,32 @@ bool AdInterfacePrivate::search_paged_internal(const char *base, const int scope int result; LDAPMessage *res = NULL; LDAPControl *page_control = NULL; + LDAPControl *sd_control = NULL; LDAPControl **returned_controls = NULL; struct berval *prev_cookie = cookie->cookie; struct berval *new_cookie = NULL; - BerElement *sd_control_value_be = NULL; - berval *sd_control_value_bv = NULL; auto cleanup = [&]() { ldap_msgfree(res); ldap_control_free(page_control); + ldap_control_free(sd_control); ldap_controls_free(returned_controls); ber_bvfree(prev_cookie); ber_bvfree(new_cookie); - ber_free(sd_control_value_be, 1); - ber_bvfree(sd_control_value_bv); }; - // NOTE: this control is needed so that ldap returns - // security descriptor attribute when we ask for all - // attributes. Otherwise it won't includ the descriptor - // in attributes. This might break something when the - // app is used by a client with not enough rights to get - // some/all parts of the descriptor. Investigate. - LDAPControl sd_control; - const char *sd_control_oid = LDAP_SERVER_SD_FLAGS_OID; - sd_control.ldctl_oid = (char *) sd_control_oid; - // NOTE: sacl part of the sd can only be obtained by - // administrators, so for normal operations we omit it. - // For some operations sacl is required so there's an - // option to get it. - const int sd_control_value_int = [&]() { - if (get_sacl) { - return (OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION); - } else { - return (OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION); - } - }(); - sd_control_value_be = ber_alloc_t(LBER_USE_DER); - ber_printf(sd_control_value_be, "{i}", sd_control_value_int); - ber_flatten(sd_control_value_be, &sd_control_value_bv); - sd_control.ldctl_value.bv_len = sd_control_value_bv->bv_len; - sd_control.ldctl_value.bv_val = sd_control_value_bv->bv_val; - sd_control.ldctl_iscritical = (char) 1; + const int is_critical = 1; + + result = create_sd_control(get_sacl, is_critical, &sd_control); + if (result != LDAP_SUCCESS) { + qDebug() << "Failed to create sd control: " << ldap_err2string(result); + + cleanup(); + return false; + } // Create page control - const ber_int_t page_size = 1000; - const int is_critical = 1; + const ber_int_t page_size = 100; result = ldap_create_page_control(ld, page_size, prev_cookie, is_critical, &page_control); if (result != LDAP_SUCCESS) { qDebug() << "Failed to create page control: " << ldap_err2string(result); @@ -427,7 +402,7 @@ bool AdInterfacePrivate::search_paged_internal(const char *base, const int scope cleanup(); return false; } - LDAPControl *server_controls[3] = {page_control, &sd_control, NULL}; + LDAPControl *server_controls[3] = {page_control, sd_control, NULL}; // Perform search const int attrsonly = 0; @@ -500,32 +475,33 @@ bool AdInterfacePrivate::search_paged_internal(const char *base, const int scope } // Get page response control + // + // NOTE: not sure if absence of page response control is + // an error. Decided to not treat it as error because + // searching the rootDSE doesn't return this control. LDAPControl *pageresponse_control = ldap_control_find(LDAP_CONTROL_PAGEDRESULTS, returned_controls, NULL); - if (pageresponse_control == NULL) { - qDebug() << "Failed to find PAGEDRESULTS control"; + if (pageresponse_control != NULL) { + // Parse page response control to determine whether + // there are more pages + ber_int_t total_count; + new_cookie = (struct berval *) malloc(sizeof(struct berval)); + result = ldap_parse_pageresponse_control(ld, pageresponse_control, &total_count, new_cookie); + if (result != LDAP_SUCCESS) { + qDebug() << "Failed to parse pageresponse control: " << ldap_err2string(result); - cleanup(); - return false; - } + cleanup(); + return false; + } - // Parse page response control to determine whether - // there are more pages - ber_int_t total_count; - new_cookie = (struct berval *) malloc(sizeof(struct berval)); - result = ldap_parse_pageresponse_control(ld, pageresponse_control, &total_count, new_cookie); - if (result != LDAP_SUCCESS) { - qDebug() << "Failed to parse pageresponse control: " << ldap_err2string(result); - - cleanup(); - return false; - } - - // Switch to new cookie if there are more pages - // NOTE: there are more pages if the cookie isn't - // empty - const bool more_pages = (new_cookie->bv_len > 0); - if (more_pages) { - cookie->cookie = ber_bvdup(new_cookie); + // Switch to new cookie if there are more pages + // NOTE: there are more pages if the cookie isn't + // empty + const bool more_pages = (new_cookie->bv_len > 0); + if (more_pages) { + cookie->cookie = ber_bvdup(new_cookie); + } else { + cookie->cookie = NULL; + } } else { cookie->cookie = NULL; } @@ -538,23 +514,6 @@ QHash AdInterface::search(const QString &base, const SearchSc AdCookie cookie; QHash results; - if (AdInterfacePrivate::s_log_searches) { - const QString attributes_string = "{" + attributes.join(",") + "}"; - - const QString scope_string = [&scope]() -> QString { - switch (scope) { - case SearchScope_Object: return "object"; - case SearchScope_Children: return "children"; - case SearchScope_Descendants: return "descendants"; - case SearchScope_All: return "all"; - default: break; - } - return QString(); - }(); - - d->success_message(QString(tr("Search:\n\tfilter = \"%1\"\n\tattributes = %2\n\tscope = \"%3\"\n\tbase = \"%4\"")).arg(filter, attributes_string, scope_string, base)); - } - while (true) { const bool success = search_paged(base, scope, filter, attributes, &results, &cookie, get_sacl); @@ -571,6 +530,27 @@ QHash AdInterface::search(const QString &base, const SearchSc } bool AdInterface::search_paged(const QString &base, const SearchScope scope, const QString &filter, const QList &attributes, QHash *results, AdCookie *cookie, const bool get_sacl) { + // NOTE: only log once per cycle of search pages, + // to avoid duplicate messages + const bool is_first_page = results->isEmpty(); + const bool need_to_log = (AdInterfacePrivate::s_log_searches && is_first_page); + if (need_to_log) { + const QString attributes_string = "{" + attributes.join(",") + "}"; + + const QString scope_string = [&scope]() -> QString { + switch (scope) { + case SearchScope_Object: return "object"; + case SearchScope_Children: return "children"; + case SearchScope_Descendants: return "descendants"; + case SearchScope_All: return "all"; + default: break; + } + return QString(); + }(); + + d->success_message(QString(tr("Search:\n\tfilter = \"%1\"\n\tattributes = %2\n\tscope = \"%3\"\n\tbase = \"%4\"")).arg(filter, attributes_string, scope_string, base)); + } + const char *base_cstr = cstr(base); const int scope_int = [&]() { @@ -805,18 +785,40 @@ bool AdInterface::attribute_replace_datetime(const QString &dn, const QString &a return result; } -bool AdInterface::object_add(const QString &dn, const QString &object_class) { - const char *classes[2] = {cstr(object_class), NULL}; +bool AdInterface::object_add(const QString &dn, const QHash> &attrs_map) { + LDAPMod **attrs = [&attrs_map]() { + LDAPMod **out = (LDAPMod **) malloc((attrs_map.size() + 1) * sizeof(LDAPMod *)); - LDAPMod attr; - attr.mod_op = LDAP_MOD_ADD; - attr.mod_type = (char *) "objectClass"; - attr.mod_values = (char **) classes; + const QList attrs_map_keys = attrs_map.keys(); + for (int i = 0; i < attrs_map_keys.size(); i++) { + LDAPMod *attr = (LDAPMod *) malloc(sizeof(LDAPMod)); - LDAPMod *attrs[] = {&attr, NULL}; + const QString attr_name = attrs_map_keys[i]; + const QList value_list = attrs_map[attr_name]; + + char **value_array = (char **) malloc((value_list.size() + 1) * sizeof(char *)); + for (int j = 0; j < value_list.size(); j++) { + const QString value = value_list[j]; + value_array[j] = (char *) strdup(cstr(value)); + } + value_array[value_list.size()] = NULL; + + attr->mod_type = (char *) strdup(cstr(attr_name)); + attr->mod_op = LDAP_MOD_ADD; + attr->mod_values = value_array; + + out[i] = attr; + } + + out[attrs_map.size()] = NULL; + + return out; + }(); const int result = ldap_add_ext_s(d->ld, cstr(dn), attrs, NULL, NULL); + ldap_mods_free(attrs, 1); + if (result == LDAP_SUCCESS) { d->success_message(QString(tr("Object %1 was created.")).arg(dn)); @@ -850,6 +852,16 @@ bool AdInterface::object_add(const QString &dn, const QString &object_class) { } } +bool AdInterface::object_add(const QString &dn, const QString &object_class) { + const QHash> attrs_map = { + {"objectClass", {object_class}}, + }; + + const bool success = object_add(dn, attrs_map); + + return success; +} + bool AdInterface::object_delete(const QString &dn, const DoStatusMsg do_msg) { int result; LDAPControl *tree_delete_control = NULL; @@ -863,14 +875,6 @@ bool AdInterface::object_delete(const QString &dn, const DoStatusMsg do_msg) { const QString error_context = QString(tr("Failed to delete object %1.")).arg(name); // Use a tree delete control to enable recursive delete - tree_delete_control = (LDAPControl *) malloc(sizeof(LDAPControl)); - if (tree_delete_control == NULL) { - d->error_message(error_context, tr("LDAP Operation error - Failed to allocate tree delete control.")); - cleanup(); - - return false; - } - result = ldap_control_create(LDAP_CONTROL_X_TREE_DELETE, 1, NULL, 0, &tree_delete_control); if (result != LDAP_SUCCESS) { d->error_message(error_context, tr("LDAP Operation error - Failed to create tree delete control.")); @@ -879,7 +883,12 @@ bool AdInterface::object_delete(const QString &dn, const DoStatusMsg do_msg) { return false; } - LDAPControl *server_controls[2] = {tree_delete_control, NULL}; + LDAPControl *server_controls[2] = {NULL, NULL}; + + const bool tree_delete_is_supported = adconfig()->control_is_supported(LDAP_CONTROL_X_TREE_DELETE); + if (tree_delete_is_supported) { + server_controls[0] = tree_delete_control; + } result = ldap_delete_ext_s(d->ld, cstr(dn), server_controls, NULL); @@ -1000,12 +1009,12 @@ bool AdInterface::group_set_scope(const QString &dn, GroupScope scope, const DoS const GroupScope this_scope = (GroupScope) i; const int this_scope_bit = group_scope_bit(this_scope); - group_type = bit_set(group_type, this_scope_bit, false); + group_type = bitmask_set(group_type, this_scope_bit, false); } // Set given scope bit const int scope_bit = group_scope_bit(scope); - group_type = bit_set(group_type, scope_bit, true); + group_type = bitmask_set(group_type, scope_bit, true); const QString name = dn_get_name(dn); const QString scope_string = group_scope_string(scope); @@ -1030,7 +1039,7 @@ bool AdInterface::group_set_type(const QString &dn, GroupType type) { const bool set_security_bit = type == GroupType_Security; - const int update_group_type = bit_set(group_type, GROUP_TYPE_BIT_SECURITY, set_security_bit); + const int update_group_type = bitmask_set(group_type, GROUP_TYPE_BIT_SECURITY, set_security_bit); const QString update_group_type_string = QString::number(update_group_type); const QString name = dn_get_name(dn); @@ -1131,22 +1140,7 @@ bool AdInterface::user_set_account_option(const QString &dn, AccountOption optio switch (option) { case AccountOption_CantChangePassword: { - const AdObject object = search_object(dn, {ATTRIBUTE_SECURITY_DESCRIPTOR}); - const auto old_security_state = object.get_security_state(d->adconfig); - - const QByteArray self_trustee = sid_string_to_bytes(SID_NT_SELF); - - const PermissionState new_permission_state = [&]() { - if (set) { - return PermissionState_Denied; - } else { - return PermissionState_Allowed; - } - }(); - - const auto new_security_state = ad_security_modify(old_security_state, self_trustee, AcePermission_ChangePassword, new_permission_state); - - success = attribute_replace_security_descriptor(this, dn, new_security_state); + success = ad_security_set_user_cant_change_pass(this, dn, set); break; } @@ -1169,7 +1163,7 @@ bool AdInterface::user_set_account_option(const QString &dn, AccountOption optio }(); const int bit = account_option_bit(option); - const int updated_uac = bit_set(uac, bit, set); + const int updated_uac = bitmask_set(uac, bit, set); success = attribute_replace_int(dn, ATTRIBUTE_USER_ACCOUNT_CONTROL, updated_uac, DoStatusMsg_No); } @@ -1296,7 +1290,7 @@ bool AdInterface::gpo_add(const QString &display_name, QString &dn_out) { // Ex: "\\domain.alt\sysvol\domain.alt\Policies\{FF7E0880-F3AD-4540-8F1D-4472CB4A7044}" const QString filesys_path = QString("\\\\%1\\sysvol\\%2\\Policies\\%3").arg(d->domain.toLower(), d->domain.toLower(), uuid); const QString gpt_path = filesys_path_to_smb_path(filesys_path); - const QString gpc_dn = QString("CN=%1,CN=Policies,CN=System,%2").arg(uuid, d->domain_head); + const QString gpc_dn = QString("CN=%1,CN=Policies,CN=System,%2").arg(uuid, adconfig()->domain_dn()); // After each error case we need to clean up whatever we // have created successfully so far. Don't just use @@ -1534,7 +1528,7 @@ bool AdInterface::gpo_delete(const QString &dn, bool *deleted_object) { } // Unlink policy - const QString base = d->domain_head; + const QString base = adconfig()->domain_dn(); const SearchScope scope = SearchScope_All; const QList attributes = {ATTRIBUTE_GPLINK}; const QString filter = filter_CONDITION(Condition_Contains, ATTRIBUTE_GPLINK, dn); @@ -1676,7 +1670,33 @@ bool AdInterface::gpo_check_perms(const QString &gpo, bool *ok) { return false; } - const bool sd_match = (gpc_sd == gpt_sd); + // SD's match if they both contain all lines of the + // other one. Order doesn't matter. Note that + // simple equality doesn't work because entry order + // may not match. + // + // NOTE: there's also a weird thing where RSAT + // creates GPO's with duplicate ace's for Domain + // Admins. Not sure why that happens but this + // matching method ignores that quirk. + const bool sd_match = [&]() { + const QList gpt_list = QString(gpt_sd).split(","); + const QList gpc_list = QString(gpc_sd).split(","); + + for (const QString &line : gpt_list) { + if (!gpc_list.contains(line)) { + return false; + } + } + + for (const QString &line : gpc_list) { + if (!gpt_list.contains(line)) { + return false; + } + } + + return true; + }(); return sd_match; } @@ -1908,6 +1928,45 @@ bool AdInterfacePrivate::smb_path_is_dir(const QString &path, bool *ok) { } } +// NOTE: this f-n is analogous to +// ldap_create_page_control() and others. See pagectl.c +// in ldap sources for examples. Extracted to contain +// the C madness. +int create_sd_control(bool get_sacl, int iscritical, LDAPControl **ctrlp) { + BerElement *value_be = NULL; + struct berval value; + + // Create berval to load into control + // + // NOTE: SACL part of the sd can only be obtained + // by administrators. For most operations we don't + // need SACL. Some operations, like creating a GPO, + // do require SACL, so for those operations we turn + // on the "get_sacl" options. + const int value_int = [&]() { + if (get_sacl) { + return (OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | SACL_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION); + } else { + return (OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION); + } + }(); + value_be = ber_alloc_t(LBER_USE_DER); + ber_printf(value_be, "{i}", value_int); + ber_flatten2(value_be, &value, 1); + + // Create control + const int result = ldap_control_create( LDAP_SERVER_SD_FLAGS_OID, + iscritical, &value, 0, ctrlp); + + if (result != LDAP_SUCCESS) { + ber_memfree(value.bv_val); + } + + ber_free(value_be, 1); + + return result; +} + bool AdInterface::logged_in_as_admin() { const QString user_dn = [&]() { const QString sam_account_name = [&]() { @@ -1922,7 +1981,7 @@ bool AdInterface::logged_in_as_admin() { } const QString filter = filter_CONDITION(Condition_Equals, ATTRIBUTE_SAM_ACCOUNT_NAME, sam_account_name); - const QHash results = search(d->domain_head, SearchScope_All, filter, QList()); + const QHash results = search(adconfig()->domain_dn(), SearchScope_All, filter, QList()); if (results.isEmpty()) { return QString(); @@ -1938,7 +1997,7 @@ bool AdInterface::logged_in_as_admin() { } const bool user_is_admin = [&]() { - const QString domain_admins_dn = QString("CN=Domain Admins,CN=Users,%1").arg(d->domain_head); + const QString domain_admins_dn = QString("CN=Domain Admins,CN=Users,%1").arg(adconfig()->domain_dn()); const AdObject domain_admins_object = search_object(domain_admins_dn); const QList member_list = domain_admins_object.get_strings(ATTRIBUTE_MEMBER); @@ -1951,6 +2010,10 @@ bool AdInterface::logged_in_as_admin() { return user_is_admin; } +QString AdInterface::get_dc() const { + return d->dc; +} + QList get_domain_hosts(const QString &domain, const QString &site) { QList hosts; @@ -2140,7 +2203,7 @@ int sasl_interact_gssapi(LDAP *ld, unsigned flags, void *indefaults, void *in) { QString get_gpt_sd_string(const AdObject &gpc_object, const AceMaskFormat format_enum) { TALLOC_CTX *mem_ctx = talloc_new(NULL); - security_descriptor *gpc_sd = gpc_object.get_sd(mem_ctx); + security_descriptor *gpc_sd = gpc_object.get_security_descriptor(mem_ctx); struct security_descriptor *gpt_sd; const NTSTATUS create_sd_status = gp_create_gpt_security_descriptor(mem_ctx, gpc_sd, &gpt_sd); @@ -2152,7 +2215,7 @@ QString get_gpt_sd_string(const AdObject &gpc_object, const AceMaskFormat format return QString(); } - ad_security_sort_dacl(gpt_sd); + security_descriptor_sort_dacl(gpt_sd); QList all_elements; diff --git a/src/adldap/ad_interface.h b/src/adldap/ad_interface.h index 149eae5a..92148843 100644 --- a/src/adldap/ad_interface.h +++ b/src/adldap/ad_interface.h @@ -104,7 +104,6 @@ public: static void set_sasl_nocanon(const bool is_on); static void set_port(const int port); static void set_cert_strategy(const CertStrategy strategy); - static QString get_dc(); bool is_connected() const; QList messages() const; @@ -113,6 +112,7 @@ public: AdConfig *adconfig() const; QString client_user() const; bool logged_in_as_admin(); + QString get_dc() const; // NOTE: If request attributes list is empty, all // attributes are returned @@ -143,7 +143,15 @@ public: bool attribute_replace_int(const QString &dn, const QString &attribute, const int value, const DoStatusMsg do_msg = DoStatusMsg_Yes); bool attribute_replace_datetime(const QString &dn, const QString &attribute, const QDateTime &datetime); + // NOTE: attrs_map should contain attribute values + // that will be added to the newly created object. + // Note that it *must* contain a valid value for + // objectClass attribute. + bool object_add(const QString &dn, const QHash> &attrs_map); + // Simplified version that only only adds one + // objectClass value bool object_add(const QString &dn, const QString &object_class); + bool object_delete(const QString &dn, const DoStatusMsg do_msg = DoStatusMsg_Yes); bool object_move(const QString &dn, const QString &new_container); bool object_rename(const QString &dn, const QString &new_name); diff --git a/src/adldap/ad_interface_p.h b/src/adldap/ad_interface_p.h index 0e655a76..304c2df2 100644 --- a/src/adldap/ad_interface_p.h +++ b/src/adldap/ad_interface_p.h @@ -41,7 +41,6 @@ public: LDAP *ld; bool is_connected; QString domain; - QString domain_head; QString dc; QString client_user; QList messages; diff --git a/src/adldap/ad_object.cpp b/src/adldap/ad_object.cpp index 0de627b1..db4b0efb 100644 --- a/src/adldap/ad_object.cpp +++ b/src/adldap/ad_object.cpp @@ -162,7 +162,7 @@ bool AdObject::get_bool(const QString &attribute) const { bool AdObject::get_system_flag(const SystemFlagsBit bit) const { if (contains(ATTRIBUTE_SYSTEM_FLAGS)) { const int system_flags_bits = get_int(ATTRIBUTE_SYSTEM_FLAGS); - const bool is_set = bit_is_set(system_flags_bits, bit); + const bool is_set = bitmask_is_set(system_flags_bits, bit); return is_set; } else { @@ -173,16 +173,9 @@ bool AdObject::get_system_flag(const SystemFlagsBit bit) const { bool AdObject::get_account_option(AccountOption option, AdConfig *adconfig) const { switch (option) { case AccountOption_CantChangePassword: { - if (contains(ATTRIBUTE_SECURITY_DESCRIPTOR)) { - const auto security_state = get_security_state(adconfig); - const QByteArray self_trustee = sid_string_to_bytes(SID_NT_SELF); - const PermissionState permission_state = security_state[self_trustee][AcePermission_ChangePassword]; - const bool denied = (permission_state == PermissionState_Denied); + const bool out = ad_security_get_user_cant_change_pass(this, adconfig); - return denied; - } else { - return false; - } + return out; } case AccountOption_PasswordExpired: { if (contains(ATTRIBUTE_PWD_LAST_SET)) { @@ -230,7 +223,7 @@ GroupScope AdObject::get_group_scope() const { const GroupScope this_scope = (GroupScope) i; const int scope_bit = group_scope_bit(this_scope); - if (bit_is_set(group_type, scope_bit)) { + if (bitmask_is_set(group_type, scope_bit)) { return this_scope; } } @@ -267,25 +260,17 @@ QString AdObject::get_upn_suffix() const { return upn_split[1]; } -security_descriptor *AdObject::get_sd(TALLOC_CTX *mem_ctx) const { - const QByteArray descriptor_bytes = get_value(ATTRIBUTE_SECURITY_DESCRIPTOR); - DATA_BLOB blob = data_blob_const(descriptor_bytes.data(), descriptor_bytes.size()); +security_descriptor *AdObject::get_security_descriptor(TALLOC_CTX *mem_ctx_arg) const { + TALLOC_CTX *mem_ctx = [&]() -> void * { + if (mem_ctx_arg != nullptr) { + return mem_ctx_arg; + } else { + return NULL; + } + }(); - security_descriptor *sd = talloc(mem_ctx, struct security_descriptor); - - ndr_pull_struct_blob(&blob, mem_ctx, sd, (ndr_pull_flags_fn_t) ndr_pull_security_descriptor); - - return sd; -} - -QHash> AdObject::get_security_state(AdConfig *adconfig) const { - TALLOC_CTX *mem_ctx = talloc_new(NULL); - - security_descriptor *sd = get_sd(mem_ctx); - - const auto out = ad_security_get_state_from_sd(sd, adconfig); - - talloc_free(mem_ctx); + const QByteArray sd_bytes = get_value(ATTRIBUTE_SECURITY_DESCRIPTOR); + security_descriptor *out = security_descriptor_make_from_bytes(mem_ctx, sd_bytes); return out; } diff --git a/src/adldap/ad_object.h b/src/adldap/ad_object.h index fdedcece..60a0bb28 100644 --- a/src/adldap/ad_object.h +++ b/src/adldap/ad_object.h @@ -40,8 +40,8 @@ class QDateTime; class AdConfig; -struct security_descriptor; typedef void TALLOC_CTX; +struct security_descriptor; class AdObject { @@ -84,8 +84,8 @@ public: QString get_upn_prefix() const; QString get_upn_suffix() const; - security_descriptor *get_sd(TALLOC_CTX *mem_ctx) const; - QHash> get_security_state(AdConfig *adconfig) const; + // NOTE: if no mem_ctx is given, then returned value has to be free'd using security_descriptor_free() + security_descriptor *get_security_descriptor(TALLOC_CTX *mem_ctx = nullptr) const; private: QString dn; diff --git a/src/adldap/ad_security.cpp b/src/adldap/ad_security.cpp index 1d1c4d61..9b8dd45c 100644 --- a/src/adldap/ad_security.cpp +++ b/src/adldap/ad_security.cpp @@ -31,9 +31,32 @@ #include -QList ad_security_get_dacl(security_descriptor *sd); -QList ad_security_get_trustee_list_from_sd(security_descriptor *sd); -QList ad_security_get_trustee_list_from_sd(security_descriptor *sd); +#define UNUSED_ARG(x) (void) (x) + +QByteArray dom_sid_to_bytes(const dom_sid &sid); +dom_sid dom_sid_from_bytes(const QByteArray &bytes); +QByteArray dom_sid_string_to_bytes(const dom_sid &sid); +bool check_ace_match(const security_ace &ace, const QByteArray &trustee, const QByteArray &object_type, const bool allow, const bool inherited); +QList security_descriptor_get_dacl(const security_descriptor *sd); +void ad_security_replace_dacl(security_descriptor *sd, const QList &new_dacl); +uint32_t ad_security_map_access_mask(const uint32_t access_mask); +int ace_compare_simplified(const security_ace &ace1, const security_ace &ace2); + +// NOTE: these "base" f-ns are used by the full +// versions of add/remove right f-ns. Base f-ns do only +// the bare minimum, just adding/removing matching ace, +// without handling opposites, subordinates, superiors, +// etc. They also don't sort ACL, callers need to +// handle sorting themselves. +void security_descriptor_add_right_base(security_descriptor *sd, const QByteArray &trustee, const uint32_t access_mask, const QByteArray &object_type, const bool allow); +void security_descriptor_remove_right_base(security_descriptor *sd, const QByteArray &trustee, const uint32_t access_mask, const QByteArray &object_type, const bool allow); + +const QList ace_types_with_object = { + SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT, + SEC_ACE_TYPE_ACCESS_DENIED_OBJECT, + SEC_ACE_TYPE_SYSTEM_AUDIT_OBJECT, + SEC_ACE_TYPE_SYSTEM_ALARM_OBJECT, +}; const QList well_known_sid_list = { SID_WORLD_DOMAIN, @@ -71,7 +94,6 @@ const QList well_known_sid_list = { const QHash trustee_name_map = { {SID_WORLD_DOMAIN, "Everyone in Domain"}, {SID_WORLD, "Everyone"}, - {SID_WORLD, "Everyone"}, {SID_CREATOR_OWNER_DOMAIN, "CREATOR OWNER DOMAIN"}, {SID_CREATOR_OWNER, "CREATOR OWNER"}, {SID_CREATOR_GROUP, "CREATOR GROUP"}, @@ -101,153 +123,77 @@ const QHash trustee_name_map = { {SID_NT_OTHER_ORGANISATION, "Other Organization"}, }; -const QHash ace_permission_to_mask_map = { - {AcePermission_FullControl, SEC_ADS_GENERIC_ALL}, - {AcePermission_Read, (SEC_STD_READ_CONTROL | SEC_ADS_LIST | SEC_ADS_READ_PROP)}, - {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}, - {AcePermission_ChangePassword, SEC_ADS_CONTROL_ACCESS}, - {AcePermission_ReceiveAs, SEC_ADS_CONTROL_ACCESS}, - {AcePermission_ResetPassword, SEC_ADS_CONTROL_ACCESS}, - {AcePermission_SendAs, SEC_ADS_CONTROL_ACCESS}, - {AcePermission_ReadAccountRestrictions, SEC_ADS_READ_PROP}, - {AcePermission_WriteAccountRestrictions, SEC_ADS_WRITE_PROP}, - {AcePermission_ReadGeneralInfo, SEC_ADS_READ_PROP}, - {AcePermission_WriteGeneralInfo, SEC_ADS_WRITE_PROP}, - {AcePermission_ReadGroupMembership, SEC_ADS_READ_PROP}, - {AcePermission_ReadLogonInfo, SEC_ADS_READ_PROP}, - {AcePermission_WriteLogonInfo, SEC_ADS_WRITE_PROP}, - {AcePermission_ReadPersonalInfo, SEC_ADS_READ_PROP}, - {AcePermission_WritePersonalInfo, SEC_ADS_WRITE_PROP}, - {AcePermission_ReadPhoneAndMailOptions, SEC_ADS_READ_PROP}, - {AcePermission_WritePhoneAndMailOptions, SEC_ADS_WRITE_PROP}, - {AcePermission_ReadPrivateInfo, SEC_ADS_READ_PROP}, - {AcePermission_WritePrivateInfo, SEC_ADS_WRITE_PROP}, - {AcePermission_ReadPublicInfo, SEC_ADS_READ_PROP}, - {AcePermission_WritePublicInfo, SEC_ADS_WRITE_PROP}, - {AcePermission_ReadRemoteAccessInfo, SEC_ADS_READ_PROP}, - {AcePermission_WriteRemoteAccessInfo, SEC_ADS_WRITE_PROP}, - {AcePermission_ReadTerminalServerLicenseServer, SEC_ADS_READ_PROP}, - {AcePermission_WriteTerminalServerLicenseServer, SEC_ADS_WRITE_PROP}, - {AcePermission_ReadWebInfo, SEC_ADS_READ_PROP}, - {AcePermission_WriteWebInfo, SEC_ADS_WRITE_PROP}, +const QList cant_change_pass_trustee_cn_list = { + SID_NT_SELF, + SID_WORLD, }; -// NOTE: store right's cn value here, then search for it to -// get right's guid, which is compared to ace type. -const QHash ace_permission_to_type_map = { - {AcePermission_AllowedToAuthenticate, "Allowed-To-Authenticate"}, - {AcePermission_ChangePassword, "User-Change-Password"}, - {AcePermission_ReceiveAs, "Receive-As"}, - {AcePermission_ResetPassword, "User-Force-Change-Password"}, - {AcePermission_SendAs, "Send-As"}, - {AcePermission_ReadAccountRestrictions, "User-Account-Restrictions"}, - {AcePermission_WriteAccountRestrictions, "User-Account-Restrictions"}, - {AcePermission_ReadGeneralInfo, "General-Information"}, - {AcePermission_WriteGeneralInfo, "General-Information"}, - {AcePermission_ReadGroupMembership, "Membership"}, - {AcePermission_ReadLogonInfo, "User-Logon"}, - {AcePermission_WriteLogonInfo, "User-Logon"}, - {AcePermission_ReadPersonalInfo, "Personal-Information"}, - {AcePermission_WritePersonalInfo, "Personal-Information"}, - {AcePermission_ReadPhoneAndMailOptions, "Email-Information"}, - {AcePermission_WritePhoneAndMailOptions, "Email-Information"}, - {AcePermission_ReadPrivateInfo, "Private-Information"}, - {AcePermission_WritePrivateInfo, "Private-Information"}, - {AcePermission_ReadPublicInfo, "Public-Information"}, - {AcePermission_WritePublicInfo, "Public-Information"}, - {AcePermission_ReadRemoteAccessInfo, "RAS-Information"}, - {AcePermission_WriteRemoteAccessInfo, "RAS-Information"}, - {AcePermission_ReadTerminalServerLicenseServer, "Terminal-Server-License-Server"}, - {AcePermission_WriteTerminalServerLicenseServer, "Terminal-Server-License-Server"}, - {AcePermission_ReadWebInfo, "Web-Information"}, - {AcePermission_WriteWebInfo, "Web-Information"}}; +const QList protect_deletion_mask_list = { + SEC_STD_DELETE, + SEC_ADS_DELETE_TREE, +}; -const QList all_permissions_list = []() { - QList out; +const QSet ace_type_allow_set = { + SEC_ACE_TYPE_ACCESS_ALLOWED, + SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT, +}; +const QSet ace_type_deny_set = { + SEC_ACE_TYPE_ACCESS_DENIED, + SEC_ACE_TYPE_ACCESS_DENIED_OBJECT, +}; - for (int permission_i = 0; permission_i < AcePermission_COUNT; permission_i++) { - const AcePermission permission = (AcePermission) permission_i; - out.append(permission); - } +// NOTE: this is also used for display order +const QList common_rights_list = { + SEC_ADS_GENERIC_ALL, + SEC_ADS_GENERIC_READ, + SEC_ADS_GENERIC_WRITE, + SEC_ADS_CREATE_CHILD, + SEC_ADS_DELETE_CHILD, +}; - return out; -}(); +// NOTE: This is needed because for some reason, +// security editing in RSAT has a different value for +// generic read, without the "list object" right. Need +// to remove that bit both when setting generic read +// and when reading it. +#define GENERIC_READ_FIXED (SEC_ADS_GENERIC_READ & ~SEC_ADS_LIST_OBJECT) -const QSet all_permissions = all_permissions_list.toSet(); - -QSet get_permission_set(const uint32_t mask) { - QSet out; - - for (const AcePermission &permission : all_permissions) { - const uint32_t this_mask = ace_permission_to_mask_map[permission]; - - if (this_mask == mask) { - out.insert(permission); +SecurityRightState::SecurityRightState(const bool data_arg[SecurityRightStateInherited_COUNT][SecurityRightStateType_COUNT]) { + for (int inherited = 0; inherited < SecurityRightStateInherited_COUNT; inherited++) { + for (int type = 0; type < SecurityRightStateType_COUNT; type++) { + data[inherited][type] = data_arg[inherited][type]; } } +} + +bool SecurityRightState::get(const SecurityRightStateInherited inherited, const SecurityRightStateType type) const { + return data[inherited][type]; +} + +security_descriptor *security_descriptor_make_from_bytes(TALLOC_CTX *mem_ctx, const QByteArray &sd_bytes) { + DATA_BLOB blob = data_blob_const(sd_bytes.data(), sd_bytes.size()); + + security_descriptor *out = talloc(mem_ctx, struct security_descriptor); + + ndr_pull_struct_blob(&blob, out, out, (ndr_pull_flags_fn_t) ndr_pull_security_descriptor); return out; } -const QSet access_permissions = get_permission_set(SEC_ADS_CONTROL_ACCESS); -const QSet read_prop_permissions = get_permission_set(SEC_ADS_READ_PROP); -const QSet write_prop_permissions = get_permission_set(SEC_ADS_WRITE_PROP); +security_descriptor *security_descriptor_make_from_bytes(const QByteArray &sd_bytes) { + security_descriptor *out = security_descriptor_make_from_bytes(NULL, sd_bytes); -QHash> ad_security_modify(const QHash> ¤t, const QByteArray &trustee, const AcePermission permission, const PermissionState new_state) { - QHash> out; + return out; +} - out = current; +void security_descriptor_free(security_descriptor *sd) { + talloc_free(sd); +} - auto permission_state_set = [&](const QSet &permission_set, const PermissionState state) { - for (const AcePermission &this_permission : permission_set) { - out[trustee][this_permission] = state; - } - }; +security_descriptor *security_descriptor_copy(security_descriptor *sd) { + security_descriptor *out = talloc(NULL, struct security_descriptor); - // - // Apply state change to permission state map - // - out[trustee][permission] = new_state; - - // When children permissions change their parent - // permissions always become None (yes, for each case). - const QSet parent_permissions = [&]() { - QSet out_set; - - out_set.insert(AcePermission_FullControl); - - if (read_prop_permissions.contains(permission)) { - out_set.insert(AcePermission_Read); - } else if (write_prop_permissions.contains(permission)) { - out_set.insert(AcePermission_Write); - } - - return out_set; - }(); - permission_state_set(parent_permissions, PermissionState_None); - - // When parent permissions become Allowed or Denied, - // their children change to that state as well. - if (new_state != PermissionState_None) { - const QSet child_permissions = [&]() { - if (permission == AcePermission_FullControl) { - return all_permissions; - } else if (permission == AcePermission_Read) { - return read_prop_permissions; - } else if (permission == AcePermission_Write) { - return write_prop_permissions; - } else { - return QSet(); - } - }(); - - permission_state_set(child_permissions, new_state); - } + out = security_descriptor_copy(out, sd); return out; } @@ -269,7 +215,7 @@ QString ad_security_get_trustee_name(AdInterface &ad, const QByteArray &trustee) ATTRIBUTE_DISPLAY_NAME, ATTRIBUTE_SAM_ACCOUNT_NAME, }; - const auto trustee_search = ad.search(ad.adconfig()->domain_head(), SearchScope_All, filter, QList()); + const auto trustee_search = ad.search(ad.adconfig()->domain_dn(), SearchScope_All, filter, QList()); if (!trustee_search.isEmpty()) { // NOTE: this is some weird name selection logic // but that's how microsoft does it. Maybe need @@ -294,136 +240,12 @@ 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) { +bool ad_security_replace_security_descriptor(AdInterface &ad, const QString &dn, security_descriptor *new_sd) { const QByteArray new_descriptor_bytes = [&]() { - // Remove redundancy from permission state - const QHash> state = [&]() { - QHash> out; - - out = descriptor_state_arg; - - // Remove child permission states. For example if - // "Read" is allowed, then there's no need to - // include any other state for "read prop" - // permissions. - for (const QByteArray &trustee : out.keys()) { - const bool full_control = out[trustee].contains(AcePermission_FullControl) && (out[trustee][AcePermission_FullControl] != PermissionState_None); - const bool read = out[trustee].contains(AcePermission_Read) && (out[trustee][AcePermission_Read] != PermissionState_None); - const bool write = out[trustee].contains(AcePermission_Write) && (out[trustee][AcePermission_Write] != PermissionState_None); - - if (full_control) { - for (const AcePermission &permission : all_permissions) { - if (permission != AcePermission_FullControl) { - out[trustee].remove(permission); - } - } - } else if (read) { - for (const AcePermission &permission : read_prop_permissions) { - if (permission != AcePermission_Read) { - out[trustee].remove(permission); - } - } - } else if (write) { - for (const AcePermission &permission : write_prop_permissions) { - if (permission != AcePermission_Read) { - out[trustee].remove(permission); - } - } - } - } - - return out; - }(); - TALLOC_CTX *tmp_ctx = talloc_new(NULL); - // Use original sd as base, only remaking the dacl - const AdObject object = ad->search_object(dn, {ATTRIBUTE_SECURITY_DESCRIPTOR}); - security_descriptor *sd = object.get_sd(tmp_ctx); - - // Generate new dacl - const QList dacl_qlist = [&]() { - QList out; - - for (const QByteArray &trustee : state.keys()) { - const QHash permission_map = state[trustee]; - - for (const AcePermission &permission : permission_map.keys()) { - const PermissionState permission_state = permission_map[permission]; - - if (permission_state == PermissionState_None) { - continue; - } - - struct security_ace *ace = talloc(tmp_ctx, struct security_ace); - - const bool object_present = ace_permission_to_type_map.contains(permission); - - ace->type = [&]() { - if (permission_state == PermissionState_Allowed) { - if (object_present) { - return SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT; - } else { - return SEC_ACE_TYPE_ACCESS_ALLOWED; - } - } else if (permission_state == PermissionState_Denied) { - if (object_present) { - return SEC_ACE_TYPE_ACCESS_DENIED_OBJECT; - } else { - return SEC_ACE_TYPE_ACCESS_DENIED; - } - } - - return SEC_ACE_TYPE_ACCESS_ALLOWED; - }(); - - // NOTE: in the future, probably when - // adding advanced security dialog, - // these flags will need to be set to - // something in some cases, but for now - // just 0 - ace->flags = 0x00; - ace->access_mask = ace_permission_to_mask_map[permission]; - ace->object.object.flags = [&]() { - if (object_present) { - return SEC_ACE_OBJECT_TYPE_PRESENT; - } else { - return 0; - } - }(); - ace->object.object.type.type = [&]() { - if (object_present) { - const QString type_name_string = ace_permission_to_type_map[permission]; - const QString type_string = ad->adconfig()->get_right_guid(type_name_string); - const QByteArray type_bytes = guid_string_to_bytes(type_string); - - struct GUID guid; - memcpy(&guid, type_bytes.data(), sizeof(GUID)); - - return guid; - } else { - return GUID(); - } - }(); - memcpy(&ace->trustee, trustee.data(), sizeof(dom_sid)); - - out.append(ace); - } - } - - return out; - }(); - - // Replace dacl - talloc_free(sd->dacl); - sd->dacl = NULL; - for (security_ace *ace : dacl_qlist) { - security_descriptor_dacl_add(sd, ace); - } - ad_security_sort_dacl(sd); - DATA_BLOB blob; - ndr_push_struct_blob(&blob, tmp_ctx, sd, (ndr_push_flags_fn_t) ndr_push_security_descriptor); + ndr_push_struct_blob(&blob, tmp_ctx, new_sd, (ndr_push_flags_fn_t) ndr_push_security_descriptor); const QByteArray out = QByteArray((char *) blob.data, blob.length); @@ -432,29 +254,27 @@ bool attribute_replace_security_descriptor(AdInterface *ad, const QString &dn, c return out; }(); - const bool apply_success = ad->attribute_replace_value(dn, ATTRIBUTE_SECURITY_DESCRIPTOR, new_descriptor_bytes); + const bool apply_success = ad.attribute_replace_value(dn, ATTRIBUTE_SECURITY_DESCRIPTOR, new_descriptor_bytes); return apply_success; } -QList ad_security_get_trustee_list_from_object(const AdObject &object) { - TALLOC_CTX *mem_ctx = talloc_new(NULL); - - security_descriptor *sd = object.get_sd(mem_ctx); - - const QList out = ad_security_get_trustee_list_from_sd(sd); - - talloc_free(mem_ctx); - - return out; -} - QByteArray dom_sid_to_bytes(const dom_sid &sid) { const QByteArray bytes = QByteArray((char *) &sid, sizeof(struct dom_sid)); return bytes; } +// Copy sid bytes into dom_sid struct and adds padding +// if necessary +dom_sid dom_sid_from_bytes(const QByteArray &bytes) { + dom_sid out; + memset(&out, '\0', sizeof(dom_sid)); + memcpy(&out, bytes.data(), sizeof(dom_sid)); + + return out; +} + QByteArray dom_sid_string_to_bytes(const QString &string) { dom_sid sid; dom_sid_parse(cstr(string), &sid); @@ -463,46 +283,473 @@ QByteArray dom_sid_string_to_bytes(const QString &string) { return bytes; } -void ad_security_sort_dacl(security_descriptor *sd) { +void security_descriptor_sort_dacl(security_descriptor *sd) { qsort(sd->dacl->aces, sd->dacl->num_aces, sizeof(security_ace), ace_compare); } -QList ad_security_get_dacl(security_descriptor *sd) { - QList out; +bool ad_security_get_protected_against_deletion(const AdObject &object) { + security_descriptor *sd = object.get_security_descriptor(); + + const QByteArray trustee_everyone = sid_string_to_bytes(SID_WORLD); + + const bool is_enabled_for_trustee = [&]() { + for (const uint32_t &mask : protect_deletion_mask_list) { + const SecurityRightState state = security_descriptor_get_right(sd, trustee_everyone, mask, QByteArray()); + + const bool deny = state.get(SecurityRightStateInherited_No, SecurityRightStateType_Deny); + + if (!deny) { + return false; + } + } + + return true; + }(); + + security_descriptor_free(sd); + + return is_enabled_for_trustee; +} + +bool ad_security_get_user_cant_change_pass(const AdObject *object, AdConfig *adconfig) { + security_descriptor *sd = object->get_security_descriptor(); + + const bool enabled = [&]() { + bool out = false; + + for (const QString &trustee_cn : cant_change_pass_trustee_cn_list) { + const bool is_denied = [&]() { + const QByteArray trustee = sid_string_to_bytes(trustee_cn); + const QByteArray change_pass_right = adconfig->get_right_guid("User-Change-Password"); + const SecurityRightState state = security_descriptor_get_right(sd, trustee, SEC_ADS_CONTROL_ACCESS, change_pass_right); + const bool out_denied = state.get(SecurityRightStateInherited_No, SecurityRightStateType_Deny); + + return out_denied; + }(); + + // Enabled if enabled for either of the + // trustee's. Both don't have to be + // enabled + if (is_denied) { + out = true; + + break; + } + } + + return out; + }(); + + security_descriptor_free(sd); + + return enabled; +} + +bool ad_security_set_user_cant_change_pass(AdInterface *ad, const QString &dn, const bool enabled) { + security_descriptor *sd = [&]() { + const AdObject object = ad->search_object(dn, {ATTRIBUTE_SECURITY_DESCRIPTOR}); + security_descriptor *out = object.get_security_descriptor(); + + return out; + }(); + + for (const QString &trustee_cn : cant_change_pass_trustee_cn_list) { + const QByteArray trustee = sid_string_to_bytes(trustee_cn); + const QByteArray change_pass_right = ad->adconfig()->get_right_guid("User-Change-Password"); + + // NOTE: the logic is a bit confusing here with + // all the layers of negation but: "enabled" + // means "denied", so we remove the opposite of + // what we want, and add the type of right that + // we want + // NOTE: using "base" f-ns because we don't want + // to touch superiors/subordinates + const bool allow = !enabled; + security_descriptor_remove_right_base(sd, trustee, SEC_ADS_CONTROL_ACCESS, change_pass_right, !allow); + security_descriptor_add_right_base(sd, trustee, SEC_ADS_CONTROL_ACCESS, change_pass_right, allow); + } + + security_descriptor_sort_dacl(sd); + + const bool success = ad_security_replace_security_descriptor(*ad, dn, sd); + + security_descriptor_free(sd); + + return success; +} + +bool ad_security_set_protected_against_deletion(AdInterface &ad, const QString dn, const bool enabled) { + const AdObject object = ad.search_object(dn); + + const bool is_enabled = ad_security_get_protected_against_deletion(object); + + const bool dont_need_to_change = (is_enabled == enabled); + if (dont_need_to_change) { + return true; + } + + security_descriptor *new_sd = [&]() { + security_descriptor *out = object.get_security_descriptor(); + + const QByteArray trustee_everyone = sid_string_to_bytes(SID_WORLD); + + // NOTE: we only add/remove deny entries. If + // there are any allow entries, they are + // untouched. + for (const uint32_t &mask : protect_deletion_mask_list) { + if (enabled) { + security_descriptor_add_right_base(out, trustee_everyone, mask, QByteArray(), false); + } else { + security_descriptor_remove_right_base(out, trustee_everyone, mask, QByteArray(), false); + } + } + + return out; + }(); + + security_descriptor_sort_dacl(new_sd); + + const bool apply_success = ad_security_replace_security_descriptor(ad, dn, new_sd); + + security_descriptor_free(new_sd); + + return apply_success; +} + +QList security_descriptor_get_trustee_list(security_descriptor *sd) { + const QSet trustee_set = [&]() { + QSet out; + + const QList dacl = security_descriptor_get_dacl(sd); + + for (const security_ace &ace : dacl) { + const QByteArray trustee = dom_sid_to_bytes(ace.trustee); + + out.insert(trustee); + } + + return out; + }(); + + const QList trustee_list = trustee_set.toList(); + + return trustee_list; +} + +QList security_descriptor_get_dacl(const security_descriptor *sd) { + QList out; + + security_acl *dacl = sd->dacl; + + for (size_t i = 0; i < dacl->num_aces; i++) { + security_ace ace = dacl->aces[i]; - for (uint32_t i = 0; i < sd->dacl->num_aces; i++) { - security_ace *ace = &sd->dacl->aces[i]; out.append(ace); } return out; } -QList ad_security_get_trustee_list_from_sd(security_descriptor *sd) { - QSet out; +SecurityRightState security_descriptor_get_right(const security_descriptor *sd, const QByteArray &trustee, const uint32_t access_mask_arg, const QByteArray &object_type) { + bool out_data[SecurityRightStateInherited_COUNT][SecurityRightStateType_COUNT]; - const QList dacl = ad_security_get_dacl(sd); - for (security_ace *ace : dacl) { - const QByteArray trustee_bytes = dom_sid_to_bytes(ace->trustee); + const uint32_t access_mask = ad_security_map_access_mask(access_mask_arg); - out.insert(trustee_bytes); + for (int x = 0; x < SecurityRightStateInherited_COUNT; x++) { + for (int y = 0; y < SecurityRightStateType_COUNT; y++) { + out_data[x][y] = false; + } } - return out.toList(); + const QList dacl = security_descriptor_get_dacl(sd); + + for (const security_ace &ace : dacl) { + const bool match = [&]() { + const bool trustee_match = [&]() { + const dom_sid trustee_sid = dom_sid_from_bytes(trustee); + const bool trustees_are_equal = (dom_sid_compare(&ace.trustee, &trustee_sid) == 0); + + return trustees_are_equal; + }(); + + const bool access_mask_match = bitmask_is_set(ace.access_mask, access_mask); + + + const bool object_match = [&]() { + const bool object_present = ace_types_with_object.contains(ace.type); + + if (object_present) { + const GUID out_guid = ace.object.object.type.type; + const QByteArray ace_object_type = QByteArray((char *) &out_guid, sizeof(GUID)); + const bool types_are_equal = (ace_object_type == object_type); + + return types_are_equal; + } else { + // NOTE: if compared ace doesn't + // have an object it can still + // match if it's access mask + // matches with given ace. Example: + // ace that allows "generic read" + // (mask contains bit for "read + // property" and object is empty) + // will also allow right for + // reading personal info (mask *is* + // "read property" and contains + // some object) + return access_mask_match; + } + }(); + + const bool out = (trustee_match && access_mask_match && object_match); + + return out; + }(); + + if (match) { + bool state_list[2]; + state_list[SecurityRightStateType_Allow] = ace_type_allow_set.contains(ace.type); + state_list[SecurityRightStateType_Deny] = ace_type_deny_set.contains(ace.type); + + const int inherit_i = [&]() { + const bool ace_is_inherited = bitmask_is_set(ace.flags, SEC_ACE_FLAG_INHERITED_ACE); + + if (ace_is_inherited) { + return SecurityRightStateInherited_Yes; + } else { + return SecurityRightStateInherited_No; + } + }(); + + for (int type_i = 0; type_i < SecurityRightStateType_COUNT; type_i++) { + const bool right_state = state_list[type_i]; + + if (right_state) { + out_data[inherit_i][type_i] = true; + } + } + } + } + + const SecurityRightState out = SecurityRightState(out_data); + + return out; } -void ad_security_print_acl(security_descriptor *sd, const QByteArray &trustee) { - TALLOC_CTX *tmp_ctx = talloc_new(NULL); +void security_descriptor_print(security_descriptor *sd, AdInterface &ad) { + const QList dacl = security_descriptor_get_dacl(sd); - const QList ace_list = [&]() { - QList out; + for (const security_ace &ace : dacl) { + qInfo() << "\nace:"; + const QByteArray trustee_sid = dom_sid_to_bytes(ace.trustee); + const QString trustee_name = ad_security_get_trustee_name(ad, trustee_sid); + qInfo() << "trustee:" << trustee_name; + qInfo() << "mask:" << int_to_hex_string(ace.access_mask); + qInfo() << "type:" << ace.type; + } +} - const QList dacl = ad_security_get_dacl(sd); - for (security_ace *ace : dacl) { - const QByteArray this_trustee_bytes = dom_sid_to_bytes(ace->trustee); +void security_descriptor_add_right_base(security_descriptor *sd, const QByteArray &trustee, const uint32_t access_mask_arg, const QByteArray &object_type, const bool allow) { + const uint32_t access_mask = ad_security_map_access_mask(access_mask_arg); - const bool trustee_match = (this_trustee_bytes == trustee); - if (trustee_match) { + const QList dacl = security_descriptor_get_dacl(sd); + + const int matching_index = [&]() { + for (int i = 0; i < dacl.size(); i++) { + const security_ace ace = dacl[i]; + + // NOTE: access mask match doesn't matter + // because we also want to add right to + // existing ace, if it exists. In that case + // such ace would not match by mask and + // that's fine. + const bool match = check_ace_match(ace, trustee, object_type, allow, false); + + if (match) { + return i; + } + } + + return -1; + }(); + + if (matching_index != -1) { + const bool right_already_set = [&]() { + const security_ace matching_ace = dacl[matching_index]; + const bool out = bitmask_is_set(matching_ace.access_mask, access_mask); + + return out; + }(); + + // Matching ace exists, so reuse it by adding + // given mask to this ace, but only if it's not set already + if (!right_already_set) { + security_ace new_ace = dacl[matching_index]; + new_ace.access_mask = bitmask_set(new_ace.access_mask, access_mask, true); + sd->dacl->aces[matching_index] = new_ace; + } + } else { + // No matching ace, so make a new ace for this + // right + const security_ace ace = [&]() { + security_ace out; + + const bool object_present = !object_type.isEmpty(); + + out.type = [&]() { + if (allow) { + if (object_present) { + return SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT; + } else { + return SEC_ACE_TYPE_ACCESS_ALLOWED; + } + } else { + if (object_present) { + return SEC_ACE_TYPE_ACCESS_DENIED_OBJECT; + } else { + return SEC_ACE_TYPE_ACCESS_DENIED; + } + } + + return SEC_ACE_TYPE_ACCESS_ALLOWED; + }(); + + out.flags = 0x00; + out.access_mask = access_mask; + out.object.object.flags = [&]() { + if (object_present) { + return SEC_ACE_OBJECT_TYPE_PRESENT; + } else { + return 0; + } + }(); + + if (object_present) { + out.object.object.type.type = [&]() { + struct GUID type_guid; + memcpy(&type_guid, object_type.data(), sizeof(GUID)); + + return type_guid; + }(); + } + + out.trustee = dom_sid_from_bytes(trustee); + + return out; + }(); + + security_descriptor_dacl_add(sd, &ace); + } +} + +// Checks if ace matches given members. Note that +// access masks are not compared. Compare them yourself +// if you need to further filter by masks. +bool check_ace_match(const security_ace &ace, const QByteArray &trustee, const QByteArray &object_type, const bool allow, const bool inherited) { + const bool type_match = [&]() { + const security_ace_type ace_type = ace.type; + + const bool ace_allow = ace_type_allow_set.contains(ace_type); + const bool ace_deny = ace_type_deny_set.contains(ace_type); + + if (allow && ace_allow) { + return true; + } else if (!allow && ace_deny) { + return true; + } else { + return false; + } + }(); + + const bool flags_match = [&]() { + const bool ace_is_inherited = bitmask_is_set(ace.flags, SEC_ACE_FLAG_INHERITED_ACE); + const bool out = (ace_is_inherited == inherited); + + return out; + }(); + + const bool trustee_match = [&]() { + const dom_sid trustee_sid = dom_sid_from_bytes(trustee); + const bool trustees_are_equal = (dom_sid_compare( + &ace.trustee, &trustee_sid) == 0); + + return trustees_are_equal; + }(); + + const bool object_match = [&]() { + const bool object_present = ace_types_with_object.contains(ace.type); + + if (object_present) { + const GUID ace_object_type_guid = ace.object.object.type.type; + const QByteArray ace_object_type = QByteArray((char *) &ace_object_type_guid, sizeof(GUID)); + const bool types_are_equal = (ace_object_type == object_type); + + return types_are_equal; + } else { + return object_type.isEmpty(); + } + }(); + + const bool out_match = (type_match && flags_match && trustee_match && object_match); + + return out_match; +} + +void security_descriptor_remove_right_base(security_descriptor *sd, const QByteArray &trustee, const uint32_t access_mask_arg, const QByteArray &object_type, const bool allow) { + const uint32_t access_mask = ad_security_map_access_mask(access_mask_arg); + + const QList new_dacl = [&]() { + QList out; + + const QList old_dacl = security_descriptor_get_dacl(sd); + + for (const security_ace &ace : old_dacl) { + const bool match = check_ace_match(ace, trustee, object_type, allow, false); + const bool ace_mask_contains_mask = bitmask_is_set(ace.access_mask, access_mask); + + if (match && ace_mask_contains_mask) { + const security_ace edited_ace = [&]() { + security_ace out_ace = ace; + + // NOTE: need to handle a special + // case due to read and write + // rights sharing the "read + // control" bit. When setting + // either read/write, don't change + // that shared bit if the other of + // these rights is set + const uint32_t mask_to_unset = [&]() { + const QHash opposite_map = { + {GENERIC_READ_FIXED, SEC_ADS_GENERIC_WRITE}, + {SEC_ADS_GENERIC_WRITE, GENERIC_READ_FIXED}, + }; + + if (opposite_map.contains(access_mask)) { + const uint32_t opposite = opposite_map[access_mask]; + const bool opposite_is_set = bitmask_is_set(ace.access_mask, opposite); + + if (opposite_is_set) { + const uint32_t out_mask = (access_mask & ~SEC_STD_READ_CONTROL); + + return out_mask; + } else { + return access_mask; + } + } else { + return access_mask; + } + }(); + + out_ace.access_mask = bitmask_set(ace.access_mask, mask_to_unset, false); + + return out_ace; + }(); + + const bool edited_ace_became_empty = (edited_ace.access_mask == 0); + + if (!edited_ace_became_empty) { + out.append(edited_ace); + } + } else { out.append(ace); } } @@ -510,135 +757,382 @@ void ad_security_print_acl(security_descriptor *sd, const QByteArray &trustee) { return out; }(); - for (security_ace *ace : ace_list) { - const QString ace_string = [&]() { - char *ace_cstr = ndr_print_struct_string(tmp_ctx, (ndr_print_fn_t) ndr_print_security_ace, - "ace", ace); - - const QString out = QString(ace_cstr); - - return out; - }(); - - qDebug().noquote() << ace_string; - } - - talloc_free(tmp_ctx); + ad_security_replace_dacl(sd, new_dacl); } -QHash> ad_security_get_state_from_sd(security_descriptor *sd, AdConfig *adconfig) { - QHash> out; +void security_descriptor_remove_trustee(security_descriptor *sd, const QList &trustee_list) { + const QList new_dacl = [&]() { + QList out; - // Initialize to None by default - const QList trustee_list = ad_security_get_trustee_list_from_sd(sd); - for (const QByteArray &trustee : trustee_list) { - for (const AcePermission &permission : all_permissions) { - out[trustee][permission] = PermissionState_None; - } - } + const QList old_dacl = security_descriptor_get_dacl(sd); - // Then go through acl and set allowed/denied permission - // states - const QList dacl = ad_security_get_dacl(sd); - for (security_ace *ace : dacl) { - const QByteArray trustee = dom_sid_to_bytes(ace->trustee); + for (const security_ace &ace : old_dacl) { + const bool match = [&]() { + const bool trustee_match = [&]() { + for (const QByteArray &trustee : trustee_list) { + const dom_sid trustee_sid = dom_sid_from_bytes(trustee); + const bool trustees_are_equal = (dom_sid_compare(&ace.trustee, &trustee_sid) == 0); - for (const AcePermission &permission : all_permissions) { - const uint32_t permission_mask = ace_permission_to_mask_map[permission]; + if (trustees_are_equal) { + return true; + } + } - const bool mask_match = ((ace->access_mask & permission_mask) == permission_mask); - if (!mask_match) { - continue; - } - - const bool object_match = [&]() { - const bool object_present = ((ace->object.object.flags & SEC_ACE_OBJECT_TYPE_PRESENT) != 0); - if (!object_present) { return false; - } - - const QString rights_guid = [&]() { - const QString right_cn = ace_permission_to_type_map[permission]; - const QString guid_out = adconfig->get_right_guid(right_cn); - - return guid_out; }(); - const QString ace_type_guid = [&]() { - const GUID type = ace->object.object.type.type; - const QByteArray type_bytes = QByteArray((char *) &type, sizeof(GUID)); + const bool inherited = bitmask_is_set(ace.flags, SEC_ACE_FLAG_INHERITED_ACE); - return attribute_display_value(ATTRIBUTE_OBJECT_GUID, type_bytes, adconfig); - }(); + const bool out_match = trustee_match && !inherited; - return (rights_guid.toLower() == ace_type_guid.toLower()); + return out_match; }(); - switch (ace->type) { - case SEC_ACE_TYPE_ACCESS_ALLOWED: { - out[trustee][permission] = PermissionState_Allowed; - break; - } - case SEC_ACE_TYPE_ACCESS_DENIED: { - out[trustee][permission] = PermissionState_Denied; - break; - } - case SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT: { - if (object_match) { - out[trustee][permission] = PermissionState_Allowed; - } - break; - } - case SEC_ACE_TYPE_ACCESS_DENIED_OBJECT: { - if (object_match) { - out[trustee][permission] = PermissionState_Denied; - } - break; - } - default: break; + if (!match) { + out.append(ace); } } - } - - 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; + ad_security_replace_dacl(sd, new_dacl); +} + +// TODO: Need to verify SACL order as well, because +// advanced security dialog(to be implemented) edits +// SACL. +bool security_descriptor_verify_acl_order(security_descriptor *sd) { + security_descriptor *copy = security_descriptor_copy(sd); + + const bool order_is_correct = [&]() { + bool out = true; + + QList dacl = security_descriptor_get_dacl(copy); + + security_ace curr = dacl.takeFirst(); + + while (!dacl.isEmpty()) { + security_ace next = dacl.takeFirst(); + const int comparison = ace_compare_simplified(curr, next); + const bool order_is_good = (comparison <= 0); + + if (!order_is_good) { + out = false; + } + + curr = next; + } + + return out; + }(); + + security_descriptor_free(copy); + + return order_is_correct; +} + +QString ad_security_get_right_name(AdConfig *adconfig, const uint32_t access_mask, const QByteArray &object_type, const QLocale::Language language) { + const QString object_type_name = adconfig->get_right_name(object_type, language); + + if (access_mask == SEC_ADS_CONTROL_ACCESS) { + return object_type_name; + } else if (access_mask == SEC_ADS_READ_PROP) { + return QString(QCoreApplication::translate("ad_security.cpp", "Read %1")).arg(object_type_name); + } else if (access_mask == SEC_ADS_WRITE_PROP) { + return QString(QCoreApplication::translate("ad_security.cpp", "Write %1")).arg(object_type_name); + } else { + const QHash common_right_name_map = { + {SEC_ADS_GENERIC_ALL, QCoreApplication::translate("ad_security.cpp", "Full control")}, + {SEC_ADS_GENERIC_READ, QCoreApplication::translate("ad_security.cpp", "Read")}, + {SEC_ADS_GENERIC_WRITE, QCoreApplication::translate("ad_security.cpp", "Write")}, + {SEC_STD_DELETE, QCoreApplication::translate("ad_security.cpp", "Delete")}, + {SEC_ADS_CREATE_CHILD, QCoreApplication::translate("ad_security.cpp", "Create all child objects")}, + {SEC_ADS_DELETE_CHILD, QCoreApplication::translate("ad_security.cpp", "Delete all child objects")}, + }; + + return common_right_name_map.value(access_mask, QCoreApplication::translate("ad_security.cpp", "")); + } +} + +void security_descriptor_add_right(security_descriptor *sd, AdConfig *adconfig, const QList &class_list, const QByteArray &trustee, const uint32_t access_mask, const QByteArray &object_type, const bool allow) { + const QList superior_list = ad_security_get_superior_right_list(access_mask, object_type); + for (const SecurityRight &superior : superior_list) { + const bool opposite_superior_is_set = [&]() { + const SecurityRightState state = security_descriptor_get_right(sd, trustee, superior.access_mask, superior.object_type); + const SecurityRightStateType type = [&]() { + // NOTE: opposite! + if (!allow) { + return SecurityRightStateType_Allow; + } else { + return SecurityRightStateType_Deny; + } + }(); + const bool out = state.get(SecurityRightStateInherited_No, type); + + return out; + }(); + + // NOTE: skip superior if it's not set, so that + // we don't add opposite subordinate rights + // when not needed + if (!opposite_superior_is_set) { + continue; + } + + // Remove opposite superior + security_descriptor_remove_right_base(sd, trustee, superior.access_mask, superior.object_type, !allow); + + // Add opposite superior subordinates + const QList superior_subordinate_list = ad_security_get_subordinate_right_list(adconfig, superior.access_mask, superior.object_type, class_list); + for (const SecurityRight &subordinate : superior_subordinate_list) { + + security_descriptor_add_right_base(sd, trustee, subordinate.access_mask, subordinate.object_type, !allow); + } + } + + // Remove subordinates + const QList subordinate_list = ad_security_get_subordinate_right_list(adconfig, access_mask, object_type, class_list); + for (const SecurityRight &subordinate : subordinate_list) { + security_descriptor_remove_right_base(sd, trustee, subordinate.access_mask, subordinate.object_type, allow); + } + + // Remove opposite + security_descriptor_remove_right_base(sd, trustee, access_mask, object_type, !allow); + + // Remove opposite subordinates + for (const SecurityRight &subordinate : subordinate_list) { + security_descriptor_remove_right_base(sd, trustee, subordinate.access_mask, subordinate.object_type, !allow); + } + + // Add target + security_descriptor_add_right_base(sd, trustee, access_mask, object_type, allow); + + security_descriptor_sort_dacl(sd); +} + +void security_descriptor_remove_right(security_descriptor *sd, AdConfig *adconfig, const QList &class_list, const QByteArray &trustee, const uint32_t access_mask, const QByteArray &object_type, const bool allow) { + const QList target_superior_list = ad_security_get_superior_right_list(access_mask, object_type); + + // Remove superiors + for (const SecurityRight &superior : target_superior_list) { + const bool superior_is_set = [&]() { + const SecurityRightState state = security_descriptor_get_right(sd, trustee, superior.access_mask, superior.object_type); + const SecurityRightStateType type = [&]() { + if (allow) { + return SecurityRightStateType_Allow; + } else { + return SecurityRightStateType_Deny; + } + }(); + const bool out = state.get(SecurityRightStateInherited_No, type); + + return out; + }(); + + // NOTE: skip superior if it's not set, so that we don't add opposite subordinate rights when not needed + if (!superior_is_set) { + continue; + } + + security_descriptor_remove_right_base(sd, trustee, superior.access_mask, superior.object_type, allow); + + const QList superior_subordinate_list = ad_security_get_subordinate_right_list(adconfig, superior.access_mask, superior.object_type, class_list); + + // Add opposite subordinate rights + for (const SecurityRight &subordinate : superior_subordinate_list) { + security_descriptor_add_right_base(sd, trustee, subordinate.access_mask, subordinate.object_type, allow); + } + } + + // Remove target right + security_descriptor_remove_right_base(sd, trustee, access_mask, object_type, allow); + + // Add target subordinate rights + const QList tarad_security_get_subordinate_right_list = ad_security_get_subordinate_right_list(adconfig, access_mask, object_type, class_list); + for (const SecurityRight &subordinate : tarad_security_get_subordinate_right_list) { + security_descriptor_add_right_base(sd, trustee, subordinate.access_mask, subordinate.object_type, allow); + } + + security_descriptor_sort_dacl(sd); +} + +QList ad_security_get_right_list_for_class(AdConfig *adconfig, const QList &class_list) { + QList out; + + for (const uint32_t &access_mask : common_rights_list) { + SecurityRight right; + right.access_mask = access_mask; + right.object_type = QByteArray(); + + out.append(right); + } + + const QList extended_rights_list = adconfig->get_extended_rights_list(class_list); + for (const QString &rights : extended_rights_list) { + const int valid_accesses = adconfig->get_rights_valid_accesses(rights); + const QByteArray rights_guid = adconfig->get_right_guid(rights); + const QList access_mask_list = { + SEC_ADS_CONTROL_ACCESS, + SEC_ADS_READ_PROP, + SEC_ADS_WRITE_PROP, + }; + + for (const uint32_t &access_mask : access_mask_list) { + const bool mask_match = bitmask_is_set(valid_accesses, access_mask); + + if (mask_match) { + SecurityRight right; + right.access_mask = access_mask; + right.object_type = rights_guid; + + out.append(right); + } + } + } + + return out; +} + +QList ad_security_get_superior_right_list(const uint32_t access_mask, const QByteArray &object_type) { + QList out; + + const bool object_present = !object_type.isEmpty(); + + const SecurityRight generic_all = {SEC_ADS_GENERIC_ALL, QByteArray()}; + const SecurityRight generic_read = {SEC_ADS_GENERIC_READ, QByteArray()}; + const SecurityRight generic_write = {SEC_ADS_GENERIC_WRITE, QByteArray()}; + const SecurityRight all_extended_rights = {SEC_ADS_CONTROL_ACCESS, QByteArray()}; + + // NOTE: order is important, because we want to + // process "more superior" rights first. "Generic + // all" is more superior than others. + if (object_present) { + if (access_mask == SEC_ADS_READ_PROP) { + out.append(generic_all); + out.append(generic_read); + } else if (access_mask == SEC_ADS_WRITE_PROP) { + out.append(generic_all); + out.append(generic_write); + } else if (access_mask == SEC_ADS_CONTROL_ACCESS) { + out.append(generic_all); + out.append(all_extended_rights); + } + } else { + if (access_mask == SEC_ADS_GENERIC_READ || access_mask == SEC_ADS_GENERIC_WRITE) { + out.append(generic_all); + } + } + + return out; +} + +QList ad_security_get_subordinate_right_list(AdConfig *adconfig, const uint32_t access_mask, const QByteArray &object_type, const QList &class_list) { + QList out; + + const bool object_present = !object_type.isEmpty(); + + const QList right_list_for_target = ad_security_get_right_list_for_class(adconfig, class_list); + + for (const SecurityRight &right : right_list_for_target) { + const bool match = [&]() { + const bool right_object_present = !right.object_type.isEmpty(); + + if (object_present) { + return false; + } else { + if (access_mask == SEC_ADS_GENERIC_ALL) { + // All except full control + return (right.access_mask != access_mask); + } else if (access_mask == SEC_ADS_GENERIC_READ) { + // All read property rights + return (right.access_mask == SEC_ADS_READ_PROP && right_object_present); + } else if (access_mask == SEC_ADS_GENERIC_WRITE) { + // All write property rights + return (right.access_mask == SEC_ADS_WRITE_PROP && right_object_present); + } else if (access_mask == SEC_ADS_CONTROL_ACCESS) { + // All extended rights + return (right.access_mask == SEC_ADS_CONTROL_ACCESS && right_object_present); + } else { + return false; + } + } + }(); + + if (match) { + out.append(right); + } + } + + return out; +} + +void ad_security_replace_dacl(security_descriptor *sd, const QList &new_dacl) { + + // Free old dacl + talloc_free(sd->dacl); + sd->dacl = NULL; + + // Fill new dacl + // NOTE: dacl_add() allocates new dacl + for (const security_ace &ace : new_dacl) { + security_descriptor_dacl_add(sd, &ace); + } +} + +// This f-n is only necessary to band-aid one problem +// with generic read. +uint32_t ad_security_map_access_mask(const uint32_t access_mask) { + const bool is_generic_read = (access_mask == SEC_ADS_GENERIC_READ); + + if (is_generic_read) { + return GENERIC_READ_FIXED; + } else { + return access_mask; + } +} + +// This simplified version of ace_compare() for +// verifying ACL order. Only includes necessary +// comparisons specified by Microsoft here: +// https://docs.microsoft.com/en-us/windows/win32/secauthz/order-of-aces-in-a-dacl +// +// TODO: currently missing one comparison: +// +// "Inherited ACE's are placed in the order in which +// they are inherited" +// +// Not a big problem because inherited ACE's are added +// to ACL by the server. Clients cannot manually add +// such ACE's, so theoretically their order should +// always be correct. But do implement this at some +// point, just in case. Using order requirements listed +// here: +int ace_compare_simplified(const security_ace &ace1, const security_ace &ace2) +{ + bool b1; + bool b2; + + /* If the ACEs are equal, we have nothing more to do. */ + if (security_ace_equal(&ace1, &ace2)) { + return 0; + } + + /* Inherited follow non-inherited */ + b1 = ((ace1.flags & SEC_ACE_FLAG_INHERITED_ACE) != 0); + b2 = ((ace2.flags & SEC_ACE_FLAG_INHERITED_ACE) != 0); + if (b1 != b2) { + return (b1 ? 1 : -1); + } + + /* Allowed ACEs follow denied ACEs */ + b1 = (ace1.type == SEC_ACE_TYPE_ACCESS_ALLOWED || + ace1.type == SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT); + b2 = (ace2.type == SEC_ACE_TYPE_ACCESS_ALLOWED || + ace2.type == SEC_ACE_TYPE_ACCESS_ALLOWED_OBJECT); + if (b1 != b2) { + return (b1 ? 1 : -1); + } + + return 0; } diff --git a/src/adldap/ad_security.h b/src/adldap/ad_security.h index fe96f1af..4a32705a 100644 --- a/src/adldap/ad_security.h +++ b/src/adldap/ad_security.h @@ -19,7 +19,8 @@ */ /** - * Functions for working with security descriptors. + * Functions for working with AD security and security + * descriptors. */ #ifndef AD_SECURITY_H @@ -28,40 +29,84 @@ #include "ad_defines.h" #include -#include -#include -#include +#include class AdInterface; class AdConfig; class AdObject; struct security_descriptor; struct dom_sid; +typedef void TALLOC_CTX; extern const QList well_known_sid_list; -extern const QHash ace_permission_to_mask_map; -extern const QHash ace_permission_to_type_map; -extern const QList all_permissions_list; -extern const QSet all_permissions; -extern const QSet access_permissions; -extern const QSet read_prop_permissions; -extern const QSet write_prop_permissions; +extern const QList common_rights_list; + +enum SecurityRightStateType { + SecurityRightStateType_Allow, + SecurityRightStateType_Deny, + SecurityRightStateType_COUNT, +}; + +enum SecurityRightStateInherited { + SecurityRightStateInherited_Yes, + SecurityRightStateInherited_No, + SecurityRightStateInherited_COUNT, +}; + +class SecurityRightState { +public: + SecurityRightState(const bool data[SecurityRightStateInherited_COUNT][SecurityRightStateType_COUNT]); + + bool get(const SecurityRightStateInherited inherited, const SecurityRightStateType type) const; + +private: + bool data[SecurityRightStateInherited_COUNT][SecurityRightStateType_COUNT]; +}; + +class SecurityRight { +public: + uint32_t access_mask; + QByteArray object_type; +}; -QHash> ad_security_modify(const QHash> ¤t, const QByteArray &trustee, const AcePermission permission, const PermissionState new_state); QString ad_security_get_well_known_trustee_name(const QByteArray &trustee); 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); +bool ad_security_get_protected_against_deletion(const AdObject &object); +bool ad_security_set_protected_against_deletion(AdInterface &ad, const QString dn, const bool enabled); +bool ad_security_get_user_cant_change_pass(const AdObject *object, AdConfig *adconfig); +bool ad_security_set_user_cant_change_pass(AdInterface *ad, const QString &dn, const bool enabled); +bool ad_security_replace_security_descriptor(AdInterface &ad, const QString &dn, security_descriptor *new_sd); -// NOTE: have to talloc_free() returned sd -security_descriptor *ad_security_get_sd(const AdObject &object); +// Returns the full right name, adding "Write" or +// "Read" depending on access mask. +QString ad_security_get_right_name(AdConfig *adconfig, const uint32_t access_mask, const QByteArray &object_type, QLocale::Language language); -void ad_security_sort_dacl(security_descriptor *sd); +// NOTE: returned sd needs to be free'd with +// security_descriptor_free() +security_descriptor *security_descriptor_make_from_bytes(const QByteArray &sd_bytes); +security_descriptor *security_descriptor_make_from_bytes(TALLOC_CTX *mem_ctx, const QByteArray &sd_bytes); +security_descriptor *security_descriptor_copy(security_descriptor *sd); +void security_descriptor_free(security_descriptor *sd); +void security_descriptor_sort_dacl(security_descriptor *sd); +QList security_descriptor_get_trustee_list(security_descriptor *sd); +SecurityRightState security_descriptor_get_right(const security_descriptor *sd, const QByteArray &trustee, const uint32_t access_mask, const QByteArray &object_type); +void security_descriptor_print(security_descriptor *sd, AdInterface &ad); +bool security_descriptor_verify_acl_order(security_descriptor *sd); -QByteArray dom_sid_to_bytes(const dom_sid &sid); -QByteArray dom_sid_string_to_bytes(const dom_sid &sid); +// Remove all ACE's from DACL for given trustee. Note +// that inherited ACE's are untouched, so trustee might +// still have ace's remaining after this is called. +void security_descriptor_remove_trustee(security_descriptor *sd, const QList &trustee_list); + +// "Complete" versions of add/remove right f-ns that do A +// LOT more than just add rights. They also take care +// of superior and subordinate rights to follow a logic +// of a typical gui checklist of rights. +void security_descriptor_add_right(security_descriptor *sd, AdConfig *adconfig, const QList &class_list, const QByteArray &trustee, const uint32_t access_mask, const QByteArray &object_type, const bool allow); +void security_descriptor_remove_right(security_descriptor *sd, AdConfig *adconfig, const QList &class_list, const QByteArray &trustee, const uint32_t access_mask, const QByteArray &object_type, const bool allow); + +QList ad_security_get_right_list_for_class(AdConfig *adconfig, const QList &class_list); +QList ad_security_get_superior_right_list(const uint32_t access_mask, const QByteArray &object_type); +QList ad_security_get_subordinate_right_list(AdConfig *adconfig, const uint32_t access_mask, const QByteArray &object_type, const QList &class_list); #endif /* AD_SECURITY_H */ diff --git a/src/adldap/ad_utils.cpp b/src/adldap/ad_utils.cpp index 14676427..e0c42151 100644 --- a/src/adldap/ad_utils.cpp +++ b/src/adldap/ad_utils.cpp @@ -126,9 +126,10 @@ QString account_option_string(const AccountOption &option) { switch (option) { case AccountOption_Disabled: return QCoreApplication::translate("ad_utils", "Account disabled"); case AccountOption_CantChangePassword: return QCoreApplication::translate("ad_utils", "User cannot change password"); + case AccountOption_AllowReversibleEncryption: return QCoreApplication::translate("ad_utils", "Store password using reversible encryption"); case AccountOption_PasswordExpired: return QCoreApplication::translate("ad_utils", "User must change password on next logon"); case AccountOption_DontExpirePassword: return QCoreApplication::translate("ad_utils", "Don't expire password"); - case AccountOption_UseDesKey: return QCoreApplication::translate("ad_utils", "Store password using reversible encryption"); + case AccountOption_UseDesKey: return QCoreApplication::translate("ad_utils", "Use Kerberos DES encryption types for this account"); case AccountOption_SmartcardRequired: return QCoreApplication::translate("ad_utils", "Smartcard is required for interactive logon"); case AccountOption_CantDelegate: return QCoreApplication::translate("ad_utils", "Account is sensitive and cannot be delegated"); case AccountOption_DontRequirePreauth: return QCoreApplication::translate("ad_utils", "Don't require Kerberos preauthentication"); @@ -142,18 +143,20 @@ QString account_option_string(const AccountOption &option) { int account_option_bit(const AccountOption &option) { switch (option) { case AccountOption_Disabled: - return 0x00000002; + return UAC_ACCOUNTDISABLE; + case AccountOption_AllowReversibleEncryption: + return UAC_ENCRYPTED_TEXT_PASSWORD_ALLOWED; case AccountOption_DontExpirePassword: - return 0x00010000; + return UAC_DONT_EXPIRE_PASSWORD; case AccountOption_UseDesKey: - return 0x00200000; + return UAC_USE_DES_KEY_ONLY; case AccountOption_SmartcardRequired: - return 0x00040000; + return UAC_SMARTCARD_REQUIRED; case AccountOption_DontRequirePreauth: - return 0x00400000; - case AccountOption_CantDelegate: return 0x00100000; + return UAC_DONT_REQUIRE_PREAUTH; + case AccountOption_CantDelegate: return UAC_NOT_DELEGATED; case AccountOption_TrustedForDelegation: - return 0x00080000; + return UAC_TRUSTED_FOR_DELEGATION; // NOTE: not all account options can be directly // mapped to bits @@ -344,28 +347,16 @@ QString get_default_domain_from_krb5() { return out; } -// Transform domain to domain DN -// "DOMAIN.COM" => "DC=domain,DC=com" -QString domain_to_domain_dn(const QString &domain) { - QString domain_dn = domain; - - domain_dn = domain_dn.toLower(); - domain_dn = "DC=" + domain_dn; - domain_dn = domain_dn.replace(".", ",DC="); - - return domain_dn; -} - -int bit_set(int bitmask, int bit, bool set) { - if (set) { - return bitmask | bit; +int bitmask_set(const int input_mask, const int mask_to_set, const bool is_set) { + if (is_set) { + return input_mask | mask_to_set; } else { - return bitmask & ~bit; + return input_mask & ~mask_to_set; } } -bool bit_is_set(int bitmask, int bit) { - return ((bitmask & bit) != 0); +bool bitmask_is_set(const int input_mask, const int mask_to_read) { + return ((input_mask & mask_to_read) == mask_to_read); } const char *cstr(const QString &qstr) { @@ -452,3 +443,7 @@ QString attribute_type_display_string(const AttributeType type) { } return QString(); } + +QString int_to_hex_string(const int n) { + return QString("0x%1").arg(n, 8, 16, QLatin1Char('0')); +} diff --git a/src/adldap/ad_utils.h b/src/adldap/ad_utils.h index 99829dfe..e3f72f70 100644 --- a/src/adldap/ad_utils.h +++ b/src/adldap/ad_utils.h @@ -64,10 +64,9 @@ QString dn_canonical(const QString &dn); QString dn_from_name_and_parent(const QString &name, const QString &parent, const QString &object_class); QString get_default_domain_from_krb5(); -QString domain_to_domain_dn(const QString &domain); -int bit_set(int bitmask, int bit, bool set); -bool bit_is_set(int bitmask, int bit); +int bitmask_set(const int input_mask, const int mask_to_set, const bool is_set); +bool bitmask_is_set(const int input_mask, const int mask_to_read); // NOTE: uses a buffer that is capped at 100 strings, so // pointers returned from this become invalid after 99 more @@ -84,4 +83,6 @@ QByteArray sid_string_to_bytes(const QString &sid_string); QString attribute_type_display_string(const AttributeType type); +QString int_to_hex_string(const int n); + #endif /* AD_UTILS_H */ diff --git a/src/adldap/adldap_en.ts b/src/adldap/adldap_en.ts index 3b192391..9d58b467 100644 --- a/src/adldap/adldap_en.ts +++ b/src/adldap/adldap_en.ts @@ -4,136 +4,536 @@ AdConfig - - + + Distinguished name - + Description - + Class - - + + Name - + Object class - + Created - + Changed - + USN created - + USN changed - + Account expires - + Type - + Profile path - + Logon script - + Logon name (pre-Windows 2000) - + E-mail - + Location + + + DS Replication Get Changes + + + + + DS Replication Get Changes All + + + + + Phone and Mail Options + + + + + Bypass the quota restrictions during creation. + + + + + Receive As + + + + + Unexpire Password + + + + + Do Garbage Collection + + + + + Allowed To Authenticate + + + + + Change PDC + + + + + Reanimate Tombstones + + + + + msmq Peek Dead Letter + + + + + AutoEnrollment + + + + + DS Install Replica + + + + + Domain Password & Lockout Policies + + + + + Generate RSoP Logging + + + + + Run Protect Admin Groups Task + + + + + Self Membership + + + + + Allow a DC to create a clone of itself + + + + + Other Domain Parameters (for use by SAM) + + + + + SAM Enumerate Entire Domain + + + + + Write secret attributes of objects in a Partition + + + + + Send As + + + + + DS Replication Manage Topology + + + + + Set Owner of an object during creation. + + + + + Generate RSoP Planning + + + + + Certificate Enrollment + + + + + Web Information + + + + + Create Inbound Forest Trust + + + + + Migrate SID History + + + + + Update Password Not Required Bit + + + + + MS-TS-GatewayAccess + + + + + Validated write to MS DS Additional DNS Host Name + + + + + msmq Receive + + + + + Validated DNS Host Name + + + + + Send To + + + + + DS Replication Get Changes In Filtered Set + + + + + Read Only Replication Secret Synchronization + + + + + Validated write to MS DS behavior version + + + + + msmq Open Connector + + + + + Terminal Server License Server + + + + + Change Schema Master + + + + + Recalculate Hierarchy + + + + + DS Check Stale Phantoms + + + + + msmq Receive computer Journal + + + + + User Force Change Password + + + + + Domain Administer Server + + + + + DS Replication Synchronize + + + + + Personal Information + + + + + msmq Peek + + + + + General Information + + + + + Group Membership + + + + + Add GUID + + + + + Remote Access Information + + + + + DS Execute Intentions Script + + + + + Allocate Rids + + + + + Update Schema Cache + + + + + Apply Group Policy + + + + + Account Restrictions + + + + + Validated SPN + + + + + Read secret attributes of objects in a Partition + + + + + Logon Information + + + + + DS Query Self Quota + + + + + Change Infrastructure Master + + + + + Open Address Book + + + + + User Change Password + + + + + msmq Peek computer Journal + + + + + Change Domain Master + + + + + msmq Send + + + + + Change Rid Master + + + + + Recalculate Security Inheritance + + + + + Refresh Group Cache + + + + + Manage Optional Features + + + + + Reload SSL Certificate + + + + + Enable Per User Reversibly Encrypted Password + + + + + DS Replication Monitor Topology + + + + + Public Information + + + + + Private Information + + + + + msmq Receive Dead Letter + + + + + msmq Receive journal + + + + + DNS Host Name Attributes + + + + + <unknown rights> + + AdInterface - + Failed to connect. Failed to connect - - Failed to get a domain. - - - - + Failed to find domain controllers. Make sure your computer is in the domain and that domain controllers are operational. - + Failed to load DC defined in settings. Switching to default DC - + Failed to initialize LDAP library. Failed to initialize LDAP library - + Failed to set ldap option %1. Failed to set ldap option %1 - + Failed to connect to server. Check your connection and make sure you have initialized your credentials using kinit. - + Failed to initialize SMB context. Failed to initialize SMB context - + Search: filter = "%1" attributes = %2 @@ -142,369 +542,368 @@ - + Failed to change attribute %1 of object %2 from "%3" to "%4". - + Value "%1" was added for attribute %2 of object %3. - + Failed to add value "%1" for attribute %2 of object %3. - + Value "%1" for attribute %2 of object %3 was deleted. Value "%1" for attribute %2 of object %3 was deleted - + Failed to create object %1. - + Can't create OU under this object type. - + Failed to delete object %1. - + Object %1 was deleted. - + Failed to move object %1 to %2. - + Failed to rename object %1 to %2. - + Failed to add object %1 to group %2. - + Failed to remove object %1 from group %2. - + Failed to change group scope for %1 to "%2". - + Failed to change group type for %1 to "%2". - + Failed to change primary group for user %1 to %2. - + Object %1 has been disabled. - + Object %1 has been enabled. - + Account option "%1" was turned ON for object %2. - + Account option "%1" was turned OFF for object %2. - + Failed to disable object %1. - + Failed to enable object %1. - + Failed to turn ON account option "%1" for object %2. - + Failed to turn OFF account option "%1" for object %2. - + Failed to unlock user %1. - + Computer "%1" was reset. - + Failed to reset computer %1. - + Failed to create GPO. Failed to create GPO - + Insufficient rights. - + Failed to create GPT root dir. Failed to create policy main dir - + Failed to create GPT machine dir. Failed to create policy machine dir - + Failed to create GPT user dir. Failed to create policy user dir - + Failed to open GPT ini file. Failed to open policy ini - + Failed to write GPT ini file. Failed to write policy ini - + Failed to create GPC object. - + Failed to set GPC attribute %1. - + Failed to create user folder object for GPO. - + Failed to create machine folder object for GPO. - + Failed to check permissions for GPO "%1". - + Failed to get GPT security descriptor. - + Failed to get GPT security descriptor, %1. - + Failed to sync permissions of GPO "%1". - + Failed to generate GPT security descriptor. - + Failed to read GPT contents of "%1". - + Failed to set permissions, %1. - + Failed to load GPO's sysvol version. - - + + Failed to open GPT.INI, %1. - + Failed to extract version from GPT.INI, %1. - + Failed to delete GPC. - + Failed to delete GPT. - + Group policy %1 was deleted. - + Errors happened while trying to delete policy %1. - + Failed to delete policy %1. - + Synced permissions of GPO "%1". - + Attribute %1 of object %2 was changed from "%3" to "%4". Failed to change attribute "%1" of object "%2" from "%3" to "%4" - + + Failed to get a domain. Check that you have initialized kerberos credentials (kinit). + + + + Failed to delete value "%1" for attribute %2 of object %3. Failed to delete value "%1" for attribute "%2" of object "%3". - + Object %1 was created. Failed to create object "%1" - - LDAP Operation error - Failed to allocate tree delete control. - LDAP Operation error - Failed to allocate tree delete control - - - - + LDAP Operation error - Failed to create tree delete control. LDAP Operation error - Failed to create tree delete control - + Object %1 was moved to %2. Failed to move object "%1" to "%2" - + Object %1 was renamed to %2. Failed to rename object "%1" to "%2" - + Object %1 was added to group %2. Failed to add user "%1" to group "%2" - + Object %1 was removed from group %2. Failed to remove user "%1" from group "%2" - + Group scope for %1 was changed to "%2". Group scope for %1 to "%2". - + Group type for %1 was changed to "%2". Failed to set type for group "%1" to "%2" - + Primary group for object %1 was changed to %2. Failed to set primary group for user "%1" to "%2" - + Password for object %1 was changed. Failed to set password for user "%1" - + Failed to change password for object %1. - + Password doesn't match rules. Password doesn't match rules - + User "%1" was unlocked. Failed to unlock user "%1" @@ -513,66 +912,114 @@ AdInterfacePrivate - + Failed to get contents of GPT "%1". - + Failed to open dir. - + Failed to read dir. - + Error: "%1" - + No such object - + Constraint violation - + Server is unwilling to perform Server is unwilling to perform - + Already exists Object already exists - + Server error: %1 - + Failed to delete GPT folder %1. - + Failed to delete GPT file %1. - + Failed to get filestat for "%1". + + ad_security.cpp + + + Read %1 + + + + + Write %1 + + + + + Full control + + + + + Read + + + + + Write + + + + + Delete + + + + + Create all child objects + + + + + Delete all child objects + + + + + <unknown right> + + + ad_utils @@ -586,72 +1033,77 @@ - + User must change password on next logon - + Don't expire password - + Store password using reversible encryption - Smartcard is required for interactive logon + Use Kerberos DES encryption types for this account - Account is sensitive and cannot be delegated + Smartcard is required for interactive logon - Don't require Kerberos preauthentication + Account is sensitive and cannot be delegated + Don't require Kerberos preauthentication + + + + Trusted for delegation - + Global - + Domain Local - + Universal - + Security - + Distribution - + Security Group - + Distribution Group @@ -659,102 +1111,102 @@ ad_utils.cpp - + Boolean - + Enumeration - + Integer - + Large Integer - + String Case - + IA5 - + NT Security Descriptor - + Numeric - + Object Identifier - + Octet - + Replica Link - + Printable - + SID - + Teletex - + Unicode String - + UTC Time - + Generalized Time - + DN String - + DN Binary - + Distinguished Name @@ -762,20 +1214,31 @@ attribute_display - + <BINARY VALUE> - + <unset> - + (never) + + + <invalid UAC value> + + + + + + <invalid value> + + filter @@ -818,7 +1281,7 @@ managedBy - + Managed by diff --git a/src/adldap/adldap_ru.ts b/src/adldap/adldap_ru.ts index 94d465ac..f226d520 100644 --- a/src/adldap/adldap_ru.ts +++ b/src/adldap/adldap_ru.ts @@ -4,136 +4,536 @@ AdConfig - - + + Distinguished name Уникальное имя - + Description Описание - + Class Класс - - + + Name Имя - + Object class Класс объекта - + Created Создан - + Changed Изменён - + USN created USN создан - + USN changed USN изменен - + Account expires Срок действия учетной записи - + Type Тип - + Profile path Путь профиля - + Logon script Логон скрипт - + Logon name (pre-Windows 2000) Имя входа (пред-Windows 2000) - + E-mail Электронная почта - + Location Местонахождение + + + DS Replication Get Changes + DS репликация получать изменения + + + + DS Replication Get Changes All + DS репликация получать все изменения + + + + Phone and Mail Options + Опции почт и телефонов + + + + Bypass the quota restrictions during creation. + Обход ограничений квот во время создания. + + + + Receive As + Получать как + + + + Unexpire Password + Востанавливать просроченый пароль + + + + Do Garbage Collection + Запускать Garbage Collection + + + + Allowed To Authenticate + Разрешено аутентифицировать + + + + Change PDC + Изменять PDC + + + + Reanimate Tombstones + Возраждать надгробия + + + + msmq Peek Dead Letter + msmq просмотр недоставленных писем + + + + AutoEnrollment + АвтоЗачисление + + + + DS Install Replica + DS Устанавливать Реплики + + + + Domain Password & Lockout Policies + Доменный пароль и политики блокировки + + + + Generate RSoP Logging + Создание журнала RSoP + + + + Run Protect Admin Groups Task + Запускать защищенные задачи групп администраторов + + + + Self Membership + Собственное членство + + + + Allow a DC to create a clone of itself + Разрешать контроллеру домена создавать свой клон + + + + Other Domain Parameters (for use by SAM) + Другие параметры домена (для использования SAM) + + + + SAM Enumerate Entire Domain + SAM перечислять весь домен + + + + Write secret attributes of objects in a Partition + Писать секретные атрибуты объектов в разделе + + + + Send As + Отправлять как + + + + DS Replication Manage Topology + DS репликация управление топологией + + + + Set Owner of an object during creation. + Установить владельца объекта во время создания. + + + + Generate RSoP Planning + Создание планирования RSoP + + + + Certificate Enrollment + Регистрация сертификата + + + + Web Information + Веб-информация + + + + Create Inbound Forest Trust + Создание доверия входящего леса + + + + Migrate SID History + Мигрировать SID историю + + + + Update Password Not Required Bit + Обновлять бит "Пароль не требуется" + + + + MS-TS-GatewayAccess + MS-TS-GatewayAccess + + + + Validated write to MS DS Additional DNS Host Name + Подтвержденная запись в MS DS дополнительное DNS хост имя + + + + msmq Receive + msmq Получать + + + + Validated DNS Host Name + Подтвержденное имя хоста DNS + + + + Send To + Отправлять к + + + + DS Replication Get Changes In Filtered Set + DS репликация получять изменения в отфильтрованном наборе + + + + Read Only Replication Secret Synchronization + Синхронизация секрета репликации только для чтения + + + + Validated write to MS DS behavior version + Проверенная запись в MS DS версию поведения + + + + msmq Open Connector + msmq Open Connector + + + + Terminal Server License Server + Сервер терминала Сервер лицензий + + + + Change Schema Master + Изменить мастера схемы + + + + Recalculate Hierarchy + Пересчитать иерархию + + + + DS Check Stale Phantoms + DS Проверить устаревшие фантомы + + + + msmq Receive computer Journal + msmq Receive computer Journal + + + + User Force Change Password + Принудительно изменить пароль пользователя + + + + Domain Administer Server + Сервер администрирования домена + + + + DS Replication Synchronize + DS синхронизация репликации + + + + Personal Information + Персональная информация + + + + msmq Peek + msmq Peek + + + + General Information + Общая информация + + + + Group Membership + Членство в группах + + + + Add GUID + Добавить GUID + + + + Remote Access Information + Информация об удаленном доступе + + + + DS Execute Intentions Script + DS сценарий выполнения намерений + + + + Allocate Rids + Распределить Rids + + + + Update Schema Cache + Обновить кэш схемы + + + + Apply Group Policy + Применить групповые политики + + + + Account Restrictions + Ограничения учетной записи + + + + Validated SPN + Проверенный SPN + + + + Read secret attributes of objects in a Partition + Читать секретные атрибуты объектов в разделе + + + + Logon Information + Информация для входа + + + + DS Query Self Quota + DS Самостоятельная квота запроса + + + + Change Infrastructure Master + Изменить мастера инфраструктуры + + + + Open Address Book + Открыть книгу адресов + + + + User Change Password + Изменить пароль пользователя + + + + msmq Peek computer Journal + msmq Peek computer Journal + + + + Change Domain Master + Изменить мастера домена + + + + msmq Send + msmq Send + + + + Change Rid Master + Изменить мастера Rid + + + + Recalculate Security Inheritance + Пересчитать наследование безопасности + + + + Refresh Group Cache + Обновить групповой кэш + + + + Manage Optional Features + Управление дополнительными функциями + + + + Reload SSL Certificate + Перезагрузить SSL-сертификат + + + + Enable Per User Reversibly Encrypted Password + Включить реверсивно зашифрованный пароль для каждого пользователя + + + + DS Replication Monitor Topology + DS Топология монитора репликации + + + + Public Information + Публичная информация + + + + Private Information + Приватная информация + + + + msmq Receive Dead Letter + msmq Receive Dead Letter + + + + msmq Receive journal + msmq Receive journal + + + + DNS Host Name Attributes + Атрибуты имени хоста DNS + + + + <unknown rights> + <неизвестные права> + AdInterface - + Failed to connect. Failed to connect Не удалось подключиться. - - Failed to get a domain. - Не удалось получить домен. - - - + Failed to find domain controllers. Make sure your computer is in the domain and that domain controllers are operational. Не удалось найти контроллеры домена. Убедитесь, что ваш компьютер находится в домене и что контроллеры домена работают. - + Failed to load DC defined in settings. Switching to default DC Не удалось загрузить DC, указанный в настройках. Переходим на DC по умолчанию - + Failed to initialize LDAP library. Failed to initialize LDAP library Не удалось инициализировать библиотеку LDAP. - + Failed to set ldap option %1. Failed to set ldap option %1 Не удалось установить параметр ldap%1. - + Failed to connect to server. Check your connection and make sure you have initialized your credentials using kinit. Не удалось подключиться к серверу. Проверьте ваше соединение и убедитесь, что вы инициализировали свои учетные данные с помощью kinit. - + Failed to initialize SMB context. Failed to initialize SMB context Не удалось инициализировать библиотеку SMB. - + Search: filter = "%1" attributes = %2 @@ -142,369 +542,368 @@ - + Failed to change attribute %1 of object %2 from "%3" to "%4". Не удалось изменить значение атрибута %1 для обьекта %2 с "%3" на "%4". - + Value "%1" was added for attribute %2 of object %3. Значение "%1" было добавлено к атрубиту %2 для объекта %3. - + Failed to add value "%1" for attribute %2 of object %3. Не удалось добавить значение "%1" для атрибута %2 для объекта %3. - + Value "%1" for attribute %2 of object %3 was deleted. Value "%1" for attribute %2 of object %3 was deleted Значение "%1" для атрибута %2 для объекта %3 было удалено. - + Failed to create object %1. Не удалось создать объект %1. - + Can't create OU under this object type. Невозможно создать подразделение под таким объектом. - + Failed to delete object %1. Не удалось удалить объект %1. - + Object %1 was deleted. Объект %1 был удалён. - + Failed to move object %1 to %2. Не удалось переместить объект %1 в %2. - + Failed to rename object %1 to %2. Не удалось переименовать объект %1 на %2. - + Failed to add object %1 to group %2. Не удалось добавить объект %1 в группу %2. - + Failed to remove object %1 from group %2. Не удалось удалить объект %1 из группы %2. - + Failed to change group scope for %1 to "%2". Не удалось изменить сферу группы %1 на "%2". - + Failed to change group type for %1 to "%2". Не удалось изменить тип группы %1 на "%2". - + Failed to change primary group for user %1 to %2. Не удалось изменить основную группу для пользователя %1 на %2. - + Object %1 has been disabled. Объект %1 был выключен. - + Object %1 has been enabled. Объект %1 был включен. - + Account option "%1" was turned ON for object %2. Опция учетной записи "%1" была включена для объекта %2. - + Account option "%1" was turned OFF for object %2. Опция учетной записи "%1" была выключена для объекта %2. - + Failed to disable object %1. Не удалось выключить объект %1. - + Failed to enable object %1. Не удалось включить объект %1. - + Failed to turn ON account option "%1" for object %2. Не удалось включить опцию учетной записи "%1" для объекта %2. - + Failed to turn OFF account option "%1" for object %2. Не удалось выключить опцию учетной записи "%1" для объекта %2. - + Failed to unlock user %1. Не удалось разблокировать пользователя %1. - + Computer "%1" was reset. Компьютер "%1" был сброшен. - + Failed to reset computer %1. Не удалось сбросить компьютер %1. - + Failed to create GPO. Failed to create GPO Не удалось создать GPO. - + Insufficient rights. Недостаточно прав. - + Failed to create GPT root dir. Failed to create policy main dir Не удалось создать папку политики. - + Failed to create GPT machine dir. Failed to create policy machine dir Не удалось создать папку Machine. - + Failed to create GPT user dir. Failed to create policy user dir Не удалось создать папку User. - + Failed to open GPT ini file. Failed to open policy ini Не удалось открыть ini политики. - + Failed to write GPT ini file. Failed to write policy ini Не удалось написать .ini политики. - + Failed to create GPC object. Не удалось создать GPC объект. - + Failed to set GPC attribute %1. Не удалось назначить атрибуты GPC обьекта %1. - + Failed to create user folder object for GPO. Не удалось создать объект пользователся для GPO. - + Failed to create machine folder object for GPO. Не удалось создать объект машины для GPO. - + Failed to check permissions for GPO "%1". Не удалось проверить права для GPO "%1". - + Failed to get GPT security descriptor. Не удалось получить описатель безопасности для GPT. - + Failed to get GPT security descriptor, %1. Не удалось получить GPT security descriptor, %1. - + Failed to sync permissions of GPO "%1". Не удалось обновить права для GPO "%1". - + Failed to generate GPT security descriptor. Не удалось создать описатель безопасности для GPT. - + Failed to read GPT contents of "%1". Не удалось прочитать содержимое GPT "%1". - + Failed to set permissions, %1. Не удалось назначить права, %1. - + Failed to load GPO's sysvol version. Не удалось загрузить GPO sysvol версию. - - + + Failed to open GPT.INI, %1. Не удалось открыть GPT.INI, %1. - + Failed to extract version from GPT.INI, %1. Не удалось получить версию из GPT.INI, %1. - + Failed to delete GPC. Не удалось удалить GPC. - + Failed to delete GPT. Не удалось удалить GPT. - + Group policy %1 was deleted. Политика %1 была удалена. - + Errors happened while trying to delete policy %1. Произошли ошибки во время удаления политики %1. - + Failed to delete policy %1. Не удалось удалить политику %1. - + Synced permissions of GPO "%1". Права для политики "%1" были поправлены. - + Attribute %1 of object %2 was changed from "%3" to "%4". Failed to change attribute "%1" of object "%2" from "%3" to "%4" Атрибут %1 объекта %2 был изменен с "%3" на "%4". - + + Failed to get a domain. Check that you have initialized kerberos credentials (kinit). + Не удалось получить домен. Проверьте что вы инициализировали kerberos (kinit). + + + Failed to delete value "%1" for attribute %2 of object %3. Failed to delete value "%1" for attribute "%2" of object "%3". Не удалось удалить значение "%1" для атрибута %2 объекта %3. - + Object %1 was created. Failed to create object "%1" Объект %1 был создан. - - LDAP Operation error - Failed to allocate tree delete control. - LDAP Operation error - Failed to allocate tree delete control - Ошибка LDAP - Не удалось выделить контрол удаления дерева. - - - + LDAP Operation error - Failed to create tree delete control. LDAP Operation error - Failed to create tree delete control Ошибка LDAP - Не удалось создать контрол удаления дерева. - + Object %1 was moved to %2. Failed to move object "%1" to "%2" Объект %1 был перемещен в %2. - + Object %1 was renamed to %2. Failed to rename object "%1" to "%2" Объект %1 был переименован на %2. - + Object %1 was added to group %2. Failed to add user "%1" to group "%2" Объект %1 был добавлен в группу %2. - + Object %1 was removed from group %2. Failed to remove user "%1" from group "%2" Объект %1 был удален из группы %2. - + Group scope for %1 was changed to "%2". Group scope for %1 to "%2". Сфера группы %1 была изменена на "%2". - + Group type for %1 was changed to "%2". Failed to set type for group "%1" to "%2" Тип группы %1 был изменен на "%2". - + Primary group for object %1 was changed to %2. Failed to set primary group for user "%1" to "%2" Основная группа пользователя %1 была изменена на %2. - + Password for object %1 was changed. Failed to set password for user "%1" Пароль для объекта %1 был изменен. - + Failed to change password for object %1. Не удалось изменить пароль для объекта %1. - + Password doesn't match rules. Password doesn't match rules Пароль не соответствует правилам. - + User "%1" was unlocked. Failed to unlock user "%1" Пользователь %1 был разблокирован. @@ -513,66 +912,114 @@ AdInterfacePrivate - + Failed to get contents of GPT "%1". Не удалось прочитать содержимое GPT "%1". - + Failed to open dir. Не удалось открыть папку. - + Failed to read dir. Не удалось прочитать папку. - + Error: "%1" Ошибка: "%1" - + No such object Нет такого объекта - + Constraint violation Нарушение ограничений - + Server is unwilling to perform Сервер не хочет выполнять - + Already exists Объект уже существует - + Server error: %1 Ошибка сервера: %1 - + Failed to delete GPT folder %1. Не удалось удалить папку GPT %1. - + Failed to delete GPT file %1. Не удалось удалить файл GPT %1. - + Failed to get filestat for "%1". Не удалось получить filestat для "%1". + + ad_security.cpp + + + Read %1 + Читать свойство - %1 + + + + Write %1 + Изменять свойство - %1 + + + + Full control + Полный доступ + + + + Read + Читать + + + + Write + Изменять + + + + Delete + Удалять + + + + Create all child objects + Создавать дочерние объекты + + + + Delete all child objects + Удалять все дочерние объекты + + + + <unknown right> + <неизвестные права> + + ad_utils @@ -586,72 +1033,77 @@ Пользователь не может изменить пароль - + User must change password on next logon Пользователь должен сменить пароль при следующем входе в систему - + Don't expire password Пароль не истекает - + Store password using reversible encryption Хранить пароль с использованием обратимого шифрования + Use Kerberos DES encryption types for this account + Использовать Kerberos DES тип шифровки для этой учетной записи + + + Smartcard is required for interactive logon Смарт-карта необходима для интерактивного входа в систему - + Account is sensitive and cannot be delegated Учетная запись является конфиденциальной и не может быть делегирована - + Don't require Kerberos preauthentication Не требовать предварительной аутентификации Kerberos - + Trusted for delegation Доверяется для делегации - + Global Глобальная - + Domain Local Домен локальная - + Universal Универсальная - + Security Безопасность - + Distribution Рассылка - + Security Group Группа Безопасности - + Distribution Group Группа Рассылки @@ -659,102 +1111,102 @@ ad_utils.cpp - + Boolean Логическое - + Enumeration Перечисление - + Integer Целое число - + Large Integer Большое целое число - + String Case Строка с учётом регистра - + IA5 IA5 - + NT Security Descriptor Дескриптор безопасности NT - + Numeric Числовое - + Object Identifier Идентификатор объекта - + Octet Октет - + Replica Link Ссылка на реплику - + Printable Печатаемый - + SID SID - + Teletex Teletex - + Unicode String Юникод - + UTC Time Время UTC - + Generalized Time Обобщённое время - + DN String Строка DN - + DN Binary Двоичное DN - + Distinguished Name Различающееся имя @@ -762,20 +1214,31 @@ attribute_display - + <BINARY VALUE> <БИНАРНОЕ ЗНАЧЕНИЕ> - + <unset> <без значения> - + (never) (никогда) + + + <invalid UAC value> + <неправильно значение UAC> + + + + + <invalid value> + <неправильное значение> + filter @@ -818,7 +1281,7 @@ managedBy - + Managed by Управляется diff --git a/src/adldap/gplink.cpp b/src/adldap/gplink.cpp index fde65c06..b922e7cf 100644 --- a/src/adldap/gplink.cpp +++ b/src/adldap/gplink.cpp @@ -205,7 +205,7 @@ bool Gplink::get_option(const QString &gpo_case, const GplinkOption option) cons } const int option_bits = options[gpo]; - const bool is_set = bit_is_set(option_bits, (int) option); + const bool is_set = bitmask_is_set(option_bits, (int) option); return is_set; } @@ -218,7 +218,7 @@ void Gplink::set_option(const QString &gpo_case, const GplinkOption option, cons } const int option_bits = options[gpo]; - const int option_bits_new = bit_set(option_bits, (int) option, value); + const int option_bits_new = bitmask_set(option_bits, (int) option, value); options[gpo] = option_bits_new; } diff --git a/src/admc/CMakeLists.txt b/src/admc/CMakeLists.txt index 21bd1b69..02cc9381 100644 --- a/src/admc/CMakeLists.txt +++ b/src/admc/CMakeLists.txt @@ -35,7 +35,6 @@ set(ADMC_SOURCES globals.cpp utils.cpp settings.cpp - widget_state.cpp main_window.cpp main_window_connection_error.cpp @@ -43,19 +42,22 @@ set(ADMC_SOURCES tab_widget.cpp policy_results_widget.cpp edit_query_item_widget.cpp + fsmo_tab.cpp console_filter_dialog.cpp password_dialog.cpp - rename_object_dialog.cpp + rename_object_helper.cpp rename_other_dialog.cpp rename_policy_dialog.cpp rename_user_dialog.cpp rename_group_dialog.cpp - create_object_dialog.cpp + create_object_helper.cpp create_user_dialog.cpp create_group_dialog.cpp create_computer_dialog.cpp create_ou_dialog.cpp + create_shared_folder_dialog.cpp + create_contact_dialog.cpp create_policy_dialog.cpp create_query_item_dialog.cpp create_query_folder_dialog.cpp @@ -71,9 +73,11 @@ set(ADMC_SOURCES properties_dialog.cpp properties_multi_dialog.cpp properties_warning_dialog.cpp + security_sort_warning_dialog.cpp connection_options_dialog.cpp changelog_dialog.cpp error_log_dialog.cpp + fsmo_dialog.cpp filter_widget/filter_widget.cpp filter_widget/filter_dialog.cpp @@ -85,12 +89,12 @@ set(ADMC_SOURCES filter_widget/class_filter_dialog.cpp filter_widget/select_base_widget.cpp - tabs/properties_tab.cpp tabs/general_user_tab.cpp tabs/general_group_tab.cpp tabs/general_ou_tab.cpp tabs/general_computer_tab.cpp tabs/general_policy_tab.cpp + tabs/general_shared_folder_tab.cpp tabs/general_other_tab.cpp tabs/object_tab.cpp tabs/attributes_tab.cpp @@ -100,7 +104,6 @@ set(ADMC_SOURCES tabs/membership_tab.cpp tabs/address_tab.cpp tabs/group_policy_tab.cpp - tabs/gpo_links_tab.cpp tabs/organization_tab.cpp tabs/telephones_tab.cpp tabs/profile_tab.cpp @@ -109,8 +112,9 @@ set(ADMC_SOURCES tabs/os_tab.cpp tabs/delegation_tab.cpp tabs/select_well_known_trustee_dialog.cpp + tabs/laps_tab.cpp + tabs/error_tab.cpp - multi_tabs/properties_multi_tab.cpp multi_tabs/general_user_multi_tab.cpp multi_tabs/general_other_multi_tab.cpp multi_tabs/account_multi_tab.cpp @@ -121,8 +125,10 @@ set(ADMC_SOURCES attribute_edits/attribute_edit.cpp attribute_edits/string_edit.cpp attribute_edits/string_other_edit.cpp + attribute_edits/string_list_edit.cpp attribute_edits/string_large_edit.cpp attribute_edits/sam_name_edit.cpp + attribute_edits/computer_sam_name_edit.cpp attribute_edits/country_edit.cpp attribute_edits/expiry_edit.cpp attribute_edits/expiry_widget.cpp @@ -144,14 +150,11 @@ set(ADMC_SOURCES attribute_edits/logon_computers_edit.cpp attribute_edits/logon_computers_dialog.cpp attribute_edits/protect_deletion_edit.cpp + attribute_edits/laps_expiry_edit.cpp + attribute_edits/general_name_edit.cpp - attribute_multi_edits/attribute_multi_edit.cpp - attribute_multi_edits/string_multi_edit.cpp - attribute_multi_edits/expiry_multi_edit.cpp - attribute_multi_edits/upn_multi_edit.cpp - attribute_multi_edits/account_option_multi_edit.cpp - attribute_multi_edits/country_multi_edit.cpp - attribute_multi_edits/manager_multi_edit.cpp + attribute_edits/upn_multi_edit.cpp + attribute_edits/account_option_multi_edit.cpp attribute_dialogs/attribute_dialog.cpp attribute_dialogs/list_attribute_dialog.cpp @@ -172,6 +175,7 @@ set(ADMC_SOURCES console_impls/query_item_impl.cpp console_impls/query_folder_impl.cpp console_impls/policy_root_impl.cpp + console_impls/find_root_impl.cpp admc.qrc ) @@ -211,3 +215,9 @@ if(NOT ADMC_BUILD_DEB) install(TARGETS admctest DESTINATION ${CMAKE_INSTALL_LIBDIR} PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE WORLD_READ WORLD_EXECUTE) endif(NOT ADMC_BUILD_DEB) + +install( + FILES data/admc.svg + DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps + PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ +) diff --git a/src/admc/about_dialog.ui b/src/admc/about_dialog.ui index 4b3f4566..1fc58bf8 100644 --- a/src/admc/about_dialog.ui +++ b/src/admc/about_dialog.ui @@ -6,8 +6,8 @@ 0 0 - 428 - 300 + 432 + 164 @@ -15,9 +15,16 @@ - + + + + 20 + 50 + false + + - Version x.y.z (placeholder) + ADMC Qt::AlignCenter @@ -25,19 +32,32 @@ - - - ADMC is a tool for Active Directory administration. + + + Qt::Vertical - - Qt::AlignCenter + + + 20 + 40 + - + - Copyright (C) 2021 BaseALT Ltd. + Copyright (C) 2022 BaseALT Ltd. + + + Qt::AlignCenter + + + + + + + Version x.y.z (placeholder) Qt::AlignCenter diff --git a/src/admc/admc.qrc b/src/admc/admc.qrc index 21326dd7..5eb7a738 100644 --- a/src/admc/admc.qrc +++ b/src/admc/admc.qrc @@ -3,5 +3,6 @@ data/countries.csv admc_ru.qm admc_en.qm + data/admc.svg diff --git a/src/admc/admc_en.ts b/src/admc/admc_en.ts index a80628d6..294270b9 100644 --- a/src/admc/admc_en.ts +++ b/src/admc/admc_en.ts @@ -14,13 +14,8 @@ - - ADMC is a tool for Active Directory administration. - - - - - Copyright (C) 2020 BaseALT Ltd. + + Copyright (C) 2022 BaseALT Ltd. @@ -146,12 +141,12 @@ - Don't require Kerberos pre-authentication + Use Kerberos DES encryption types for this account - Trusted for delegation + Don't require Kerberos pre-authentication @@ -219,44 +214,13 @@ AttributeDialog - + Attribute: %1 - - - Edit - - - - - View - - - - - %1 Multi-Valued %2 - This is a dialog title for attribute editors. Example: "Edit Multi-Valued String" - - AttributesTab - - - Name - - - - - Value - - - - - Type - - Edit... @@ -273,6 +237,40 @@ + + AttributesTabEdit + + + Name + + + + + Value + + + + + Type + + + + + Edit + + + + + View + + + + + %1 Multi-Valued %2 + This is a dialog title for attribute editors. Example: "Edit Multi-Valued String" + + + AttributesTabFilterMenu @@ -329,30 +327,6 @@ - - ChangeDCDialog - - - Change Domain Controller - Change domain controller - - - - - Select: - - - - - Custom: - - - - - Save this setting - - - ChangelogDialog @@ -373,6 +347,11 @@ Select Classes + + + All + + ClassFilterWidget @@ -448,25 +427,30 @@ - + Show all - + Show only these types - + Create custom - + Custom + + + Object display limit: + + ConsoleWidget @@ -529,6 +513,39 @@ + + CreateContactDialog + + + Create Contact + + + + + First name: + + + + + Last name: + + + + + Initials: + + + + + Full name: + + + + + Display name: + + + CreateGroupDialog @@ -543,7 +560,8 @@ - Logon name (pre-Windows 2000): + Group name (pre-Windows 2000): + Logon name (pre-Windows 2000): @@ -576,14 +594,14 @@ - CreateObjectDialog + CreateObjectHelper - + Failed to create object %1 - + Object %1 was created @@ -602,17 +620,17 @@ - + New Group Policy Object - + Error - + Group Policy Object with this name already exists. @@ -641,7 +659,7 @@ - + New Folder @@ -654,6 +672,24 @@ + + CreateSharedFolderDialog + + + Create Shared Folder + + + + + Name: + + + + + Network path: + + + CreateUserDialog @@ -702,30 +738,40 @@ - + + Show password + + + + Account options: - + User must change password on next logon - + User cannot change password - + Don't expire password - + Account disabled + + + Create %1 + + CustomizeColumnsDialog @@ -816,6 +862,14 @@ + + ErrorTab + + + Failed to load object information. Check your connection. + + + ExpiryWidget @@ -829,6 +883,77 @@ + + FSMODialog + + + Operations Masters + + + + + Domain DNS + + + + + Forest DNS + + + + + PDC Emulation + + + + + Schema + + + + + Domain Naming + + + + + Infrastructure + + + + + Rid Allocation + + + + + FSMOTab + + + Current master: + + + + + Change to: + + + + + Change + + + + + Error + + + + + This machine is already a master for this role. Switch to a different machine in Connection Options to change master. + + + FilterDialog @@ -966,31 +1091,36 @@ - Icons + &Icons + Icons - List + &List + List - Detail + &Detail + Detail - Customize Columns + &Customize Columns + Customize Columns - Description Bar + &Description Bar + Description Bar - + Find results @@ -1008,13 +1138,13 @@ - + Description: Description - + Location: @@ -1023,31 +1153,32 @@ GeneralGroupTab - Logon name (pre-Windows 2000): + Group name (pre-Windows 2000): + Logon name (pre-Windows 2000): - + Description: - + E-mail: - + Notes: - + Group Scope: - + Group Type: @@ -1119,31 +1250,47 @@ - + Modified: - + User version: - + Computer version: - + Unique ID: + + + GeneralPolicyTabEdit - + unknown + + GeneralSharedFolderTab + + + Description: + + + + + Keywords + + + GeneralUserMultiTab @@ -1241,31 +1388,8 @@ - - GpoLinksTab - - - Name - - - GroupPolicyTab - - - Name - - - - - Disabled - - - - - Enforced - - Add... @@ -1282,32 +1406,68 @@ Block policy inheritance + + + GroupPolicyTabEdit - + + Name + + + + + Disabled + + + + + Enforced + + + + Remove link - + Move up - + Move down - + Not found - + The GPO for this link could not be found. It maybe have been recently created and is being replicated or it could have been deleted. + + LAPSTab + + + Password: + + + + + Password expires: + + + + + Reset expiry + + + ListAttributeDialog @@ -1325,6 +1485,21 @@ Remove + + + Error + + + + + One or more values are empty. Edit or remove them to proceed. + + + + + Add Value + + LogonComputersDialog @@ -1361,6 +1536,16 @@ Edit Logon Hours + + + Logon allowed: + + + + + Logon denied: + + Sunday @@ -1397,12 +1582,12 @@ - + Local time - + UTC time @@ -1411,197 +1596,257 @@ MainWindow - File + &File + File - - Action + + &Action + Action - - View + + &View + View - - Preferences + + &Preferences + Preferences - - Language + + &Language + Language - - Help + + &Help + Help - + Tool Bar - + Message Log - - Connection Options + + &Connection Options + Connection Options - - Quit + + &Quit + Quit - + Ctrl+Q - - Manual + + &Manual + Manual - Changelog + Manual (Alt + 8) - - About ADMC + + Alt+8 + + + + + &Changelog + Changelog - Icons + &About ADMC + About ADMC - List + &Icons + Icons - Detail + &List + List - - Console Tree + + &Detail + Detail - Description Bar + &Console Tree + Console Tree - - Customize Columns... + + Description &Bar + Description Bar - - Filter Objects... + + C&ustomize Columns... + Customize Columns... - Advanced Features + &Filter Objects... + Filter Objects... - Confirm Actions + &Advanced Features + Advanced Features - Put Last Name Before First Name + &Confirm Actions + Confirm Actions - Log Searches + &Put Last Name Before First Name + Put Last Name Before First Name - Timestamps in Message Log + &Log Searches + Log Searches - Show Non-Container Objects in Console Tree + &Timestamps in Message Log + Timestamps in Message Log - + + &Show Non-Container Objects in Console Tree + Show Non-Container Objects in Console Tree + + + + + Navigate Back (Alt + -) + + + + + Navigate Forward (Alt + =) + + + + + Refresh (Alt + 9) + + + + + Alt+9 + + + + Show Login - + + &Operations Masters + Operations Masters + + + + Navigate Up - + Alt+0 - + Navigate Back - + Alt+- - + Navigate Forward - + Alt+= - + Refresh - - Dev mode - - - - + Info - + Restart the app to switch to the selected language. MainWindowConnectionError + + + Connection Error + + Failed to connect to domain. @@ -1636,38 +1881,38 @@ - + Street Address: - + City: - + State/Province: - + Country: - + Telephone Number: - + Fax Number: - - + + Other... @@ -1690,7 +1935,7 @@ - + Change Manager Select Manager @@ -1698,16 +1943,6 @@ MembershipTab - - - Name - - - - - Folder - - Properties @@ -1723,41 +1958,54 @@ Set Primary Group... - - - Add Member - - - - - Add to Group - - - - - Primary group: - - - - - Can't remove because this group is a primary group to selected user. - - Add... + + + MembershipTabEdit - + + Name + + + + + Folder + + + + + Add Member + + + + + Add to Group + + + + + Can't remove because this group is a primary group to selected user. + + + + Can't remove because selected group is a primary group to this user. - + Error + + + Primary group: + + OSTab @@ -1767,100 +2015,128 @@ - + Operating System Version: - + Operatin System Service Pack: ObjectImpl - - - User - - - - - Computer - - - - - OU - - - - - Group - - - - - Find... - - - Move... + User + &User - Add to group... + Computer + &Computer - Enable + OU + &OU - Disable + Group - Reset password + Shared Folder - Reset account + inetOrgPerson - Edit UPN suffixes + Contact + + + + + Find... + Move... + + + + + Add to group... + + + + + Enable + + + + + Disable + + + + + Reset password + + + + + Reset account + + + + + Edit UPN suffixes + + + + New - + [Filtering enabled] - + Are you sure you want to delete this object? - + Add to Group - + + Edit UPN Suffixes + + + + + Are you sure you want to reset this account? + + + + Query may be out of date @@ -1873,33 +2149,33 @@ - + Object class: - + Created: - + Changed: Changed - + USN created: - + USN changed: - + Protect against deletion: @@ -1977,16 +2253,6 @@ OrganizationTab - - - Name - - - - - Folder - - Job Title: @@ -2013,6 +2279,19 @@ + + OrganizationTabEdit + + + Name + + + + + Folder + + + PasswordDialog @@ -2042,7 +2321,12 @@ - + + Show password + + + + Option is unavailable because a conflicting account option is currently enabled. @@ -2050,18 +2334,24 @@ PasswordEdit - + Passwords don't match! - - + + + Error - + + Password cannot be empty. + + + + Password contains invalid characters @@ -2079,12 +2369,12 @@ - + Are you sure you want to delete this policy and all of it's links? - + Add Link @@ -2180,95 +2470,93 @@ PropertiesDialog - + Properties - + %1 Properties "%1" Properties - + General - + Object - + Attributes - + Account - + Organization - + Telephones - + Profile - + + LAPS + + + + Security - + Members - - + Member of - - + Managed by - + Group policy - - Links to - - - - + Operating System - + Delegation - + Address @@ -2281,28 +2569,28 @@ - - + + General - + Account - + Address - + Profile - + Organization @@ -2315,22 +2603,12 @@ - - Apply current changes - - - - - Discard changes - - - - + You're switching to attributes tab, while another tab has unapplied changes. Choose to apply or discard those changes. - + You're switching from attributes tab, while it has unapplied changes. Choose to apply or discard those changes. @@ -2343,12 +2621,12 @@ - + Can't set "%1" when "%2" is set. - + Error @@ -2380,6 +2658,22 @@ &Import query... + + + + Error + + + + + Can't cut and paste query folder into itself. + + + + + There's already an item with this name. + + QueryItemImpl @@ -2408,19 +2702,20 @@ - Logon name (pre-Windows 2000): + Group name (pre-Windows 2000): + Logon name (pre-Windows 2000): - RenameObjectDialog + RenameObjectHelper - + Object %1 was renamed. - + Failed to rename object %1 @@ -2459,7 +2754,7 @@ - + Display name: Display name @@ -2475,7 +2770,7 @@ - + Full name: @@ -2490,6 +2785,42 @@ + + SamNameEdit + + + Input field for Logon name (pre-Windows 2000) contains one or more of the following illegal characters: @ " [ ] : ; | = + * ? < > / \ , + + + + + Error + + + + + SecuritySortWarningDialog + + + Warning + + + + + This object's security descriptor contains ACL that has incorrect order. Fix order to proceed with editing. If order is not fixed, Security tab will be read only. + + + + + Fix order + + + + + Cancel + + + SecurityTab @@ -2512,198 +2843,36 @@ Permissions + + + SecurityTabEdit - - Full control - - - - - Read - - - - - Write - - - - - Delete - - - - - Delete subtree - - - - - Create child - - - - - Delete child - - - - - Allowed to authenticate - - - - - Change password - - - - - Receive as - - - - - Reset password - - - - - Send as - - - - - Read Account restrictions - - - - - Write Account restrictions - - - - - Read general info - - - - - Write general info - - - - - Read group membership - - - - - Read logon info - - - - - Write logon info - - - - - Read personal info - - - - - Write personal info - - - - - Read phone and mail options - - - - - Write phone and mail options - - - - - Read private info - - - - - Write private info - - - - - Read public info - - - - - Write public info - - - - - Read remote access info - - - - - Write remote access info - - - - - Read terminal server license server - - - - - Write terminal server license server - - - - - Read web info - - - - - Write web info - - - - + Name - + Allowed - + Denied - + Add Trustee - + Error - + Failed to add some trustee's because they are already in the list. @@ -2724,6 +2893,11 @@ Select + + + All + + SelectContainerDialog @@ -2760,17 +2934,17 @@ - + Name - + Type - + Folder @@ -2805,24 +2979,30 @@ - - - + + + + Error - + + You must select at least one object. + + + + Failed to find any matches. - + Selected object is already in the list. - + This selection accepts only one object. Remove extra objects to proceed. @@ -2856,6 +3036,22 @@ + + StringListEdit + + + Edit values + + + + + StringOtherEdit + + + Edit other values + + + TelephonesTab @@ -2909,18 +3105,18 @@ UpnEdit - - UPN may not be empty. + + Input field for User Principal Name contains one or more of the following illegal characters: # , + " \ < > (leading space) (trailing space) - - + + Error - + The specified user logon name already exists. @@ -2928,23 +3124,15 @@ country_widget - + None - - general_other_tab.cpp - - - Failed to load object information. Check your connection. - - - object_impl - + %n object(s) %n object @@ -2955,15 +3143,20 @@ object_impl.cpp - + Failed to connect to server while searching for objects. + + + Could not load all objects. Increase object display limit in Filter Options or reduce number of objects by applying a filter. Filter Options is accessible from main window's menubar via the "View" menu. + + policy_root_impl - + Group Policy Objects @@ -2971,7 +3164,7 @@ query - + Saved Queries @@ -2979,30 +3172,30 @@ query.cpp - + Name may not be empty - - - - + + + + Error - + There's already an item with this name. - + Names cannot contain "/". - + Query file is corrupted. @@ -3010,25 +3203,33 @@ query_folder.cpp - + Name - + Description + + query_folder_impl.cpp + + + Are you sure you want to delete this item? + + + query_item_impl.cpp - + Import Query - + JSON (*.json) @@ -3039,4 +3240,17 @@ + + utils.cpp + + + Input field for Name contains one or more of the following illegal characters: # , + " \ < > ; = (leading space) (trailing space) (leading question mark) + + + + + Error + + + diff --git a/src/admc/admc_ru.ts b/src/admc/admc_ru.ts index 27507b25..f606dd0b 100644 --- a/src/admc/admc_ru.ts +++ b/src/admc/admc_ru.ts @@ -14,14 +14,9 @@ О приложении ADMC - - ADMC is a tool for Active Directory administration. - ADMC - это инструмент для администрирования Active Directory. - - - - Copyright (C) 2021 BaseALT Ltd. - Авторские права: ООО «Базальт СПО», 2021. + + Copyright (C) 2022 BaseALT Ltd. + Авторские права: ООО «Базальт СПО», 2022. @@ -146,13 +141,13 @@ - Don't require Kerberos pre-authentication - Не требовать предварительной аутентификации Kerberos + Use Kerberos DES encryption types for this account + Использовать Керберос DES тип шифрования для этой учетной записи - Trusted for delegation - Доверять делегирование + Don't require Kerberos pre-authentication + Не требовать предварительной аутентификации Kerberos @@ -219,44 +214,13 @@ AttributeDialog - + Attribute: %1 Атрибут: %1 - - - Edit - Изменить - - - - View - Просмотреть - - - - %1 Multi-Valued %2 - This is a dialog title for attribute editors. Example: "Edit Multi-Valued String" - %1 %2 (Многозначный) - AttributesTab - - - Name - Имя - - - - Value - Значение - - - - Type - Тип - Edit... @@ -273,6 +237,40 @@ Фильтр + + AttributesTabEdit + + + Name + Имя + + + + Value + Значение + + + + Type + Тип + + + + Edit + Изменить + + + + View + Просмотреть + + + + %1 Multi-Valued %2 + This is a dialog title for attribute editors. Example: "Edit Multi-Valued String" + %1 %2 (Многозначный) + + AttributesTabFilterMenu @@ -329,30 +327,6 @@ Без значения - - ChangeDCDialog - - - Change Domain Controller - Change domain controller - Сменить контроллер домена - - - - Select: - Выбрать: - - - - Custom: - Ввести другой: - - - - Save this setting - Сохранить выбор - - ChangelogDialog @@ -373,6 +347,11 @@ Select Classes Выбор классов + + + All + Все + ClassFilterWidget @@ -448,25 +427,30 @@ Фильтровать объекты - + Show all Показывать все - + Show only these types Показывать только эти типы - + Create custom Создать настраиваемый - + Custom Настраиваемый + + + Object display limit: + Лимит отображения объектов: + ConsoleWidget @@ -529,6 +513,39 @@ Имя для входа (до Windows 2000): + + CreateContactDialog + + + Create Contact + Создать контакт + + + + First name: + Имя: + + + + Last name: + Фамилия: + + + + Initials: + Инициалы: + + + + Full name: + Полное имя: + + + + Display name: + Отображаемое имя: + + CreateGroupDialog @@ -543,8 +560,9 @@ - Logon name (pre-Windows 2000): - Имя для входа (до Windows 2000): + Group name (pre-Windows 2000): + Logon name (pre-Windows 2000): + Имя группы (до Windows 2000): @@ -576,14 +594,14 @@ - CreateObjectDialog + CreateObjectHelper - + Failed to create object %1 Не удалось создать объект %1 - + Object %1 was created Объект %1 был создан @@ -602,17 +620,17 @@ Имя: - + New Group Policy Object Новый объект групповой политики - + Error Ошибка - + Group Policy Object with this name already exists. Объект групповой политики с этим именем уже существует. @@ -641,7 +659,7 @@ Новая папка - + New Folder Новая папка @@ -654,6 +672,24 @@ Создать запрос + + CreateSharedFolderDialog + + + Create Shared Folder + Создать общую папку + + + + Name: + Имя: + + + + Network path: + Сетевой путь: + + CreateUserDialog @@ -702,30 +738,40 @@ Подтвердите пароль: - + + Show password + Показывать пароль + + + Account options: Параметры учётной записи: - + User must change password on next logon Пользователь должен сменить пароль при следующем входе в систему - + User cannot change password Пользователь не может изменить пароль - + Don't expire password Пароль не истекает - + Account disabled Учётная запись отключена + + + Create %1 + Создать объект - %1 + CustomizeColumnsDialog @@ -816,6 +862,14 @@ Произошли ошибки + + ErrorTab + + + Failed to load object information. Check your connection. + Не удалось загрузить информацию об объекте. Проверьте соединение. + + ExpiryWidget @@ -829,6 +883,77 @@ Конец: + + FSMODialog + + + Operations Masters + Мастера Операций + + + + Domain DNS + DNS домена + + + + Forest DNS + DNS леса + + + + PDC Emulation + PDC эмуляция + + + + Schema + Схема + + + + Domain Naming + Имена домена + + + + Infrastructure + Инфраструктура + + + + Rid Allocation + RID распределение + + + + FSMOTab + + + Current master: + Текущий мастер: + + + + Change to: + Изменить на: + + + + Change + Изменить + + + + Error + Ошибка + + + + This machine is already a master for this role. Switch to a different machine in Connection Options to change master. + Эта машина уже является мастером этой роли. Переключитесь на другой компьютер в опциях подключения, чтобы изменить мастер. + + FilterDialog @@ -966,31 +1091,36 @@ - Icons - Значки + &Icons + Icons + &Значки - List - Список + &List + List + &Список - Detail - Подробно + &Detail + Detail + &Подробно - Customize Columns - Настроить колонки + &Customize Columns + Customize Columns + &Настроить колонки - Description Bar - Область описания + &Description Bar + Description Bar + &Область описания - + Find results Результаты поиска @@ -1008,13 +1138,13 @@ Имя узла DNS: - + Description: Description Описание: - + Location: Местонахождение: @@ -1023,31 +1153,32 @@ GeneralGroupTab - Logon name (pre-Windows 2000): - Имя для входа (до Windows 2000): + Group name (pre-Windows 2000): + Logon name (pre-Windows 2000): + Имя группы (до Windows 2000): - + Description: Описание: - + E-mail: Электронная почта: - + Notes: Примечания: - + Group Scope: Область группы: - + Group Type: Тип группы: @@ -1119,31 +1250,47 @@ Создан: - + Modified: Изменен: - + User version: Пользовательская версия: - + Computer version: Машинная версия: - + Unique ID: Уникальный ID: + + + GeneralPolicyTabEdit - + unknown неизвестно + + GeneralSharedFolderTab + + + Description: + Описание: + + + + Keywords + Ключевые слова + + GeneralUserMultiTab @@ -1241,31 +1388,8 @@ Другие... - - GpoLinksTab - - - Name - Имя - - GroupPolicyTab - - - Name - Имя - - - - Disabled - Отключено - - - - Enforced - Принудительно - Add... @@ -1282,32 +1406,68 @@ Block policy inheritance Заблокировать наследование политик + + + GroupPolicyTabEdit - + + Name + Имя + + + + Disabled + Отключено + + + + Enforced + Принудительно + + + Remove link Удалить связь - + Move up Переместить вверх - + Move down Переместить вниз - + Not found Не найдено - + The GPO for this link could not be found. It maybe have been recently created and is being replicated or it could have been deleted. Не удалось найти объект групповой политики для этой связи. Возможно, он был недавно создан и реплицируется, а может быть, был удалён. + + LAPSTab + + + Password: + Пароль: + + + + Password expires: + Пароль истекает: + + + + Reset expiry + Сбросить срок истекания + + ListAttributeDialog @@ -1325,6 +1485,21 @@ Remove Удалить + + + Error + Ошибка + + + + One or more values are empty. Edit or remove them to proceed. + Одна или более значений пуста. Редактируйте или удалите эти значения чтобы продолжить. + + + + Add Value + Добавить значение + LogonComputersDialog @@ -1361,6 +1536,16 @@ Edit Logon Hours Изменить время входа + + + Logon allowed: + Вход разрешен: + + + + Logon denied: + Вход запрещён: + Sunday @@ -1397,12 +1582,12 @@ Суббота - + Local time Местное время - + UTC time Время UTC @@ -1411,197 +1596,257 @@ MainWindow - File - Файл + &File + File + &Файл - - Action - Действие + + &Action + Action + &Действие - - View - Вид + + &View + View + &Вид - - Preferences - Настройки + + &Preferences + Preferences + &Настройки - - Language - Язык + + &Language + Language + &Язык - - Help - Помощь + + &Help + Help + &Помощь - + Tool Bar Панель инструментов - + Message Log Журнал сообщений - - Connection Options - Параметры подключения + + &Connection Options + Connection Options + &Параметры подключения - - Quit - Выйти + + &Quit + Quit + &Выйти - + Ctrl+Q Ctrl+Q - - Manual - Руководство + + &Manual + Manual + &Руководство - Changelog - Журнал изменений + Manual (Alt + 8) + Помощь (Alt + 8) - - About ADMC - О приложении ADMC + + Alt+8 + Alt+8 + + + + &Changelog + Changelog + &Журнал изменений - Icons - Значки + &About ADMC + About ADMC + &О приложении ADMC - List - Список + &Icons + Icons + &Значки - Detail - Подробно + &List + List + &Список - - Console Tree - Дерево консоли + + &Detail + Detail + &Подробно - Description Bar - Область описания + &Console Tree + Console Tree + &Дерево консоли - - Customize Columns... - Настроить колонки... + + Description &Bar + Description Bar + &Область описания - - Filter Objects... - Фильтровать объекты... + + C&ustomize Columns... + Customize Columns... + &Настроить колонки... - Advanced Features - Дополнительные возможности + &Filter Objects... + Filter Objects... + &Фильтровать объекты... - Confirm Actions - Подтверждать действия + &Advanced Features + Advanced Features + &Дополнительные возможности - Put Last Name Before First Name - Ставить фамилию перед именем + &Confirm Actions + Confirm Actions + &Подтверждать действия - Log Searches - Вносить информацию о поиске в журнал сообщений + &Put Last Name Before First Name + Put Last Name Before First Name + &Ставить фамилию перед именем - Timestamps in Message Log - Метки времени в журнале сообщений + &Log Searches + Log Searches + &Вносить информацию о поиске в журнал сообщений - Show Non-Container Objects in Console Tree - Показывать неконтейнерные объекты в дереве консоли + &Timestamps in Message Log + Timestamps in Message Log + &Метки времени в журнале сообщений - + + &Show Non-Container Objects in Console Tree + Show Non-Container Objects in Console Tree + П&оказывать неконтейнерные объекты в дереве консоли + + + + Navigate Back (Alt + -) + Назад (Alt + -) + + + + Navigate Forward (Alt + =) + Вперед (Alt + =) + + + + Refresh (Alt + 9) + Обновить (Alt + 9) + + + + Alt+9 + Alt+9 + + + Show Login Показывать логин - + + &Operations Masters + Operations Masters + &Мастера Операций + + + Navigate Up Наверх - + Alt+0 Alt+0 - + Navigate Back Назад - + Alt+- Alt+- - + Navigate Forward Вперед - + Alt+= Alt+= - + Refresh Обновить - - Dev mode - Режим разработки - - - + Info Информация - + Restart the app to switch to the selected language. Для переключения на выбранный язык необходимо перезапустить приложение. MainWindowConnectionError + + + Connection Error + Ошибка соединения + Failed to connect to domain. @@ -1636,38 +1881,38 @@ Расположение офиса: - + Street Address: Улица, дом: - + City: Город: - + State/Province: Регион: - + Country: Страна: - + Telephone Number: Номер телефона: - + Fax Number: Номер факса: - - + + Other... Другие... @@ -1690,7 +1935,7 @@ Очистить - + Change Manager Select Manager Изменить руководителя @@ -1698,16 +1943,6 @@ MembershipTab - - - Name - Имя - - - - Folder - Папка - Properties @@ -1723,41 +1958,54 @@ Set Primary Group... Задать основную группу... - - - Add Member - Добавить участника - - - - Add to Group - Добавить в группу - - - - Primary group: - Основная группа: - - - - Can't remove because this group is a primary group to selected user. - Не удалось удалить группу, так как она является основной для выбранного пользователя. - Add... Добавить... + + + MembershipTabEdit - + + Name + Имя + + + + Folder + Папка + + + + Add Member + Добавить участника + + + + Add to Group + Добавить в группу + + + + Can't remove because this group is a primary group to selected user. + Не удалось удалить группу, так как она является основной для выбранного пользователя. + + + Can't remove because selected group is a primary group to this user. Не удалось удалить выбранную группу, так как она является основной для этого пользователя. - + Error Ошибка + + + Primary group: + Основная группа: + OSTab @@ -1767,12 +2015,12 @@ Операционная система: - + Operating System Version: Версия операционное системы: - + Operatin System Service Pack: Пакет обновления операционной системы: @@ -1780,87 +2028,115 @@ ObjectImpl - + User + &User Пользователь - + Computer + &Computer Компьютер - + OU + &OU Подразделение - + Group Группа - + + Shared Folder + Общая папка + + + + inetOrgPerson + inetOrgPerson + + + + Contact + Контакт + + + Find... Найти... - + Move... Переместить... - + Add to group... Добавить в группу... - + Enable Включить - + Disable Отключить - + Reset password Сбросить пароль - + Reset account Сбросить учётную запись - + Edit UPN suffixes Изменить суффиксы UPN - + New Создать - + [Filtering enabled] [Фильтр включён] - + Are you sure you want to delete this object? Удалить этот объект? - + Add to Group Добавить в группу - + + Edit UPN Suffixes + Изменить суффиксы UPN + + + + Are you sure you want to reset this account? + Вы точно хотиту сбросить эту учетную запись? + + + Query may be out of date Запрос может быть устаревшим @@ -1873,33 +2149,33 @@ Различающееся имя: - + Object class: Класс объекта: - + Created: Создан: - + Changed: Changed Изменён: - + USN created: USN создан: - + USN changed: USN изменен: - + Protect against deletion: Защитить от удаления: @@ -1977,16 +2253,6 @@ OrganizationTab - - - Name - Имя - - - - Folder - Папка - Job Title: @@ -2010,7 +2276,20 @@ Reports: - Отчёты: + Прямые подчинненые: + + + + OrganizationTabEdit + + + Name + Имя + + + + Folder + Папка @@ -2042,7 +2321,12 @@ Разблокировать учётную запись - + + Show password + Показывать пароль + + + Option is unavailable because a conflicting account option is currently enabled. Опция недоступна, поскольку в настоящее время включена конфликтующая опция учетной записи. @@ -2050,18 +2334,24 @@ PasswordEdit - + Passwords don't match! Пароли не совпадают! - - + + + Error Ошибка - + + Password cannot be empty. + Пароль не может быть пустым. + + + Password contains invalid characters Пароль содержит недопустимые символы @@ -2079,12 +2369,12 @@ Изменить... - + Are you sure you want to delete this policy and all of it's links? Удалить эту политику и все её связи? - + Add Link Добавление связи @@ -2180,95 +2470,93 @@ PropertiesDialog - + Properties Свойства - + %1 Properties "%1" Properties %1 — свойства - + General Общее - + Object Объект - + Attributes Атрибуты - + Account Учётная запись - + Organization Организация - + Telephones Телефоны - + Profile Профиль - + + LAPS + LAPS + + + Security Безопасность - + Members Участники - - + Member of Группы - - + Managed by Руководство - + Group policy Групповая политика - - Links to - Связи - - - + Operating System Операционная система - + Delegation Делегирование - + Address Адрес @@ -2281,28 +2569,28 @@ Свойства для нескольких объектов - - + + General Общее - + Account Учётная запись - + Address Адрес - + Profile Профиль - + Organization Организация @@ -2315,22 +2603,12 @@ Предупреждение - - Apply current changes - Применить текущие изменения - - - - Discard changes - Отменить изменения - - - + You're switching to attributes tab, while another tab has unapplied changes. Choose to apply or discard those changes. При переключении на вкладку атрибутов на предыдущей вкладке не были применены изменения. Выберите, применить или отменить эти изменения. - + You're switching from attributes tab, while it has unapplied changes. Choose to apply or discard those changes. На вкладке атрибутов не были применены изменения. Выберите, применить или отменить эти изменения. @@ -2343,12 +2621,12 @@ Подтверждение действия - + Can't set "%1" when "%2" is set. Невозможно установить «%1», если задано «%2». - + Error Ошибка @@ -2380,6 +2658,22 @@ &Import query... &Импортировать запрос... + + + + Error + Ошибка + + + + Can't cut and paste query folder into itself. + Невозможно вырезать и вставить папку запросов в саму себя. + + + + There's already an item with this name. + Элемент с этим именем уже существует. + QueryItemImpl @@ -2408,19 +2702,20 @@ - Logon name (pre-Windows 2000): - Имя для входа (до Windows 2000): + Group name (pre-Windows 2000): + Logon name (pre-Windows 2000): + Имя группы (до Windows 2000): - RenameObjectDialog + RenameObjectHelper - + Object %1 was renamed. Объект %1 был переименован. - + Failed to rename object %1 Не удалось переименовать объект %1 @@ -2459,7 +2754,7 @@ Переименовать пользователя - + Display name: Display name Отображаемое имя: @@ -2475,7 +2770,7 @@ Фамилия: - + Full name: Полное имя: @@ -2490,6 +2785,42 @@ Имя для входа (до Windows 2000): + + SamNameEdit + + + Input field for Logon name (pre-Windows 2000) contains one or more of the following illegal characters: @ " [ ] : ; | = + * ? < > / \ , + Поле ввода "Имя для входа (до Windows 2000)" содержит один или несколько из следующих недопустимых символов: @ " [ ] : ; | = + * ? < > / \ , + + + + Error + Ошибка + + + + SecuritySortWarningDialog + + + Warning + Предупреждение + + + + This object's security descriptor contains ACL that has incorrect order. Fix order to proceed with editing. If order is not fixed, Security tab will be read only. + Дескриптор безопасности этого объекта содержит лист записей с неправильным порядком. Исправьте порядок, чтобы продолжить редактирование. Если порядок не будет исправлен, вкладка «Безопасность» будет доступна только для чтения. + + + + Fix order + Исправить порядок + + + + Cancel + Отменить + + SecurityTab @@ -2512,198 +2843,36 @@ Permissions Разрешения + + + SecurityTabEdit - - Full control - Полный доступ - - - - Read - Чтение - - - - Write - Запись - - - - Delete - Удаление - - - - Delete subtree - Удаление поддерева - - - - Create child - Создание дочернего объекта - - - - Delete child - Удаление дочернего объекта - - - - Allowed to authenticate - Разрешение на проверку подлинности - - - - Change password - Изменение пароля - - - - Receive as - Получить как - - - - Reset password - Сбросить пароль - - - - Send as - Отправить как - - - - Read Account restrictions - Чтение ограничений учётной записи - - - - Write Account restrictions - Запись ограничений учётной записи - - - - Read general info - Чтение общей информации - - - - Write general info - Запись общей информации - - - - Read group membership - Чтение сведений о членстве в группе - - - - Read logon info - Чтение информации о входе в систему - - - - Write logon info - Запись информации о входе в систему - - - - Read personal info - Чтение личной информации - - - - Write personal info - Запись личной информации - - - - Read phone and mail options - Чтение параметров телефона и почты - - - - Write phone and mail options - Запись параметров телефона и почты - - - - Read private info - Чтение частной информации - - - - Write private info - Запись частной информации - - - - Read public info - Чтение общедоступной информации - - - - Write public info - Запись общедоступной информации - - - - Read remote access info - Чтение информации об удалённом доступе - - - - Write remote access info - Запись информации об удалённом доступе - - - - Read terminal server license server - Чтение сервера терминалов и сервера лицензий - - - - Write terminal server license server - Запись сервера терминалов и сервера лицензий - - - - Read web info - Чтение веб-информации - - - - Write web info - Запись веб-информации - - - + Name Имя - + Allowed Разрешено - + Denied Запрещено - + Add Trustee Добавить доверенное лицо - + Error Ошибка - + Failed to add some trustee's because they are already in the list. Не удалось добавить некоторых доверенных лиц, так как они уже имеются в списке. @@ -2724,6 +2893,11 @@ Select Выбрать... + + + All + Все + SelectContainerDialog @@ -2760,17 +2934,17 @@ Добавить - + Name Имя - + Type Тип - + Folder Папка @@ -2805,24 +2979,30 @@ Выбранные объекты: - - - + + + + Error Ошибка - + + You must select at least one object. + Требуется выбрать хотя бы один объект. + + + Failed to find any matches. Совпадений не найдено. - + Selected object is already in the list. Выбранный объект уже имеется в списке. - + This selection accepts only one object. Remove extra objects to proceed. Здесь можно выбрать только один объект. Для продолжения уберите лишние объекты. @@ -2856,6 +3036,22 @@ Выбор известных доверенных лиц + + StringListEdit + + + Edit values + Редактировать значения + + + + StringOtherEdit + + + Edit other values + Редактировать другие значения + + TelephonesTab @@ -2909,18 +3105,18 @@ UpnEdit - - UPN may not be empty. - UPN не может быть пустым. + + Input field for User Principal Name contains one or more of the following illegal characters: # , + " \ < > (leading space) (trailing space) + Поле ввода "Имя для входа" содержит один или несколько из следующих недопустимых символов: # , + " \ < > (пробел в начале) (пробел в конце) - - + + Error Ошибка - + The specified user logon name already exists. Указанное имя пользователя для входа уже существует. @@ -2928,23 +3124,15 @@ country_widget - + None Нет - - general_other_tab.cpp - - - Failed to load object information. Check your connection. - Не удалось загрузить информацию об объекте. Проверьте соединение. - - object_impl - + %n object(s) %n объект @@ -2956,15 +3144,20 @@ object_impl.cpp - + Failed to connect to server while searching for objects. Не удалось подключиться к серверу во время поиска объектов. + + + Could not load all objects. Increase object display limit in Filter Options or reduce number of objects by applying a filter. Filter Options is accessible from main window's menubar via the "View" menu. + Не удалось загрузить все объекты. Увеличьте лимит отображения объектов в опциях или уменьшите количество объектов, применив фильтр. Параметры фильтра доступны в меню «Вид». + policy_root_impl - + Group Policy Objects Объекты групповой политики @@ -2972,7 +3165,7 @@ query - + Saved Queries Сохранённые запросы @@ -2980,30 +3173,30 @@ query.cpp - + Name may not be empty Имя не может быть пустым - - - - + + + + Error Ошибка - + There's already an item with this name. Элемент с этим именем уже существует. - + Names cannot contain "/". Имена не могут содержать «/». - + Query file is corrupted. Файл запроса повреждён. @@ -3011,25 +3204,33 @@ query_folder.cpp - + Name Имя - + Description Описание + + query_folder_impl.cpp + + + Are you sure you want to delete this item? + Удалить этот элемент? + + query_item_impl.cpp - + Import Query Импортировать запрос - + JSON (*.json) JSON (*.json) @@ -3040,4 +3241,17 @@ Экспортировать запрос + + utils.cpp + + + Input field for Name contains one or more of the following illegal characters: # , + " \ < > ; = (leading space) (trailing space) (leading question mark) + Поле ввода "Имя" содержит один или несколько из следующих недопустимых символов: # , + " \ < > ; = (пробел в начале) (пробел в конце) (? в начале) + + + + Error + Ошибка + + diff --git a/src/admc/attribute_dialogs/attribute_dialog.cpp b/src/admc/attribute_dialogs/attribute_dialog.cpp index c4c1044c..b27fe60d 100644 --- a/src/admc/attribute_dialogs/attribute_dialog.cpp +++ b/src/admc/attribute_dialogs/attribute_dialog.cpp @@ -30,29 +30,6 @@ AttributeDialog::AttributeDialog(const QString &attribute, const bool read_only, : QDialog(parent) { m_attribute = attribute; m_read_only = read_only; - - const QString title = [&]() { - const AttributeType type = g_adconfig->get_attribute_type(m_attribute); - const bool single_valued = g_adconfig->get_attribute_is_single_valued(attribute); - - const QString title_action = [&]() { - if (m_read_only) { - return tr("Edit"); - } else { - return tr("View"); - } - }(); - - const QString title_attribute = attribute_type_display_string(type); - - if (single_valued) { - return QString("%1 %2").arg(title_action, title_attribute); - } else { - return QString(tr("%1 Multi-Valued %2", "This is a dialog title for attribute editors. Example: \"Edit Multi-Valued String\"")).arg(title_action, title_attribute); - } - }(); - - setWindowTitle(title); } QString AttributeDialog::get_attribute() const { diff --git a/src/admc/attribute_dialogs/list_attribute_dialog.cpp b/src/admc/attribute_dialogs/list_attribute_dialog.cpp index a81f4e40..bb8e6e61 100644 --- a/src/admc/attribute_dialogs/list_attribute_dialog.cpp +++ b/src/admc/attribute_dialogs/list_attribute_dialog.cpp @@ -37,6 +37,9 @@ ListAttributeDialog::ListAttributeDialog(const QList &value_list, co setAttribute(Qt::WA_DeleteOnClose); + // Default value indiciating no max length + max_length = 0; + AttributeDialog::load_attribute_label(ui->attribute_label); ui->add_button->setVisible(!read_only); @@ -60,6 +63,35 @@ ListAttributeDialog::~ListAttributeDialog() { delete ui; } +void ListAttributeDialog::accept() { + const bool contains_empty_values = [&]() { + const bool is_bool = (g_adconfig->get_attribute_type(get_attribute()) == AttributeType_Boolean); + if (is_bool) { + return false; + } + + const QList value_list = get_value_list(); + + for (const QByteArray &value : value_list) { + const QString value_string = QString(value); + const bool value_is_all_spaces = (value.count(' ') == value.length()); + const bool value_is_empty = value.isEmpty() || value_is_all_spaces; + + if (value_is_empty) { + return true; + } + } + + return false; + }(); + + if (contains_empty_values) { + message_box_warning(this, tr("Error"), tr("One or more values are empty. Edit or remove them to proceed.")); + } else { + QDialog::accept(); + } +} + void ListAttributeDialog::on_add_button() { AttributeDialog *dialog = [this]() -> AttributeDialog * { const bool is_bool = (g_adconfig->get_attribute_type(get_attribute()) == AttributeType_Boolean); @@ -70,14 +102,19 @@ void ListAttributeDialog::on_add_button() { if (is_bool) { return new BoolAttributeDialog(value_list, attribute, read_only, this); } else { - return new StringAttributeDialog(value_list, attribute, read_only, this); + auto string_dialog = new StringAttributeDialog(value_list, attribute, read_only, this); + string_dialog->set_max_length(max_length); + + return string_dialog; } }(); + dialog->setWindowTitle(tr("Add Value")); dialog->open(); connect( dialog, &QDialog::accepted, + this, [this, dialog]() { const QList new_values = dialog->get_value_list(); @@ -110,9 +147,18 @@ QList ListAttributeDialog::get_value_list() const { return new_values; } +void ListAttributeDialog::set_value_max_length(const int max_length_arg) { + max_length = max_length_arg; +} + void ListAttributeDialog::add_value(const QByteArray value) { const QString text = bytes_to_string(value); - ui->list_widget->addItem(text); + const QList find_results = ui->list_widget->findItems(text, Qt::MatchExactly); + const bool value_already_exists = !find_results.isEmpty(); + + if (!value_already_exists) { + ui->list_widget->addItem(text); + } } ListAttributeDialogType ListAttributeDialog::get_type() const { diff --git a/src/admc/attribute_dialogs/list_attribute_dialog.h b/src/admc/attribute_dialogs/list_attribute_dialog.h index 7e03aa9c..8fe99999 100644 --- a/src/admc/attribute_dialogs/list_attribute_dialog.h +++ b/src/admc/attribute_dialogs/list_attribute_dialog.h @@ -44,9 +44,21 @@ public: ListAttributeDialog(const QList &value_list, const QString &attribute, const bool read_only, QWidget *parent); ~ListAttributeDialog(); + void accept() override; + QList get_value_list() const override; + // Sets max length for input of individual values + // of the list attribute, if it's of string type. + // Note that this *does not* apply to the list + // attribute itself. By default the limit from + // schema is used, but most list attributes are + // unlimited. + void set_value_max_length(const int max_length); + private: + int max_length; + void on_add_button(); void on_remove_button(); void add_value(const QByteArray value); diff --git a/src/admc/attribute_dialogs/string_attribute_dialog.cpp b/src/admc/attribute_dialogs/string_attribute_dialog.cpp index 16e6eb19..86c0da23 100644 --- a/src/admc/attribute_dialogs/string_attribute_dialog.cpp +++ b/src/admc/attribute_dialogs/string_attribute_dialog.cpp @@ -47,7 +47,7 @@ StringAttributeDialog::StringAttributeDialog(const QList &value_list const QString value_string = QString(value); ui->edit->setText(value_string); - settings_setup_dialog_geometry(SETTING_list_attribute_dialog_geometry, this); + settings_setup_dialog_geometry(SETTING_string_attribute_dialog_geometry, this); } StringAttributeDialog::~StringAttributeDialog() { @@ -64,3 +64,9 @@ QList StringAttributeDialog::get_value_list() const { return {new_value}; } } + +void StringAttributeDialog::set_max_length(const int max_length) { + if (max_length > 0) { + ui->edit->setMaxLength(max_length); + } +} diff --git a/src/admc/attribute_dialogs/string_attribute_dialog.h b/src/admc/attribute_dialogs/string_attribute_dialog.h index 9f086aad..09197d0e 100644 --- a/src/admc/attribute_dialogs/string_attribute_dialog.h +++ b/src/admc/attribute_dialogs/string_attribute_dialog.h @@ -37,6 +37,11 @@ public: ~StringAttributeDialog(); QList get_value_list() const override; + + // NOTE: by default, most (not all) attributes have + // a pre-defined max length that is obtained from + // schema. Calling this f-n overrides it. + void set_max_length(const int max_length); }; #endif /* STRING_ATTRIBUTE_DIALOG_H */ diff --git a/src/admc/attribute_edits/account_option_edit.cpp b/src/admc/attribute_edits/account_option_edit.cpp index bfceb456..5a4403d4 100644 --- a/src/admc/attribute_edits/account_option_edit.cpp +++ b/src/admc/attribute_edits/account_option_edit.cpp @@ -29,8 +29,8 @@ #include #include -AccountOptionEdit::AccountOptionEdit(QCheckBox *check_arg, const AccountOption option_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) { +AccountOptionEdit::AccountOptionEdit(QCheckBox *check_arg, const AccountOption option_arg, QObject *parent) +: AttributeEdit(parent) { option = option_arg; check = check_arg; @@ -39,17 +39,13 @@ AccountOptionEdit::AccountOptionEdit(QCheckBox *check_arg, const AccountOption o this, &AttributeEdit::edited); } -void AccountOptionEdit::load_internal(AdInterface &ad, const AdObject &object) { +void AccountOptionEdit::load(AdInterface &ad, const AdObject &object) { UNUSED_ARG(ad); const bool option_is_set = object.get_account_option(option, g_adconfig); check->setChecked(option_is_set); } -void AccountOptionEdit::set_read_only(const bool read_only) { - check->setDisabled(read_only); -} - bool AccountOptionEdit::apply(AdInterface &ad, const QString &dn) const { const bool new_value = check->isChecked(); const bool success = ad.user_set_account_option(dn, option, new_value); @@ -74,6 +70,7 @@ void account_option_setup_conflicts(const QHash &che QObject::connect( subject, &QCheckBox::clicked, + blocker, [subject, blocker, subject_option, blocker_option]() { const bool conflict = (subject->isChecked() && blocker->isChecked()); if (conflict) { diff --git a/src/admc/attribute_edits/account_option_edit.h b/src/admc/attribute_edits/account_option_edit.h index b939726e..c36265c7 100644 --- a/src/admc/attribute_edits/account_option_edit.h +++ b/src/admc/attribute_edits/account_option_edit.h @@ -31,8 +31,10 @@ class QWidget; class AccountOptionEdit final : public AttributeEdit { Q_OBJECT public: - AccountOptionEdit(QCheckBox *check, const AccountOption option_arg, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + AccountOptionEdit(QCheckBox *check, const AccountOption option_arg, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + bool apply(AdInterface &ad, const QString &dn) const override; private: AccountOption option; diff --git a/src/admc/attribute_multi_edits/account_option_multi_edit.cpp b/src/admc/attribute_edits/account_option_multi_edit.cpp similarity index 83% rename from src/admc/attribute_multi_edits/account_option_multi_edit.cpp rename to src/admc/attribute_edits/account_option_multi_edit.cpp index 51f40e11..f901cc08 100644 --- a/src/admc/attribute_multi_edits/account_option_multi_edit.cpp +++ b/src/admc/attribute_edits/account_option_multi_edit.cpp @@ -18,7 +18,7 @@ * along with this program. If not, see . */ -#include "attribute_multi_edits/account_option_multi_edit.h" +#include "attribute_edits/account_option_multi_edit.h" #include "adldap.h" #include "attribute_edits/account_option_edit.h" @@ -26,13 +26,11 @@ #include -AccountOptionMultiEdit::AccountOptionMultiEdit(const QHash &check_map_arg, QCheckBox *check, QList &edits_out, QObject *parent) -: AttributeMultiEdit(check, edits_out, parent) { +AccountOptionMultiEdit::AccountOptionMultiEdit(const QHash &check_map_arg, QObject *parent) +: AttributeEdit(parent) { check_map = check_map_arg; account_option_setup_conflicts(check_map); - - set_enabled(false); } // NOTE: this is slightly inefficient because every account @@ -40,7 +38,7 @@ AccountOptionMultiEdit::AccountOptionMultiEdit(const QHash option_change_list = [&]() { QList out; @@ -77,9 +75,7 @@ bool AccountOptionMultiEdit::apply_internal(AdInterface &ad, const QString &targ } void AccountOptionMultiEdit::set_enabled(const bool enabled) { - if (!enabled) { - for (QCheckBox *check : check_map.values()) { - check->setChecked(false); - } + for (QCheckBox *check : check_map.values()) { + check->setEnabled(enabled); } } diff --git a/src/admc/attribute_multi_edits/account_option_multi_edit.h b/src/admc/attribute_edits/account_option_multi_edit.h similarity index 69% rename from src/admc/attribute_multi_edits/account_option_multi_edit.h rename to src/admc/attribute_edits/account_option_multi_edit.h index 3b9ac36c..1c35d28e 100644 --- a/src/admc/attribute_multi_edits/account_option_multi_edit.h +++ b/src/admc/attribute_edits/account_option_multi_edit.h @@ -21,18 +21,28 @@ #ifndef ACCOUNT_OPTION_MULTI_EDIT_H #define ACCOUNT_OPTION_MULTI_EDIT_H -#include "attribute_multi_edits/attribute_multi_edit.h" +/** + * Edit for editing account options of multiple objects + * at the same time. Needed because in this case all of + * the checks are grouped together, unlike the normal + * account option edit. + */ + +#include "attribute_edits/attribute_edit.h" #include "ad_defines.h" #include -class AccountOptionMultiEdit final : public AttributeMultiEdit { +class QCheckBox; + +class AccountOptionMultiEdit final : public AttributeEdit { Q_OBJECT public: - AccountOptionMultiEdit(const QHash &check_map_arg, QCheckBox *check, QList &edits_out, QObject *parent); + AccountOptionMultiEdit(const QHash &check_map, QObject *parent); - DECL_ATTRIBUTE_MULTI_EDIT_VIRTUALS(); + bool apply(AdInterface &ad, const QString &target) const override; + void set_enabled(const bool enabled) override; private: QHash check_map; diff --git a/src/admc/attribute_edits/attribute_edit.cpp b/src/admc/attribute_edits/attribute_edit.cpp index 0d283294..617c6537 100644 --- a/src/admc/attribute_edits/attribute_edit.cpp +++ b/src/admc/attribute_edits/attribute_edit.cpp @@ -20,34 +20,8 @@ #include "attribute_edits/attribute_edit.h" -#include "tabs/properties_tab.h" #include "utils.h" -#include - -AttributeEdit::AttributeEdit(QList *edits_out, QObject *parent) -: QObject(parent) { - if (edits_out != nullptr) { - if (edits_out->contains(this)) { - qDebug() << "ERROR: attribute edit added twice to list!"; - } else { - edits_out->append(this); - } - } - - m_modified = false; - connect( - this, &AttributeEdit::edited, - [this]() { - m_modified = true; - }); -} - -void AttributeEdit::load(AdInterface &ad, const AdObject &object) { - load_internal(ad, object); - m_modified = false; -} - bool AttributeEdit::verify(AdInterface &ad, const QString &dn) const { UNUSED_ARG(ad); UNUSED_ARG(dn); @@ -55,70 +29,50 @@ bool AttributeEdit::verify(AdInterface &ad, const QString &dn) const { return true; } -bool AttributeEdit::modified() const { - return m_modified; -} +bool AttributeEdit::verify(const QList &edit_list, AdInterface &ad, const QString &dn) { + for (auto edit : edit_list) { + const bool verify_success = edit->verify(ad, dn); -void AttributeEdit::set_modified(const bool modified) { - m_modified = modified; -} - -void AttributeEdit::reset_modified() { - m_modified = false; -} - -bool edits_verify(AdInterface &ad, QList edits, const QString &dn, const bool ignore_modified) { - for (auto edit : edits) { - if (edit->modified() || ignore_modified) { - const bool verify_success = edit->verify(ad, dn); - if (!verify_success) { - return false; - } + if (!verify_success) { + return false; } } return true; } -bool edits_apply(AdInterface &ad, QList edits, const QString &dn) { +bool AttributeEdit::apply(const QList &edit_list, AdInterface &ad, const QString &dn) { bool success = true; - for (auto edit : edits) { - if (edit->modified()) { - const bool apply_success = edit->apply(ad, dn); - if (apply_success) { - edit->reset_modified(); - } else { - success = false; - } + for (auto edit : edit_list) { + const bool apply_success = edit->apply(ad, dn); + + if (!apply_success) { + success = false; } } return success; } -void edits_load(QList edits, AdInterface &ad, const AdObject &object) { - for (auto edit : edits) { +void AttributeEdit::load(const QList &edit_list, AdInterface &ad, const AdObject &object) { + for (auto edit : edit_list) { edit->load(ad, object); } } -void edits_connect_to_tab(QList edits, PropertiesTab *tab) { - for (auto edit : edits) { - QObject::connect( - edit, &AttributeEdit::edited, - tab, &PropertiesTab::on_edit_edited); - } +void AttributeEdit::load(AdInterface &ad, const AdObject &object) { + UNUSED_ARG(ad); + UNUSED_ARG(object); } -void edits_set_read_only(QList edits, const bool read_only) { - for (AttributeEdit *edit : edits) { - edit->set_read_only(read_only); - } +bool AttributeEdit::apply(AdInterface &ad, const QString &dn) const { + UNUSED_ARG(ad); + UNUSED_ARG(dn); + + return true; } -void edits_set_modified(QList edits, const bool modified) { - for (AttributeEdit *edit : edits) { - edit->set_modified(modified); - } +void AttributeEdit::set_enabled(const bool enabled) { + UNUSED_ARG(enabled); } diff --git a/src/admc/attribute_edits/attribute_edit.h b/src/admc/attribute_edits/attribute_edit.h index 93a9c3ea..33ed5346 100644 --- a/src/admc/attribute_edits/attribute_edit.h +++ b/src/admc/attribute_edits/attribute_edit.h @@ -30,20 +30,34 @@ * are used to represent different data types. */ -class PropertiesTab; class AdInterface; class AdObject; class AttributeEdit : public QObject { Q_OBJECT public: - AttributeEdit(QList *edits_out, QObject *parent); - // Load state from object, used to initialize or reset edit - // Calls load_internal() implemented by subclasses - void load(AdInterface &ad, const AdObject &object); + // Verify edit. Verify process will stop on first + // failure. This is so that only one failure message is + // shown at a time. + static bool verify(const QList &edit_list, AdInterface &ad, const QString &dn); - virtual void set_read_only(const bool read_only) = 0; + // Applies edits. If one of the edits fails to apply + // midway, the apply process still continues. This is + // so that if more errors occur, they are all gathered + // together and presented to the user together. If + // process stopped on first error, the user would have + // to apply multiple times while fixing errors to see + // all of them. + static bool apply(const QList &edit_list, AdInterface &ad, const QString &dn); + + static void load(const QList &edit_list, AdInterface &ad, const AdObject &object); + + using QObject::QObject; + + // Load state from object, used to initialize or + // reset edit. + virtual void load(AdInterface &ad, const AdObject &object); // Verify current input. This is for the kinds of errors // that the server doesn't or can't check for. For @@ -53,67 +67,13 @@ public: // Apply current input by making a modification to the // AD server - virtual bool apply(AdInterface &ad, const QString &dn) const = 0; + virtual bool apply(AdInterface &ad, const QString &dn) const; - // Returns whether edit was edited by user. Rsets on - // load(). Note that this will be true if user EVER - // edited this edit. This won't become false if user - // manually undoes changes by retyping original value - // for StringEdit for example. - bool modified() const; - void set_modified(const bool modified); - - void reset_modified(); + virtual void set_enabled(const bool enabled); signals: // Emitted when edit was edited by user void edited(); - -protected: - virtual void load_internal(AdInterface &ad, const AdObject &object) = 0; - -private: - bool m_modified; }; -#define DECL_ATTRIBUTE_EDIT_VIRTUALS() \ - void set_read_only(const bool read_only) override; \ - bool apply(AdInterface &ad, const QString &dn) const override; \ - \ -protected: \ - void load_internal(AdInterface &ad, const AdObject &object) override; \ - \ -public: - -// Helper f-ns that iterate over edit lists for you -void edits_connect_to_tab(QList edits, PropertiesTab *tab); - -// NOTE: "ignore_modified" argument is solely for a edge -// case in CreateObjectDialog. If it's set to true, then edits are -// verified/applied regardless or whether they are modified -// or not - -// Verify all edits that were modified. Verify process will -// stop on first failure. This is so that only one failure -// message is shown at a time. -bool edits_verify(AdInterface &ad, QList edits, const QString &dn, const bool ignore_modified = false); - -// Applies all edits that were modified. If one of the edits -// fails to apply midway, the apply process still continues. -// This is so that if more errors occur, they are all -// gathered together and presented to the user together. If -// process stopped on first error, the user would have to -// apply multiple times while fixing errors to see all of -// them. -bool edits_apply(AdInterface &ad, QList edits, const QString &dn); - -void edits_load(QList edits, AdInterface &ad, const AdObject &object); - -// NOTE: not all edits might support read-only mode, see -// specific edit headers to verify that they implement -// set_read_only() -void edits_set_read_only(QList edits, const bool read_only); - -void edits_set_modified(QList edits, const bool modified); - #endif /* ATTRIBUTE_EDIT_H */ diff --git a/src/admc/attribute_edits/computer_sam_name_edit.cpp b/src/admc/attribute_edits/computer_sam_name_edit.cpp new file mode 100644 index 00000000..8435efe5 --- /dev/null +++ b/src/admc/attribute_edits/computer_sam_name_edit.cpp @@ -0,0 +1,88 @@ +/* + * 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 "attribute_edits/computer_sam_name_edit.h" + +#include "adldap.h" +#include "globals.h" +#include "utils.h" +#include "attribute_edits/sam_name_edit.h" + +#include +#include + +ComputerSamNameEdit::ComputerSamNameEdit(QLineEdit *edit_arg, QLineEdit *domain_edit, QObject *parent) +: AttributeEdit(parent) { + edit = edit_arg; + + edit->setMaxLength(SAM_NAME_COMPUTER_MAX_LENGTH); + + const QString domain_text = []() { + const QString domain = g_adconfig->domain(); + const QString domain_name = domain.split(".")[0]; + const QString out = domain_name + "\\"; + + return out; + }(); + + domain_edit->setText(domain_text); + + connect( + edit, &QLineEdit::textChanged, + this, &AttributeEdit::edited); +} + +void ComputerSamNameEdit::load(AdInterface &ad, const AdObject &object) { + UNUSED_ARG(ad); + + // NOTE: display value without the '$' at the end + const QString value = [&]() { + QString out = object.get_string(ATTRIBUTE_SAM_ACCOUNT_NAME); + + if (out.endsWith('$')) { + out.chop(1); + } + + return out; + }(); + edit->setText(value); +} + +// NOTE: requirements are from here +// https://social.technet.microsoft.com/wiki/contents/articles/11216.active-directory-requirements-for-creating-objects.aspx#Note_Regarding_the_quot_quot_Character_in_sAMAccountName +bool ComputerSamNameEdit::verify(AdInterface &ad, const QString &dn) const { + UNUSED_ARG(ad); + UNUSED_ARG(dn); + + const bool out = sam_name_edit_verify(edit); + + return out; +} + +bool ComputerSamNameEdit::apply(AdInterface &ad, const QString &dn) const { + const QString new_value = QString("%1$").arg(edit->text()); + const bool success = ad.attribute_replace_string(dn, ATTRIBUTE_SAM_ACCOUNT_NAME, new_value); + + return success; +} + +void ComputerSamNameEdit::set_enabled(const bool enabled) { + edit->setEnabled(enabled); +} diff --git a/src/admc/attribute_edits/computer_sam_name_edit.h b/src/admc/attribute_edits/computer_sam_name_edit.h new file mode 100644 index 00000000..c704c6cb --- /dev/null +++ b/src/admc/attribute_edits/computer_sam_name_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 COMPUTER_SAM_NAME_EDIT_H +#define COMPUTER_SAM_NAME_EDIT_H + +#include "attribute_edits/attribute_edit.h" + +class QLineEdit; + +class ComputerSamNameEdit final : public AttributeEdit { + Q_OBJECT +public: + ComputerSamNameEdit(QLineEdit *edit, QLineEdit *domain_edit, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + bool verify(AdInterface &ad, const QString &dn) const override; + bool apply(AdInterface &ad, const QString &dn) const override; + + void set_enabled(const bool enabled); + +private: + QLineEdit *edit; +}; + +#endif /* COMPUTER_SAM_NAME_EDIT_H */ diff --git a/src/admc/attribute_edits/country_combo.cpp b/src/admc/attribute_edits/country_combo.cpp index 7a375907..9ecf7cc1 100644 --- a/src/admc/attribute_edits/country_combo.cpp +++ b/src/admc/attribute_edits/country_combo.cpp @@ -22,6 +22,7 @@ #include "adldap.h" #include "globals.h" +#include "settings.h" #include "status.h" #include "utils.h" @@ -34,10 +35,19 @@ #define COUNTRY_CODE_NONE 0 bool loaded_country_data = false; -QList all_countries; QHash string_to_code; QHash country_strings; +QHash country_strings_ru; QHash country_abbreviations; +QHash abbreviation_to_code; + +enum CountryColumn { + CountryColumn_Country, + CountryColumn_CountryRu, + CountryColumn_Abbreviation, + CountryColumn_Code, + CountryColumn_COUNT, +}; void country_combo_load_data() { if (loaded_country_data) { @@ -60,48 +70,131 @@ void country_combo_load_data() { while (!file.atEnd()) { const QByteArray line_array = file.readLine(); - const QString line(line_array); - const QList line_split = line.split(','); + const QString line = QString(line_array); + + // Split line by comma's, taking into + // account that some comma's are inside + // quoted parts and ignoring those. + // + // NOTE: there's definitely a better way to + // do this + const QList line_split = [&]() -> QList { + + if (line.contains('\"')) { + QList split_by_quotes = line.split('\"'); + split_by_quotes.removeAll(""); + + if (split_by_quotes.size() == 2) { + QList split_rest = split_by_quotes[1].split(','); + split_rest.removeAll(""); + + QList out; + out.append(split_by_quotes[0]); + out.append(split_rest); + + return out; + } else { + return QList(); + } + } else { + return line.split(','); + } + }(); + + if (line_split.size() != CountryColumn_COUNT) { + qDebug() << "country.csv contains malformed line: " << line; - if (line_split.size() != 3) { continue; } - const QString country_string = line_split[0]; - const QString abbreviation = line_split[1]; - const QString code_string = line_split[2]; + const QString country_string = line_split[CountryColumn_Country]; + const QString country_string_ru = line_split[CountryColumn_CountryRu]; + const QString abbreviation = line_split[CountryColumn_Abbreviation]; + const QString code_string = line_split[CountryColumn_Code]; const int code = code_string.toInt(); country_strings[code] = country_string; + country_strings_ru[code] = country_string_ru; country_abbreviations[code] = abbreviation; + abbreviation_to_code[abbreviation] = code; - all_countries.append(country_string); string_to_code[country_string] = code; } file.close(); } - // Sort countries by name - std::sort(all_countries.begin(), all_countries.end()); - - // Special case for "None" country - const QString none_string = QCoreApplication::translate("country_widget", "None"); - string_to_code[none_string] = COUNTRY_CODE_NONE; - all_countries.insert(0, none_string); - country_strings[COUNTRY_CODE_NONE] = ""; - country_abbreviations[COUNTRY_CODE_NONE] = ""; - loaded_country_data = true; } void country_combo_init(QComboBox *combo) { - // Fill combo with country names. Add country codes to - // item data. - for (auto country_string : all_countries) { - const int code = string_to_code[country_string]; + const QHash name_map = [&]() { + const bool locale_is_ru = [&]() { + const QLocale locale = settings_get_variant(SETTING_locale).toLocale(); + const bool out = (locale.language() == QLocale::Russian); - combo->addItem(country_string, code); + return out; + }(); + + if (locale_is_ru) { + return country_strings_ru; + } else { + return country_strings; + } + }(); + + // Generate order of countries that will be used to + // fill the combo. + // + // NOTE: modify order of countries in the combo to + // put a particular country at the top of the list. + // If this program ever happens to be used outside + // of that particular country, there is a feature + // flag "SETTING_feature_current_locale_first". + const QList country_list = [&]() { + const QString country_russia = [&]() { + const QLocale top_locale = [&]() { + const bool current_locale_first = settings_get_variant(SETTING_feature_current_locale_first).toBool(); + + if (current_locale_first) { + const QLocale current_locale = settings_get_variant(SETTING_locale).toLocale(); + + return current_locale; + } else { + const QLocale russia_locale = QLocale(QLocale::Russian, QLocale::Russia); + + return russia_locale; + } + }(); + const QString locale_name = top_locale.name(); + const QList locale_name_split = locale_name.split("_"); + + if (locale_name_split.size() == 2) { + const QString abbreviation = locale_name_split[1]; + const int code = abbreviation_to_code[abbreviation]; + const QString country_name = name_map[code]; + + return country_name; + } else { + return QString(); + } + }(); + + QList out = name_map.values(); + std::sort(out.begin(), out.end()); + out.removeAll(country_russia); + out.prepend(country_russia); + + return out; + }(); + + // Add "None" at the start + combo->addItem(QCoreApplication::translate("country_widget", "None"), COUNTRY_CODE_NONE); + + for (const QString &country : country_list) { + const int code = name_map.key(country); + + combo->addItem(country, code); } } @@ -123,17 +216,11 @@ void country_combo_load(QComboBox *combo, const AdObject &object) { bool country_combo_apply(const QComboBox *combo, AdInterface &ad, const QString &dn) { const int code = combo->currentData().toInt(); - const bool country_code_is_known = (country_strings.contains(code) && country_abbreviations.contains(code)); - - if (!country_code_is_known) { - qDebug() << "Unknown country code:" << code; - - return false; - } - + // NOTE: this handles the COUNTRY_CODE_NONE case by + // using empty strings for it's values const QString code_string = QString::number(code); - const QString country_string = country_strings[code]; - const QString abbreviation = country_abbreviations[code]; + const QString country_string = country_strings.value(code, QString()); + const QString abbreviation = country_abbreviations.value(code, QString()); bool success = true; success = success && ad.attribute_replace_string(dn, ATTRIBUTE_COUNTRY_CODE, code_string); diff --git a/src/admc/attribute_edits/country_edit.cpp b/src/admc/attribute_edits/country_edit.cpp index 06fa4311..85e69cde 100644 --- a/src/admc/attribute_edits/country_edit.cpp +++ b/src/admc/attribute_edits/country_edit.cpp @@ -27,8 +27,8 @@ #include -CountryEdit::CountryEdit(QComboBox *combo_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) { +CountryEdit::CountryEdit(QComboBox *combo_arg, QObject *parent) +: AttributeEdit(parent) { combo = combo_arg; country_combo_init(combo); @@ -38,16 +38,16 @@ CountryEdit::CountryEdit(QComboBox *combo_arg, QList *edits_out this, &AttributeEdit::edited); } -void CountryEdit::load_internal(AdInterface &ad, const AdObject &object) { +void CountryEdit::load(AdInterface &ad, const AdObject &object) { UNUSED_ARG(ad); country_combo_load(combo, object); } -void CountryEdit::set_read_only(const bool read_only) { - combo->setEnabled(!read_only); -} - bool CountryEdit::apply(AdInterface &ad, const QString &dn) const { return country_combo_apply(combo, ad, dn); } + +void CountryEdit::set_enabled(const bool enabled) { + combo->setEnabled(enabled); +} diff --git a/src/admc/attribute_edits/country_edit.h b/src/admc/attribute_edits/country_edit.h index d3b25f02..faf8acd5 100644 --- a/src/admc/attribute_edits/country_edit.h +++ b/src/admc/attribute_edits/country_edit.h @@ -28,8 +28,11 @@ class QComboBox; class CountryEdit final : public AttributeEdit { Q_OBJECT public: - CountryEdit(QComboBox *combo, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + CountryEdit(QComboBox *combo, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + bool apply(AdInterface &ad, const QString &dn) const override; + void set_enabled(const bool enabled) override; private: QComboBox *combo; diff --git a/src/admc/attribute_edits/datetime_edit.cpp b/src/admc/attribute_edits/datetime_edit.cpp index 2fe2ae44..13656f82 100644 --- a/src/admc/attribute_edits/datetime_edit.cpp +++ b/src/admc/attribute_edits/datetime_edit.cpp @@ -26,8 +26,8 @@ #include -DateTimeEdit::DateTimeEdit(QDateTimeEdit *edit_arg, const QString &attribute_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) { +DateTimeEdit::DateTimeEdit(QDateTimeEdit *edit_arg, const QString &attribute_arg, QObject *parent) +: AttributeEdit(parent) { edit = edit_arg; attribute = attribute_arg; attribute = attribute_arg; @@ -39,7 +39,7 @@ DateTimeEdit::DateTimeEdit(QDateTimeEdit *edit_arg, const QString &attribute_arg this, &AttributeEdit::edited); } -void DateTimeEdit::load_internal(AdInterface &ad, const AdObject &object) { +void DateTimeEdit::load(AdInterface &ad, const AdObject &object) { UNUSED_ARG(ad); const QDateTime datetime = object.get_datetime(attribute, g_adconfig); @@ -48,10 +48,6 @@ void DateTimeEdit::load_internal(AdInterface &ad, const AdObject &object) { edit->setDateTime(datetime_local); } -void DateTimeEdit::set_read_only(const bool read_only) { - edit->setDisabled(read_only); -} - bool DateTimeEdit::apply(AdInterface &ad, const QString &dn) const { const QDateTime datetime_local = edit->dateTime(); const QDateTime datetime = datetime_local.toUTC(); diff --git a/src/admc/attribute_edits/datetime_edit.h b/src/admc/attribute_edits/datetime_edit.h index 68ca516e..f0ae6514 100644 --- a/src/admc/attribute_edits/datetime_edit.h +++ b/src/admc/attribute_edits/datetime_edit.h @@ -30,8 +30,10 @@ class QDateTimeEdit; class DateTimeEdit final : public AttributeEdit { Q_OBJECT public: - DateTimeEdit(QDateTimeEdit *edit, const QString &attribute_arg, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + DateTimeEdit(QDateTimeEdit *edit, const QString &attribute_arg, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + bool apply(AdInterface &ad, const QString &dn) const override; private: QString attribute; diff --git a/src/admc/attribute_edits/delegation_edit.cpp b/src/admc/attribute_edits/delegation_edit.cpp index 7f0d939a..545dd7c7 100644 --- a/src/admc/attribute_edits/delegation_edit.cpp +++ b/src/admc/attribute_edits/delegation_edit.cpp @@ -26,8 +26,8 @@ #include -DelegationEdit::DelegationEdit(QRadioButton *off_button_arg, QRadioButton *on_button_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) { +DelegationEdit::DelegationEdit(QRadioButton *off_button_arg, QRadioButton *on_button_arg, QObject *parent) +: AttributeEdit(parent) { off_button = off_button_arg; on_button = on_button_arg; @@ -39,7 +39,7 @@ DelegationEdit::DelegationEdit(QRadioButton *off_button_arg, QRadioButton *on_bu this, &AttributeEdit::edited); } -void DelegationEdit::load_internal(AdInterface &ad, const AdObject &object) { +void DelegationEdit::load(AdInterface &ad, const AdObject &object) { UNUSED_ARG(ad); const bool is_on = object.get_account_option(AccountOption_TrustedForDelegation, g_adconfig); @@ -51,11 +51,6 @@ void DelegationEdit::load_internal(AdInterface &ad, const AdObject &object) { } } -void DelegationEdit::set_read_only(const bool read_only) { - on_button->setEnabled(read_only); - off_button->setEnabled(read_only); -} - bool DelegationEdit::apply(AdInterface &ad, const QString &dn) const { const bool is_on = [&]() { if (on_button->isChecked()) { diff --git a/src/admc/attribute_edits/delegation_edit.h b/src/admc/attribute_edits/delegation_edit.h index e3138913..db853469 100644 --- a/src/admc/attribute_edits/delegation_edit.h +++ b/src/admc/attribute_edits/delegation_edit.h @@ -28,8 +28,10 @@ class QRadioButton; class DelegationEdit final : public AttributeEdit { Q_OBJECT public: - DelegationEdit(QRadioButton *off_button, QRadioButton *on_button, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + DelegationEdit(QRadioButton *off_button, QRadioButton *on_button, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + bool apply(AdInterface &ad, const QString &dn) const override; private: QRadioButton *off_button; diff --git a/src/admc/attribute_edits/expiry_edit.cpp b/src/admc/attribute_edits/expiry_edit.cpp index 2588439f..fff04cd9 100644 --- a/src/admc/attribute_edits/expiry_edit.cpp +++ b/src/admc/attribute_edits/expiry_edit.cpp @@ -25,8 +25,8 @@ #include "globals.h" #include "utils.h" -ExpiryEdit::ExpiryEdit(ExpiryWidget *edit_widget_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) { +ExpiryEdit::ExpiryEdit(ExpiryWidget *edit_widget_arg, QObject *parent) +: AttributeEdit(parent) { edit_widget = edit_widget_arg; connect( @@ -34,16 +34,16 @@ ExpiryEdit::ExpiryEdit(ExpiryWidget *edit_widget_arg, QList *ed this, &AttributeEdit::edited); } -void ExpiryEdit::load_internal(AdInterface &ad, const AdObject &object) { +void ExpiryEdit::load(AdInterface &ad, const AdObject &object) { UNUSED_ARG(ad); edit_widget->load(object); } -void ExpiryEdit::set_read_only(const bool read_only) { - edit_widget->set_read_only(read_only); -} - bool ExpiryEdit::apply(AdInterface &ad, const QString &dn) const { return edit_widget->apply(ad, dn); } + +void ExpiryEdit::set_enabled(const bool enabled) { + edit_widget->setEnabled(enabled); +} diff --git a/src/admc/attribute_edits/expiry_edit.h b/src/admc/attribute_edits/expiry_edit.h index 3decb256..9c691c2e 100644 --- a/src/admc/attribute_edits/expiry_edit.h +++ b/src/admc/attribute_edits/expiry_edit.h @@ -28,8 +28,11 @@ class ExpiryWidget; class ExpiryEdit final : public AttributeEdit { Q_OBJECT public: - ExpiryEdit(ExpiryWidget *edit_widget, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + ExpiryEdit(ExpiryWidget *edit_widget, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + bool apply(AdInterface &ad, const QString &dn) const override; + void set_enabled(const bool enabled) override; private: ExpiryWidget *edit_widget; diff --git a/src/admc/attribute_edits/expiry_widget.cpp b/src/admc/attribute_edits/expiry_widget.cpp index 54768696..576d5d20 100644 --- a/src/admc/attribute_edits/expiry_widget.cpp +++ b/src/admc/attribute_edits/expiry_widget.cpp @@ -33,18 +33,15 @@ ExpiryWidget::ExpiryWidget(QWidget *parent) ui = new Ui::ExpiryWidget(); ui->setupUi(this); - ui->never_check->setAutoExclusive(true); - ui->end_of_check->setAutoExclusive(true); - auto button_group = new QButtonGroup(this); button_group->addButton(ui->never_check); button_group->addButton(ui->end_of_check); connect( - ui->never_check, &QCheckBox::stateChanged, + ui->never_check, &QRadioButton::toggled, this, &ExpiryWidget::on_never_check); connect( - ui->end_of_check, &QCheckBox::stateChanged, + ui->end_of_check, &QRadioButton::toggled, this, &ExpiryWidget::on_end_of_check); connect( ui->date_edit, &QDateEdit::dateChanged, @@ -86,12 +83,6 @@ void ExpiryWidget::load(const AdObject &object) { ui->date_edit->setDate(date); } -void ExpiryWidget::set_read_only(const bool read_only) { - ui->never_check->setDisabled(read_only); - ui->end_of_check->setDisabled(read_only); - ui->date_edit->setReadOnly(read_only); -} - bool ExpiryWidget::apply(AdInterface &ad, const QString &dn) const { const bool never = ui->never_check->isChecked(); diff --git a/src/admc/attribute_edits/expiry_widget.h b/src/admc/attribute_edits/expiry_widget.h index 0e41e835..f8db16ae 100644 --- a/src/admc/attribute_edits/expiry_widget.h +++ b/src/admc/attribute_edits/expiry_widget.h @@ -39,7 +39,6 @@ public: ~ExpiryWidget(); void load(const AdObject &object); - void set_read_only(const bool read_only); bool apply(AdInterface &ad, const QString &dn) const; signals: diff --git a/src/admc/attribute_edits/expiry_widget.ui b/src/admc/attribute_edits/expiry_widget.ui index 1fb6d580..1bd1dd70 100644 --- a/src/admc/attribute_edits/expiry_widget.ui +++ b/src/admc/attribute_edits/expiry_widget.ui @@ -15,7 +15,7 @@ - + Never @@ -24,7 +24,7 @@ - + End of: diff --git a/src/admc/attribute_edits/general_name_edit.cpp b/src/admc/attribute_edits/general_name_edit.cpp new file mode 100644 index 00000000..ef47f3ad --- /dev/null +++ b/src/admc/attribute_edits/general_name_edit.cpp @@ -0,0 +1,53 @@ +/* + * 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 "attribute_edits/general_name_edit.h" + +#include "adldap.h" +#include "utils.h" + +#include + +GeneralNameEdit::GeneralNameEdit(QLabel *label_arg, QObject *parent) +: AttributeEdit(parent) { + label = label_arg; +} + +void GeneralNameEdit::load(AdInterface &ad, const AdObject &object) { + UNUSED_ARG(ad); + + const QString label_text = [&]() { + const QString name_attribute = [&]() { + const bool is_gpc = object.is_class(CLASS_GP_CONTAINER); + + if (is_gpc) { + return ATTRIBUTE_DISPLAY_NAME; + } else { + return ATTRIBUTE_NAME; + } + }(); + + const QString name = object.get_string(name_attribute); + + return name; + }(); + + label->setText(label_text); +} diff --git a/src/admc/tabs/gpo_links_tab.h b/src/admc/attribute_edits/general_name_edit.h similarity index 65% rename from src/admc/tabs/gpo_links_tab.h rename to src/admc/attribute_edits/general_name_edit.h index e0237460..e3fc6052 100644 --- a/src/admc/tabs/gpo_links_tab.h +++ b/src/admc/attribute_edits/general_name_edit.h @@ -18,37 +18,27 @@ * along with this program. If not, see . */ -#ifndef GPO_LINKS_TAB_H -#define GPO_LINKS_TAB_H - -#include "tabs/properties_tab.h" - -class QTreeView; -class QStandardItemModel; +#ifndef GENERAL_NAME_EDIT_H +#define GENERAL_NAME_EDIT_H /** - * List objects that this GPO links to. Does not provide a - * way to edit, editing should be done in group policy tabs - * of objects. + * Edit for displaying name of object in a label. Used + * in general tabs of the properties dialog */ -namespace Ui { -class GpoLinksTab; -} +#include "attribute_edits/attribute_edit.h" -class GpoLinksTab final : public PropertiesTab { +class QLabel; + +class GeneralNameEdit final : public AttributeEdit { Q_OBJECT - public: - Ui::GpoLinksTab *ui; - - GpoLinksTab(); - ~GpoLinksTab(); + GeneralNameEdit(QLabel *label, QObject *parent); void load(AdInterface &ad, const AdObject &object) override; private: - QStandardItemModel *model; + QLabel *label; }; -#endif /* GPO_LINKS_TAB_H */ +#endif /* GENERAL_NAME_EDIT_H */ diff --git a/src/admc/attribute_edits/gpoptions_edit.cpp b/src/admc/attribute_edits/gpoptions_edit.cpp index 445c1725..f8391543 100644 --- a/src/admc/attribute_edits/gpoptions_edit.cpp +++ b/src/admc/attribute_edits/gpoptions_edit.cpp @@ -25,8 +25,8 @@ #include -GpoptionsEdit::GpoptionsEdit(QCheckBox *check_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) { +GpoptionsEdit::GpoptionsEdit(QCheckBox *check_arg, QObject *parent) +: AttributeEdit(parent) { check = check_arg; connect( @@ -34,7 +34,7 @@ GpoptionsEdit::GpoptionsEdit(QCheckBox *check_arg, QList *edits this, &AttributeEdit::edited); } -void GpoptionsEdit::load_internal(AdInterface &ad, const AdObject &object) { +void GpoptionsEdit::load(AdInterface &ad, const AdObject &object) { UNUSED_ARG(ad); const QString value = object.get_string(ATTRIBUTE_GPOPTIONS); @@ -43,10 +43,6 @@ void GpoptionsEdit::load_internal(AdInterface &ad, const AdObject &object) { check->setChecked(checked); } -void GpoptionsEdit::set_read_only(const bool read_only) { - check->setDisabled(read_only); -} - bool GpoptionsEdit::apply(AdInterface &ad, const QString &dn) const { const QString new_value = [this]() { const bool checked = check->isChecked(); diff --git a/src/admc/attribute_edits/gpoptions_edit.h b/src/admc/attribute_edits/gpoptions_edit.h index e1c2f0ce..80d8015e 100644 --- a/src/admc/attribute_edits/gpoptions_edit.h +++ b/src/admc/attribute_edits/gpoptions_edit.h @@ -28,8 +28,10 @@ class QCheckBox; class GpoptionsEdit final : public AttributeEdit { Q_OBJECT public: - GpoptionsEdit(QCheckBox *check, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + GpoptionsEdit(QCheckBox *check, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + bool apply(AdInterface &ad, const QString &dn) const override; private: QCheckBox *check; diff --git a/src/admc/attribute_edits/group_scope_edit.cpp b/src/admc/attribute_edits/group_scope_edit.cpp index 9e75236c..560003c5 100644 --- a/src/admc/attribute_edits/group_scope_edit.cpp +++ b/src/admc/attribute_edits/group_scope_edit.cpp @@ -26,8 +26,8 @@ #include #include -GroupScopeEdit::GroupScopeEdit(QComboBox *combo_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) { +GroupScopeEdit::GroupScopeEdit(QComboBox *combo_arg, QObject *parent) +: AttributeEdit(parent) { combo = combo_arg; for (int i = 0; i < GroupScope_COUNT; i++) { @@ -42,16 +42,18 @@ GroupScopeEdit::GroupScopeEdit(QComboBox *combo_arg, QList *edi this, &AttributeEdit::edited); } -void GroupScopeEdit::load_internal(AdInterface &ad, const AdObject &object) { +void GroupScopeEdit::load(AdInterface &ad, const AdObject &object) { UNUSED_ARG(ad); const GroupScope scope = object.get_group_scope(); combo->setCurrentIndex((int) scope); -} -void GroupScopeEdit::set_read_only(const bool read_only) { - combo->setDisabled(read_only); + const bool is_critical_system_object = object.get_bool(ATTRIBUTE_IS_CRITICAL_SYSTEM_OBJECT); + + if (is_critical_system_object) { + combo->setDisabled(true); + } } bool GroupScopeEdit::apply(AdInterface &ad, const QString &dn) const { diff --git a/src/admc/attribute_edits/group_scope_edit.h b/src/admc/attribute_edits/group_scope_edit.h index d81e9690..18e3b6a2 100644 --- a/src/admc/attribute_edits/group_scope_edit.h +++ b/src/admc/attribute_edits/group_scope_edit.h @@ -28,8 +28,10 @@ class QComboBox; class GroupScopeEdit final : public AttributeEdit { Q_OBJECT public: - GroupScopeEdit(QComboBox *combo, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + GroupScopeEdit(QComboBox *combo, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + bool apply(AdInterface &ad, const QString &dn) const override; private: QComboBox *combo; diff --git a/src/admc/attribute_edits/group_type_edit.cpp b/src/admc/attribute_edits/group_type_edit.cpp index 7eb70354..37783ad6 100644 --- a/src/admc/attribute_edits/group_type_edit.cpp +++ b/src/admc/attribute_edits/group_type_edit.cpp @@ -25,8 +25,8 @@ #include -GroupTypeEdit::GroupTypeEdit(QComboBox *combo_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) { +GroupTypeEdit::GroupTypeEdit(QComboBox *combo_arg, QObject *parent) +: AttributeEdit(parent) { combo = combo_arg; for (int i = 0; i < GroupType_COUNT; i++) { @@ -41,16 +41,18 @@ GroupTypeEdit::GroupTypeEdit(QComboBox *combo_arg, QList *edits this, &AttributeEdit::edited); } -void GroupTypeEdit::load_internal(AdInterface &ad, const AdObject &object) { +void GroupTypeEdit::load(AdInterface &ad, const AdObject &object) { UNUSED_ARG(ad); const GroupType type = object.get_group_type(); combo->setCurrentIndex((int) type); -} -void GroupTypeEdit::set_read_only(const bool read_only) { - combo->setDisabled(read_only); + const bool is_critical_system_object = object.get_bool(ATTRIBUTE_IS_CRITICAL_SYSTEM_OBJECT); + + if (is_critical_system_object) { + combo->setDisabled(true); + } } bool GroupTypeEdit::apply(AdInterface &ad, const QString &dn) const { diff --git a/src/admc/attribute_edits/group_type_edit.h b/src/admc/attribute_edits/group_type_edit.h index 1646add8..57593ab0 100644 --- a/src/admc/attribute_edits/group_type_edit.h +++ b/src/admc/attribute_edits/group_type_edit.h @@ -28,8 +28,10 @@ class QComboBox; class GroupTypeEdit final : public AttributeEdit { Q_OBJECT public: - GroupTypeEdit(QComboBox *combo, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + GroupTypeEdit(QComboBox *combo, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + bool apply(AdInterface &ad, const QString &dn) const override; private: QComboBox *combo; diff --git a/src/admc/attribute_edits/laps_expiry_edit.cpp b/src/admc/attribute_edits/laps_expiry_edit.cpp new file mode 100644 index 00000000..657be2a7 --- /dev/null +++ b/src/admc/attribute_edits/laps_expiry_edit.cpp @@ -0,0 +1,65 @@ +/* + * 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 "attribute_edits/laps_expiry_edit.h" + +#include "adldap.h" +#include "globals.h" +#include "utils.h" + +#include +#include + +LAPSExpiryEdit::LAPSExpiryEdit(QDateTimeEdit *edit_arg, QPushButton *reset_expiry_button, QObject *parent) +: AttributeEdit(parent) { + edit = edit_arg; + + connect( + edit, &QDateTimeEdit::dateTimeChanged, + this, &AttributeEdit::edited); + connect( + reset_expiry_button, &QPushButton::clicked, + this, &LAPSExpiryEdit::reset_expiry); +} + +void LAPSExpiryEdit::load(AdInterface &ad, const AdObject &object) { + UNUSED_ARG(ad); + + const QDateTime datetime = object.get_datetime(ATTRIBUTE_LAPS_EXPIRATION, g_adconfig); + const QDateTime datetime_local = datetime.toLocalTime(); + + edit->setDateTime(datetime_local); +} + +bool LAPSExpiryEdit::apply(AdInterface &ad, const QString &dn) const { + const QDateTime datetime_local = edit->dateTime(); + const QDateTime datetime = datetime_local.toUTC(); + + const bool success = ad.attribute_replace_datetime(dn, ATTRIBUTE_LAPS_EXPIRATION, datetime); + + return success; +} + +void LAPSExpiryEdit::reset_expiry() { + const QDateTime current_datetime_local = QDateTime::currentDateTime(); + edit->setDateTime(current_datetime_local); + + emit edited(); +} diff --git a/src/admc/tabs/properties_tab.cpp b/src/admc/attribute_edits/laps_expiry_edit.h similarity index 62% rename from src/admc/tabs/properties_tab.cpp rename to src/admc/attribute_edits/laps_expiry_edit.h index 92aea706..a513839a 100644 --- a/src/admc/tabs/properties_tab.cpp +++ b/src/admc/attribute_edits/laps_expiry_edit.h @@ -18,24 +18,26 @@ * along with this program. If not, see . */ -#include "tabs/properties_tab.h" +#ifndef LAPS_EXPIRY_EDIT_H +#define LAPS_EXPIRY_EDIT_H #include "attribute_edits/attribute_edit.h" -#include "adldap.h" +class QDateTimeEdit; +class QPushButton; -void PropertiesTab::load(AdInterface &ad, const AdObject &object) { - edits_load(edits, ad, object); -} +class LAPSExpiryEdit final : public AttributeEdit { + Q_OBJECT +public: + LAPSExpiryEdit(QDateTimeEdit *edit_arg, QPushButton *reset_expiry_button, QObject *parent); -bool PropertiesTab::verify(AdInterface &ad, const QString &target) const { - return edits_verify(ad, edits, target); -} + void load(AdInterface &ad, const AdObject &object) override; + bool apply(AdInterface &ad, const QString &dn) const override; -bool PropertiesTab::apply(AdInterface &ad, const QString &target) { - return edits_apply(ad, edits, target); -} +private: + QDateTimeEdit *edit; -void PropertiesTab::on_edit_edited() { - emit edited(); -} + void reset_expiry(); +}; + +#endif /* LAPS_EXPIRY_EDIT_H */ diff --git a/src/admc/attribute_edits/logon_computers_dialog.cpp b/src/admc/attribute_edits/logon_computers_dialog.cpp index 9248b35c..ce998315 100644 --- a/src/admc/attribute_edits/logon_computers_dialog.cpp +++ b/src/admc/attribute_edits/logon_computers_dialog.cpp @@ -25,13 +25,25 @@ #include "utils.h" #include "settings.h" -LogonComputersDialog::LogonComputersDialog(QWidget *parent) +#include + +LogonComputersDialog::LogonComputersDialog(const QString &value, QWidget *parent) : QDialog(parent) { ui = new Ui::LogonComputersDialog(); ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + if (!value.isEmpty()) { + const QList value_list = value.split(","); + + for (const QString &subvalue : value_list) { + ui->list->addItem(subvalue); + } + } + + enable_widget_on_selection(ui->remove_button, ui->list); + settings_setup_dialog_geometry(SETTING_logon_computers_dialog_geometry, this); connect( @@ -46,20 +58,6 @@ LogonComputersDialog::~LogonComputersDialog() { delete ui; } -void LogonComputersDialog::load(const QString &value) { - ui->list->clear(); - - if (value.isEmpty()) { - return; - } - - const QList value_list = value.split(","); - - for (const QString &subvalue : value_list) { - ui->list->addItem(subvalue); - } -} - QString LogonComputersDialog::get() const { const QList value_list = [&]() { QList out; diff --git a/src/admc/attribute_edits/logon_computers_dialog.h b/src/admc/attribute_edits/logon_computers_dialog.h index 2bea94af..ccf87fd6 100644 --- a/src/admc/attribute_edits/logon_computers_dialog.h +++ b/src/admc/attribute_edits/logon_computers_dialog.h @@ -33,10 +33,9 @@ class LogonComputersDialog final : public QDialog { public: Ui::LogonComputersDialog *ui; - LogonComputersDialog(QWidget *parent); + LogonComputersDialog(const QString &value, QWidget *parent); ~LogonComputersDialog(); - void load(const QString &value); QString get() const; private: diff --git a/src/admc/attribute_edits/logon_computers_edit.cpp b/src/admc/attribute_edits/logon_computers_edit.cpp index c6119a1c..b9452995 100644 --- a/src/admc/attribute_edits/logon_computers_edit.cpp +++ b/src/admc/attribute_edits/logon_computers_edit.cpp @@ -27,8 +27,8 @@ #include -LogonComputersEdit::LogonComputersEdit(QPushButton *button_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) { +LogonComputersEdit::LogonComputersEdit(QPushButton *button_arg, QObject *parent) +: AttributeEdit(parent) { button = button_arg; connect( @@ -36,16 +36,12 @@ LogonComputersEdit::LogonComputersEdit(QPushButton *button_arg, QListsetEnabled(read_only); -} - bool LogonComputersEdit::apply(AdInterface &ad, const QString &dn) const { const bool success = ad.attribute_replace_string(dn, ATTRIBUTE_USER_WORKSTATIONS, current_value); @@ -53,12 +49,12 @@ bool LogonComputersEdit::apply(AdInterface &ad, const QString &dn) const { } void LogonComputersEdit::open_dialog() { - auto dialog = new LogonComputersDialog(button); - dialog->load(current_value); + auto dialog = new LogonComputersDialog(current_value, button); dialog->open(); connect( dialog, &QDialog::accepted, + this, [this, dialog]() { current_value = dialog->get(); diff --git a/src/admc/attribute_edits/logon_computers_edit.h b/src/admc/attribute_edits/logon_computers_edit.h index 126a6568..d7b3846e 100644 --- a/src/admc/attribute_edits/logon_computers_edit.h +++ b/src/admc/attribute_edits/logon_computers_edit.h @@ -28,8 +28,10 @@ class QPushButton; class LogonComputersEdit final : public AttributeEdit { Q_OBJECT public: - LogonComputersEdit(QPushButton *button, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + LogonComputersEdit(QPushButton *button, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + bool apply(AdInterface &ad, const QString &dn) const override; private: QPushButton *button; diff --git a/src/admc/attribute_edits/logon_hours_dialog.cpp b/src/admc/attribute_edits/logon_hours_dialog.cpp index 13a65988..86fd1d78 100644 --- a/src/admc/attribute_edits/logon_hours_dialog.cpp +++ b/src/admc/attribute_edits/logon_hours_dialog.cpp @@ -30,7 +30,7 @@ QList shift_list(const QList &list, const int shift_amount); -LogonHoursDialog::LogonHoursDialog(QWidget *parent) +LogonHoursDialog::LogonHoursDialog(const QByteArray &value, QWidget *parent) : QDialog(parent) { ui = new Ui::LogonHoursDialog(); ui->setupUi(this); @@ -48,6 +48,18 @@ LogonHoursDialog::LogonHoursDialog(QWidget *parent) tr("Saturday"), }); + const QList horizontalheader_labels = []() { + QList out; + + for (int i = 0; i < HOURS_IN_DAY; i++) { + const QString label = QString::number(i); + out.append(label); + } + + return out; + }(); + model->setHorizontalHeaderLabels(horizontalheader_labels); + ui->view->setModel(model); ui->view->horizontalHeader()->setSectionResizeMode(QHeaderView::Fixed); ui->view->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); @@ -59,8 +71,28 @@ LogonHoursDialog::LogonHoursDialog(QWidget *parent) ui->local_time_button->setChecked(true); is_local_time = true; + load(value); + settings_setup_dialog_geometry(SETTING_logon_hours_dialog_geometry, this); + const QString allowed_style_sheet = [&]() { + const QPalette palette = ui->view->palette(); + const QColor color = palette.highlight().color(); + const QString out = QString("background-color: rgb(%1, %2, %3);").arg(QString::number(color.red()), QString::number(color.green()), QString::number(color.blue())); + + return out; + }(); + ui->legend_allowed->setStyleSheet(allowed_style_sheet); + + const QString denied_style_sheet = [&]() { + const QPalette palette = ui->view->palette(); + const QColor color = palette.base().color(); + const QString out = QString("background-color: rgb(%1, %2, %3);").arg(QString::number(color.red()), QString::number(color.green()), QString::number(color.blue())); + + return out; + }(); + ui->legend_denied->setStyleSheet(denied_style_sheet); + connect( ui->local_time_button, &QRadioButton::toggled, this, &LogonHoursDialog::on_local_time_button_toggled); @@ -73,10 +105,7 @@ LogonHoursDialog::~LogonHoursDialog() { void LogonHoursDialog::load(const QByteArray &value) { ui->view->clearSelection(); - // NOTE: value may be empty if it's undefined - if (value.size() != LOGON_HOURS_SIZE) { - return; - } + original_value = value; const QList> bools = logon_hours_to_bools(value, get_offset()); @@ -110,9 +139,19 @@ QByteArray LogonHoursDialog::get() const { return out; }(); - const QByteArray out = logon_hours_to_bytes(bools, get_offset()); + const QList> original_bools = logon_hours_to_bools(original_value); - return out; + // NOTE: input has to always be equal to output. + // Therefore, for the case where original value was + // unset, we need this special logic so that input + // doesn't change to a non-empty bytearray. + if (bools == original_bools) { + return original_value; + } else { + const QByteArray out = logon_hours_to_bytes(bools, get_offset()); + + return out; + } } // Get current value, change time state and reload value @@ -142,7 +181,20 @@ int LogonHoursDialog::get_offset() const { } } -QList> logon_hours_to_bools(const QByteArray &byte_list, const int time_offset) { +QList> logon_hours_to_bools(const QByteArray &byte_list_arg, const int time_offset) { + // NOTE: value may be empty or malformed. In that + // case treat both as values that "allow all logon + // times" (all bits set). This also handles the + // case where value is unset and we need to treat + // it as "allow all logon times". + const QByteArray byte_list = [&]() { + if (byte_list_arg.size() == LOGON_HOURS_SIZE) { + return byte_list_arg; + } else { + return QByteArray(LOGON_HOURS_SIZE, (char) 0xFF); + } + }(); + // Convet byte array to list of bools const QList joined = [&]() { QList out; @@ -150,7 +202,7 @@ QList> logon_hours_to_bools(const QByteArray &byte_list, const int t for (const char byte : byte_list) { for (int bit_i = 0; bit_i < 8; bit_i++) { const int bit = (0x01 << bit_i); - const bool is_set = bit_is_set((int) byte, bit); + const bool is_set = bitmask_is_set((int) byte, bit); out.append(is_set); } } @@ -197,7 +249,7 @@ QByteArray logon_hours_to_bytes(const QList> bool_list, const int ti int byte = 0; for (int bit_i = 0; bit_i < 8; bit_i++) { const int bit = (0x01 << bit_i); - byte = bit_set(byte, bit, byte_list[bit_i]); + byte = bitmask_set(byte, bit, byte_list[bit_i]); } bytes.append(byte); diff --git a/src/admc/attribute_edits/logon_hours_dialog.h b/src/admc/attribute_edits/logon_hours_dialog.h index 0cbb194e..942336af 100644 --- a/src/admc/attribute_edits/logon_hours_dialog.h +++ b/src/admc/attribute_edits/logon_hours_dialog.h @@ -56,15 +56,16 @@ class LogonHoursDialog : public QDialog { public: Ui::LogonHoursDialog *ui; - LogonHoursDialog(QWidget *parent); + LogonHoursDialog(const QByteArray &value, QWidget *parent); ~LogonHoursDialog(); - void load(const QByteArray &value); QByteArray get() const; private: QStandardItemModel *model; + QByteArray original_value; + void load(const QByteArray &value); void switch_to_local_time(); void on_local_time_button_toggled(bool checked); int get_offset() const; diff --git a/src/admc/attribute_edits/logon_hours_dialog.ui b/src/admc/attribute_edits/logon_hours_dialog.ui index f1212940..3c740f4f 100644 --- a/src/admc/attribute_edits/logon_hours_dialog.ui +++ b/src/admc/attribute_edits/logon_hours_dialog.ui @@ -7,7 +7,7 @@ 0 0 400 - 300 + 318 @@ -15,21 +15,115 @@ - - - - - - Local time + + + QAbstractItemView::NoEditTriggers + + + QAbstractItemView::MultiSelection - - - UTC time + + + 0 - + + + + 0 + + + + + UTC time + + + + + + + Local time + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 0 + + + + + 0 + + + + + Logon allowed: + + + + + + + + 40 + 40 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + Logon denied: + + + + + + + + 40 + 40 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + + diff --git a/src/admc/attribute_edits/logon_hours_edit.cpp b/src/admc/attribute_edits/logon_hours_edit.cpp index 8c79a7b3..8d5dab5e 100644 --- a/src/admc/attribute_edits/logon_hours_edit.cpp +++ b/src/admc/attribute_edits/logon_hours_edit.cpp @@ -26,8 +26,8 @@ #include -LogonHoursEdit::LogonHoursEdit(QPushButton *button_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) { +LogonHoursEdit::LogonHoursEdit(QPushButton *button_arg, QObject *parent) +: AttributeEdit(parent) { button = button_arg; connect( @@ -35,16 +35,12 @@ LogonHoursEdit::LogonHoursEdit(QPushButton *button_arg, QList * this, &LogonHoursEdit::open_dialog); } -void LogonHoursEdit::load_internal(AdInterface &ad, const AdObject &object) { +void LogonHoursEdit::load(AdInterface &ad, const AdObject &object) { UNUSED_ARG(ad); current_value = object.get_value(ATTRIBUTE_LOGON_HOURS); } -void LogonHoursEdit::set_read_only(const bool read_only) { - button->setEnabled(read_only); -} - bool LogonHoursEdit::apply(AdInterface &ad, const QString &dn) const { const bool success = ad.attribute_replace_value(dn, ATTRIBUTE_LOGON_HOURS, current_value); @@ -52,15 +48,20 @@ bool LogonHoursEdit::apply(AdInterface &ad, const QString &dn) const { } void LogonHoursEdit::open_dialog() { - auto dialog = new LogonHoursDialog(button); - dialog->load(current_value); + auto dialog = new LogonHoursDialog(current_value, button); dialog->open(); connect( dialog, &QDialog::accepted, + this, [this, dialog]() { - current_value = dialog->get(); + const QByteArray new_value = dialog->get(); + const bool value_changed = (new_value != current_value); - emit edited(); + if (value_changed) { + current_value = dialog->get(); + + emit edited(); + } }); } diff --git a/src/admc/attribute_edits/logon_hours_edit.h b/src/admc/attribute_edits/logon_hours_edit.h index a06d6c8f..12f760d3 100644 --- a/src/admc/attribute_edits/logon_hours_edit.h +++ b/src/admc/attribute_edits/logon_hours_edit.h @@ -28,8 +28,10 @@ class QPushButton; class LogonHoursEdit final : public AttributeEdit { Q_OBJECT public: - LogonHoursEdit(QPushButton *button, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + LogonHoursEdit(QPushButton *button, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + bool apply(AdInterface &ad, const QString &dn) const override; private: QPushButton *button; diff --git a/src/admc/attribute_edits/manager_edit.cpp b/src/admc/attribute_edits/manager_edit.cpp index 49bbe58e..15a10b87 100644 --- a/src/admc/attribute_edits/manager_edit.cpp +++ b/src/admc/attribute_edits/manager_edit.cpp @@ -25,8 +25,8 @@ #include "globals.h" #include "utils.h" -ManagerEdit::ManagerEdit(ManagerWidget *widget_arg, const QString &manager_attribute_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) { +ManagerEdit::ManagerEdit(ManagerWidget *widget_arg, const QString &manager_attribute_arg, QObject *parent) +: AttributeEdit(parent) { manager_attribute = manager_attribute_arg; widget = widget_arg; @@ -37,20 +37,20 @@ ManagerEdit::ManagerEdit(ManagerWidget *widget_arg, const QString &manager_attri this, &AttributeEdit::edited); } -void ManagerEdit::load_internal(AdInterface &ad, const AdObject &object) { +void ManagerEdit::load(AdInterface &ad, const AdObject &object) { UNUSED_ARG(ad); widget->load(object); } -void ManagerEdit::set_read_only(const bool read_only) { - widget->setEnabled(!read_only); -} - bool ManagerEdit::apply(AdInterface &ad, const QString &dn) const { return widget->apply(ad, dn); } +void ManagerEdit::set_enabled(const bool enabled) { + widget->setEnabled(enabled); +} + QString ManagerEdit::get_manager() const { return widget->get_manager(); } diff --git a/src/admc/attribute_edits/manager_edit.h b/src/admc/attribute_edits/manager_edit.h index e2e73ba3..7164187b 100644 --- a/src/admc/attribute_edits/manager_edit.h +++ b/src/admc/attribute_edits/manager_edit.h @@ -36,8 +36,11 @@ class ManagerWidget; class ManagerEdit final : public AttributeEdit { Q_OBJECT public: - ManagerEdit(ManagerWidget *widget_arg, const QString &manager_attribute_arg, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + ManagerEdit(ManagerWidget *widget_arg, const QString &manager_attribute_arg, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + bool apply(AdInterface &ad, const QString &dn) const override; + void set_enabled(const bool enabled) override; QString get_manager() const; diff --git a/src/admc/attribute_edits/manager_widget.cpp b/src/admc/attribute_edits/manager_widget.cpp index dc40c14d..c52201c0 100644 --- a/src/admc/attribute_edits/manager_widget.cpp +++ b/src/admc/attribute_edits/manager_widget.cpp @@ -23,6 +23,7 @@ #include "adldap.h" #include "globals.h" +#include "utils.h" #include "properties_dialog.h" #include "select_object_dialog.h" @@ -31,6 +32,8 @@ ManagerWidget::ManagerWidget(QWidget *parent) ui = new Ui::ManagerWidget(); ui->setupUi(this); + ui->manager_display->setReadOnly(true); + connect( ui->change_button, &QPushButton::clicked, this, &ManagerWidget::on_change); @@ -76,6 +79,7 @@ void ManagerWidget::on_change() { connect( dialog, &SelectObjectDialog::accepted, + this, [this, dialog]() { const QList selected = dialog->get_selected(); @@ -88,7 +92,12 @@ void ManagerWidget::on_change() { } void ManagerWidget::on_properties() { - PropertiesDialog::open_for_target(current_value); + AdInterface ad; + if (ad_failed(ad, this)) { + return; + } + + PropertiesDialog::open_for_target(ad, current_value); } void ManagerWidget::on_clear() { @@ -100,8 +109,8 @@ void ManagerWidget::on_clear() { void ManagerWidget::load_value(const QString &value) { current_value = value; - const QString rdn = dn_get_name(current_value); - ui->manager_display->setText(current_value); + const QString name = dn_get_name(current_value); + ui->manager_display->setText(name); const bool have_manager = !current_value.isEmpty(); ui->properties_button->setEnabled(have_manager); diff --git a/src/admc/attribute_edits/password_edit.cpp b/src/admc/attribute_edits/password_edit.cpp index b39cc068..e89509bf 100644 --- a/src/admc/attribute_edits/password_edit.cpp +++ b/src/admc/attribute_edits/password_edit.cpp @@ -22,13 +22,15 @@ #include "adldap.h" #include "globals.h" +#include "settings.h" #include "utils.h" #include +#include #include -PasswordEdit::PasswordEdit(QLineEdit *edit_arg, QLineEdit *confirm_edit_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) { +PasswordEdit::PasswordEdit(QLineEdit *edit_arg, QLineEdit *confirm_edit_arg, QCheckBox *show_password_check, QObject *parent) +: AttributeEdit(parent) { edit = edit_arg; confirm_edit = confirm_edit_arg; @@ -38,9 +40,15 @@ PasswordEdit::PasswordEdit(QLineEdit *edit_arg, QLineEdit *confirm_edit_arg, QLi connect( edit, &QLineEdit::textChanged, this, &AttributeEdit::edited); + connect( + show_password_check, &QCheckBox::toggled, + this, &PasswordEdit::on_show_password_check); + + const bool show_password_is_ON = settings_get_variant(SETTING_show_password).toBool(); + show_password_check->setChecked(show_password_is_ON); } -void PasswordEdit::load_internal(AdInterface &ad, const AdObject &object) { +void PasswordEdit::load(AdInterface &ad, const AdObject &object) { UNUSED_ARG(ad); UNUSED_ARG(object); @@ -48,16 +56,19 @@ void PasswordEdit::load_internal(AdInterface &ad, const AdObject &object) { confirm_edit->clear(); } -void PasswordEdit::set_read_only(const bool read_only) { - edit->setDisabled(read_only); - confirm_edit->setDisabled(read_only); -} - bool PasswordEdit::verify(AdInterface &ad, const QString &) const { UNUSED_ARG(ad); const QString pass = edit->text(); const QString confirm_pass = confirm_edit->text(); + + if (pass.isEmpty()) { + const QString error_text = QString(tr("Password cannot be empty.")); + message_box_warning(edit, tr("Error"), error_text); + + return false; + } + if (pass != confirm_pass) { const QString error_text = QString(tr("Passwords don't match!")); message_box_warning(edit, tr("Error"), error_text); @@ -81,7 +92,7 @@ bool PasswordEdit::apply(AdInterface &ad, const QString &dn) const { const QString new_value = edit->text(); const bool success = ad.user_set_pass(dn, new_value); - + return success; } @@ -92,3 +103,18 @@ QLineEdit *PasswordEdit::get_edit() const { QLineEdit *PasswordEdit::get_confirm_edit() const { return confirm_edit; } + +void PasswordEdit::on_show_password_check(bool checked) { + const QLineEdit::EchoMode echo_mode = [&]() { + if (checked) { + return QLineEdit::Normal; + } else { + return QLineEdit::Password; + } + }(); + + edit->setEchoMode(echo_mode); + confirm_edit->setEchoMode(echo_mode); + + settings_set_variant(SETTING_show_password, checked); +} diff --git a/src/admc/attribute_edits/password_edit.h b/src/admc/attribute_edits/password_edit.h index c4dd338e..0d262456 100644 --- a/src/admc/attribute_edits/password_edit.h +++ b/src/admc/attribute_edits/password_edit.h @@ -24,14 +24,16 @@ #include "attribute_edits/attribute_edit.h" class QLineEdit; +class QCheckBox; class PasswordEdit final : public AttributeEdit { Q_OBJECT public: - PasswordEdit(QLineEdit *edit_arg, QLineEdit *confirm_edit_arg, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + PasswordEdit(QLineEdit *edit_arg, QLineEdit *confirm_edit_arg, QCheckBox *show_password_check, QObject *parent); + void load(AdInterface &ad, const AdObject &object) override; bool verify(AdInterface &ad, const QString &dn) const override; + bool apply(AdInterface &ad, const QString &dn) const override; QLineEdit *get_edit() const; QLineEdit *get_confirm_edit() const; @@ -39,6 +41,8 @@ public: private: QLineEdit *edit; QLineEdit *confirm_edit; + + void on_show_password_check(bool checked); }; #endif /* PASSWORD_EDIT_H */ diff --git a/src/admc/attribute_edits/protect_deletion_edit.cpp b/src/admc/attribute_edits/protect_deletion_edit.cpp index 7eec5611..291d248b 100644 --- a/src/admc/attribute_edits/protect_deletion_edit.cpp +++ b/src/admc/attribute_edits/protect_deletion_edit.cpp @@ -30,8 +30,8 @@ // permissions for "delete" and "delete subtree" for // "WORLD"(everyone) trustee -ProtectDeletionEdit::ProtectDeletionEdit(QCheckBox *check_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) { +ProtectDeletionEdit::ProtectDeletionEdit(QCheckBox *check_arg, QObject *parent) +: AttributeEdit(parent) { check = check_arg; connect( @@ -43,21 +43,17 @@ void ProtectDeletionEdit::set_enabled(const bool enabled) { check->setChecked(enabled); } -void ProtectDeletionEdit::load_internal(AdInterface &ad, const AdObject &object) { +void ProtectDeletionEdit::load(AdInterface &ad, const AdObject &object) { UNUSED_ARG(ad); - const bool enabled = ad_security_get_protected_against_deletion(object, g_adconfig); + const bool enabled = ad_security_get_protected_against_deletion(object); check->setChecked(enabled); } -void ProtectDeletionEdit::set_read_only(const bool read_only) { - check->setDisabled(read_only); -} - 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); + const bool apply_success = ad_security_set_protected_against_deletion(ad, dn, enabled); return apply_success; } diff --git a/src/admc/attribute_edits/protect_deletion_edit.h b/src/admc/attribute_edits/protect_deletion_edit.h index e4b990ab..4ecb0904 100644 --- a/src/admc/attribute_edits/protect_deletion_edit.h +++ b/src/admc/attribute_edits/protect_deletion_edit.h @@ -33,8 +33,10 @@ class QCheckBox; class ProtectDeletionEdit final : public AttributeEdit { Q_OBJECT public: - ProtectDeletionEdit(QCheckBox *check, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + ProtectDeletionEdit(QCheckBox *check, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + bool apply(AdInterface &ad, const QString &dn) const override; void set_enabled(const bool enabled); diff --git a/src/admc/attribute_edits/sam_name_edit.cpp b/src/admc/attribute_edits/sam_name_edit.cpp index 78be33ec..1759e960 100644 --- a/src/admc/attribute_edits/sam_name_edit.cpp +++ b/src/admc/attribute_edits/sam_name_edit.cpp @@ -25,40 +25,14 @@ #include "utils.h" #include +#include -SamNameEdit::SamNameEdit(QLineEdit *edit_arg, QLineEdit *domain_edit_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) { +SamNameEdit::SamNameEdit(QLineEdit *edit_arg, QLineEdit *domain_edit, QObject *parent) +: AttributeEdit(parent) { edit = edit_arg; - domain_edit = domain_edit_arg; - limit_edit(edit, ATTRIBUTE_SAM_ACCOUNT_NAME); + edit->setMaxLength(SAM_NAME_MAX_LENGTH); - connect( - edit, &QLineEdit::textChanged, - this, &AttributeEdit::edited); -} - -void SamNameEdit::load_internal(AdInterface &ad, const AdObject &object) { - UNUSED_ARG(ad); - - const QString value = object.get_string(ATTRIBUTE_SAM_ACCOUNT_NAME); - edit->setText(value); - - load_domain(); -} - -void SamNameEdit::set_read_only(const bool read_only) { - edit->setDisabled(read_only); -} - -bool SamNameEdit::apply(AdInterface &ad, const QString &dn) const { - const QString new_value = edit->text(); - const bool success = ad.attribute_replace_string(dn, ATTRIBUTE_SAM_ACCOUNT_NAME, new_value); - - return success; -} - -void SamNameEdit::load_domain() { const QString domain_text = []() { const QString domain = g_adconfig->domain(); const QString domain_name = domain.split(".")[0]; @@ -68,4 +42,56 @@ void SamNameEdit::load_domain() { }(); domain_edit->setText(domain_text); + + connect( + edit, &QLineEdit::textChanged, + this, &AttributeEdit::edited); +} + +void SamNameEdit::load(AdInterface &ad, const AdObject &object) { + UNUSED_ARG(ad); + + const QString value = object.get_string(ATTRIBUTE_SAM_ACCOUNT_NAME); + edit->setText(value); +} + +bool SamNameEdit::verify(AdInterface &ad, const QString &dn) const { + UNUSED_ARG(ad); + UNUSED_ARG(dn); + + const bool out = sam_name_edit_verify(edit); + + return out; +} + +// NOTE: requirements are from here +// https://social.technet.microsoft.com/wiki/contents/articles/11216.active-directory-requirements-for-creating-objects.aspx#Note_Regarding_the_quot_quot_Character_in_sAMAccountName +bool sam_name_edit_verify(QLineEdit *edit) { + const QString new_value = edit->text().trimmed(); + + const bool contains_bad_chars = string_contains_bad_chars(new_value, SAM_NAME_BAD_CHARS); + + const bool ends_with_dot = new_value.endsWith("."); + + const bool value_is_valid = (!contains_bad_chars && !ends_with_dot); + + if (!value_is_valid) { + const QString error_text = QString(QCoreApplication::translate("SamNameEdit", "Input field for Logon name (pre-Windows 2000) contains one or more of the following illegal characters: @ \" [ ] : ; | = + * ? < > / \\ ,")); + message_box_warning(edit, QCoreApplication::translate("SamNameEdit", "Error"), error_text); + + return false; + } + + return true; +} + +bool SamNameEdit::apply(AdInterface &ad, const QString &dn) const { + const QString new_value = edit->text().trimmed(); + const bool success = ad.attribute_replace_string(dn, ATTRIBUTE_SAM_ACCOUNT_NAME, new_value); + + return success; +} + +void SamNameEdit::set_enabled(const bool enabled) { + edit->setEnabled(enabled); } diff --git a/src/admc/attribute_edits/sam_name_edit.h b/src/admc/attribute_edits/sam_name_edit.h index 37e73dd5..1a26e924 100644 --- a/src/admc/attribute_edits/sam_name_edit.h +++ b/src/admc/attribute_edits/sam_name_edit.h @@ -28,14 +28,18 @@ class QLineEdit; class SamNameEdit final : public AttributeEdit { Q_OBJECT public: - SamNameEdit(QLineEdit *edit, QLineEdit *domain_edit_arg, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + SamNameEdit(QLineEdit *edit, QLineEdit *domain_edit, QObject *parent); - void load_domain(); + void load(AdInterface &ad, const AdObject &object) override; + bool verify(AdInterface &ad, const QString &dn) const override; + bool apply(AdInterface &ad, const QString &dn) const override; + + void set_enabled(const bool enabled); private: QLineEdit *edit; - QLineEdit *domain_edit; }; +bool sam_name_edit_verify(QLineEdit *edit); + #endif /* SAM_NAME_EDIT_H */ diff --git a/src/admc/attribute_edits/string_edit.cpp b/src/admc/attribute_edits/string_edit.cpp index 98d9ec3e..00fb8d4e 100644 --- a/src/admc/attribute_edits/string_edit.cpp +++ b/src/admc/attribute_edits/string_edit.cpp @@ -26,8 +26,8 @@ #include -StringEdit::StringEdit(QLineEdit *edit_arg, const QString &attribute_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) { +StringEdit::StringEdit(QLineEdit *edit_arg, const QString &attribute_arg, QObject *parent) +: AttributeEdit(parent) { attribute = attribute_arg; edit = edit_arg; @@ -42,7 +42,7 @@ StringEdit::StringEdit(QLineEdit *edit_arg, const QString &attribute_arg, QList< this, &AttributeEdit::edited); } -void StringEdit::load_internal(AdInterface &ad, const AdObject &object) { +void StringEdit::load(AdInterface &ad, const AdObject &object) { UNUSED_ARG(ad); const QString value = [=]() { @@ -58,13 +58,13 @@ void StringEdit::load_internal(AdInterface &ad, const AdObject &object) { edit->setText(value); } -void StringEdit::set_read_only(const bool read_only) { - edit->setDisabled(read_only); -} - bool StringEdit::apply(AdInterface &ad, const QString &dn) const { - const QString new_value = edit->text(); + const QString new_value = edit->text().trimmed(); const bool success = ad.attribute_replace_string(dn, attribute, new_value); return success; } + +void StringEdit::set_enabled(const bool enabled) { + edit->setEnabled(enabled); +} diff --git a/src/admc/attribute_edits/string_edit.h b/src/admc/attribute_edits/string_edit.h index fe282083..e1d08b7e 100644 --- a/src/admc/attribute_edits/string_edit.h +++ b/src/admc/attribute_edits/string_edit.h @@ -28,8 +28,11 @@ class QLineEdit; class StringEdit final : public AttributeEdit { Q_OBJECT public: - StringEdit(QLineEdit *edit_arg, const QString &attribute_arg, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + StringEdit(QLineEdit *edit_arg, const QString &attribute_arg, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + bool apply(AdInterface &ad, const QString &dn) const override; + void set_enabled(const bool enabled) override; private: QLineEdit *edit; diff --git a/src/admc/attribute_edits/string_large_edit.cpp b/src/admc/attribute_edits/string_large_edit.cpp index ea1a6a93..103ff8f9 100644 --- a/src/admc/attribute_edits/string_large_edit.cpp +++ b/src/admc/attribute_edits/string_large_edit.cpp @@ -22,20 +22,25 @@ #include "adldap.h" #include "utils.h" +#include "globals.h" #include -StringLargeEdit::StringLargeEdit(QPlainTextEdit *edit_arg, const QString &attribute_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) { +StringLargeEdit::StringLargeEdit(QPlainTextEdit *edit_arg, const QString &attribute_arg, QObject *parent) +: AttributeEdit(parent) { attribute = attribute_arg; edit = edit_arg; + ignore_on_text_changed = false; connect( edit, &QPlainTextEdit::textChanged, this, &AttributeEdit::edited); + connect( + edit, &QPlainTextEdit::textChanged, + this, &StringLargeEdit::on_text_changed); } -void StringLargeEdit::load_internal(AdInterface &ad, const AdObject &object) { +void StringLargeEdit::load(AdInterface &ad, const AdObject &object) { UNUSED_ARG(ad); const QString value = object.get_string(attribute); @@ -43,13 +48,26 @@ void StringLargeEdit::load_internal(AdInterface &ad, const AdObject &object) { edit->setPlainText(value); } -void StringLargeEdit::set_read_only(const bool read_only) { - edit->setDisabled(read_only); -} - bool StringLargeEdit::apply(AdInterface &ad, const QString &dn) const { const QString new_value = edit->toPlainText(); const bool success = ad.attribute_replace_string(dn, attribute, new_value); return success; } + +// NOTE: this is a custom length limit mechanism +// because QPlainText doesn't have it built-in +void StringLargeEdit::on_text_changed() { + ignore_on_text_changed = true; + { + const int range_upper = g_adconfig->get_attribute_range_upper(attribute); + + const QString value = edit->toPlainText(); + + if (value.length() > range_upper) { + const QString shortened_value = value.left(range_upper); + edit->setPlainText(shortened_value); + } + } + ignore_on_text_changed = false; +} diff --git a/src/admc/attribute_edits/string_large_edit.h b/src/admc/attribute_edits/string_large_edit.h index 6a159508..120c94ca 100644 --- a/src/admc/attribute_edits/string_large_edit.h +++ b/src/admc/attribute_edits/string_large_edit.h @@ -33,12 +33,17 @@ class QPlainTextEdit; class StringLargeEdit final : public AttributeEdit { Q_OBJECT public: - StringLargeEdit(QPlainTextEdit *edit, const QString &attribute_arg, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + StringLargeEdit(QPlainTextEdit *edit, const QString &attribute_arg, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + bool apply(AdInterface &ad, const QString &dn) const override; private: QPlainTextEdit *edit; QString attribute; + bool ignore_on_text_changed; + + void on_text_changed(); }; #endif /* STRING_LARGE_EDIT_H */ diff --git a/src/admc/attribute_edits/string_list_edit.cpp b/src/admc/attribute_edits/string_list_edit.cpp new file mode 100644 index 00000000..1cbf88f3 --- /dev/null +++ b/src/admc/attribute_edits/string_list_edit.cpp @@ -0,0 +1,65 @@ +/* + * 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 "attribute_edits/string_list_edit.h" + +#include "adldap.h" +#include "utils.h" +#include "attribute_dialogs/list_attribute_dialog.h" + +#include + +StringListEdit::StringListEdit(QPushButton *button_arg, const QString &attribute_arg, QObject *parent) +: AttributeEdit(parent) { + button = button_arg; + attribute = attribute_arg; + + connect( + button, &QPushButton::clicked, + this, &StringListEdit::on_button); +} + +void StringListEdit::load(AdInterface &ad, const AdObject &object) { + UNUSED_ARG(ad); + + values = object.get_values(attribute); +} + +bool StringListEdit::apply(AdInterface &ad, const QString &dn) const { + const bool success = ad.attribute_replace_values(dn, attribute, values); + + return success; +} + +void StringListEdit::on_button() { + const bool read_only = false; + auto dialog = new ListAttributeDialog(values, attribute, read_only, button); + dialog->setWindowTitle(tr("Edit values")); + dialog->open(); + + connect( + dialog, &QDialog::accepted, + this, + [this, dialog]() { + values = dialog->get_value_list(); + + emit edited(); + }); +} diff --git a/src/admc/attribute_multi_edits/string_multi_edit.h b/src/admc/attribute_edits/string_list_edit.h similarity index 62% rename from src/admc/attribute_multi_edits/string_multi_edit.h rename to src/admc/attribute_edits/string_list_edit.h index a9a0832d..77c991cd 100644 --- a/src/admc/attribute_multi_edits/string_multi_edit.h +++ b/src/admc/attribute_edits/string_list_edit.h @@ -18,27 +18,28 @@ * along with this program. If not, see . */ -#ifndef STRING_MULTI_EDIT_H -#define STRING_MULTI_EDIT_H +#ifndef STRING_LIST_EDIT_H +#define STRING_LIST_EDIT_H -#include "attribute_multi_edits/attribute_multi_edit.h" +#include "attribute_edits/attribute_edit.h" -class QLineEdit; +class QPushButton; -/** - * Edit for editing string attributes of multiple objects. - */ - -class StringMultiEdit : public AttributeMultiEdit { +class StringListEdit final : public AttributeEdit { Q_OBJECT public: - StringMultiEdit(QLineEdit *edit, QCheckBox *check, const QString &attribute_arg, QList &edits_out, QObject *parent); + StringListEdit(QPushButton *button, const QString &attribute, QObject *parent); - DECL_ATTRIBUTE_MULTI_EDIT_VIRTUALS(); + void load(AdInterface &ad, const AdObject &object) override; + + bool apply(AdInterface &ad, const QString &dn) const override; private: - QLineEdit *edit; + QPushButton *button; QString attribute; + QList values; + + void on_button(); }; -#endif /* STRING_MULTI_EDIT_H */ +#endif /* STRING_LIST_EDIT_H */ diff --git a/src/admc/attribute_edits/string_other_edit.cpp b/src/admc/attribute_edits/string_other_edit.cpp index 26e16c5a..4b203dde 100644 --- a/src/admc/attribute_edits/string_other_edit.cpp +++ b/src/admc/attribute_edits/string_other_edit.cpp @@ -28,10 +28,12 @@ #include #include -StringOtherEdit::StringOtherEdit(QLineEdit *line_edit_arg, QPushButton *other_button_arg, const QString &main_attribute, const QString &other_attribute_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) +StringOtherEdit::StringOtherEdit(QLineEdit *line_edit_arg, QPushButton *other_button_arg, const QString &main_attribute, const QString &other_attribute_arg, QObject *parent) +: AttributeEdit(parent) , other_attribute(other_attribute_arg) { - main_edit = new StringEdit(line_edit_arg, main_attribute, nullptr, parent); + main_edit = new StringEdit(line_edit_arg, main_attribute, parent); + + line_edit = line_edit_arg; other_button = other_button_arg; @@ -43,7 +45,7 @@ StringOtherEdit::StringOtherEdit(QLineEdit *line_edit_arg, QPushButton *other_bu this, &StringOtherEdit::on_other_button); } -void StringOtherEdit::load_internal(AdInterface &ad, const AdObject &object) { +void StringOtherEdit::load(AdInterface &ad, const AdObject &object) { main_edit->load(ad, object); other_values = object.get_values(other_attribute); @@ -51,7 +53,7 @@ void StringOtherEdit::load_internal(AdInterface &ad, const AdObject &object) { void StringOtherEdit::set_read_only(const bool read_only_arg) { read_only = read_only_arg; - main_edit->set_read_only(read_only); + line_edit->setReadOnly(read_only); } bool StringOtherEdit::apply(AdInterface &ad, const QString &dn) const { @@ -63,10 +65,12 @@ bool StringOtherEdit::apply(AdInterface &ad, const QString &dn) const { void StringOtherEdit::on_other_button() { auto dialog = new ListAttributeDialog(other_values, other_attribute, read_only, other_button); + dialog->setWindowTitle(tr("Edit other values")); dialog->open(); connect( dialog, &QDialog::accepted, + this, [this, dialog]() { other_values = dialog->get_value_list(); diff --git a/src/admc/attribute_edits/string_other_edit.h b/src/admc/attribute_edits/string_other_edit.h index c4bac785..9555d758 100644 --- a/src/admc/attribute_edits/string_other_edit.h +++ b/src/admc/attribute_edits/string_other_edit.h @@ -38,12 +38,22 @@ class QLineEdit; class StringOtherEdit final : public AttributeEdit { Q_OBJECT public: - StringOtherEdit(QLineEdit *line_edit, QPushButton *other_button, const QString &main_attribute_arg, const QString &other_attribute_arg, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + StringOtherEdit(QLineEdit *line_edit, QPushButton *other_button, const QString &main_attribute_arg, const QString &other_attribute_arg, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + + // Sets read only property on both main edit and + // edit dialog for "Other" values. Dialog for + // "Other" values can still be opened to view them, + // while read only is active. + void set_read_only(const bool read_only); + + bool apply(AdInterface &ad, const QString &dn) const override; private: StringEdit *main_edit; QPushButton *other_button; + QLineEdit *line_edit; bool read_only; const QString other_attribute; diff --git a/src/admc/attribute_edits/unlock_edit.cpp b/src/admc/attribute_edits/unlock_edit.cpp index d3d083ef..5b69cbbc 100644 --- a/src/admc/attribute_edits/unlock_edit.cpp +++ b/src/admc/attribute_edits/unlock_edit.cpp @@ -25,8 +25,8 @@ #include -UnlockEdit::UnlockEdit(QCheckBox *check_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) { +UnlockEdit::UnlockEdit(QCheckBox *check_arg, QObject *parent) +: AttributeEdit(parent) { check = check_arg; connect( @@ -38,17 +38,13 @@ QString UnlockEdit::label_text() { return tr("Unlock account"); } -void UnlockEdit::load_internal(AdInterface &ad, const AdObject &object) { +void UnlockEdit::load(AdInterface &ad, const AdObject &object) { UNUSED_ARG(ad); UNUSED_ARG(object); check->setChecked(false); } -void UnlockEdit::set_read_only(const bool read_only) { - check->setDisabled(read_only); -} - bool UnlockEdit::apply(AdInterface &ad, const QString &dn) const { if (check->isChecked()) { diff --git a/src/admc/attribute_edits/unlock_edit.h b/src/admc/attribute_edits/unlock_edit.h index e547e40a..be1d5c71 100644 --- a/src/admc/attribute_edits/unlock_edit.h +++ b/src/admc/attribute_edits/unlock_edit.h @@ -39,8 +39,10 @@ class QCheckBox; class UnlockEdit final : public AttributeEdit { Q_OBJECT public: - UnlockEdit(QCheckBox *check_arg, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + UnlockEdit(QCheckBox *check_arg, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + bool apply(AdInterface &ad, const QString &dn) const override; static QString label_text(); diff --git a/src/admc/attribute_edits/upn_edit.cpp b/src/admc/attribute_edits/upn_edit.cpp index 8220f529..575329d5 100644 --- a/src/admc/attribute_edits/upn_edit.cpp +++ b/src/admc/attribute_edits/upn_edit.cpp @@ -28,26 +28,26 @@ #include #include -UpnEdit::UpnEdit(QLineEdit *prefix_edit_arg, QComboBox *upn_suffix_combo_arg, QList *edits_out, QObject *parent) -: AttributeEdit(edits_out, parent) { +UpnEdit::UpnEdit(QLineEdit *prefix_edit_arg, QComboBox *upn_suffix_combo_arg, QObject *parent) +: AttributeEdit(parent) { prefix_edit = prefix_edit_arg; upn_suffix_combo = upn_suffix_combo_arg; - limit_edit(prefix_edit, ATTRIBUTE_USER_PRINCIPAL_NAME); - connect( prefix_edit, &QLineEdit::textChanged, this, &AttributeEdit::edited); connect( upn_suffix_combo, &QComboBox::currentTextChanged, - this, &AttributeEdit::edited); + this, &UpnEdit::on_suffix_combo_changed); } void UpnEdit::init_suffixes(AdInterface &ad) { upn_suffix_combo_init(upn_suffix_combo, ad); + + on_suffix_combo_changed(); } -void UpnEdit::load_internal(AdInterface &ad, const AdObject &object) { +void UpnEdit::load(AdInterface &ad, const AdObject &object) { UNUSED_ARG(ad); upn_suffix_combo_load(upn_suffix_combo, object); @@ -56,15 +56,22 @@ void UpnEdit::load_internal(AdInterface &ad, const AdObject &object) { prefix_edit->setText(prefix); } -void UpnEdit::set_read_only(const bool read_only) { - prefix_edit->setDisabled(read_only); -} - bool UpnEdit::verify(AdInterface &ad, const QString &dn) const { const QString new_value = get_new_value(); + const QString new_prefix = prefix_edit->text(); - if (new_value.isEmpty()) { - const QString text = tr("UPN may not be empty."); + const bool contains_bad_chars = [&]() { + const bool some_bad_chars = string_contains_bad_chars(new_prefix, UPN_BAD_CHARS); + const bool starts_with_space = new_prefix.startsWith(" "); + const bool ends_with_space = new_prefix.endsWith(" "); + + const bool out = (some_bad_chars || starts_with_space || ends_with_space); + + return out; + }(); + + if (contains_bad_chars) { + const QString text = tr("Input field for User Principal Name contains one or more of the following illegal characters: # , + \" \\ < > (leading space) (trailing space)"); message_box_warning(prefix_edit, tr("Error"), text); return false; @@ -72,7 +79,7 @@ bool UpnEdit::verify(AdInterface &ad, const QString &dn) const { // Check that new upn is unique // NOTE: filter has to also check that it's not the same object because of attribute edit weirdness. If user edits logon name, then retypes original, then applies, the edit will apply because it was modified by the user, even if the value didn't change. Without "not_object_itself", this check would determine that object's logon name conflicts with itself. - const QString base = g_adconfig->domain_head(); + const QString base = g_adconfig->domain_dn(); const SearchScope scope = SearchScope_All; const QString filter = [=]() { const QString not_object_itself = filter_CONDITION(Condition_NotEquals, ATTRIBUTE_DN, dn); @@ -104,7 +111,21 @@ bool UpnEdit::apply(AdInterface &ad, const QString &dn) const { } QString UpnEdit::get_new_value() const { - const QString prefix = prefix_edit->text(); + const QString prefix = prefix_edit->text().trimmed(); const QString suffix = upn_suffix_combo->currentText(); return QString("%1@%2").arg(prefix, suffix); } + +void UpnEdit::on_suffix_combo_changed() { + const int prefix_range_upper = [&]() { + const int total_range_upper = g_adconfig->get_attribute_range_upper(ATTRIBUTE_USER_PRINCIPAL_NAME); + const int at_length = QString("@").length(); + const int suffix_length = upn_suffix_combo->currentText().length(); + const int out = total_range_upper - at_length - suffix_length; + + return out; + }(); + prefix_edit->setMaxLength(prefix_range_upper); + + emit edited(); +} diff --git a/src/admc/attribute_edits/upn_edit.h b/src/admc/attribute_edits/upn_edit.h index b9033d04..e281e55b 100644 --- a/src/admc/attribute_edits/upn_edit.h +++ b/src/admc/attribute_edits/upn_edit.h @@ -30,13 +30,14 @@ class QComboBox; class UpnEdit final : public AttributeEdit { Q_OBJECT public: - UpnEdit(QLineEdit *prefix_edit, QComboBox *suffix_combo, QList *edits_out, QObject *parent); - DECL_ATTRIBUTE_EDIT_VIRTUALS(); + UpnEdit(QLineEdit *prefix_edit, QComboBox *suffix_combo, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + bool verify(AdInterface &ad, const QString &dn) const override; + bool apply(AdInterface &ad, const QString &dn) const override; void init_suffixes(AdInterface &ad); - bool verify(AdInterface &ad, const QString &dn) const override; - private: QLineEdit *prefix_edit; QComboBox *upn_suffix_combo; @@ -44,6 +45,8 @@ private: friend class StringOtherEdit; QString get_new_value() const; + void on_suffix_combo_changed(); + void update_prefix_limit(); }; #endif /* UPN_EDIT_H */ diff --git a/src/admc/attribute_multi_edits/upn_multi_edit.cpp b/src/admc/attribute_edits/upn_multi_edit.cpp similarity index 76% rename from src/admc/attribute_multi_edits/upn_multi_edit.cpp rename to src/admc/attribute_edits/upn_multi_edit.cpp index 16a56dc9..7dbb5a7d 100644 --- a/src/admc/attribute_multi_edits/upn_multi_edit.cpp +++ b/src/admc/attribute_edits/upn_multi_edit.cpp @@ -18,7 +18,7 @@ * along with this program. If not, see . */ -#include "attribute_multi_edits/upn_multi_edit.h" +#include "attribute_edits/upn_multi_edit.h" #include "adldap.h" #include "attribute_edits/upn_suffix_combo.h" @@ -26,19 +26,13 @@ #include -UpnMultiEdit::UpnMultiEdit(QComboBox *upn_suffix_combo_arg, QCheckBox *check, QList &edits_out, AdInterface &ad, QObject *parent) -: AttributeMultiEdit(check, edits_out, parent) { +UpnMultiEdit::UpnMultiEdit(QComboBox *upn_suffix_combo_arg, AdInterface &ad, QObject *parent) +: AttributeEdit(parent) { upn_suffix_combo = upn_suffix_combo_arg; upn_suffix_combo_init(upn_suffix_combo, ad); - - connect( - upn_suffix_combo, &QComboBox::currentTextChanged, - this, &AttributeMultiEdit::edited); - - set_enabled(false); } -bool UpnMultiEdit::apply_internal(AdInterface &ad, const QString &target) { +bool UpnMultiEdit::apply(AdInterface &ad, const QString &target) const { const QString new_value = [&]() { const AdObject current_object = ad.search_object(target); const QString current_prefix = current_object.get_upn_prefix(); diff --git a/src/admc/attribute_multi_edits/upn_multi_edit.h b/src/admc/attribute_edits/upn_multi_edit.h similarity index 65% rename from src/admc/attribute_multi_edits/upn_multi_edit.h rename to src/admc/attribute_edits/upn_multi_edit.h index ba7799e1..cb796afc 100644 --- a/src/admc/attribute_multi_edits/upn_multi_edit.h +++ b/src/admc/attribute_edits/upn_multi_edit.h @@ -22,20 +22,23 @@ #define UPN_MULTI_EDIT_H /** - * Edit for editing UPN suffixes of multiple objects. Note - * that prefix remains unchanged. + * Edit for editing UPN suffixes of multiple objects. + * This special version is needed because when editing + * multiple objects, only the suffix is edited, so + * normal UpnEdit doesn't work for this. */ -#include "attribute_multi_edits/attribute_multi_edit.h" +#include "attribute_edits/attribute_edit.h" class QComboBox; -class UpnMultiEdit final : public AttributeMultiEdit { +class UpnMultiEdit final : public AttributeEdit { Q_OBJECT public: - UpnMultiEdit(QComboBox *upn_suffix_combo_arg, QCheckBox *check, QList &edits_out, AdInterface &ad, QObject *parent); + UpnMultiEdit(QComboBox *upn_suffix_combo, AdInterface &ad, QObject *parent); - DECL_ATTRIBUTE_MULTI_EDIT_VIRTUALS(); + bool apply(AdInterface &ad, const QString &target) const override; + void set_enabled(const bool enabled) override; private: QComboBox *upn_suffix_combo; diff --git a/src/admc/attribute_multi_edits/attribute_multi_edit.cpp b/src/admc/attribute_multi_edits/attribute_multi_edit.cpp deleted file mode 100644 index 7a011263..00000000 --- a/src/admc/attribute_multi_edits/attribute_multi_edit.cpp +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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 "attribute_multi_edits/attribute_multi_edit.h" - -#include "multi_tabs/properties_multi_tab.h" - -#include -#include - -AttributeMultiEdit::AttributeMultiEdit(QCheckBox *apply_check_arg, QList &edits_out, QObject *parent) -: QObject(parent) { - if (edits_out.contains(this)) { - qDebug() << "ERROR: attribute edit added twice to list!"; - } else { - edits_out.append(this); - } - - apply_check = apply_check_arg; - - connect( - apply_check, &QAbstractButton::toggled, - this, &AttributeMultiEdit::on_check_toggled); -} - -bool AttributeMultiEdit::apply(AdInterface &ad, const QList &target_list) { - const bool need_to_apply = apply_check->isChecked(); - if (!need_to_apply) { - return true; - } - - bool total_success = true; - - for (const QString &target : target_list) { - const bool success = apply_internal(ad, target); - - if (!success) { - total_success = false; - } - } - - apply_check->setChecked(false); - - return total_success; -} - -void AttributeMultiEdit::reset() { - // NOTE: when apply_check is unchecked, the - // on_check_toggled() calls set_enabled() - apply_check->setChecked(false); -} - -void AttributeMultiEdit::on_check_toggled() { - if (apply_check->isChecked()) { - emit edited(); - } - - // NOTE: call set_enabled() of the subclass - const bool enabled = apply_check->isChecked(); - set_enabled(enabled); -} - -void multi_edits_connect_to_tab(const QList &edits, PropertiesMultiTab *tab) { - for (auto edit : edits) { - QObject::connect( - edit, &AttributeMultiEdit::edited, - tab, &PropertiesMultiTab::on_edit_edited); - } -} diff --git a/src/admc/attribute_multi_edits/attribute_multi_edit.h b/src/admc/attribute_multi_edits/attribute_multi_edit.h deleted file mode 100644 index ad7271f0..00000000 --- a/src/admc/attribute_multi_edits/attribute_multi_edit.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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 ATTRIBUTE_MULTI_EDIT_H -#define ATTRIBUTE_MULTI_EDIT_H - -#include - -class AdInterface; -class PropertiesMultiTab; -class QCheckBox; - -/** - * Base class for attribute multi edits. - */ - -class AttributeMultiEdit : public QObject { - Q_OBJECT -public: - AttributeMultiEdit(QCheckBox *check, QList &edits_out, QObject *parent); - - bool apply(AdInterface &ad, const QList &target_list); - void reset(); - -private slots: - void on_check_toggled(); - -signals: - void edited(); - -protected: - QCheckBox *apply_check; - - virtual bool apply_internal(AdInterface &ad, const QString &target) = 0; - virtual void set_enabled(const bool enabled) = 0; -}; - -#define DECL_ATTRIBUTE_MULTI_EDIT_VIRTUALS() \ -protected: \ - bool apply_internal(AdInterface &ad, const QString &target) override; \ - void set_enabled(const bool enabled) override; \ - \ -public: - -void multi_edits_connect_to_tab(const QList &edits, PropertiesMultiTab *tab); - -#endif /* ATTRIBUTE_MULTI_EDIT_H */ diff --git a/src/admc/attribute_multi_edits/country_multi_edit.cpp b/src/admc/attribute_multi_edits/country_multi_edit.cpp deleted file mode 100644 index a25cb540..00000000 --- a/src/admc/attribute_multi_edits/country_multi_edit.cpp +++ /dev/null @@ -1,47 +0,0 @@ -/* - * 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 "attribute_multi_edits/country_multi_edit.h" - -#include "adldap.h" -#include "attribute_edits/country_combo.h" -#include "globals.h" - -#include -#include - -CountryMultiEdit::CountryMultiEdit(QComboBox *country_combo_arg, QCheckBox *check, QList &edits_out, QObject *parent) -: AttributeMultiEdit(check, edits_out, parent) { - country_combo = country_combo_arg; - - connect( - country_combo, QOverload::of(&QComboBox::currentIndexChanged), - this, &AttributeMultiEdit::edited); - - set_enabled(false); -} - -bool CountryMultiEdit::apply_internal(AdInterface &ad, const QString &target) { - return country_combo_apply(country_combo, ad, target); -} - -void CountryMultiEdit::set_enabled(const bool enabled) { - country_combo->setEnabled(enabled); -} diff --git a/src/admc/attribute_multi_edits/expiry_multi_edit.cpp b/src/admc/attribute_multi_edits/expiry_multi_edit.cpp deleted file mode 100644 index 43890512..00000000 --- a/src/admc/attribute_multi_edits/expiry_multi_edit.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 "attribute_multi_edits/expiry_multi_edit.h" - -#include "adldap.h" -#include "attribute_edits/expiry_widget.h" -#include "globals.h" - -#include -#include - -ExpiryMultiEdit::ExpiryMultiEdit(ExpiryWidget *edit_widget_arg, QCheckBox *check, QList &edits_out, QObject *parent) -: AttributeMultiEdit(check, edits_out, parent) { - edit_widget = edit_widget_arg; - - const QString label_text = g_adconfig->get_attribute_display_name(ATTRIBUTE_ACCOUNT_EXPIRES, "") + ":"; - apply_check->setText(label_text); - - connect( - edit_widget, &ExpiryWidget::edited, - this, &AttributeMultiEdit::edited); - - set_enabled(false); -} - -bool ExpiryMultiEdit::apply_internal(AdInterface &ad, const QString &target) { - return edit_widget->apply(ad, target); -} - -void ExpiryMultiEdit::set_enabled(const bool enabled) { - if (!enabled) { - edit_widget->load(AdObject()); - } - - edit_widget->setEnabled(enabled); -} diff --git a/src/admc/attribute_multi_edits/manager_multi_edit.cpp b/src/admc/attribute_multi_edits/manager_multi_edit.cpp deleted file mode 100644 index b6b75929..00000000 --- a/src/admc/attribute_multi_edits/manager_multi_edit.cpp +++ /dev/null @@ -1,54 +0,0 @@ -/* - * 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 "attribute_multi_edits/manager_multi_edit.h" - -#include "adldap.h" -#include "attribute_edits/manager_widget.h" -#include "globals.h" - -#include -#include - -ManagerMultiEdit::ManagerMultiEdit(ManagerWidget *widget_arg, QCheckBox *check, QList &edits_out, QObject *parent) -: AttributeMultiEdit(check, edits_out, parent) { - const QString label_text = g_adconfig->get_attribute_display_name(ATTRIBUTE_MANAGER, CLASS_USER) + ":"; - apply_check->setText(label_text); - - widget = widget_arg; - - connect( - widget, &ManagerWidget::edited, - this, &AttributeMultiEdit::edited); - - set_enabled(false); -} - -bool ManagerMultiEdit::apply_internal(AdInterface &ad, const QString &target) { - return widget->apply(ad, target); -} - -void ManagerMultiEdit::set_enabled(const bool enabled) { - if (!enabled) { - widget->reset(); - } - - widget->setEnabled(enabled); -} diff --git a/src/admc/attribute_multi_edits/string_multi_edit.cpp b/src/admc/attribute_multi_edits/string_multi_edit.cpp deleted file mode 100644 index e6b7f4a7..00000000 --- a/src/admc/attribute_multi_edits/string_multi_edit.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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 "attribute_multi_edits/string_multi_edit.h" - -#include "adldap.h" -#include "globals.h" - -#include - -StringMultiEdit::StringMultiEdit(QLineEdit *edit_arg, QCheckBox *check, const QString &attribute_arg, QList &edits_out, QObject *parent) -: AttributeMultiEdit(check, edits_out, parent) { - attribute = attribute_arg; - - edit = edit_arg; - - connect( - edit, &QLineEdit::textChanged, - this, &AttributeMultiEdit::edited); - - set_enabled(false); -} - -bool StringMultiEdit::apply_internal(AdInterface &ad, const QString &target) { - const QString new_value = edit->text(); - return ad.attribute_replace_string(target, attribute, new_value); -} - -void StringMultiEdit::set_enabled(const bool enabled) { - if (!enabled) { - edit->setText(QString()); - } - - edit->setEnabled(enabled); -} diff --git a/src/admc/change_dc_dialog.ui b/src/admc/change_dc_dialog.ui deleted file mode 100644 index 8fe5aee0..00000000 --- a/src/admc/change_dc_dialog.ui +++ /dev/null @@ -1,136 +0,0 @@ - - - ChangeDCDialog - - - - 0 - 0 - 509 - 452 - - - - - 500 - 200 - - - - Change Domain Controller - - - - - - Select: - - - true - - - - - - - - - - Custom: - - - - - - - false - - - - - - - Save this setting - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - button_box - accepted() - ChangeDCDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - button_box - rejected() - ChangeDCDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - custom_button - toggled(bool) - custom_edit - setEnabled(bool) - - - 254 - 318 - - - 254 - 354 - - - - - select_button - toggled(bool) - select_listwidget - setEnabled(bool) - - - 254 - 24 - - - 254 - 171 - - - - - diff --git a/src/admc/changelog_dialog.cpp b/src/admc/changelog_dialog.cpp index 669c8544..c527e893 100644 --- a/src/admc/changelog_dialog.cpp +++ b/src/admc/changelog_dialog.cpp @@ -59,10 +59,16 @@ ChangelogDialog::ChangelogDialog(QWidget *parent) return fail_text; } - const QString out = file.readAll(); + QString out = file.readAll(); file.close(); + // Remove forced word wrap contained in + // CHANGELOG.txt so that resizing the dialog + // expands text width (all wrapped lines start + // with 2 spaces) + out.replace("\n ", " "); + return out; }(); diff --git a/src/admc/connection_options_dialog.cpp b/src/admc/connection_options_dialog.cpp index 56455c1b..92712940 100644 --- a/src/admc/connection_options_dialog.cpp +++ b/src/admc/connection_options_dialog.cpp @@ -27,7 +27,7 @@ #include -const QString CERT_STRATEGY_NEVER = "never"; +const QString CERT_STRATEGY_NEVER = CERT_STRATEGY_NEVER_define; const QString CERT_STRATEGY_HARD = "hard"; const QString CERT_STRATEGY_DEMAND = "demand"; const QString CERT_STRATEGY_ALLOW = "allow"; @@ -55,10 +55,10 @@ ConnectionOptionsDialog::ConnectionOptionsDialog(QWidget *parent) const int port = settings_get_variant(SETTING_port).toInt(); ui->port_spinbox->setValue(port); - const bool sasl_nocanon = settings_get_bool(SETTING_sasl_nocanon); + const bool sasl_nocanon = settings_get_variant(SETTING_sasl_nocanon).toBool(); ui->canonize_check->setChecked(sasl_nocanon); - const QString cert_strategy = settings_get_variant(SETTING_cert_strategy, CERT_STRATEGY_NEVER).toString(); + const QString cert_strategy = settings_get_variant(SETTING_cert_strategy).toString(); const int cert_strategy_index = ui->cert_combo->findText(cert_strategy); ui->cert_combo->setCurrentIndex(cert_strategy_index); @@ -75,7 +75,7 @@ ConnectionOptionsDialog::ConnectionOptionsDialog(QWidget *parent) ui->host_select_list->addItem(host); } - const QString saved_host = settings_get_variant(SETTING_host, QString()).toString(); + const QString saved_host = settings_get_variant(SETTING_host).toString(); if (!saved_host.isEmpty()) { // Select saved host in list, if it's there. diff --git a/src/admc/connection_options_dialog.h b/src/admc/connection_options_dialog.h index 6c17c008..434075fa 100644 --- a/src/admc/connection_options_dialog.h +++ b/src/admc/connection_options_dialog.h @@ -27,11 +27,7 @@ #include -extern const QString CERT_STRATEGY_NEVER; -extern const QString CERT_STRATEGY_HARD; -extern const QString CERT_STRATEGY_DEMAND; -extern const QString CERT_STRATEGY_ALLOW; -extern const QString CERT_STRATEGY_TRY; +#define CERT_STRATEGY_NEVER_define "never" namespace Ui { class ConnectionOptionsDialog; diff --git a/src/admc/console_filter_dialog.cpp b/src/admc/console_filter_dialog.cpp index 9694d319..7240a584 100644 --- a/src/admc/console_filter_dialog.cpp +++ b/src/admc/console_filter_dialog.cpp @@ -47,6 +47,11 @@ ConsoleFilterDialog::ConsoleFilterDialog(QWidget *parent) }; ui->class_filter_widget->set_classes(class_list_for_widget, class_list_for_widget); + custom_filter = settings_get_variant(SETTING_object_filter).toString(); + + const int object_display_limit = settings_get_variant(SETTING_object_display_limit).toInt(); + ui->limit_spinbox->setValue(object_display_limit); + settings_setup_dialog_geometry(SETTING_console_filter_dialog_geometry, this); button_state_name_map = { @@ -78,6 +83,13 @@ ConsoleFilterDialog::~ConsoleFilterDialog() { delete ui; } +void ConsoleFilterDialog::accept() { + const int object_display_limit = ui->limit_spinbox->value(); + settings_set_variant(SETTING_object_display_limit, object_display_limit); + + QDialog::accept(); +} + QVariant ConsoleFilterDialog::save_state() const { QHash state; @@ -120,12 +132,11 @@ QString ConsoleFilterDialog::get_filter() const { } void ConsoleFilterDialog::open_custom_dialog() { - auto dialog = new FilterDialog(this); - // NOTE: Using only non-container classes for filtering // because container classes need to always be visible const QList noncontainer_classes = g_adconfig->get_noncontainer_classes(); - dialog->set_classes(noncontainer_classes, noncontainer_classes); + + auto dialog = new FilterDialog(noncontainer_classes, noncontainer_classes, this); dialog->restore_state(filter_dialog_state); @@ -133,6 +144,7 @@ void ConsoleFilterDialog::open_custom_dialog() { connect( dialog, &QDialog::accepted, + this, [this, dialog]() { filter_dialog_state = dialog->save_state(); diff --git a/src/admc/console_filter_dialog.h b/src/admc/console_filter_dialog.h index 7b5b701e..20a26a2d 100644 --- a/src/admc/console_filter_dialog.h +++ b/src/admc/console_filter_dialog.h @@ -45,6 +45,8 @@ public: ConsoleFilterDialog(QWidget *parent); ~ConsoleFilterDialog(); + void accept() override; + QVariant save_state() const; void restore_state(const QVariant &state); diff --git a/src/admc/console_filter_dialog.ui b/src/admc/console_filter_dialog.ui index 2cf1626b..5bd1b4ee 100644 --- a/src/admc/console_filter_dialog.ui +++ b/src/admc/console_filter_dialog.ui @@ -15,56 +15,102 @@ - - - Show all + + + QFrame::StyledPanel - - true + + QFrame::Raised + + + + + Show all + + + true + + + + + + + Show only these types + + + + + + + + + + + + Create custom + + + + + + + Custom + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + - - - Show only these types - - - - - - - - + - + - Create custom + Object display limit: - - - Custom + + + 1 + + + 1000000 - - - - Qt::Horizontal - - - - 40 - 20 - - - - + + + + Qt::Vertical + + + + 20 + 40 + + + + diff --git a/src/admc/console_impls/find_root_impl.cpp b/src/admc/console_impls/find_root_impl.cpp new file mode 100644 index 00000000..4eaea172 --- /dev/null +++ b/src/admc/console_impls/find_root_impl.cpp @@ -0,0 +1,58 @@ +/* + * 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 "console_impls/find_root_impl.h" + +#include "adldap.h" +#include "console_impls/object_impl.h" +#include "console_widget/results_view.h" +#include "item_type.h" + +#include +#include + +FindRootImpl::FindRootImpl(ConsoleWidget *console_arg) +: ConsoleImpl(console_arg) { + set_results_view(new ResultsView(console_arg)); +} + +QString FindRootImpl::get_description(const QModelIndex &index) const { + const QString object_count_text = console_object_count_string(console, index); + + return object_count_text; +} + +QList FindRootImpl::column_labels() const { + return object_impl_column_labels(); +} + +QList FindRootImpl::default_columns() const { + return object_impl_default_columns(); +} + +QModelIndex get_find_tree_root(ConsoleWidget *console) { + const QList index_list = console->search_items(QModelIndex(), ItemType_FindRoot); + + if (!index_list.isEmpty()) { + return index_list[0]; + } else { + return QModelIndex(); + } +} diff --git a/src/admc/console_impls/find_root_impl.h b/src/admc/console_impls/find_root_impl.h new file mode 100644 index 00000000..a92f8284 --- /dev/null +++ b/src/admc/console_impls/find_root_impl.h @@ -0,0 +1,40 @@ +/* + * 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 FIND_ROOT_IMPL_H +#define FIND_ROOT_IMPL_H + +#include "console_widget/console_impl.h" + +class FindRootImpl final : public ConsoleImpl { + Q_OBJECT + +public: + FindRootImpl(ConsoleWidget *console_arg); + + QString get_description(const QModelIndex &index) const override; + + QList column_labels() const override; + QList default_columns() const override; +}; + +QModelIndex get_find_tree_root(ConsoleWidget *console); + +#endif /* FIND_ROOT_IMPL_H */ diff --git a/src/admc/console_impls/item_type.h b/src/admc/console_impls/item_type.h index 77f7855a..c8e94031 100644 --- a/src/admc/console_impls/item_type.h +++ b/src/admc/console_impls/item_type.h @@ -28,6 +28,7 @@ enum ItemType { ItemType_Policy, ItemType_QueryFolder, ItemType_QueryItem, + ItemType_FindRoot, ItemType_LAST, }; diff --git a/src/admc/console_impls/object_impl.cpp b/src/admc/console_impls/object_impl.cpp index d6b53867..0f41a46f 100644 --- a/src/admc/console_impls/object_impl.cpp +++ b/src/admc/console_impls/object_impl.cpp @@ -25,11 +25,14 @@ #include "console_impls/policy_impl.h" #include "console_impls/query_folder_impl.h" #include "console_impls/query_item_impl.h" +#include "console_impls/find_root_impl.h" #include "console_widget/results_view.h" #include "create_computer_dialog.h" #include "create_group_dialog.h" #include "create_ou_dialog.h" #include "create_user_dialog.h" +#include "create_shared_folder_dialog.h" +#include "create_contact_dialog.h" #include "attribute_dialogs/list_attribute_dialog.h" #include "find_object_dialog.h" #include "globals.h" @@ -53,6 +56,8 @@ #include #include +#include + enum DropType { DropType_Move, DropType_AddToGroup, @@ -61,8 +66,8 @@ enum DropType { DropType console_object_get_drop_type(const QModelIndex &dropped, const QModelIndex &target); QList index_list_to_dn_list(const QList &index_list); -QList get_selected_dn_list_object(ConsoleWidget *console); -QString get_selected_dn_object(ConsoleWidget *console); +QList get_action_target_dn_list_object(ConsoleWidget *console); +QString get_action_target_dn_object(ConsoleWidget *console); void console_object_delete(ConsoleWidget *console, const QList &dn_list, const QModelIndex &tree_root); ObjectImpl::ObjectImpl(ConsoleWidget *console_arg) @@ -75,13 +80,16 @@ ObjectImpl::ObjectImpl(ConsoleWidget *console_arg) find_action_enabled = true; refresh_action_enabled = true; - object_filter = settings_get_variant(SETTING_object_filter, QString()).toString(); - object_filter_enabled = settings_get_variant(SETTING_object_filter_enabled, false).toBool(); + object_filter = settings_get_variant(SETTING_object_filter).toString(); + object_filter_enabled = settings_get_variant(SETTING_object_filter_enabled).toBool(); - auto new_user_action = new QAction(tr("User"), this); - auto new_computer_action = new QAction(tr("Computer"), this); - auto new_ou_action = new QAction(tr("OU"), this); - auto new_group_action = new QAction(tr("Group"), this); + new_action_map[CLASS_USER] = new QAction(tr("User"), this); + new_action_map[CLASS_COMPUTER] = new QAction(tr("Computer"), this); + new_action_map[CLASS_OU] = new QAction(tr("OU"), this); + new_action_map[CLASS_GROUP] = new QAction(tr("Group"), this); + new_action_map[CLASS_SHARED_FOLDER] = new QAction(tr("Shared Folder"), this); + new_action_map[CLASS_INET_ORG_PERSON] = new QAction(tr("inetOrgPerson"), this); + new_action_map[CLASS_CONTACT] = new QAction(tr("Contact"), this); find_action = new QAction(tr("Find..."), this); move_action = new QAction(tr("Move..."), this); add_to_group_action = new QAction(tr("Add to group..."), this); @@ -94,23 +102,38 @@ ObjectImpl::ObjectImpl(ConsoleWidget *console_arg) auto new_menu = new QMenu(tr("New"), console_arg); new_action = new_menu->menuAction(); - new_menu->addAction(new_user_action); - new_menu->addAction(new_computer_action); - new_menu->addAction(new_ou_action); - new_menu->addAction(new_group_action); + const QList new_action_keys_sorted = [&]() { + QList out = new_action_map.keys(); + std::sort(out.begin(), out.end()); + + return out; + }(); + for (const QString &key : new_action_keys_sorted) { + QAction *action = new_action_map[key]; + new_menu->addAction(action); + } connect( - new_user_action, &QAction::triggered, + new_action_map[CLASS_USER], &QAction::triggered, this, &ObjectImpl::on_new_user); connect( - new_computer_action, &QAction::triggered, + new_action_map[CLASS_COMPUTER], &QAction::triggered, this, &ObjectImpl::on_new_computer); connect( - new_ou_action, &QAction::triggered, + new_action_map[CLASS_OU], &QAction::triggered, this, &ObjectImpl::on_new_ou); connect( - new_group_action, &QAction::triggered, + new_action_map[CLASS_GROUP], &QAction::triggered, this, &ObjectImpl::on_new_group); + connect( + new_action_map[CLASS_SHARED_FOLDER], &QAction::triggered, + this, &ObjectImpl::on_new_shared_folder); + connect( + new_action_map[CLASS_INET_ORG_PERSON], &QAction::triggered, + this, &ObjectImpl::on_new_inet_org_person); + connect( + new_action_map[CLASS_CONTACT], &QAction::triggered, + this, &ObjectImpl::on_new_contact); connect( move_action, &QAction::triggered, this, &ObjectImpl::on_move); @@ -166,8 +189,7 @@ void ObjectImpl::fetch(const QModelIndex &index) { out = filter_OR({object_filter, out}); } - advanced_features_filter(out); - dev_mode_filter(out); + out = advanced_features_filter(out); return out; }(); @@ -176,10 +198,10 @@ void ObjectImpl::fetch(const QModelIndex &index) { // NOTE: do an extra search before real search for // objects that should be visible in dev mode - const bool dev_mode = settings_get_bool(SETTING_dev_mode); + const bool dev_mode = settings_get_variant(SETTING_feature_dev_mode).toBool(); if (dev_mode) { AdInterface ad; - if (ad_connected(ad)) { + if (ad_connected(ad, console)) { QHash results; dev_mode_search_results(results, ad, base); @@ -334,6 +356,27 @@ QSet ObjectImpl::get_custom_actions(const QModelIndex &index, const b out.insert(move_action); + // NOTE: have to manually call setVisible here + // because "New" actions are contained inside "New" + // sub-menu, so console widget can't manage them + for (const QString &action_object_class : new_action_map.keys()) { + QAction *action = new_action_map[action_object_class]; + + const bool is_visible = [&action_object_class, &object_class]() { + // NOTE: to get full list of possible + // superiors, need to use the all of the parent + // classes too, not just the leaf class + const QList action_object_class_list = g_adconfig->get_inherit_chain(action_object_class); + const QList possible_superiors = g_adconfig->get_possible_superiors(QList(action_object_class_list)); + const bool is_visible_out = possible_superiors.contains(object_class); + + return is_visible_out; + }(); + + + action->setVisible(is_visible); + } + return out; } @@ -364,7 +407,19 @@ QSet ObjectImpl::get_standard_actions(const QModelIndex &index, out.insert(StandardAction_Refresh); } - if (single_selection) { + const bool can_rename = [&]() { + const QList renamable_class_list = { + CLASS_USER, + CLASS_GROUP, + CLASS_OU, + }; + const QString object_class = index.data(ObjectRole_ObjectClasses).toStringList().last(); + const bool can_rename_out = (single_selection && renamable_class_list.contains(object_class)); + + return can_rename_out; + }(); + + if (can_rename) { out.insert(StandardAction_Rename); } @@ -393,6 +448,13 @@ QSet ObjectImpl::get_disabled_standard_actions(const QModelIndex } void ObjectImpl::rename(const QList &index_list) { + AdInterface ad; + if (ad_failed(ad, console)) { + return; + } + + const QString old_dn = get_action_target_dn_object(console); + RenameObjectDialog *dialog = [&]() -> RenameObjectDialog * { const QModelIndex index = index_list[0]; const QString object_class = index.data(ObjectRole_ObjectClasses).toStringList().last(); @@ -400,46 +462,61 @@ void ObjectImpl::rename(const QList &index_list) { const bool is_group = (object_class == CLASS_GROUP); if (is_user) { - return new RenameUserDialog(console); + return new RenameUserDialog(ad, old_dn, console); } else if (is_group) { - return new RenameGroupDialog(console); + return new RenameGroupDialog(ad, old_dn, console); } else { - return new RenameOtherDialog(console); + return new RenameOtherDialog(ad, old_dn, console); } }(); - const QString old_dn = get_selected_dn_object(console); - - dialog->set_target(old_dn); dialog->open(); connect( dialog, &QDialog::accepted, + this, [this, dialog, old_dn]() { - AdInterface ad; - if (ad_failed(ad)) { + AdInterface ad_inner; + if (ad_failed(ad_inner, console)) { return; } const QString new_dn = dialog->get_new_dn(); const QString parent_dn = dn_get_parent(old_dn); - move_and_rename(ad, {{old_dn, new_dn}}, parent_dn); + move_and_rename(ad_inner, {{old_dn, new_dn}}, parent_dn); }); } void ObjectImpl::properties(const QList &index_list) { + AdInterface ad; + if (ad_failed(ad, console)) { + return; + } + const QList dn_list = index_list_to_dn_list(index_list); - auto on_object_properties_applied = [=]() { - AdInterface ad; - if (ad_failed(ad)) { + auto on_object_properties_applied = [this, dn_list]() { + AdInterface ad2; + if (ad_failed(ad2, console)) { return; } - auto apply_changes = [&ad, &dn_list](ConsoleWidget *target_console) { + auto apply_changes = [&ad2, &dn_list](ConsoleWidget *target_console) { for (const QString &dn : dn_list) { - const AdObject object = ad.search_object(dn); + const AdObject object = ad2.search_object(dn); + + // TODO: band-aid for the situations + // where properties dialog interacts + // with deleted objects. Bad stuff can + // still happen if properties is opened + // while object exists, then object is + // deleted and properties is applied. + // Remove this or improve it when you + // tackle this issue head-on. + if (object.is_empty()) { + continue; + } // NOTE: search for indexes instead of using the // list given to f-n because we want to update @@ -458,14 +535,14 @@ void ObjectImpl::properties(const QList &index_list) { apply_changes(buddy_console); } - g_status->display_ad_messages(ad, console); + g_status->display_ad_messages(ad2, console); }; if (dn_list.size() == 1) { const QString dn = dn_list[0]; bool dialog_is_new; - PropertiesDialog *dialog = PropertiesDialog::open_for_target(dn, &dialog_is_new); + PropertiesDialog *dialog = PropertiesDialog::open_for_target(ad, dn, &dialog_is_new); if (dialog_is_new) { connect( @@ -485,7 +562,7 @@ void ObjectImpl::properties(const QList &index_list) { return out.toList(); }(); - auto dialog = new PropertiesMultiDialog(dn_list, class_list); + auto dialog = new PropertiesMultiDialog(ad, dn_list, class_list); dialog->open(); connect( @@ -512,7 +589,7 @@ void ObjectImpl::delete_action(const QList &index_list) { } AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, console)) { return; } @@ -535,15 +612,16 @@ void ObjectImpl::delete_action(const QList &index_list) { }(); auto apply_changes = [&ad, &deleted_list](ConsoleWidget *target_console) { - const QModelIndex object_root = get_object_tree_root(target_console); - if (object_root.isValid()) { - console_object_delete(target_console, deleted_list, object_root); - } - - // NOTE: also delete in query tree - const QModelIndex query_root = get_query_tree_root(target_console); - if (query_root.isValid()) { - console_object_delete(target_console, deleted_list, query_root); + const QList root_list = { + get_object_tree_root(target_console), + get_query_tree_root(target_console), + get_find_tree_root(target_console), + }; + + for (const QModelIndex &root : root_list) { + if (root.isValid()) { + console_object_delete(target_console, deleted_list, root); + } } }; @@ -597,6 +675,7 @@ void ObjectImpl::open_console_filter_dialog() { connect( dialog, &QDialog::accepted, + this, [this, dialog]() { object_filter = dialog->get_filter(); object_filter_enabled = dialog->get_filter_enabled(); @@ -611,34 +690,52 @@ void ObjectImpl::open_console_filter_dialog() { } void ObjectImpl::on_new_user() { - new_object(new CreateUserDialog(console)); + new_object(CLASS_USER); } void ObjectImpl::on_new_computer() { - new_object(new CreateComputerDialog(console)); + new_object(CLASS_COMPUTER); } void ObjectImpl::on_new_ou() { - new_object(new CreateOUDialog(console)); + new_object(CLASS_OU); } void ObjectImpl::on_new_group() { - new_object(new CreateGroupDialog(console)); + new_object(CLASS_GROUP); +} + +void ObjectImpl::on_new_shared_folder() { + new_object(CLASS_SHARED_FOLDER); +} + +void ObjectImpl::on_new_inet_org_person() { + new_object(CLASS_INET_ORG_PERSON); +} + +void ObjectImpl::on_new_contact() { + new_object(CLASS_CONTACT); } void ObjectImpl::on_move() { - auto dialog = new SelectContainerDialog(console); + AdInterface ad; + if (ad_failed(ad, console)) { + return; + } + + auto dialog = new SelectContainerDialog(ad, console); dialog->open(); connect( dialog, &QDialog::accepted, + this, [this, dialog]() { - AdInterface ad; - if (ad_failed(ad)) { + AdInterface ad2; + if (ad_failed(ad2, console)) { return; } - const QList dn_list = get_selected_dn_list_object(console); + const QList dn_list = get_action_target_dn_list_object(console); show_busy_indicator(); @@ -649,7 +746,7 @@ void ObjectImpl::on_move() { QList out; for (const QString &dn : dn_list) { - const bool success = ad.object_move(dn, new_parent_dn); + const bool success = ad2.object_move(dn, new_parent_dn); if (success) { out.append(dn); @@ -659,10 +756,10 @@ void ObjectImpl::on_move() { return out; }(); - g_status->display_ad_messages(ad, nullptr); + g_status->display_ad_messages(ad2, nullptr); // Then move in console - move(ad, moved_objects, new_parent_dn); + move(ad2, moved_objects, new_parent_dn); hide_busy_indicator(); }); @@ -683,15 +780,16 @@ void ObjectImpl::on_add_to_group() { connect( dialog, &SelectObjectDialog::accepted, - [=]() { + this, + [this, dialog]() { AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, console)) { return; } show_busy_indicator(); - const QList target_list = get_selected_dn_list_object(console); + const QList target_list = get_action_target_dn_list_object(console); const QList groups = dialog->get_selected(); @@ -708,26 +806,29 @@ void ObjectImpl::on_add_to_group() { } void ObjectImpl::on_find() { - const QList dn_list = get_selected_dn_list_object(console); + const QList dn_list = get_action_target_dn_list_object(console); const QString dn = dn_list[0]; - auto find_dialog = new FindObjectDialog(dn, console); - find_dialog->set_buddy_console(console); + auto find_dialog = new FindObjectDialog(console, dn, console); find_dialog->open(); } void ObjectImpl::on_reset_password() { - auto dialog = new PasswordDialog(console); + AdInterface ad; + if (ad_failed(ad, console)) { + return; + } - const QString dn = get_selected_dn_object(console); - dialog->set_target(dn); + const QString dn = get_action_target_dn_object(console); + + auto dialog = new PasswordDialog(ad, dn, console); dialog->open(); } void ObjectImpl::on_edit_upn_suffixes() { AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, console)) { return; } @@ -742,14 +843,25 @@ void ObjectImpl::on_edit_upn_suffixes() { const QString attribute = ATTRIBUTE_UPN_SUFFIXES; const bool read_only = false; auto dialog = new ListAttributeDialog(current_values, attribute, read_only, console); + dialog->setWindowTitle(tr("Edit UPN Suffixes")); + // NOTE: arbitrarily use half of total max length + // of upn. Just a decent choice in absence of any + // "official" value. + const int upn_suffix_max_length = [&]() { + const int upn_max_length = g_adconfig->get_attribute_range_upper(ATTRIBUTE_USER_PRINCIPAL_NAME); + const int out = upn_max_length / 2; + return out; + }(); + dialog->set_value_max_length(upn_suffix_max_length); dialog->open(); connect( dialog, &QDialog::accepted, + this, [this, dialog, partitions_dn]() { AdInterface ad_inner; - if (ad_failed(ad_inner)) { + if (ad_failed(ad_inner, console)) { return; } @@ -761,29 +873,82 @@ void ObjectImpl::on_edit_upn_suffixes() { } void ObjectImpl::on_reset_account() { - AdInterface ad; - if (ad_failed(ad)) { + const bool confirmed = confirmation_dialog(tr("Are you sure you want to reset this account?"), console); + if (!confirmed) { return; } - const QList target_list = get_selected_dn_list_object(console); + AdInterface ad; + if (ad_failed(ad, console)) { + return; + } + + show_busy_indicator(); + + const QList target_list = get_action_target_dn_list_object(console); for (const QString &target : target_list) { ad.computer_reset_account(target); } + + hide_busy_indicator(); + + g_status->display_ad_messages(ad, console); } -void ObjectImpl::new_object(CreateObjectDialog *dialog) { - const QString parent_dn = get_selected_dn_object(console); +void ObjectImpl::new_object(const QString &object_class) { + AdInterface ad; + if (ad_failed(ad, console)) { + return; + } + + const QString parent_dn = get_action_target_dn_object(console); + + // NOTE: creating dialogs here instead of directly + // in "on_new_x" slots looks backwards but it's + // necessary to avoid even more code duplication + // due to having to pass "ad" and "parent_dn" args + // to dialog ctors + CreateObjectDialog *dialog = [&]() -> CreateObjectDialog * { + const bool is_user = (object_class == CLASS_USER); + const bool is_group = (object_class == CLASS_GROUP); + const bool is_computer = (object_class == CLASS_COMPUTER); + const bool is_ou = (object_class == CLASS_OU); + const bool is_shared_folder = (object_class == CLASS_SHARED_FOLDER); + const bool is_inet_org_person = (object_class == CLASS_INET_ORG_PERSON); + const bool is_contact = (object_class == CLASS_CONTACT); + + if (is_user) { + return new CreateUserDialog(ad, parent_dn, CLASS_USER, console); + } else if (is_group) { + return new CreateGroupDialog(parent_dn, console); + } else if (is_computer) { + return new CreateComputerDialog(parent_dn, console); + } else if (is_ou) { + return new CreateOUDialog(parent_dn, console); + } else if (is_shared_folder) { + return new CreateSharedFolderDialog(parent_dn, console); + } else if (is_inet_org_person) { + return new CreateUserDialog(ad, parent_dn, CLASS_INET_ORG_PERSON, console); + } else if (is_contact) { + return new CreateContactDialog(parent_dn, console); + } else { + return nullptr; + } + }(); + + if (dialog == nullptr) { + return; + } - dialog->set_parent_dn(parent_dn); dialog->open(); connect( dialog, &QDialog::accepted, + this, [this, dialog, parent_dn]() { - AdInterface ad; - if (ad_failed(ad)) { + AdInterface ad_inner; + if (ad_failed(ad_inner, console)) { return; } @@ -810,7 +975,16 @@ void ObjectImpl::new_object(CreateObjectDialog *dialog) { } const QModelIndex scope_parent_index = search_parent[0]; - object_impl_add_objects_to_console_from_dns(target_console, ad, {created_dn}, scope_parent_index); + object_impl_add_objects_to_console_from_dns(target_console, ad_inner, {created_dn}, scope_parent_index); + + // NOTE: changes are not applied to + // find tree because creation of + // objects shouldn't affect find + // results. If it does affect them by + // creating a new object that fits + // search criteria, then find dialog + // can just be considered out of date + // and should be updated }; apply_changes(console); @@ -825,7 +999,7 @@ void ObjectImpl::new_object(CreateObjectDialog *dialog) { void ObjectImpl::set_disabled(const bool disabled) { AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, console)) { return; } @@ -834,7 +1008,7 @@ void ObjectImpl::set_disabled(const bool disabled) { const QList changed_objects = [&]() { QList out; - const QList dn_list = get_selected_dn_list_object(console); + const QList dn_list = get_action_target_dn_list_object(console); for (const QString &dn : dn_list) { const bool success = ad.user_set_account_option(dn, AccountOption_Disabled, disabled); @@ -872,7 +1046,7 @@ void ObjectImpl::drop_objects(const QList &dropped_list, const QString target_dn = target.data(ObjectRole_DN).toString(); AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, console)) { return; } @@ -929,7 +1103,30 @@ void ObjectImpl::drop_policies(const QList &dropped_list, } } -void ObjectImpl::move_and_rename(AdInterface &ad, const QHash &old_to_new_dn_map, const QString &new_parent_dn) { +void ObjectImpl::move_and_rename(AdInterface &ad, const QHash &old_to_new_dn_map_arg, const QString &new_parent_dn) { + // NOTE: sometimes, some objects that are supposed + // to be moved don't actually need to be. For + // example, if an object were to be moved to an + // object that is already it's current parent. If + // we were to move them that would cause gui + // glitches because that kind of move is not + // considered in the update logic. Instead, we skip + // these kinds of objects. + const QHash &old_to_new_dn_map = [&]() { + QHash out; + + for (const QString &old_dn : old_to_new_dn_map_arg.keys()) { + const QString new_dn = old_to_new_dn_map_arg[old_dn]; + const bool dn_changed = (new_dn != old_dn); + + if (dn_changed) { + out[old_dn] = new_dn; + } + } + + return out; + }(); + const QList old_dn_list = old_to_new_dn_map.keys(); const QList new_dn_list = old_to_new_dn_map.values(); @@ -1028,6 +1225,38 @@ void ObjectImpl::move_and_rename(AdInterface &ad, const QHash console_object_load(row, object); } } + + // TODO: decrease code duplication + // For find tree, we only reload the rows to + // update name, and attributes (DN for example) + const QModelIndex find_root = get_query_tree_root(target_console); + if (find_root.isValid()) { + // Find indexes of modified objects in find + // tree + const QHash index_map = [&]() { + QHash out; + + for (const QString &old_dn : old_dn_list) { + const QList results = target_console->search_items(find_root, ObjectRole_DN, old_dn, ItemType_Object); + + for (const QModelIndex &index : results) { + out[old_dn] = index; + } + } + + return out; + }(); + + for (const QString &old_dn : index_map.keys()) { + const QModelIndex index = index_map[old_dn]; + + // Update item row + const QList row = target_console->get_row(index); + const QString new_dn = old_to_new_dn_map[old_dn]; + const AdObject object = object_map[new_dn]; + console_object_load(row, object); + } + } }; apply_changes(console); @@ -1082,7 +1311,7 @@ void object_impl_add_objects_to_console(ConsoleWidget *console, const QList console_object_search_attributes() { attributes += ATTRIBUTE_SYSTEM_FLAGS; attributes += ATTRIBUTE_USER_ACCOUNT_CONTROL; + + // NOTE: needed to know which icon to use for object + attributes += ATTRIBUTE_OBJECT_CATEGORY; return attributes; } @@ -1305,9 +1537,8 @@ void console_object_search(ConsoleWidget *console, const QModelIndex &index, con return; } - if (search_thread->failed_to_connect()) { - search_thread_error_log(console); - } + g_status->display_ad_messages(search_thread->get_ad_messages(), console); + search_thread_display_errors(search_thread, console); QStandardItem *item_now = console->get_item(persistent_index); @@ -1328,6 +1559,8 @@ void console_object_search(ConsoleWidget *console, const QModelIndex &index, con item_now->setData(false, ObjectRole_Fetching); item_now->setDragEnabled(true); + + search_thread->deleteLater(); }, Qt::QueuedConnection); @@ -1338,12 +1571,15 @@ void console_object_tree_init(ConsoleWidget *console, AdInterface &ad) { const QList row = console->add_scope_item(ItemType_Object, QModelIndex()); auto root = row[0]; - const QString top_dn = g_adconfig->domain_head(); + const QString top_dn = g_adconfig->domain_dn(); const AdObject top_object = ad.search_object(top_dn); console_object_item_data_load(root, top_object); - const QString domain_head = g_adconfig->domain().toLower(); - root->setText(domain_head); + const QString domain = g_adconfig->domain().toLower(); + root->setText(domain); + + // Select object tree root in scope pane on startup + console->set_current_scope(root->index()); } bool console_object_is_ou(const QModelIndex &index) { @@ -1354,7 +1590,7 @@ bool console_object_is_ou(const QModelIndex &index) { } QModelIndex get_object_tree_root(ConsoleWidget *console) { - const QString head_dn = g_adconfig->domain_head(); + const QString head_dn = g_adconfig->domain_dn(); const QList search_results = console->search_items(QModelIndex(), ObjectRole_DN, head_dn, ItemType_Object); if (!search_results.isEmpty()) { @@ -1440,12 +1676,12 @@ QList index_list_to_dn_list(const QList &index_list) { return out; } -QList get_selected_dn_list_object(ConsoleWidget *console) { - return get_selected_dn_list(console, ItemType_Object, ObjectRole_DN); +QList get_action_target_dn_list_object(ConsoleWidget *console) { + return get_action_target_dn_list(console, ItemType_Object, ObjectRole_DN); } -QString get_selected_dn_object(ConsoleWidget *console) { - return get_selected_dn(console, ItemType_Object, ObjectRole_DN); +QString get_action_target_dn_object(ConsoleWidget *console) { + return get_action_target_dn(console, ItemType_Object, ObjectRole_DN); } void console_object_delete(ConsoleWidget *console, const QList &dn_list, const QModelIndex &tree_root) { diff --git a/src/admc/console_impls/object_impl.h b/src/admc/console_impls/object_impl.h index 9d2fa827..3f24c44c 100644 --- a/src/admc/console_impls/object_impl.h +++ b/src/admc/console_impls/object_impl.h @@ -35,7 +35,6 @@ template class QList; class PolicyImpl; class ConsoleWidget; -class CreateObjectDialog; class ConsoleFilterDialog; /** @@ -103,6 +102,9 @@ private slots: void on_new_computer(); void on_new_ou(); void on_new_group(); + void on_new_shared_folder(); + void on_new_inet_org_person(); + void on_new_contact(); void on_move(); void on_enable(); void on_disable(); @@ -127,11 +129,12 @@ private: QAction *reset_account_action; QAction *edit_upn_suffixes_action; QAction *new_action; + QHash new_action_map; bool find_action_enabled; bool refresh_action_enabled; - void new_object(CreateObjectDialog *dialog); + void new_object(const QString &object_class); void set_disabled(const bool disabled); void drop_objects(const QList &dropped_list, const QPersistentModelIndex &target); void drop_policies(const QList &dropped_list, const QPersistentModelIndex &target); diff --git a/src/admc/console_impls/policy_impl.cpp b/src/admc/console_impls/policy_impl.cpp index 6804d449..87911800 100644 --- a/src/admc/console_impls/policy_impl.cpp +++ b/src/admc/console_impls/policy_impl.cpp @@ -151,22 +151,27 @@ QSet PolicyImpl::get_standard_actions(const QModelIndex &index, void PolicyImpl::rename(const QList &index_list) { UNUSED_ARG(index_list); - const QModelIndex index = console->get_selected_item(ItemType_Policy); + AdInterface ad; + if (ad_failed(ad, console)) { + return; + } + + const QModelIndex index = console->get_action_target(ItemType_Policy); const QString dn = index.data(PolicyRole_DN).toString(); - auto dialog = new RenamePolicyDialog(console); - dialog->set_target(dn); + auto dialog = new RenamePolicyDialog(ad, dn, console); dialog->open(); connect( dialog, &QDialog::accepted, + this, [this, index, dn]() { - AdInterface ad; - if (ad_failed(ad)) { + AdInterface ad_inner; + if (ad_failed(ad_inner, console)) { return; } - const AdObject object = ad.search_object(dn); + const AdObject object = ad_inner.search_object(dn); const QList row = console->get_row(index); console_policy_load(row, object); @@ -180,7 +185,7 @@ void PolicyImpl::delete_action(const QList &index_list) { } AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, console)) { return; } @@ -215,11 +220,16 @@ void PolicyImpl::refresh(const QList &index_list) { } void PolicyImpl::properties(const QList &index_list) { + AdInterface ad; + if (ad_failed(ad, console)) { + return; + } + const QModelIndex index = index_list[0]; const QString dn = index.data(PolicyRole_DN).toString(); bool dialog_is_new; - PropertiesDialog *dialog = PropertiesDialog::open_for_target(dn, &dialog_is_new); + PropertiesDialog *dialog = PropertiesDialog::open_for_target(ad, dn, &dialog_is_new); // Need to update results when properties are applied, // in case links were modified in links tab @@ -237,7 +247,7 @@ void PolicyImpl::properties(const QList &index_list) { void PolicyImpl::add_link(const QList &policy_list, const QList &ou_list) { AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, console)) { return; } @@ -277,8 +287,9 @@ void PolicyImpl::on_add_link() { connect( dialog, &SelectObjectDialog::accepted, - [=]() { - const QList gpos = get_selected_dn_list(console, ItemType_Policy, PolicyRole_DN); + this, + [this, dialog]() { + const QList gpos = get_action_target_dn_list(console, ItemType_Policy, PolicyRole_DN); const QList ou_list = dialog->get_selected(); @@ -290,27 +301,53 @@ void PolicyImpl::on_add_link() { } void PolicyImpl::on_edit() { - const QString dn = get_selected_dn(console, ItemType_Policy, PolicyRole_DN); + const QString dn = get_action_target_dn(console, ItemType_Policy, PolicyRole_DN); - const QString filesys_path = [&]() { + // TODO: remove this when gpui is able to load + // policy name on their own + const QString policy_name = [&]() { AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, console)) { return QString(); } const AdObject object = ad.search_object(dn); - const QString out = object.get_string(ATTRIBUTE_GPC_FILE_SYS_PATH); + return object.get_string(ATTRIBUTE_DISPLAY_NAME); + }(); - return out; + const QString path = [&]() { + AdInterface ad; + if (ad_failed(ad, console)) { + return QString(); + } + + const AdObject object = ad.search_object(dn); + QString filesys_path = object.get_string(ATTRIBUTE_GPC_FILE_SYS_PATH); + + const QString current_dc = ad.get_dc(); + + filesys_path.replace(QString("\\"),QString("/")); + auto contents = filesys_path.split("/", QString::KeepEmptyParts); + if (contents.size() > 3 && !current_dc.isEmpty()) + { + contents[2] = current_dc; + } + filesys_path = contents.join("/"); + filesys_path.prepend(QString("smb:")); + + return filesys_path; }(); auto process = new QProcess(console); - process->setProgram("gpui"); + process->setProgram("gpui-main"); const QList args = { - dn, - filesys_path, + QString("-p"), + path, + QString("-n"), + policy_name }; + process->setArguments(args); connect( diff --git a/src/admc/console_impls/policy_root_impl.cpp b/src/admc/console_impls/policy_root_impl.cpp index a01bb4b9..80a7932e 100644 --- a/src/admc/console_impls/policy_root_impl.cpp +++ b/src/admc/console_impls/policy_root_impl.cpp @@ -57,11 +57,11 @@ void PolicyRootImpl::fetch(const QModelIndex &index) { UNUSED_ARG(index); AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, console)) { return; } - const QString base = g_adconfig->domain_head(); + const QString base = g_adconfig->domain_dn(); const SearchScope scope = SearchScope_All; const QString filter = filter_CONDITION(Condition_Equals, ATTRIBUTE_OBJECT_CLASS, CLASS_GP_CONTAINER); const QList attributes = console_policy_search_attributes(); @@ -118,14 +118,20 @@ QList PolicyRootImpl::default_columns() const { } void PolicyRootImpl::create_policy() { - auto dialog = new CreatePolicyDialog(console); + AdInterface ad; + if (ad_failed(ad, console)) { + return; + } + + auto dialog = new CreatePolicyDialog(ad, console); dialog->open(); connect( dialog, &QDialog::accepted, + this, [this, dialog]() { - AdInterface ad; - if (ad_failed(ad)) { + AdInterface ad2; + if (ad_failed(ad2, console)) { return; } @@ -133,7 +139,7 @@ void PolicyRootImpl::create_policy() { const SearchScope scope = SearchScope_Object; const QString filter = QString(); const QList attributes = console_policy_search_attributes(); - const QHash results = ad.search(dn, scope, filter, attributes); + const QHash results = ad2.search(dn, scope, filter, attributes); const AdObject object = results[dn]; diff --git a/src/admc/console_impls/query_folder_impl.cpp b/src/admc/console_impls/query_folder_impl.cpp index 837d60e7..5e682feb 100644 --- a/src/admc/console_impls/query_folder_impl.cpp +++ b/src/admc/console_impls/query_folder_impl.cpp @@ -79,7 +79,7 @@ QueryFolderImpl::QueryFolderImpl(ConsoleWidget *console_arg) void QueryFolderImpl::on_create_query_folder() { auto dialog = new CreateQueryFolderDialog(console); - const QModelIndex parent_index = console->get_selected_item(ItemType_QueryFolder); + const QModelIndex parent_index = console->get_action_target(ItemType_QueryFolder); const QList sibling_name_list = get_sibling_name_list(parent_index, QModelIndex()); dialog->set_sibling_name_list(sibling_name_list); @@ -88,6 +88,7 @@ void QueryFolderImpl::on_create_query_folder() { connect( dialog, &QDialog::accepted, + this, [this, dialog, parent_index]() { const QString name = dialog->name(); const QString description = dialog->description(); @@ -99,17 +100,15 @@ void QueryFolderImpl::on_create_query_folder() { } void QueryFolderImpl::on_create_query_item() { - auto dialog = new CreateQueryItemDialog(console); - - const QModelIndex parent_index = console->get_selected_item(ItemType_QueryFolder); + const QModelIndex parent_index = console->get_action_target(ItemType_QueryFolder); const QList sibling_name_list = get_sibling_name_list(parent_index, QModelIndex()); - dialog->set_sibling_name_list(sibling_name_list); - + auto dialog = new CreateQueryItemDialog(sibling_name_list, console); dialog->open(); connect( dialog, &QDialog::accepted, + this, [this, dialog, parent_index]() { const QString name = dialog->name(); const QString description = dialog->description(); @@ -127,7 +126,7 @@ void QueryFolderImpl::on_create_query_item() { void QueryFolderImpl::on_edit_query_folder() { auto dialog = new EditQueryFolderDialog(console); - const QModelIndex index = console->get_selected_item(ItemType_QueryFolder); + const QModelIndex index = console->get_action_target(ItemType_QueryFolder); { const QString name = index.data(Qt::DisplayRole).toString(); @@ -142,6 +141,7 @@ void QueryFolderImpl::on_edit_query_folder() { connect( dialog, &QDialog::accepted, + this, [this, dialog, index]() { const QString name = dialog->name(); const QString description = dialog->description(); @@ -158,6 +158,12 @@ bool QueryFolderImpl::can_drop(const QList &dropped_list, UNUSED_ARG(target); UNUSED_ARG(target_type); + const bool dropped_is_target = dropped_list.contains(target); + + if (dropped_is_target) { + return false; + } + const bool dropped_are_query_item_or_folder = (dropped_type_list - QSet({ItemType_QueryItem, ItemType_QueryFolder})).isEmpty(); return dropped_are_query_item_or_folder; @@ -183,12 +189,24 @@ QList QueryFolderImpl::get_all_custom_actions() const { QSet QueryFolderImpl::get_custom_actions(const QModelIndex &index, const bool single_selection) const { UNUSED_ARG(index); + const bool is_root = [&]() { + QStandardItem *item = console->get_item(index); + const bool out = item->data(QueryItemRole_IsRoot).toBool(); + + return out; + }(); + QSet out; if (single_selection) { - out.insert(new_action); - out.insert(edit_action); - out.insert(import_action); + if (is_root) { + out.insert(new_action); + out.insert(import_action); + } else { + out.insert(new_action); + out.insert(edit_action); + out.insert(import_action); + } } return out; @@ -197,14 +215,27 @@ QSet QueryFolderImpl::get_custom_actions(const QModelIndex &index, co QSet QueryFolderImpl::get_standard_actions(const QModelIndex &index, const bool single_selection) const { UNUSED_ARG(index); + const bool is_root = [&]() { + QStandardItem *item = console->get_item(index); + const bool out = item->data(QueryItemRole_IsRoot).toBool(); + + return out; + }(); + QSet out; - out.insert(StandardAction_Delete); + if (!is_root) { + out.insert(StandardAction_Delete); + } if (single_selection) { - out.insert(StandardAction_Cut); - out.insert(StandardAction_Copy); - out.insert(StandardAction_Paste); + if (is_root) { + out.insert(StandardAction_Paste); + } else { + out.insert(StandardAction_Cut); + out.insert(StandardAction_Copy); + out.insert(StandardAction_Paste); + } } return out; @@ -227,12 +258,44 @@ void QueryFolderImpl::copy(const QList &index_list) { void QueryFolderImpl::paste(const QList &index_list) { const QModelIndex parent_index = index_list[0]; + const bool recursive_cut_and_paste = (copied_is_cut && copied_list.contains(parent_index)); + if (recursive_cut_and_paste) { + message_box_warning(console, tr("Error"), tr("Can't cut and paste query folder into itself.")); + + return; + } + + const bool parent_is_same = [&]() { + for (const QModelIndex &index : copied_list) { + const QModelIndex this_parent = index.parent(); + + if (this_parent == parent_index) { + return true; + } + } + + return false; + }(); + + // TODO: this is a band-aid on top of name conflict + // check inside move(), try to make this + // centralized if possible + + // Prohibit copy+paste into same parent. + // console_query_move() does check for name + // conflicts but doesn't handle this edge case. + if (!copied_is_cut && parent_is_same) { + message_box_warning(console, tr("Error"), tr("There's already an item with this name.")); + + return; + } + const bool delete_old_branch = copied_is_cut; console_query_move(console, copied_list, parent_index, delete_old_branch); } void QueryFolderImpl::on_import() { - const QModelIndex parent_index = console->get_selected_item(ItemType_QueryFolder); + const QModelIndex parent_index = console->get_action_target(ItemType_QueryFolder); const QString file_path = [&]() { const QString caption = QCoreApplication::translate("query_item_impl.cpp", "Import Query"); @@ -510,6 +573,11 @@ bool console_query_or_folder_name_is_good(const QString &name, const QModelIndex } void query_action_delete(ConsoleWidget *console, const QList &index_list) { + const bool confirmed = confirmation_dialog(QCoreApplication::translate("query_folder_impl.cpp", "Are you sure you want to delete this item?"), console); + if (!confirmed) { + return; + } + const QList persistent_list = persistent_index_list(index_list); for (const QPersistentModelIndex &index : persistent_list) { @@ -539,6 +607,7 @@ void console_query_move(ConsoleWidget *console, const QList stack; + QList created_list; stack.append(old_index); while (!stack.isEmpty()) { const QPersistentModelIndex index = stack.pop(); @@ -554,19 +623,32 @@ void console_query_move(ConsoleWidget *console, const QListrowCount(index); row++) { const QModelIndex child = index_model->index(row, 0, index); - stack.append(QPersistentModelIndex(child)); + + // NOTE: don't add indexes that were + // created during the move process, to + // avoid infinite loop. This can happen + // when copying and pasting a folder + // into itself. + const bool child_was_created = created_list.contains(QPersistentModelIndex(child)); + if (!child_was_created) { + stack.append(QPersistentModelIndex(child)); + } } } diff --git a/src/admc/console_impls/query_item_impl.cpp b/src/admc/console_impls/query_item_impl.cpp index e0e55670..759b2c06 100644 --- a/src/admc/console_impls/query_item_impl.cpp +++ b/src/admc/console_impls/query_item_impl.cpp @@ -164,7 +164,7 @@ QList QueryItemImpl::default_columns() const { } void QueryItemImpl::on_export() { - const QModelIndex index = console->get_selected_item(ItemType_QueryItem); + const QModelIndex index = console->get_action_target(ItemType_QueryItem); const QString file_path = [&]() { const QString query_name = index.data(Qt::DisplayRole).toString(); @@ -203,10 +203,12 @@ void console_query_item_load(const QList row, const QString &na row[QueryColumn_Description]->setText(description); } -void console_query_item_create(ConsoleWidget *console, const QString &name, const QString &description, const QString &filter, const QByteArray &filter_state, const QString &base, const bool scope_is_children, const QModelIndex &parent) { +QModelIndex console_query_item_create(ConsoleWidget *console, const QString &name, const QString &description, const QString &filter, const QByteArray &filter_state, const QString &base, const bool scope_is_children, const QModelIndex &parent) { const QList row = console->add_scope_item(ItemType_QueryItem, parent); console_query_item_load(row, name, description, filter, filter_state, base, scope_is_children); + + return row[0]->index(); } QHash console_query_item_save_hash(const QModelIndex &index) { @@ -248,13 +250,12 @@ void console_query_item_load_hash(ConsoleWidget *console, const QHashget_selected_item(ItemType_QueryItem); + const QModelIndex index = console->get_action_target(ItemType_QueryItem); const QModelIndex parent_index = index.parent(); const QList sibling_name_list = get_sibling_name_list(parent_index, index); - dialog->set_sibling_name_list(sibling_name_list); + + auto dialog = new EditQueryItemDialog(sibling_name_list, console); { QString name; @@ -270,6 +271,7 @@ void QueryItemImpl::on_edit_query_item() { connect( dialog, &QDialog::accepted, + this, [this, dialog, index]() { const QString name = dialog->name(); const QString description = dialog->description(); diff --git a/src/admc/console_impls/query_item_impl.h b/src/admc/console_impls/query_item_impl.h index b11d5550..3c7bdd02 100644 --- a/src/admc/console_impls/query_item_impl.h +++ b/src/admc/console_impls/query_item_impl.h @@ -79,7 +79,7 @@ private: }; void console_query_item_load(const QList row, const QString &name, const QString &description, const QString &filter, const QByteArray &filter_state, const QString &base, const bool scope_is_children); -void console_query_item_create(ConsoleWidget *console, const QString &name, const QString &description, const QString &filter, const QByteArray &filter_state, const QString &base, const bool scope_is_children, const QModelIndex &parent); +QModelIndex console_query_item_create(ConsoleWidget *console, const QString &name, const QString &description, const QString &filter, const QByteArray &filter_state, const QString &base, const bool scope_is_children, const QModelIndex &parent); QHash console_query_item_save_hash(const QModelIndex &index); void console_query_item_load_hash(ConsoleWidget *console, const QHash &data, const QModelIndex &parent_index); void get_query_item_data(const QModelIndex &index, QString *name, QString *description, bool *scope_is_children, QByteArray *filter_state, QString *filter); diff --git a/src/admc/console_widget/console_widget.cpp b/src/admc/console_widget/console_widget.cpp index dba146c2..293e4174 100644 --- a/src/admc/console_widget/console_widget.cpp +++ b/src/admc/console_widget/console_widget.cpp @@ -165,6 +165,7 @@ ConsoleWidget::ConsoleWidget(QWidget *parent) connect( action, &QAction::triggered, + this, [this, action_enum]() { d->on_standard_action(action_enum); }); @@ -172,7 +173,7 @@ ConsoleWidget::ConsoleWidget(QWidget *parent) connect( d->scope_view, &QWidget::customContextMenuRequested, - d, &ConsoleWidgetPrivate::on_context_menu); + d, &ConsoleWidgetPrivate::on_scope_context_menu); connect( qApp, &QApplication::focusChanged, @@ -203,7 +204,7 @@ void ConsoleWidget::register_impl(const int type, ConsoleImpl *impl) { d, &ConsoleWidgetPrivate::on_results_activated); connect( results_view, &ResultsView::context_menu, - d, &ConsoleWidgetPrivate::on_context_menu); + d, &ConsoleWidgetPrivate::on_results_context_menu); } } @@ -274,7 +275,7 @@ void ConsoleWidget::delete_item(const QModelIndex &index) { void ConsoleWidget::set_current_scope(const QModelIndex &index) { const QModelIndex index_proxy = d->scope_proxy_model->mapFromSource(index); - d->scope_view->selectionModel()->setCurrentIndex(index_proxy, QItemSelectionModel::Current | QItemSelectionModel::ClearAndSelect); + d->scope_view->selectionModel()->setCurrentIndex(index_proxy, QItemSelectionModel::Current | QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } void ConsoleWidget::refresh_scope(const QModelIndex &index) { @@ -301,16 +302,29 @@ QList ConsoleWidget::get_selected_items(const int type) const { return out; } -QModelIndex ConsoleWidget::get_selected_item(const int type) const { - const QList selected_list = get_selected_items(type); +QModelIndex ConsoleWidget::get_action_target(const int type) const { + const QList index_list = get_action_target_items(type); - if (!selected_list.isEmpty()) { - return selected_list[0]; + if (!index_list.isEmpty()) { + return index_list[0]; } else { return QModelIndex(); } } +QList ConsoleWidget::get_action_target_items(const int type) const { + QList out; + + for (const QModelIndex &index : d->action_target_list) { + const int this_type = console_item_get_type(index); + if (this_type == type) { + out.append(index); + } + } + + return out; +} + QList ConsoleWidget::search_items(const QModelIndex &parent, int role, const QVariant &value, const int type) const { const QList all_matches = [&]() { QList out; @@ -356,6 +370,34 @@ QList ConsoleWidget::search_items(const QModelIndex &parent, int ro return filtered_matches; } +// TODO: remove duplication between two search_items() +// f-ns and deal with the weirdness around whether +// search iteration includes parent or not. Once this +// is done it may be possible to simplify +// get_x_tree_root() f-ns. +QList ConsoleWidget::search_items(const QModelIndex &parent, const int type) const { + QList out; + + const int role = ConsoleRole_Type; + const int value = type; + + // NOTE: start index may be invalid if parent has no + // children + const QModelIndex start_index = d->model->index(0, 0, parent); + if (start_index.isValid()) { + const QList descendant_matches = d->model->match(start_index, role, value, -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive)); + out.append(descendant_matches); + } + + const QVariant parent_value = parent.data(role); + const bool parent_is_match = (parent_value.isValid() && parent_value == value); + if (parent_is_match) { + out.append(parent); + } + + return out; +} + QModelIndex ConsoleWidget::get_current_scope_item() const { const QModelIndex index = d->scope_view->selectionModel()->currentIndex(); @@ -416,10 +458,12 @@ void ConsoleWidget::restore_state(const QVariant &state_variant) { const QByteArray splitter_state = state.value(SPLITTER_STATE, QVariant()).toByteArray(); d->splitter->restoreState(splitter_state); - d->actions.toggle_console_tree->setChecked(state[CONSOLE_TREE_STATE].toBool()); + const bool toggle_console_tree_value = state.value(CONSOLE_TREE_STATE, true).toBool(); + d->actions.toggle_console_tree->setChecked(toggle_console_tree_value); d->on_toggle_console_tree(); - d->actions.toggle_description_bar->setChecked(state[DESCRIPTION_BAR_STATE].toBool()); + const bool toggle_description_bar_value = state.value(DESCRIPTION_BAR_STATE, true).toBool(); + d->actions.toggle_description_bar->setChecked(toggle_description_bar_value); d->on_toggle_description_bar(); for (const int type : d->impl_map.keys()) { @@ -436,6 +480,30 @@ void ConsoleWidget::restore_state(const QVariant &state_variant) { impl->restore_state(results_state); } + + // Restore displayed of currently selected view + // type setting + QAction *current_view_type_action = [&]() -> QAction * { + const ResultsViewType current_results_view_type = [&]() { + ConsoleImpl *current_impl = d->get_current_scope_impl(); + ResultsView *current_results_view = current_impl->view(); + const ResultsViewType out = current_results_view->current_view_type(); + + return out; + }(); + + switch (current_results_view_type) { + case ResultsViewType_Icons: return d->actions.view_icons; + case ResultsViewType_List: return d->actions.view_list; + case ResultsViewType_Detail: return d->actions.view_detail; + } + + return nullptr; + }(); + + if (current_view_type_action != nullptr) { + current_view_type_action->setChecked(true); + } } void ConsoleWidget::set_scope_view_visible(const bool visible) { @@ -448,9 +516,17 @@ void ConsoleWidget::delete_children(const QModelIndex &parent) { // Show/hide actions based on what's selected and emit the // signal -void ConsoleWidget::add_actions(QMenu *menu) { +void ConsoleWidget::setup_menubar_action_menu(QMenu *menu) { + d->add_actions(menu); + + connect( + menu, &QMenu::aboutToShow, + d, &ConsoleWidgetPrivate::on_menubar_action_menu_open); +} + +void ConsoleWidgetPrivate::add_actions(QMenu *menu) { // Add custom actions - const QList custom_action_list = d->get_custom_action_list(); + const QList custom_action_list = get_custom_action_list(); for (QAction *action : custom_action_list) { menu->addAction(action); @@ -459,25 +535,32 @@ void ConsoleWidget::add_actions(QMenu *menu) { menu->addSeparator(); // Add standard actions - menu->addAction(d->standard_action_map[StandardAction_Copy]); - menu->addAction(d->standard_action_map[StandardAction_Cut]); - menu->addAction(d->standard_action_map[StandardAction_Rename]); - menu->addAction(d->standard_action_map[StandardAction_Delete]); - menu->addAction(d->standard_action_map[StandardAction_Paste]); - menu->addAction(d->standard_action_map[StandardAction_Print]); - menu->addAction(d->standard_action_map[StandardAction_Refresh]); + menu->addAction(standard_action_map[StandardAction_Copy]); + menu->addAction(standard_action_map[StandardAction_Cut]); + menu->addAction(standard_action_map[StandardAction_Rename]); + menu->addAction(standard_action_map[StandardAction_Delete]); + menu->addAction(standard_action_map[StandardAction_Paste]); + menu->addAction(standard_action_map[StandardAction_Print]); + menu->addAction(standard_action_map[StandardAction_Refresh]); menu->addSeparator(); - menu->addAction(d->standard_action_map[StandardAction_Properties]); + menu->addAction(standard_action_map[StandardAction_Properties]); } -void ConsoleWidget::update_actions() { - const QList selected_list = d->get_all_selected_items(); +void ConsoleWidgetPrivate::on_menubar_action_menu_open() { + // Action menu opened from menubar, so use regular + // selected items + action_target_list = get_all_selected_items(); - if (!selected_list.isEmpty() && !selected_list[0].isValid()) { - return; + update_actions(); +} + +// Returns whether any action is visible +bool ConsoleWidgetPrivate::update_actions() { + if (!action_target_list.isEmpty() && !action_target_list[0].isValid()) { + return false; } - const bool single_selection = (selected_list.size() == 1); + const bool single_selection = (action_target_list.size() == 1); // // Collect information about action state from impl's @@ -486,10 +569,10 @@ void ConsoleWidget::update_actions() { const QSet visible_custom_action_set = [&]() { QSet out; - for (int i = 0; i < selected_list.size(); i++) { - const QModelIndex index = selected_list[i]; + for (int i = 0; i < action_target_list.size(); i++) { + const QModelIndex index = action_target_list[i]; - ConsoleImpl *impl = d->get_impl(index); + ConsoleImpl *impl = get_impl(index); QSet for_this_index = impl->get_custom_actions(index, single_selection); if (i == 0) { @@ -508,10 +591,10 @@ void ConsoleWidget::update_actions() { const QSet disabled_custom_action_set = [&]() { QSet out; - for (int i = 0; i < selected_list.size(); i++) { - const QModelIndex index = selected_list[i]; + for (int i = 0; i < action_target_list.size(); i++) { + const QModelIndex index = action_target_list[i]; - ConsoleImpl *impl = d->get_impl(index); + ConsoleImpl *impl = get_impl(index); QSet for_this_index = impl->get_disabled_custom_actions(index, single_selection); if (i == 0) { @@ -530,10 +613,10 @@ void ConsoleWidget::update_actions() { const QSet visible_standard_actions = [&]() { QSet out; - for (int i = 0; i < selected_list.size(); i++) { - const QModelIndex index = selected_list[i]; + for (int i = 0; i < action_target_list.size(); i++) { + const QModelIndex index = action_target_list[i]; - ConsoleImpl *impl = d->get_impl(index); + ConsoleImpl *impl = get_impl(index); QSet for_this_index = impl->get_standard_actions(index, single_selection); if (i == 0) { @@ -552,10 +635,10 @@ void ConsoleWidget::update_actions() { const QSet disabled_standard_actions = [&]() { QSet out; - for (int i = 0; i < selected_list.size(); i++) { - const QModelIndex index = selected_list[i]; + for (int i = 0; i < action_target_list.size(); i++) { + const QModelIndex index = action_target_list[i]; - ConsoleImpl *impl = d->get_impl(index); + ConsoleImpl *impl = get_impl(index); QSet for_this_index = impl->get_disabled_standard_actions(index, single_selection); if (i == 0) { @@ -575,7 +658,7 @@ void ConsoleWidget::update_actions() { // Apply action state // - const QList custom_action_list = d->get_custom_action_list(); + const QList custom_action_list = get_custom_action_list(); // Show/hide custom actions for (QAction *action : custom_action_list) { @@ -595,7 +678,7 @@ void ConsoleWidget::update_actions() { for (const StandardAction &action_enum : standard_action_list) { const bool visible = visible_standard_actions.contains(action_enum); - QAction *action = d->standard_action_map[action_enum]; + QAction *action = standard_action_map[action_enum]; action->setVisible(visible); } @@ -603,14 +686,24 @@ void ConsoleWidget::update_actions() { for (const StandardAction &action_enum : standard_action_list) { const bool disabled = disabled_standard_actions.contains(action_enum); - QAction *action = d->standard_action_map[action_enum]; + QAction *action = standard_action_map[action_enum]; action->setDisabled(disabled); } + + const bool any_action_is_visible = (!visible_standard_actions.isEmpty() || !visible_custom_action_set.isEmpty()); + + return any_action_is_visible; } void ConsoleWidget::set_actions(const ConsoleWidgetActions &actions_arg) { d->actions = actions_arg; + // Setup exclusivity for view type actions + auto view_type_group = new QActionGroup(this); + view_type_group->addAction(d->actions.view_icons); + view_type_group->addAction(d->actions.view_list); + view_type_group->addAction(d->actions.view_detail); + connect( d->actions.navigate_up, &QAction::triggered, d, &ConsoleWidgetPrivate::on_navigate_up); @@ -786,19 +879,6 @@ QList ConsoleWidgetPrivate::get_all_selected_items() const { } } -QSet ConsoleWidgetPrivate::get_selected_types() const { - QSet out; - - const QList index_list = get_all_selected_items(); - - for (const QModelIndex &index : index_list) { - const int type = index.data(ConsoleRole_Type).toInt(); - out.insert(type); - } - - return out; -} - // Get all custom actions from impl's, in order QList ConsoleWidgetPrivate::get_custom_action_list() const { QList out; @@ -1071,12 +1151,21 @@ void ConsoleWidgetPrivate::on_toggle_description_bar() { } void ConsoleWidgetPrivate::on_standard_action(const StandardAction action_enum) { - const QSet type_set = get_selected_types(); + const QSet type_set = [&]() { + QSet out; + + for (const QModelIndex &index : action_target_list) { + const int type = index.data(ConsoleRole_Type).toInt(); + out.insert(type); + } + + return out; + }(); // Call impl's action f-n for all present types for (const int type : type_set) { // Filter selected list so that it only contains indexes of this type - const QList selected_of_type = q->get_selected_items(type); + const QList selected_of_type = q->get_action_target_items(type); ConsoleImpl *impl = impl_map[type]; switch (action_enum) { @@ -1124,17 +1213,65 @@ void ConsoleWidgetPrivate::on_standard_action(const StandardAction action_enum) } } -void ConsoleWidgetPrivate::on_context_menu(const QPoint &pos) { - const QModelIndex index = focused_view->indexAt(pos); +// NOTE: when action menu is opened as context menu +// from results pane, treat clicking on empty space in +// results pane as clicking on it's parent, which is +// current scope. +void ConsoleWidgetPrivate::on_results_context_menu(const QPoint &pos) { + const QAbstractItemView *results_view = [&]() { + ConsoleImpl *current_impl = get_current_scope_impl(); + ResultsView *results = current_impl->view(); + QAbstractItemView *out = results->current_view(); - if (index.isValid()) { - const QPoint global_pos = focused_view->mapToGlobal(pos); + return out; + }(); - auto menu = new QMenu(q); - menu->setAttribute(Qt::WA_DeleteOnClose); - q->add_actions(menu); - q->update_actions(); + action_target_list = [&]() { + const QModelIndex index = results_view->indexAt(pos); + const bool clicked_empty_space = !index.isValid(); + + if (clicked_empty_space) { + const QModelIndex current_scope_item = q->get_current_scope_item(); + const QList out = {current_scope_item}; + + return out; + } else { + const QList out = get_all_selected_items(); + + return out; + } + }(); + + const QPoint global_pos = results_view->mapToGlobal(pos); + open_context_menu(global_pos); +} + +void ConsoleWidgetPrivate::on_scope_context_menu(const QPoint &pos) { + const QModelIndex index = scope_view->indexAt(pos); + + if (!index.isValid()) { + return; + } + + // Scope uses real selection as target of action + // menu. If clicked on empty space, action menu + // doesn't open. + action_target_list = get_all_selected_items(); + + const QPoint global_pos = focused_view->mapToGlobal(pos); + open_context_menu(global_pos); +} + +void ConsoleWidgetPrivate::open_context_menu(const QPoint &global_pos) { + auto menu = new QMenu(q); + menu->setAttribute(Qt::WA_DeleteOnClose); + add_actions(menu); + const bool need_to_open = update_actions(); + + if (need_to_open) { menu->popup(global_pos); + } else { + delete menu; } } diff --git a/src/admc/console_widget/console_widget.h b/src/admc/console_widget/console_widget.h index e8b6065c..4e49376b 100644 --- a/src/admc/console_widget/console_widget.h +++ b/src/admc/console_widget/console_widget.h @@ -120,25 +120,36 @@ public: // type void refresh_scope(const QModelIndex &index); - // Gets selected item(s) from currently focused view, - // which could be scope or results. Only the main (first - // column) item is returned for each selected row. There - // is always at least one selected item. If results is - // currently focused but has no selection, selected - // items from scope are returned instead. + // Gets selected item(s) from currently focused + // view, which could be scope or results. Only the + // main (first column) item is returned for each + // selected row. There is always at least one + // selected item. If results is currently focused + // but has no selection, selected items from scope + // are returned instead. Note that this shouldn't + // be use for as targets of actions, use + // get_action_target_items() instead. QList get_selected_items(const int type) const; - // Get a single selected item. Use if you are sure that - // there's only one (dialog that uses one target item - // for example). Returns first item in list if there are - // multiple items selected. - QModelIndex get_selected_item(const int type) const; + // Get list of action targets. These are the items + // that should be the target of console actions, + // both default and extra from console impl's. + // Targets are equal to currently selected items or + // to current scope if context menu was opened by + // clicking on empty space in results. + QList get_action_target_items(const int type) const; + + // Get a single action target. Use if you are sure + // that there's only one (dialog that uses one + // target item for example). + QModelIndex get_action_target(const int type) const; // NOTE: Search is inclusive, examining the given parent // and all of it's descendants. Pass QModelIndex() // parent to search the whole model. If no type is // given, then items of all types will be returned. QList search_items(const QModelIndex &parent, int role, const QVariant &value, const int type = -1) const; + QList search_items(const QModelIndex &parent, const int type) const; QModelIndex get_current_scope_item() const; int get_child_count(const QModelIndex &index) const; @@ -156,15 +167,9 @@ public: void set_scope_view_visible(const bool visible); - // Adds actions to a menu. Actions come from console - // itself as well as impl's. All impl's must be - // registered before this is called. - void add_actions(QMenu *menu); - - // Updates visibility and disability state of actions - // based on current selection. Call before showing the - // menu that contains console actions. - void update_actions(); + // Setups the action menu in menubar. Action menu + // opened by right click is setup automatically. + void setup_menubar_action_menu(QMenu *menu); private: ConsoleWidgetPrivate *d; diff --git a/src/admc/console_widget/console_widget_p.h b/src/admc/console_widget/console_widget_p.h index bdd1dd4f..5833f1db 100644 --- a/src/admc/console_widget/console_widget_p.h +++ b/src/admc/console_widget/console_widget_p.h @@ -84,6 +84,8 @@ public: QList targets_past; QList targets_future; + QList action_target_list; + QHash standard_action_map; ConsoleWidgetPrivate(ConsoleWidget *q_arg); @@ -99,8 +101,11 @@ public: ConsoleImpl *get_impl(const QModelIndex &index) const; void update_description(); QList get_all_selected_items() const; - QSet get_selected_types() const; QList get_custom_action_list() const; + void open_context_menu(const QPoint &global_pos); + void add_actions(QMenu *menu); + bool update_actions(); + void on_menubar_action_menu_open(); public slots: void on_current_scope_item_changed(const QModelIndex ¤t, const QModelIndex &); @@ -117,7 +122,8 @@ public slots: void on_toggle_console_tree(); void on_toggle_description_bar(); void on_standard_action(const StandardAction action_enum); - void on_context_menu(const QPoint &pos); + void on_results_context_menu(const QPoint &pos); + void on_scope_context_menu(const QPoint &pos); void on_scope_expanded(const QModelIndex &index); void on_results_activated(const QModelIndex &index); }; diff --git a/src/admc/console_widget/results_view.cpp b/src/admc/console_widget/results_view.cpp index 9f5f3abf..655ef1a7 100644 --- a/src/admc/console_widget/results_view.cpp +++ b/src/admc/console_widget/results_view.cpp @@ -174,10 +174,24 @@ void ResultsView::restore_state(const QVariant &state_variant, const QList const ResultsViewType view_type = (ResultsViewType) state["view_type"].toInt(); set_view_type(view_type); } else { + // Set default state in absence of saved state for (int i = 0; i < header->count(); i++) { const bool hidden = !default_columns.contains(i); header->setSectionHidden(i, hidden); } + + // NOTE: it is important to set default sort + // state here by modifying header of detail + // view, NOT the detail view itself. If we + // modify detail view only, then by default + // header will be in unsorted state, that state + // will get saved and overwrite sorted state + // the next time the app is run. Also, setting + // sort state for header is enough, because it + // propagates to detail view itself and + // icons/list views, because they share + // model/proxy + header->setSortIndicator(0, Qt::AscendingOrder); } } diff --git a/src/admc/create_computer_dialog.cpp b/src/admc/create_computer_dialog.cpp index 9e845e75..5542ae29 100644 --- a/src/admc/create_computer_dialog.cpp +++ b/src/admc/create_computer_dialog.cpp @@ -22,45 +22,41 @@ #include "ui_create_computer_dialog.h" #include "adldap.h" -#include "attribute_edits/sam_name_edit.h" +#include "attribute_edits/computer_sam_name_edit.h" #include "attribute_edits/upn_edit.h" #include "utils.h" #include "settings.h" +#include "create_object_helper.h" -CreateComputerDialog::CreateComputerDialog(QWidget *parent) +CreateComputerDialog::CreateComputerDialog(const QString &parent_dn, QWidget *parent) : CreateObjectDialog(parent) { ui = new Ui::CreateComputerDialog(); ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - QList edit_list; - // NOTE: some obscure missing features: // "Assign this computer account as a pre-Windows 2000 computer". Is this needed? // "The following user or group may join this computer to a domain". Tried to figure out how this is implemented and couldn't see any easy ways via attributes, so probably something to do with setting ACL'S. // "This is a managed computer" checkbox and an edit for guid/uuid which I assume modifies objectGUID? - sam_name_edit = new SamNameEdit(ui->sam_name_edit, ui->sam_name_domain_edit, &edit_list, this); + auto sam_name_edit = new ComputerSamNameEdit(ui->sam_name_edit, ui->sam_name_domain_edit, this); + + const QList edit_list = { + sam_name_edit, + }; const QList required_list = { ui->name_edit, ui->sam_name_edit, }; - const QList widget_list = { - ui->name_edit, - ui->sam_name_edit, - // NOTE: not restoring sam domain state is intended - // ui->sam_name_domain_edit, - }; - // Autofill name -> sam account name connect( ui->name_edit, &QLineEdit::textChanged, this, &CreateComputerDialog::autofill_sam_name); - init(ui->name_edit, ui->button_box, edit_list, required_list, widget_list, CLASS_COMPUTER); + helper = new CreateObjectHelper(ui->name_edit, ui->button_box, edit_list, required_list, CLASS_COMPUTER, parent_dn, this); settings_setup_dialog_geometry(SETTING_create_computer_dialog_geometry, this); } @@ -69,12 +65,20 @@ CreateComputerDialog::~CreateComputerDialog() { delete ui; } -void CreateComputerDialog::open() { - sam_name_edit->load_domain(); +void CreateComputerDialog::accept() { + const bool accepted = helper->accept(); - CreateObjectDialog::open(); + if (accepted) { + QDialog::accept(); + } } +QString CreateComputerDialog::get_created_dn() const { + return helper->get_created_dn(); +} + +// NOTE: can't use setup_lineedit_autofill() because +// need to make input uppercase void CreateComputerDialog::autofill_sam_name() { const QString name_input = ui->name_edit->text(); ui->sam_name_edit->setText(name_input.toUpper()); diff --git a/src/admc/create_computer_dialog.h b/src/admc/create_computer_dialog.h index 43f9d40f..3c8cf079 100644 --- a/src/admc/create_computer_dialog.h +++ b/src/admc/create_computer_dialog.h @@ -23,7 +23,7 @@ #include "create_object_dialog.h" -class SamNameEdit; +class CreateObjectHelper; namespace Ui { class CreateComputerDialog; @@ -35,13 +35,14 @@ class CreateComputerDialog final : public CreateObjectDialog { public: Ui::CreateComputerDialog *ui; - CreateComputerDialog(QWidget *parent); + CreateComputerDialog(const QString &parent_dn, QWidget *parent); ~CreateComputerDialog(); - void open() override; + void accept() override; + QString get_created_dn() const override; private: - SamNameEdit *sam_name_edit; + CreateObjectHelper *helper; void autofill_sam_name(); }; diff --git a/src/admc/create_contact_dialog.cpp b/src/admc/create_contact_dialog.cpp new file mode 100644 index 00000000..7461f305 --- /dev/null +++ b/src/admc/create_contact_dialog.cpp @@ -0,0 +1,77 @@ +/* + * 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 "create_contact_dialog.h" +#include "ui_create_contact_dialog.h" + +#include "adldap.h" +#include "attribute_edits/string_edit.h" +#include "utils.h" +#include "settings.h" +#include "create_object_helper.h" + +CreateContactDialog::CreateContactDialog(const QString &parent_dn, QWidget *parent) +: CreateObjectDialog(parent) { + ui = new Ui::CreateContactDialog(); + ui->setupUi(this); + + setAttribute(Qt::WA_DeleteOnClose); + + auto first_name_edit = new StringEdit(ui->first_name_edit, ATTRIBUTE_FIRST_NAME, this); + auto last_name_edit = new StringEdit(ui->last_name_edit, ATTRIBUTE_LAST_NAME, this); + auto initials_edit = new StringEdit(ui->initials_edit, ATTRIBUTE_INITIALS, this); + auto display_name_edit = new StringEdit(ui->display_name_edit, ATTRIBUTE_DISPLAY_NAME, this); + + const QList edit_list = { + first_name_edit, + last_name_edit, + initials_edit, + display_name_edit, + }; + + const QList required_list = { + ui->first_name_edit, + ui->last_name_edit, + ui->full_name_edit, + ui->display_name_edit, + }; + + setup_full_name_autofill(ui->first_name_edit, ui->last_name_edit, ui->full_name_edit); + + helper = new CreateObjectHelper(ui->full_name_edit, ui->button_box, edit_list, required_list, CLASS_CONTACT, parent_dn, this); + + settings_setup_dialog_geometry(SETTING_create_contact_dialog_geometry, this); +} + +CreateContactDialog::~CreateContactDialog() { + delete ui; +} + +void CreateContactDialog::accept() { + const bool accepted = helper->accept(); + + if (accepted) { + QDialog::accept(); + } +} + +QString CreateContactDialog::get_created_dn() const { + return helper->get_created_dn(); +} diff --git a/src/admc/create_contact_dialog.h b/src/admc/create_contact_dialog.h new file mode 100644 index 00000000..6f6d470e --- /dev/null +++ b/src/admc/create_contact_dialog.h @@ -0,0 +1,48 @@ +/* + * 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 CREATE_CONTACT_DIALOG_H +#define CREATE_CONTACT_DIALOG_H + +#include "create_object_dialog.h" + +class CreateObjectHelper; + +namespace Ui { +class CreateContactDialog; +} + +class CreateContactDialog final : public CreateObjectDialog { + Q_OBJECT + +public: + Ui::CreateContactDialog *ui; + + CreateContactDialog(const QString &parent_dn, QWidget *parent); + ~CreateContactDialog(); + + void accept() override; + QString get_created_dn() const override; + +private: + CreateObjectHelper *helper; +}; + +#endif /* CREATE_CONTACT_DIALOG_H */ diff --git a/src/admc/create_contact_dialog.ui b/src/admc/create_contact_dialog.ui new file mode 100644 index 00000000..55fe54a2 --- /dev/null +++ b/src/admc/create_contact_dialog.ui @@ -0,0 +1,118 @@ + + + CreateContactDialog + + + + 0 + 0 + 400 + 198 + + + + Create Contact + + + + + + + + First name: + + + + + + + Last name: + + + + + + + Initials: + + + + + + + Full name: + + + + + + + Display name: + + + + + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + button_box + accepted() + CreateContactDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + button_box + rejected() + CreateContactDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/admc/create_group_dialog.cpp b/src/admc/create_group_dialog.cpp index 42fe6cda..d2930d93 100644 --- a/src/admc/create_group_dialog.cpp +++ b/src/admc/create_group_dialog.cpp @@ -29,41 +29,56 @@ #include "attribute_edits/upn_edit.h" #include "utils.h" #include "settings.h" +#include "create_object_helper.h" -CreateGroupDialog::CreateGroupDialog(QWidget *parent) +CreateGroupDialog::CreateGroupDialog(const QString &parent_dn, QWidget *parent) : CreateObjectDialog(parent) { ui = new Ui::CreateGroupDialog(); ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + + auto sam_name_edit = new SamNameEdit(ui->sam_name_edit, ui->sam_name_domain_edit, this); + auto scope_edit = new GroupScopeEdit(ui->scope_combo, this); + auto type_edit = new GroupTypeEdit(ui->type_combo, this); - QList edit_list; - sam_name_edit = new SamNameEdit(ui->sam_name_edit, ui->sam_name_domain_edit, &edit_list, this); - new GroupScopeEdit(ui->scope_combo, &edit_list, this); - new GroupTypeEdit(ui->type_combo, &edit_list, this); + const QList edit_list = { + sam_name_edit, + scope_edit, + type_edit, + }; const QList required_edits = { ui->sam_name_edit, }; - const QList widget_list = { - ui->name_edit, - ui->sam_name_edit, - ui->scope_combo, - ui->type_combo, - }; - - init(ui->name_edit, ui->button_box, edit_list, required_edits, widget_list, CLASS_GROUP); + helper = new CreateObjectHelper(ui->name_edit, ui->button_box, edit_list, required_edits, CLASS_GROUP, parent_dn, this); settings_setup_dialog_geometry(SETTING_create_group_dialog_geometry, this); + + // name -> sam account name + connect( + ui->name_edit, &QLineEdit::textChanged, + this, &CreateGroupDialog::autofill_sam_name); } CreateGroupDialog::~CreateGroupDialog() { delete ui; } -void CreateGroupDialog::open() { - sam_name_edit->load_domain(); +void CreateGroupDialog::accept() { + const bool accepted = helper->accept(); - CreateObjectDialog::open(); + if (accepted) { + QDialog::accept(); + } +} + +QString CreateGroupDialog::get_created_dn() const { + return helper->get_created_dn(); +} + +void CreateGroupDialog::autofill_sam_name() { + const QString name_input = ui->name_edit->text(); + ui->sam_name_edit->setText(name_input); } diff --git a/src/admc/create_group_dialog.h b/src/admc/create_group_dialog.h index 03187b61..ab0bf188 100644 --- a/src/admc/create_group_dialog.h +++ b/src/admc/create_group_dialog.h @@ -23,7 +23,7 @@ #include "create_object_dialog.h" -class SamNameEdit; +class CreateObjectHelper; namespace Ui { class CreateGroupDialog; @@ -35,13 +35,16 @@ class CreateGroupDialog final : public CreateObjectDialog { public: Ui::CreateGroupDialog *ui; - CreateGroupDialog(QWidget *parent); + CreateGroupDialog(const QString &parent_dn, QWidget *parent); ~CreateGroupDialog(); - void open() override; + void accept() override; + QString get_created_dn() const override; private: - SamNameEdit *sam_name_edit; + CreateObjectHelper *helper; + + void autofill_sam_name(); }; #endif /* CREATE_GROUP_DIALOG_H */ diff --git a/src/admc/create_group_dialog.ui b/src/admc/create_group_dialog.ui index 86af5093..6a92e882 100644 --- a/src/admc/create_group_dialog.ui +++ b/src/admc/create_group_dialog.ui @@ -29,7 +29,7 @@ - Logon name (pre-Windows 2000): + Group name (pre-Windows 2000): diff --git a/src/admc/create_object_dialog.cpp b/src/admc/create_object_dialog.cpp deleted file mode 100644 index 2d7b305c..00000000 --- a/src/admc/create_object_dialog.cpp +++ /dev/null @@ -1,148 +0,0 @@ -/* - * 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 "create_object_dialog.h" - -#include "adldap.h" -#include "attribute_edits/attribute_edit.h" -#include "globals.h" -#include "status.h" -#include "utils.h" - -#include -#include -#include - -void CreateObjectDialog::init(QLineEdit *name_edit_arg, QDialogButtonBox *button_box, const QList &edits_list, const QList &required_list, const QList &widget_list, const QString &object_class) { - name_edit = name_edit_arg; - m_edit_list = edits_list; - m_required_list = required_list; - m_object_class = object_class; - - ok_button = button_box->button(QDialogButtonBox::Ok); - - // Save state once on initial creation, this will be - // used as the default state that will be loaded to - // reset the dialog - m_state.set_widget_list(widget_list); - m_state.save(); - - for (QLineEdit *edit : m_required_list) { - connect( - edit, &QLineEdit::textChanged, - this, &CreateObjectDialog::on_edited); - } - on_edited(); -} - -void CreateObjectDialog::set_parent_dn(const QString &dn) { - parent_dn = dn; -} - -void CreateObjectDialog::open() { - m_state.restore(); - - QDialog::open(); -} - -void CreateObjectDialog::accept() { - AdInterface ad; - if (ad_failed(ad)) { - return; - } - - const QString name = get_created_name(); - const QString dn = get_created_dn(); - - // Verify edits - const bool verify_success = edits_verify(ad, m_edit_list, dn, true); - - if (!verify_success) { - return; - } - - auto fail_msg = [name]() { - const QString message = QString(tr("Failed to create object %1")).arg(name); - g_status->add_message(message, StatusType_Error); - }; - - const bool add_success = ad.object_add(dn, m_object_class); - - bool final_success = false; - if (add_success) { - // NOTE: since this is a new object, we don't want - // edits like unchecked account options to not - // apply. We want all edits to apply, no matter if - // they were modified by user or not. So, we - // manually set the modified flag so that all edits - // apply - edits_set_modified(m_edit_list, true); - - const bool apply_success = edits_apply(ad, m_edit_list, dn); - - if (apply_success) { - final_success = true; - } else { - ad.object_delete(dn); - } - } - - g_status->display_ad_messages(ad, this); - - if (final_success) { - const QString message = QString(tr("Object %1 was created")).arg(name); - - g_status->add_message(message, StatusType_Success); - - QDialog::accept(); - } else { - fail_msg(); - } -} - -// Enable/disable create button if all required edits filled -void CreateObjectDialog::on_edited() { - const bool all_required_filled = [this]() { - for (QLineEdit *edit : m_required_list) { - if (edit->text().isEmpty()) { - return false; - } - } - - return true; - }(); - - ok_button->setEnabled(all_required_filled); -} - -QString CreateObjectDialog::get_created_name() const { - // NOTE: trim whitespaces because server will do it - // anyway and we want a correct name - const QString name = name_edit->text().trimmed(); - - return name; -} - -QString CreateObjectDialog::get_created_dn() const { - const QString name = get_created_name(); - const QString dn = dn_from_name_and_parent(name, parent_dn, m_object_class); - - return dn; -} diff --git a/src/admc/create_object_dialog.h b/src/admc/create_object_dialog.h index b381fcdf..02aa71ed 100644 --- a/src/admc/create_object_dialog.h +++ b/src/admc/create_object_dialog.h @@ -27,41 +27,12 @@ #include -#include "widget_state.h" - -class QLineEdit; -class QDialogButtonBox; -class AttributeEdit; - class CreateObjectDialog : public QDialog { - Q_OBJECT public: using QDialog::QDialog; - static void success_msg(const QString &old_name); - static void fail_msg(const QString &old_name); - - // Required widgets will be use to determine when dialog - // can be accepted. Widget list will be used to - // save/restore their state. - void init(QLineEdit *name_edit_arg, QDialogButtonBox *button_box, const QList &edits_list, const QList &required_list, const QList &widget_list, const QString &object_class); - - void set_parent_dn(const QString &dn); - void open() override; - void accept() override; - void on_edited(); - QString get_created_name() const; - QString get_created_dn() const; - -private: - QString parent_dn; - QLineEdit *name_edit; - QList m_edit_list; - QList m_required_list; - QPushButton *ok_button; - QString m_object_class; - WidgetState m_state; + virtual QString get_created_dn() const = 0; }; #endif /* CREATE_OBJECT_DIALOG_H */ diff --git a/src/admc/create_object_helper.cpp b/src/admc/create_object_helper.cpp new file mode 100644 index 00000000..7da3f09d --- /dev/null +++ b/src/admc/create_object_helper.cpp @@ -0,0 +1,184 @@ +/* + * 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 "create_object_helper.h" + +#include "adldap.h" +#include "attribute_edits/attribute_edit.h" +#include "globals.h" +#include "status.h" +#include "utils.h" + +#include +#include +#include +#include + +// TODO: the logic of "enable/disable ok button +// depending on whether all required edits contain +// input" is duplicated in password dialog and maybe in +// other places. Can create an abstraction for it to +// reduce duplication. + +CreateObjectHelper::CreateObjectHelper(QLineEdit *name_edit_arg, QDialogButtonBox *button_box, const QList &edits_list, const QList &required_list, const QString &object_class, const QString &parent_dn_arg, QDialog *parent_dialog_arg) +: QObject(parent_dialog_arg) { + parent_dialog = parent_dialog_arg; + name_edit = name_edit_arg; + m_edit_list = edits_list; + m_required_list = required_list; + m_object_class = object_class; + parent_dn = parent_dn_arg; + + ok_button = button_box->button(QDialogButtonBox::Ok); + + limit_edit(name_edit, ATTRIBUTE_CN); + + for (QLineEdit *edit : m_required_list) { + connect( + edit, &QLineEdit::textChanged, + this, &CreateObjectHelper::on_edited); + } + on_edited(); +} + +bool CreateObjectHelper::accept() const { + AdInterface ad; + if (ad_failed(ad, parent_dialog)) { + return false; + } + + const QString name = get_created_name(); + const QString dn = get_created_dn(); + + const bool verify_name_success = verify_object_name(name, parent_dialog); + if (!verify_name_success) { + return false; + } + + // Verify edits + const bool verify_success = AttributeEdit::verify(m_edit_list, ad, dn); + + if (!verify_success) { + return false; + } + + auto fail_msg = [name]() { + const QString message = QString(tr("Failed to create object %1")).arg(name); + g_status->add_message(message, StatusType_Error); + }; + + bool final_success = true; + + const QHash> attr_map = [&]() { + if (m_object_class == CLASS_SHARED_FOLDER) { + // NOTE: for shared folders, UNC name must + // be defined on creation because it's a + // mandatory attribute + return QHash>({ + {ATTRIBUTE_OBJECT_CLASS, {m_object_class}}, + {ATTRIBUTE_UNC_NAME, {"placeholder"}}, + }); + } else { + return QHash>({ + {ATTRIBUTE_OBJECT_CLASS, {m_object_class}}, + }); + } + }(); + + const bool add_success = ad.object_add(dn, attr_map); + + final_success = (final_success && add_success); + + if (add_success) { + const bool is_user_or_person = (m_object_class == CLASS_USER || m_object_class == CLASS_INET_ORG_PERSON); + const bool is_computer = (m_object_class == CLASS_COMPUTER); + + if (is_user_or_person) { + const int uac = [this, dn, &ad]() { + const AdObject object = ad.search_object(dn, {ATTRIBUTE_USER_ACCOUNT_CONTROL}); + const int out = object.get_int(ATTRIBUTE_USER_ACCOUNT_CONTROL); + + return out; + }(); + + const int bit = UAC_PASSWD_NOTREQD; + const int updated_uac = bitmask_set(uac, bit, false); + + final_success = (final_success && ad.attribute_replace_int(dn, ATTRIBUTE_USER_ACCOUNT_CONTROL, updated_uac, DoStatusMsg_No)); + } else if (is_computer) { + // NOTE: other attributes like primary + // group and sam account type are + // automatically changed by the server when + // we set UAC to the correct value + const int uac = (UAC_PASSWD_NOTREQD | UAC_WORKSTATION_TRUST_ACCOUNT); + final_success = (final_success && ad.attribute_replace_int(dn, ATTRIBUTE_USER_ACCOUNT_CONTROL, uac)); + } + + const bool apply_success = AttributeEdit::apply(m_edit_list, ad, dn); + + final_success = (final_success && apply_success); + } + + if (!final_success && add_success) { + ad.object_delete(dn); + } + + g_status->display_ad_messages(ad, parent_dialog); + + if (final_success) { + const QString message = QString(tr("Object %1 was created")).arg(name); + + g_status->add_message(message, StatusType_Success); + } else { + fail_msg(); + } + + return final_success; +} + +// Enable/disable create button if all required edits filled +void CreateObjectHelper::on_edited() { + const bool all_required_filled = [this]() { + for (QLineEdit *edit : m_required_list) { + if (edit->text().isEmpty()) { + return false; + } + } + + return true; + }(); + + ok_button->setEnabled(all_required_filled); +} + +QString CreateObjectHelper::get_created_name() const { + // NOTE: trim whitespaces because server will do it + // anyway and we want a correct name + const QString name = name_edit->text().trimmed(); + + return name; +} + +QString CreateObjectHelper::get_created_dn() const { + const QString name = get_created_name(); + const QString dn = dn_from_name_and_parent(name, parent_dn, m_object_class); + + return dn; +} diff --git a/src/admc/create_object_helper.h b/src/admc/create_object_helper.h new file mode 100644 index 00000000..1e59b7ab --- /dev/null +++ b/src/admc/create_object_helper.h @@ -0,0 +1,57 @@ +/* + * 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 CREATE_OBJECT_HELPER_H +#define CREATE_OBJECT_HELPER_H + +/** + * Base class for dialogs that create objects. + */ + +#include + +class QDialog; +class QLineEdit; +class QDialogButtonBox; +class AttributeEdit; +class QPushButton; + +class CreateObjectHelper : public QObject { + Q_OBJECT + +public: + CreateObjectHelper(QLineEdit *name_edit, QDialogButtonBox *button_box, const QList &edits_list, const QList &required_list, const QString &object_class, const QString &parent_dn, QDialog *parent_dialog); + + bool accept() const; + void on_edited(); + QString get_created_name() const; + QString get_created_dn() const; + +private: + QDialog *parent_dialog; + QString parent_dn; + QLineEdit *name_edit; + QList m_edit_list; + QList m_required_list; + QPushButton *ok_button; + QString m_object_class; +}; + +#endif /* CREATE_OBJECT_HELPER_H */ diff --git a/src/admc/create_ou_dialog.cpp b/src/admc/create_ou_dialog.cpp index 4c7573c3..85a51020 100644 --- a/src/admc/create_ou_dialog.cpp +++ b/src/admc/create_ou_dialog.cpp @@ -27,27 +27,26 @@ #include "attribute_edits/upn_edit.h" #include "utils.h" #include "settings.h" +#include "create_object_helper.h" -CreateOUDialog::CreateOUDialog(QWidget *parent) +CreateOUDialog::CreateOUDialog(const QString &parent_dn, QWidget *parent) : CreateObjectDialog(parent) { ui = new Ui::CreateOUDialog(); ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - QList edit_list; - new ProtectDeletionEdit(ui->deletion_check, &edit_list, this); + auto deletion_edit = new ProtectDeletionEdit(ui->deletion_check, this); + + const QList edit_list = { + deletion_edit, + }; const QList required_list = { ui->name_edit, }; - const QList widget_list = { - ui->name_edit, - ui->deletion_check, - }; - - init(ui->name_edit, ui->button_box, edit_list, required_list, widget_list, CLASS_OU); + helper = new CreateObjectHelper(ui->name_edit, ui->button_box, edit_list, required_list, CLASS_OU, parent_dn, this); settings_setup_dialog_geometry(SETTING_create_ou_dialog_geometry, this); } @@ -55,3 +54,15 @@ CreateOUDialog::CreateOUDialog(QWidget *parent) CreateOUDialog::~CreateOUDialog() { delete ui; } + +void CreateOUDialog::accept() { + const bool accepted = helper->accept(); + + if (accepted) { + QDialog::accept(); + } +} + +QString CreateOUDialog::get_created_dn() const { + return helper->get_created_dn(); +} diff --git a/src/admc/create_ou_dialog.h b/src/admc/create_ou_dialog.h index 1d6a161c..6505ebb5 100644 --- a/src/admc/create_ou_dialog.h +++ b/src/admc/create_ou_dialog.h @@ -23,6 +23,8 @@ #include "create_object_dialog.h" +class CreateObjectHelper; + namespace Ui { class CreateOUDialog; } @@ -33,8 +35,14 @@ class CreateOUDialog final : public CreateObjectDialog { public: Ui::CreateOUDialog *ui; - CreateOUDialog(QWidget *parent); + CreateOUDialog(const QString &parent_dn, QWidget *parent); ~CreateOUDialog(); + + void accept() override; + QString get_created_dn() const override; + +private: + CreateObjectHelper *helper; }; #endif /* CREATE_OU_DIALOG_H */ diff --git a/src/admc/create_policy_dialog.cpp b/src/admc/create_policy_dialog.cpp index b73c9c63..a3966503 100644 --- a/src/admc/create_policy_dialog.cpp +++ b/src/admc/create_policy_dialog.cpp @@ -29,33 +29,16 @@ #include "status.h" #include "utils.h" -CreatePolicyDialog::CreatePolicyDialog(QWidget *parent) +CreatePolicyDialog::CreatePolicyDialog(AdInterface &ad, QWidget *parent) : QDialog(parent) { ui = new Ui::CreatePolicyDialog(); ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - settings_setup_dialog_geometry(SETTING_create_policy_dialog_geometry, this); -} - -CreatePolicyDialog::~CreatePolicyDialog() { - delete ui; -} - -QString CreatePolicyDialog::get_created_dn() const { - return created_dn; -} - -void CreatePolicyDialog::open() { - AdInterface ad; - if (ad_failed(ad)) { - return; - } - const QString default_name = [&]() { const QList existing_name_list = [&]() { - const QString base = g_adconfig->domain_head(); + const QString base = g_adconfig->domain_dn(); const SearchScope scope = SearchScope_All; const QString filter = filter_CONDITION(Condition_Equals, ATTRIBUTE_OBJECT_CLASS, CLASS_GP_CONTAINER); const QList attributes = {ATTRIBUTE_DISPLAY_NAME}; @@ -78,13 +61,22 @@ void CreatePolicyDialog::open() { ui->name_edit->setText(default_name); ui->name_edit->selectAll(); + limit_edit(ui->name_edit, ATTRIBUTE_DISPLAY_NAME); - QDialog::open(); + settings_setup_dialog_geometry(SETTING_create_policy_dialog_geometry, this); +} + +CreatePolicyDialog::~CreatePolicyDialog() { + delete ui; +} + +QString CreatePolicyDialog::get_created_dn() const { + return created_dn; } void CreatePolicyDialog::accept() { AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, this)) { return; } @@ -96,7 +88,7 @@ void CreatePolicyDialog::accept() { // have to manually check for conflict. Server wouldn't // catch this. const bool name_conflict = [&]() { - const QString base = g_adconfig->domain_head(); + const QString base = g_adconfig->domain_dn(); const SearchScope scope = SearchScope_All; const QString filter = filter_CONDITION(Condition_Equals, ATTRIBUTE_DISPLAY_NAME, name); const QList attributes = QList(); diff --git a/src/admc/create_policy_dialog.h b/src/admc/create_policy_dialog.h index f4291b53..a81dcc60 100644 --- a/src/admc/create_policy_dialog.h +++ b/src/admc/create_policy_dialog.h @@ -28,6 +28,7 @@ #include class QLineEdit; +class AdInterface; namespace Ui { class CreatePolicyDialog; @@ -39,13 +40,12 @@ class CreatePolicyDialog : public QDialog { public: Ui::CreatePolicyDialog *ui; - CreatePolicyDialog(QWidget *parent); + CreatePolicyDialog(AdInterface &ad, QWidget *parent); ~CreatePolicyDialog(); QString get_created_dn() const; public slots: - void open() override; void accept() override; private: diff --git a/src/admc/create_query_folder_dialog.cpp b/src/admc/create_query_folder_dialog.cpp index c85e88e3..94e391d8 100644 --- a/src/admc/create_query_folder_dialog.cpp +++ b/src/admc/create_query_folder_dialog.cpp @@ -31,7 +31,7 @@ CreateQueryFolderDialog::CreateQueryFolderDialog(QWidget *parent) ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - + settings_setup_dialog_geometry(SETTING_create_query_folder_dialog_geometry, this); } @@ -49,9 +49,7 @@ QString CreateQueryFolderDialog::description() const { void CreateQueryFolderDialog::set_sibling_name_list(const QList &list) { sibling_name_list = list; -} -void CreateQueryFolderDialog::open() { const QString default_name = [&]() { const QString out = generate_new_name(sibling_name_list, tr("New Folder")); @@ -59,9 +57,6 @@ void CreateQueryFolderDialog::open() { }(); ui->name_edit->setText(default_name); - ui->description_edit->setText(""); - - QDialog::open(); } void CreateQueryFolderDialog::accept() { diff --git a/src/admc/create_query_folder_dialog.h b/src/admc/create_query_folder_dialog.h index d3bf0604..11905474 100644 --- a/src/admc/create_query_folder_dialog.h +++ b/src/admc/create_query_folder_dialog.h @@ -41,7 +41,6 @@ public: void set_sibling_name_list(const QList &list); - void open() override; void accept() override; private: diff --git a/src/admc/create_query_item_dialog.cpp b/src/admc/create_query_item_dialog.cpp index d1fd0411..88115198 100644 --- a/src/admc/create_query_item_dialog.cpp +++ b/src/admc/create_query_item_dialog.cpp @@ -24,13 +24,15 @@ #include "console_impls/query_folder_impl.h" #include "settings.h" -CreateQueryItemDialog::CreateQueryItemDialog(QWidget *parent) +CreateQueryItemDialog::CreateQueryItemDialog(const QList &sibling_name_list_arg, QWidget *parent) : QDialog(parent) { ui = new Ui::CreateQueryItemDialog(); ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + sibling_name_list = sibling_name_list_arg; + settings_setup_dialog_geometry(SETTING_create_query_item_dialog_geometry, this); } @@ -62,16 +64,6 @@ QByteArray CreateQueryItemDialog::filter_state() const { return ui->edit_query_widget->filter_state(); } -void CreateQueryItemDialog::set_sibling_name_list(const QList &list) { - sibling_name_list = list; -} - -void CreateQueryItemDialog::open() { - ui->edit_query_widget->clear(); - - QDialog::open(); -} - void CreateQueryItemDialog::accept() { const QString name = ui->edit_query_widget->name(); const bool name_is_valid = console_query_or_folder_name_is_good(name, sibling_name_list, this); diff --git a/src/admc/create_query_item_dialog.h b/src/admc/create_query_item_dialog.h index 2be6d19d..6480aa29 100644 --- a/src/admc/create_query_item_dialog.h +++ b/src/admc/create_query_item_dialog.h @@ -35,7 +35,7 @@ class CreateQueryItemDialog : public QDialog { public: Ui::CreateQueryItemDialog *ui; - CreateQueryItemDialog(QWidget *parent); + CreateQueryItemDialog(const QList &sibling_name_list, QWidget *parent); ~CreateQueryItemDialog(); QString name() const; @@ -45,9 +45,6 @@ public: bool scope_is_children() const; QByteArray filter_state() const; - void set_sibling_name_list(const QList &list); - - void open() override; void accept() override; private: diff --git a/src/admc/create_shared_folder_dialog.cpp b/src/admc/create_shared_folder_dialog.cpp new file mode 100644 index 00000000..63c574ac --- /dev/null +++ b/src/admc/create_shared_folder_dialog.cpp @@ -0,0 +1,69 @@ +/* + * 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 "create_shared_folder_dialog.h" +#include "ui_create_shared_folder_dialog.h" + +#include "adldap.h" +#include "attribute_edits/protect_deletion_edit.h" +#include "attribute_edits/string_edit.h" +#include "attribute_edits/upn_edit.h" +#include "utils.h" +#include "settings.h" +#include "create_object_helper.h" + +CreateSharedFolderDialog::CreateSharedFolderDialog(const QString &parent_dn, QWidget *parent) +: CreateObjectDialog(parent) { + ui = new Ui::CreateSharedFolderDialog(); + ui->setupUi(this); + + setAttribute(Qt::WA_DeleteOnClose); + + auto path_edit = new StringEdit(ui->path_edit, ATTRIBUTE_UNC_NAME, this); + + const QList edit_list = { + path_edit, + }; + + const QList required_list = { + ui->name_edit, + ui->path_edit, + }; + + helper = new CreateObjectHelper(ui->name_edit, ui->button_box, edit_list, required_list, CLASS_SHARED_FOLDER, parent_dn, this); + + settings_setup_dialog_geometry(SETTING_create_shared_folder_dialog_geometry, this); +} + +CreateSharedFolderDialog::~CreateSharedFolderDialog() { + delete ui; +} + +void CreateSharedFolderDialog::accept() { + const bool accepted = helper->accept(); + + if (accepted) { + QDialog::accept(); + } +} + +QString CreateSharedFolderDialog::get_created_dn() const { + return helper->get_created_dn(); +} diff --git a/src/admc/create_shared_folder_dialog.h b/src/admc/create_shared_folder_dialog.h new file mode 100644 index 00000000..2e105801 --- /dev/null +++ b/src/admc/create_shared_folder_dialog.h @@ -0,0 +1,48 @@ +/* + * 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 CREATE_SHARED_FOLDER_DIALOG_H +#define CREATE_SHARED_FOLDER_DIALOG_H + +#include "create_object_dialog.h" + +class CreateObjectHelper; + +namespace Ui { +class CreateSharedFolderDialog; +} + +class CreateSharedFolderDialog final : public CreateObjectDialog { + Q_OBJECT + +public: + Ui::CreateSharedFolderDialog *ui; + + CreateSharedFolderDialog(const QString &parent_dn, QWidget *parent); + ~CreateSharedFolderDialog(); + + void accept() override; + QString get_created_dn() const override; + +private: + CreateObjectHelper *helper; +}; + +#endif /* CREATE_SHARED_FOLDER_DIALOG_H */ diff --git a/src/admc/create_shared_folder_dialog.ui b/src/admc/create_shared_folder_dialog.ui new file mode 100644 index 00000000..c2426f7c --- /dev/null +++ b/src/admc/create_shared_folder_dialog.ui @@ -0,0 +1,88 @@ + + + CreateSharedFolderDialog + + + + 0 + 0 + 400 + 127 + + + + Create Shared Folder + + + + + + + + + + + Name: + + + + + + + Network path: + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + button_box + accepted() + CreateSharedFolderDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + button_box + rejected() + CreateSharedFolderDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/admc/create_user_dialog.cpp b/src/admc/create_user_dialog.cpp index e23c9ecc..07d95134 100644 --- a/src/admc/create_user_dialog.cpp +++ b/src/admc/create_user_dialog.cpp @@ -29,23 +29,23 @@ #include "attribute_edits/upn_edit.h" #include "settings.h" #include "utils.h" +#include "create_object_helper.h" -CreateUserDialog::CreateUserDialog(QWidget *parent) +CreateUserDialog::CreateUserDialog(AdInterface &ad, const QString &parent_dn, const QString &user_class, QWidget *parent) : CreateObjectDialog(parent) { ui = new Ui::CreateUserDialog(); ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - QList edit_list; - new StringEdit(ui->first_name_edit, ATTRIBUTE_FIRST_NAME, &edit_list, this); - new StringEdit(ui->last_name_edit, ATTRIBUTE_LAST_NAME, &edit_list, this); - new StringEdit(ui->initials_edit, ATTRIBUTE_INITIALS, &edit_list, this); - sam_name_edit = new SamNameEdit(ui->sam_name_edit, ui->sam_name_domain_edit, &edit_list, this); + auto first_name_edit = new StringEdit(ui->first_name_edit, ATTRIBUTE_FIRST_NAME, this); + auto last_name_edit = new StringEdit(ui->last_name_edit, ATTRIBUTE_LAST_NAME, this); + auto initials_edit = new StringEdit(ui->initials_edit, ATTRIBUTE_INITIALS, this); + auto sam_name_edit = new SamNameEdit(ui->sam_name_edit, ui->sam_name_domain_edit, this); + auto password_edit = new PasswordEdit(ui->password_main_edit, ui->password_confirm_edit, ui->show_password_check, this); - new PasswordEdit(ui->password_main_edit, ui->password_confirm_edit, &edit_list, this); - - upn_edit = new UpnEdit(ui->upn_prefix_edit, ui->upn_suffix_edit, &edit_list, this); + auto upn_edit = new UpnEdit(ui->upn_prefix_edit, ui->upn_suffix_edit, this); + upn_edit->init_suffixes(ad); const QHash check_map = { {AccountOption_PasswordExpired, ui->must_change_pass_check}, @@ -54,25 +54,20 @@ CreateUserDialog::CreateUserDialog(QWidget *parent) {AccountOption_Disabled, ui->disabled_check}, }; + QList option_edit_list; + for (const AccountOption &option : check_map.keys()) { QCheckBox *check = check_map[option]; - new AccountOptionEdit(check, option, &edit_list, this); + auto edit = new AccountOptionEdit(check, option, this); + + option_edit_list.append(edit); } account_option_setup_conflicts(check_map); - // (first name + last name) -> full name - connect( - ui->first_name_edit, &QLineEdit::textChanged, - this, &CreateUserDialog::autofill_full_name); - connect( - ui->last_name_edit, &QLineEdit::textChanged, - this, &CreateUserDialog::autofill_full_name); + setup_full_name_autofill(ui->first_name_edit, ui->last_name_edit, ui->name_edit); - // upn -> sam account name - connect( - ui->upn_prefix_edit, &QLineEdit::textChanged, - this, &CreateUserDialog::autofill_sam_name); + setup_lineedit_autofill(ui->upn_prefix_edit, ui->sam_name_edit); const QList required_list = { ui->name_edit, @@ -80,26 +75,28 @@ CreateUserDialog::CreateUserDialog(QWidget *parent) ui->sam_name_edit, }; - const QList widget_list = { - ui->name_edit, - ui->first_name_edit, - ui->last_name_edit, - ui->initials_edit, - ui->sam_name_edit, - // NOTE: not restoring sam account name domain state - // is intended - // ui->sam_name_domain_edit, - ui->password_confirm_edit, - ui->upn_prefix_edit, - // NOTE: not restoring upn suffix state is intended - // ui->upn_suffix_edit, - ui->must_change_pass_check, - ui->cant_change_pass_check, - ui->dont_expire_pass_check, - ui->disabled_check, - }; + const QList edit_list = [&]() { + QList out; - init(ui->name_edit, ui->button_box, edit_list, required_list, widget_list, CLASS_USER); + out = { + first_name_edit, + last_name_edit, + initials_edit, + sam_name_edit, + password_edit, + upn_edit, + }; + + out.append(option_edit_list); + + return out; + }(); + + helper = new CreateObjectHelper(ui->name_edit, ui->button_box, edit_list, required_list, user_class, parent_dn, this); + + if (user_class != CLASS_USER) { + setWindowTitle(QString(tr("Create %1")).arg(user_class)); + } settings_setup_dialog_geometry(SETTING_create_user_dialog_geometry, this); } @@ -108,43 +105,14 @@ CreateUserDialog::~CreateUserDialog() { delete ui; } -void CreateUserDialog::open() { - AdInterface ad; - if (ad_failed(ad)) { - return; +void CreateUserDialog::accept() { + const bool accepted = helper->accept(); + + if (accepted) { + QDialog::accept(); } - - upn_edit->init_suffixes(ad); - sam_name_edit->load_domain(); - - CreateObjectDialog::open(); } -void CreateUserDialog::autofill_full_name() { - const QString full_name_value = [=]() { - const QString first_name = ui->first_name_edit->text(); - const QString last_name = ui->last_name_edit->text(); - - const bool last_name_first = settings_get_bool(SETTING_last_name_before_first_name); - if (!first_name.isEmpty() && !last_name.isEmpty()) { - if (last_name_first) { - return last_name + " " + first_name; - } else { - return first_name + " " + last_name; - } - } else if (!first_name.isEmpty()) { - return first_name; - } else if (!last_name.isEmpty()) { - return last_name; - } else { - return QString(); - } - }(); - - ui->name_edit->setText(full_name_value); +QString CreateUserDialog::get_created_dn() const { + return helper->get_created_dn(); } - -void CreateUserDialog::autofill_sam_name() { - const QString upn_input = ui->upn_prefix_edit->text(); - ui->sam_name_edit->setText(upn_input); -} \ No newline at end of file diff --git a/src/admc/create_user_dialog.h b/src/admc/create_user_dialog.h index 9e2ff455..a4a94379 100644 --- a/src/admc/create_user_dialog.h +++ b/src/admc/create_user_dialog.h @@ -23,8 +23,8 @@ #include "create_object_dialog.h" -class SamNameEdit; -class UpnEdit; +class AdInterface; +class CreateObjectHelper; namespace Ui { class CreateUserDialog; @@ -36,17 +36,19 @@ class CreateUserDialog final : public CreateObjectDialog { public: Ui::CreateUserDialog *ui; - CreateUserDialog(QWidget *parent); + // NOTE: user_class can be either CLASS_USER or + // CLASS_INET_ORG_PERSON. This is so that this + // dialog can be reused for both classes. + CreateUserDialog(AdInterface &ad, const QString &parent_dn, const QString &user_class, QWidget *parent); ~CreateUserDialog(); - void open() override; + void accept() override; + QString get_created_dn() const override; private: - SamNameEdit *sam_name_edit; - UpnEdit *upn_edit; + CreateObjectHelper *helper; void autofill_full_name(); - void autofill_sam_name(); }; #endif /* CREATE_USER_DIALOG_H */ diff --git a/src/admc/create_user_dialog.ui b/src/admc/create_user_dialog.ui index 7541e23b..9297c305 100644 --- a/src/admc/create_user_dialog.ui +++ b/src/admc/create_user_dialog.ui @@ -122,6 +122,13 @@ + + + + Show password + + + diff --git a/src/admc/data/admc.svg b/src/admc/data/admc.svg new file mode 100644 index 00000000..0533763b --- /dev/null +++ b/src/admc/data/admc.svg @@ -0,0 +1,106 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + AD + + diff --git a/src/admc/data/countries.csv b/src/admc/data/countries.csv index 6fb8e54d..3c5f6e36 100644 --- a/src/admc/data/countries.csv +++ b/src/admc/data/countries.csv @@ -1,250 +1,250 @@ -Country,Alpha-2 code,Numeric -Afghanistan,AF,4 -Albania,AL,8 -Algeria,DZ,12 -American Samoa,AS,16 -Andorra,AD,20 -Angola,AO,24 -Anguilla,AI,660 -Antarctica,AQ,10 -Antigua and Barbuda,AG,28 -Argentina,AR,32 -Armenia,AM,51 -Aruba,AW,533 -Australia,AU,36 -Austria,AT,40 -Azerbaijan,AZ,31 -Bahamas,BS,44 -Bahrain,BH,48 -Bangladesh,BD,50 -Barbados,BB,52 -Belarus,BY,112 -Belgium,BE,56 -Belize,BZ,84 -Benin,BJ,204 -Bermuda,BM,60 -Bhutan,BT,64 -Bolivia (Plurinational State of),BO,68 -"Bonaire, Sint Eustatius and Saba",BQ,535 -Bosnia and Herzegovina,BA,70 -Botswana,BW,72 -Bouvet Island,BV,74 -Brazil,BR,76 -British Indian Ocean Territory,IO,86 -Brunei Darussalam,BN,96 -Bulgaria,BG,100 -Burkina Faso,BF,854 -Burundi,BI,108 -Cabo Verde,CV,132 -Cambodia,KH,116 -Cameroon,CM,120 -Canada,CA,124 -Cayman Islands,KY,136 -Central African Republic,CF,140 -Chad,TD,148 -Chile,CL,152 -China,CN,156 -Christmas Island,CX,162 -Cocos (Keeling) Islands,CC,166 -Colombia,CO,170 -Comoros,KM,174 -Congo (the Democratic Republic of the),CD,180 -Congo,CG,178 -Cook Islands,CK,184 -Costa Rica,CR,188 -Croatia,HR,191 -Cuba,CU,192 -Curaçao,CW,531 -Cyprus,CY,196 -Czechia,CZ,203 -Côte d'Ivoire,CI,384 -Denmark,DK,208 -Djibouti,DJ,262 -Dominica,DM,212 -Dominican Republic,DO,214 -Ecuador,EC,218 -Egypt,EG,818 -El Salvador,SV,222 -Equatorial Guinea,GQ,226 -Eritrea,ER,232 -Estonia,EE,233 -Eswatini,SZ,748 -Ethiopia,ET,231 -Falkland Islands [Malvinas],FK,238 -Faroe Islands,FO,234 -Fiji,FJ,242 -Finland,FI,246 -France,FR,250 -French Guiana,GF,254 -French Polynesia,PF,258 -French Southern Territories,TF,260 -Gabon,GA,266 -Gambia,GM,270 -Georgia,GE,268 -Germany,DE,276 -Ghana,GH,288 -Gibraltar,GI,292 -Greece,GR,300 -Greenland,GL,304 -Grenada,GD,308 -Guadeloupe,GP,312 -Guam,GU,316 -Guatemala,GT,320 -Guernsey,GG,831 -Guinea,GN,324 -Guinea-Bissau,GW,624 -Guyana,GY,328 -Haiti,HT,332 -Heard Island and McDonald Islands,HM,334 -Holy See,VA,336 -Honduras,HN,340 -Hong Kong,HK,344 -Hungary,HU,348 -Iceland,IS,352 -India,IN,356 -Indonesia,ID,360 -Iran (Islamic Republic of),IR,364 -Iraq,IQ,368 -Ireland,IE,372 -Isle of Man,IM,833 -Israel,IL,376 -Italy,IT,380 -Jamaica,JM,388 -Japan,JP,392 -Jersey,JE,832 -Jordan,JO,400 -Kazakhstan,KZ,398 -Kenya,KE,404 -Kiribati,KI,296 -Korea (the Democratic People's Republic of),KP,408 -Korea (the Republic of),KR,410 -Kuwait,KW,414 -Kyrgyzstan,KG,417 -Lao People's Democratic Republic,LA,418 -Latvia,LV,428 -Lebanon,LB,422 -Lesotho,LS,426 -Liberia,LR,430 -Libya,LY,434 -Liechtenstein,LI,438 -Lithuania,LT,440 -Luxembourg,LU,442 -Macao,MO,446 -Madagascar,MG,450 -Malawi,MW,454 -Malaysia,MY,458 -Maldives,MV,462 -Mali,ML,466 -Malta,MT,470 -Marshall Islands,MH,584 -Martinique,MQ,474 -Mauritania,MR,478 -Mauritius,MU,480 -Mayotte,YT,175 -Mexico,MX,484 -Micronesia (Federated States of),FM,583 -Moldova (the Republic of),MD,498 -Monaco,MC,492 -Mongolia,MN,496 -Montenegro,ME,499 -Montserrat,MS,500 -Morocco,MA,504 -Mozambique,MZ,508 -Myanmar,MM,104 -Namibia,NA,516 -Nauru,NR,520 -Nepal,NP,524 -Netherlands,NL,528 -New Caledonia,NC,540 -New Zealand,NZ,554 -Nicaragua,NI,558 -Niger,NE,562 -Nigeria,NG,566 -Niue,NU,570 -Norfolk Island,NF,574 -Northern Mariana Islands,MP,580 -Norway,NO,578 -Oman,OM,512 -Pakistan,PK,586 -Palau,PW,585 -"Palestine, State of",PS,275 -Panama,PA,591 -Papua New Guinea,PG,598 -Paraguay,PY,600 -Peru,PE,604 -Philippines,PH,608 -Pitcairn,PN,612 -Poland,PL,616 -Portugal,PT,620 -Puerto Rico,PR,630 -Qatar,QA,634 -Republic of North Macedonia,MK,807 -Romania,RO,642 -Russian Federation,RU,643 -Rwanda,RW,646 -Réunion,RE,638 -Saint Barthélemy,BL,652 -"Saint Helena, Ascension and Tristan da Cunha",SH,654 -Saint Kitts and Nevis,KN,659 -Saint Lucia,LC,662 -Saint Martin (French part),MF,663 -Saint Pierre and Miquelon,PM,666 -Saint Vincent and the Grenadines,VC,670 -Samoa,WS,882 -San Marino,SM,674 -Sao Tome and Principe,ST,678 -Saudi Arabia,SA,682 -Senegal,SN,686 -Serbia,RS,688 -Seychelles,SC,690 -Sierra Leone,SL,694 -Singapore,SG,702 -Sint Maarten (Dutch part),SX,534 -Slovakia,SK,703 -Slovenia,SI,705 -Solomon Islands,SB,90 -Somalia,SO,706 -South Africa,ZA,710 -South Georgia and the South Sandwich Islands,GS,239 -South Sudan,SS,728 -Spain,ES,724 -Sri Lanka,LK,144 -Sudan,SD,729 -Suriname,SR,740 -Svalbard and Jan Mayen,SJ,744 -Sweden,SE,752 -Switzerland,CH,756 -Syrian Arab Republic,SY,760 -Taiwan (Province of China),TW,158 -Tajikistan,TJ,762 -"Tanzania, United Republic of",TZ,834 -Thailand,TH,764 -Timor-Leste,TL,626 -Togo,TG,768 -Tokelau,TK,772 -Tonga,TO,776 -Trinidad and Tobago,TT,780 -Tunisia,TN,788 -Turkey,TR,792 -Turkmenistan,TM,795 -Turks and Caicos Islands,TC,796 -Tuvalu,TV,798 -Uganda,UG,800 -Ukraine,UA,804 -United Arab Emirates,AE,784 -United Kingdom of Great Britain and Northern Ireland,GB,826 -United States Minor Outlying Islands,UM,581 -United States of America,US,840 -Uruguay,UY,858 -Uzbekistan,UZ,860 -Vanuatu,VU,548 -Venezuela (Bolivarian Republic of),VE,862 -Viet Nam,VN,704 -Virgin Islands (British),VG,92 -Virgin Islands (U.S.),VI,850 -Wallis and Futuna,WF,876 -Western Sahara,EH,732 -Yemen,YE,887 -Zambia,ZM,894 -Zimbabwe,ZW,716 -Åland Islands,AX,248 \ No newline at end of file +Country,Country_ru,Alpha-2 code,Numeric +Afghanistan,Афганистан,AF,4 +Albania,Албания,AL,8 +Algeria,Алжир,DZ,12 +American Samoa,Американское Самоа,AS,16 +Andorra,Андорра,AD,20 +Angola,Ангола,AO,24 +Anguilla,Ангилья,AI,660 +Antarctica,Антарктика,AQ,10 +Antigua and Barbuda,Антигуа и Барбуда,AG,28 +Argentina,Аргентина,AR,32 +Armenia,Армения,AM,51 +Aruba,Аруба,AW,533 +Australia,Австралия,AU,36 +Austria,Австрия,AT,40 +Azerbaijan,Азербайджан,AZ,31 +Bahamas,Багамские Острова,BS,44 +Bahrain,Бахрейн,BH,48 +Bangladesh,Бангладеш,BD,50 +Barbados,Барбадос,BB,52 +Belarus,Белоруссия,BY,112 +Belgium,Бельгия,BE,56 +Belize,Белиз,BZ,84 +Benin,Бенин,BJ,204 +Bermuda,Бермудские Острова,BM,60 +Bhutan,Бутан,BT,64 +Bolivia (Plurinational State of),Боливия,BO,68 +"Bonaire, Sint Eustatius and Saba",Бонайре,BQ,535 +Bosnia and Herzegovina,Босния и Герцеговина,BA,70 +Botswana,Ботсвана,BW,72 +Bouvet Island,Остров Буве,BV,74 +Brazil,Бразилия,BR,76 +British Indian Ocean Territory,Британская Территория в Индийском Океане,IO,86 +Brunei Darussalam,Бруней,BN,96 +Bulgaria,Болгария,BG,100 +Burkina Faso,Буркина-Фасо,BF,854 +Burundi,Бурунди,BI,108 +Cabo Verde,Кабо-Верде,CV,132 +Cambodia,Камбоджа,KH,116 +Cameroon,Камерун,CM,120 +Canada,Канада,CA,124 +Cayman Islands,Острова Кайман,KY,136 +Central African Republic,Центральноафриканская Республика,CF,140 +Chad,Чад,TD,148 +Chile,Чили,CL,152 +China,Китай,CN,156 +Christmas Island,Остров Рождества,CX,162 +Cocos (Keeling) Islands,Кокосовые острова,CC,166 +Colombia,Колумбия,CO,170 +Comoros,Коморские острова,KM,174 +Congo (the Democratic Republic of the),Демократическая Республика Конго,CD,180 +Congo,Республика Конго,CG,178 +Cook Islands,Острова Кука,CK,184 +Costa Rica,Коста-Рика,CR,188 +Croatia,Хорватия,HR,191 +Cuba,Куба,CU,192 +Curaçao,Кюрасао,CW,531 +Cyprus,Кипр,CY,196 +Czechia,Чехия,CZ,203 +Côte d'Ivoire,Кот-д'Ивуар,CI,384 +Denmark,Дания,DK,208 +Djibouti,Джибути,DJ,262 +Dominica,Доминика,DM,212 +Dominican Republic,Доминиканская Республика,DO,214 +Ecuador,Эквадор,EC,218 +Egypt,Египет,EG,818 +El Salvador,Сальвадор,SV,222 +Equatorial Guinea,Экваториальная Гвинея,GQ,226 +Eritrea,Эритрея,ER,232 +Estonia,Эстония,EE,233 +Eswatini,Эсватини,SZ,748 +Ethiopia,Эфиопия,ET,231 +Falkland Islands [Malvinas],Фолклендские острова,FK,238 +Faroe Islands,Фарерские острова,FO,234 +Fiji,Фиджи,FJ,242 +Finland,Финляндия,FI,246 +France,Франция,FR,250 +French Guiana,Гвиана,GF,254 +French Polynesia,Французская Полинезия,PF,258 +French Southern Territories,Французские Южные территории,TF,260 +Gabon,Габон,GA,266 +Gambia,Гамбия,GM,270 +Georgia,Грузия,GE,268 +Germany,Германия,DE,276 +Ghana,Гана,GH,288 +Gibraltar,Гибралтар,GI,292 +Greece,Греция,GR,300 +Greenland,Гренландия,GL,304 +Grenada,Гренада,GD,308 +Guadeloupe,Гваделупа,GP,312 +Guam,Гуам,GU,316 +Guatemala,Гватемала,GT,320 +Guernsey,Гернси ,GG,831 +Guinea,Гвинея,GN,324 +Guinea-Bissau,Гвинея-Бисау,GW,624 +Guyana,Гайана,GY,328 +Haiti,Гаити,HT,332 +Heard Island and McDonald Islands,Остров Херд и острова Макдональдс,HM,334 +Holy See,Святой Престол,VA,336 +Honduras,Гондурас,HN,340 +Hong Kong,Гонконг,HK,344 +Hungary,Венгрия,HU,348 +Iceland,Исландия,IS,352 +India,Индия,IN,356 +Indonesia,Индонезия,ID,360 +Iran (Islamic Republic of),Иран (Персия),IR,364 +Iraq,Ирак,IQ,368 +Ireland,Ирландия,IE,372 +Isle of Man,Остров Мэн,IM,833 +Israel,Израиль,IL,376 +Italy,Италия,IT,380 +Jamaica,Ямайка,JM,388 +Japan,Япония,JP,392 +Jersey,Джерси ,JE,832 +Jordan,Иордания,JO,400 +Kazakhstan,Казахстан,KZ,398 +Kenya,Кения,KE,404 +Kiribati,Кирибати,KI,296 +Korea (the Democratic People's Republic of),Северная Корея (КНДР),KP,408 +Korea (the Republic of),Ю́жная Коре́я (Республика Корея),KR,410 +Kuwait,Кувейт,KW,414 +Kyrgyzstan,Киргизия,KG,417 +Lao People's Democratic Republic,Лаос,LA,418 +Latvia,Латвия,LV,428 +Lebanon,Ливан,LB,422 +Lesotho,Лесото,LS,426 +Liberia,Либерия,LR,430 +Libya,Ливия,LY,434 +Liechtenstein,Лихтенштейн,LI,438 +Lithuania,Литва,LT,440 +Luxembourg,Люксембург,LU,442 +Macao,Макао,MO,446 +Madagascar,Мадагаскар,MG,450 +Malawi,Малави,MW,454 +Malaysia,Малайзия,MY,458 +Maldives,Мальдивы,MV,462 +Mali,Мали,ML,466 +Malta,Мальта,MT,470 +Marshall Islands,Маршалловы Острова,MH,584 +Martinique,Мартиника,MQ,474 +Mauritania,Мавритания,MR,478 +Mauritius,Маврикий,MU,480 +Mayotte,Майотта,YT,175 +Mexico,Мексика,MX,484 +Micronesia (Federated States of),Микронезия,FM,583 +Moldova (the Republic of),Молдавия,MD,498 +Monaco,Монако,MC,492 +Mongolia,Монголия,MN,496 +Montenegro,Черногория,ME,499 +Montserrat,Монтсеррат,MS,500 +Morocco,Марокко,MA,504 +Mozambique,Мозамбик,MZ,508 +Myanmar,Мьянма,MM,104 +Namibia,Намибия,NA,516 +Nauru,Науру,NR,520 +Nepal,Непал,NP,524 +Netherlands,Нидерланды,NL,528 +New Caledonia,Новая Каледония,NC,540 +New Zealand,Новая Зеландия,NZ,554 +Nicaragua,Никарагуа,NI,558 +Niger,Нигер,NE,562 +Nigeria,Нигерия,NG,566 +Niue,Ниуэ,NU,570 +Norfolk Island,Остров Норфолк,NF,574 +Northern Mariana Islands,Северные Марианские острова,MP,580 +Norway,Норвегия,NO,578 +Oman,Оман,OM,512 +Pakistan,Пакистан,PK,586 +Palau,Палау,PW,585 +"Palestine, State of",Государство Палестина,PS,275 +Panama,Панама,PA,591 +Papua New Guinea,Папуа — Новая Гвинея,PG,598 +Paraguay,Парагвай,PY,600 +Peru,Перу,PE,604 +Philippines,Филиппины,PH,608 +Pitcairn,Питкэрн,PN,612 +Poland,Польша,PL,616 +Portugal,Португалия,PT,620 +Puerto Rico,Пуэрто-Рико,PR,630 +Qatar,Катар,QA,634 +Republic of North Macedonia,Республика Северная Македония,MK,807 +Romania,Румыния,RO,642 +Russian Federation,Российская Федерация,RU,643 +Rwanda,Руанда,RW,646 +Réunion,Реюньон,RE,638 +Saint Barthélemy,Сен-Бартельми,BL,652 +"Saint Helena, Ascension and Tristan da Cunha",Остров Святой Елены Вознесение и Тристан-да-Кунья,SH,654 +Saint Kitts and Nevis,Сент-Китс и Невис,KN,659 +Saint Lucia,Сент-Люсия,LC,662 +Saint Martin (French part),Сен-Мартен (французская часть), MF,663 +Saint Pierre and Miquelon,Сен-Пьер и Микелон,PM,666 +Saint Vincent and the Grenadines,Сент-Винсент и Гренадины,VC,670 +Samoa,Самоа,WS,882 +San Marino,Сан-Марино,SM,674 +Sao Tome and Principe,Сан-Томе и Принсипи,ST,678 +Saudi Arabia,Саудовская Аравия,SA,682 +Senegal,Сенегал,SN,686 +Serbia,Сербия,RS,688 +Seychelles,Сейшельские Острова,SC,690 +Sierra Leone,Сьерра-Леоне,SL,694 +Singapore,Сингапур,SG,702 +Sint Maarten (Dutch part),Синт-Мартен (голландская часть),SX,534 +Slovakia,Словакия,SK,703 +Slovenia,Словения,SI,705 +Solomon Islands,Соломоновы Острова,SB,90 +Somalia,Сомали,SO,706 +South Africa,Южная Африка,ZA,710 +South Georgia and the South Sandwich Islands,Южная Георгия и Южные Сандвичевы острова,GS,239 +South Sudan,Южный Судан,SS,728 +Spain,Испания,ES,724 +Sri Lanka,Шри-Ланка,LK,144 +Sudan,Судан,SD,729 +Suriname,Суринам,SR,740 +Svalbard and Jan Mayen,Шпицберген и Ян-Майен,SJ,744 +Sweden,Швеция,SE,752 +Switzerland,Швейцария,CH,756 +Syrian Arab Republic,Сирия,SY,760 +Taiwan (Province of China),Тайвань (Китайская Республика),TW,158 +Tajikistan,Таджикистан,TJ,762 +"Tanzania, United Republic of",Танзания,TZ,834 +Thailand,Таиланд,TH,764 +Timor-Leste,Восточный Тимор,TL,626 +Togo,Того,TG,768 +Tokelau,Токелау,TK,772 +Tonga,Тонга,TO,776 +Trinidad and Tobago,Тринидад и Тобаго,TT,780 +Tunisia,Тунис,TN,788 +Turkey,Турция,TR,792 +Turkmenistan,Туркмения,TM,795 +Turks and Caicos Islands,Острова Теркс и Кайкос,TC,796 +Tuvalu,Тувалу,TV,798 +Uganda,Уганда,UG,800 +Ukraine,Украина,UA,804 +United Arab Emirates,Объединённые Арабские Эмираты (ОАЭ),AE,784 +United Kingdom of Great Britain and Northern Ireland,Великобритания,GB,826 +United States Minor Outlying Islands,Внешние малые острова США,UM,581 +United States of America,Соединённые Штаты Америки,US,840 +Uruguay,Уругвай,UY,858 +Uzbekistan,Узбекистан,UZ,860 +Vanuatu,Вануату,VU,548 +Venezuela (Bolivarian Republic of),Венесуэла,VE,862 +Viet Nam,Вьетнам,VN,704 +Virgin Islands (British),Виргинские Острова (Великобритания),VG,92 +Virgin Islands (U.S.),Виргинские Острова (США),VI,850 +Wallis and Futuna,Уоллис и Футуна,WF,876 +Western Sahara,Западная Сахара,EH,732 +Yemen,Йемен,YE,887 +Zambia,Замбия,ZM,894 +Zimbabwe,Зимбабве,ZW,716 +Åland Islands,Аландские острова,AX,248 \ No newline at end of file diff --git a/src/admc/edit_query_item_dialog.cpp b/src/admc/edit_query_item_dialog.cpp index 5d89fd61..6f39261b 100644 --- a/src/admc/edit_query_item_dialog.cpp +++ b/src/admc/edit_query_item_dialog.cpp @@ -24,13 +24,15 @@ #include "console_impls/query_folder_impl.h" #include "settings.h" -EditQueryItemDialog::EditQueryItemDialog(QWidget *parent) +EditQueryItemDialog::EditQueryItemDialog(const QList &sibling_name_list_arg, QWidget *parent) : QDialog(parent) { ui = new Ui::EditQueryItemDialog(); ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + sibling_name_list = sibling_name_list_arg; + settings_setup_dialog_geometry(SETTING_edit_query_item_dialog_geometry, this); } @@ -66,15 +68,6 @@ QByteArray EditQueryItemDialog::filter_state() const { return ui->edit_query_item_widget->filter_state(); } -void EditQueryItemDialog::set_sibling_name_list(const QList &sibling_name_list_arg) { - sibling_name_list = sibling_name_list_arg; -} - -void EditQueryItemDialog::open() { - - QDialog::open(); -} - void EditQueryItemDialog::accept() { const QString name = ui->edit_query_item_widget->name(); const bool name_is_valid = console_query_or_folder_name_is_good(name, sibling_name_list, this); diff --git a/src/admc/edit_query_item_dialog.h b/src/admc/edit_query_item_dialog.h index c7ed26a6..67b7c30e 100644 --- a/src/admc/edit_query_item_dialog.h +++ b/src/admc/edit_query_item_dialog.h @@ -35,7 +35,7 @@ class EditQueryItemDialog : public QDialog { public: Ui::EditQueryItemDialog *ui; - EditQueryItemDialog(QWidget *parent); + EditQueryItemDialog(const QList &sibling_name_list, QWidget *parent); ~EditQueryItemDialog(); void set_data(const QString &name, const QString &description, const bool scope_is_children, const QByteArray &filter_state, const QString &filter); @@ -47,9 +47,6 @@ public: bool scope_is_children() const; QByteArray filter_state() const; - void set_sibling_name_list(const QList &sibling_name_list_arg); - - void open() override; void accept() override; private: diff --git a/src/admc/edit_query_item_widget.cpp b/src/admc/edit_query_item_widget.cpp index 37b378db..09de7511 100644 --- a/src/admc/edit_query_item_widget.cpp +++ b/src/admc/edit_query_item_widget.cpp @@ -72,16 +72,6 @@ QByteArray EditQueryItemWidget::filter_state() const { return out; } -void EditQueryItemWidget::clear() { - const QString name = QString(); - const QString description = QString(); - const bool scope_is_children = false; - const QByteArray filter_state = QByteArray(); - const QString filter = QString(); - - set_data(name, description, scope_is_children, filter_state, filter); -} - void EditQueryItemWidget::set_data(const QString &name, const QString &description, const bool scope_is_children, const QByteArray &filter_state, const QString &filter) { QDataStream filter_state_stream(filter_state); QHash state; @@ -99,14 +89,15 @@ void EditQueryItemWidget::set_data(const QString &name, const QString &descripti } void EditQueryItemWidget::open_filter_dialog() { - auto dialog = new FilterDialog(this); - dialog->set_classes(filter_classes, filter_classes); + auto dialog = new FilterDialog(filter_classes, filter_classes, this); + dialog->enable_filtering_all_classes(); dialog->restore_state(filter_dialog_state); dialog->open(); connect( dialog, &QDialog::accepted, + this, [this, dialog]() { filter_dialog_state = dialog->save_state(); diff --git a/src/admc/edit_query_item_widget.h b/src/admc/edit_query_item_widget.h index 5097ac0a..dd9d6539 100644 --- a/src/admc/edit_query_item_widget.h +++ b/src/admc/edit_query_item_widget.h @@ -49,7 +49,6 @@ public: bool scope_is_children() const; QByteArray filter_state() const; - void clear(); void set_data(const QString &name, const QString &description, const bool scope_is_children, const QByteArray &filter_state, const QString &filter); private: diff --git a/src/admc/filter_widget/class_filter_dialog.cpp b/src/admc/filter_widget/class_filter_dialog.cpp index 77d8b84c..13d3178f 100644 --- a/src/admc/filter_widget/class_filter_dialog.cpp +++ b/src/admc/filter_widget/class_filter_dialog.cpp @@ -25,16 +25,40 @@ #include -ClassFilterDialog::ClassFilterDialog(QWidget *parent) +// NOTE: "all" checkbox functionality is messy due to +// reuse and composition. It is what it is. + +ClassFilterDialog::ClassFilterDialog(const QList &class_list, const QList &selected_list, const bool filtering_all_classes_is_enabled, const bool all_is_checked, QWidget *parent) : QDialog(parent) { ui = new Ui::ClassFilterDialog(); ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + ui->class_filter_widget->set_classes(class_list, selected_list); + + original_state = ui->class_filter_widget->save_state(); + + ui->all_checkbox->setVisible(filtering_all_classes_is_enabled); + if (filtering_all_classes_is_enabled) { + ui->all_checkbox->setChecked(all_is_checked); + } + connect( ui->button_box->button(QDialogButtonBox::Reset), &QPushButton::clicked, this, &ClassFilterDialog::reset); + connect( + ui->all_checkbox, &QCheckBox::toggled, + this, &ClassFilterDialog::on_all_checkbox); + connect( + ui->all_checkbox, &QCheckBox::toggled, + this, &ClassFilterDialog::on_input_changed); + connect( + ui->class_filter_widget, &ClassFilterWidget::changed, + this, &ClassFilterDialog::on_input_changed); + + on_all_checkbox(); + on_input_changed(); settings_setup_dialog_geometry(SETTING_class_filter_dialog_geometry, this); } @@ -43,10 +67,6 @@ ClassFilterDialog::~ClassFilterDialog() { delete ui; } -void ClassFilterDialog::set_classes(const QList &class_list, const QList &selected_list) { - return ui->class_filter_widget->set_classes(class_list, selected_list); -} - QString ClassFilterDialog::get_filter() const { return ui->class_filter_widget->get_filter(); } @@ -55,29 +75,32 @@ QList ClassFilterDialog::get_selected_classes() const { return ui->class_filter_widget->get_selected_classes(); } -QVariant ClassFilterDialog::save_state() const { - return ui->class_filter_widget->save_state(); -} - -void ClassFilterDialog::restore_state(const QVariant &state) { - ui->class_filter_widget->restore_state(state); -} - - -void ClassFilterDialog::open() { - // Save state to later restore if dialog is is - // rejected - state_to_restore = ui->class_filter_widget->save_state(); - - QDialog::open(); -} - -void ClassFilterDialog::reject() { - reset(); - - QDialog::reject(); +bool ClassFilterDialog::get_all_is_checked() const { + return ui->all_checkbox->isChecked(); } void ClassFilterDialog::reset() { - ui->class_filter_widget->restore_state(state_to_restore); + ui->all_checkbox->setChecked(false); + ui->class_filter_widget->restore_state(original_state); +} + +void ClassFilterDialog::on_input_changed() { + const bool input_is_valid = [&]() { + const bool all_is_selected = ui->all_checkbox->isChecked(); + const QList selected_classes = get_selected_classes(); + const bool any_specific_class_selected = !selected_classes.isEmpty(); + const bool out = (all_is_selected || any_specific_class_selected); + + return out; + }(); + + QPushButton *ok_button = ui->button_box->button(QDialogButtonBox::Ok); + ok_button->setEnabled(input_is_valid); +} + +// Disable checkboxes for specific classes when "All" +// checkbox is checked. +void ClassFilterDialog::on_all_checkbox() { + const bool all_is_checked = ui->all_checkbox->isChecked(); + ui->class_filter_widget->setDisabled(all_is_checked); } diff --git a/src/admc/filter_widget/class_filter_dialog.h b/src/admc/filter_widget/class_filter_dialog.h index 4a8fa438..ca006880 100644 --- a/src/admc/filter_widget/class_filter_dialog.h +++ b/src/admc/filter_widget/class_filter_dialog.h @@ -41,22 +41,20 @@ class ClassFilterDialog final : public QDialog { public: Ui::ClassFilterDialog *ui; - ClassFilterDialog(QWidget *parent); + ClassFilterDialog(const QList &class_list, const QList &selected_list, const bool filtering_all_classes_is_enabled, const bool all_is_checked, QWidget *parent); ~ClassFilterDialog(); - void set_classes(const QList &class_list, const QList &selected_list); QString get_filter() const; QList get_selected_classes() const; - QVariant save_state() const; - void restore_state(const QVariant &state); - - void open() override; - void reject() override; + bool get_all_is_checked() const; private: - QVariant state_to_restore; + QVariant original_state; void reset(); + + void on_input_changed(); + void on_all_checkbox(); }; #endif /* CLASS_FILTER_DIALOG_H */ diff --git a/src/admc/filter_widget/class_filter_dialog.ui b/src/admc/filter_widget/class_filter_dialog.ui index 5d8b7835..5766353a 100644 --- a/src/admc/filter_widget/class_filter_dialog.ui +++ b/src/admc/filter_widget/class_filter_dialog.ui @@ -6,14 +6,21 @@ 0 0 - 400 - 300 + 282 + 103 Select Classes + + + + All + + + diff --git a/src/admc/filter_widget/class_filter_widget.cpp b/src/admc/filter_widget/class_filter_widget.cpp index 409073f8..31b4750a 100644 --- a/src/admc/filter_widget/class_filter_widget.cpp +++ b/src/admc/filter_widget/class_filter_widget.cpp @@ -23,6 +23,7 @@ #include "adldap.h" #include "globals.h" +#include "utils.h" #include @@ -43,7 +44,9 @@ ClassFilterWidget::~ClassFilterWidget() { delete ui; } -void ClassFilterWidget::set_classes(const QList &class_list, const QList &selected_list) { +void ClassFilterWidget::set_classes(const QList &class_list_arg, const QList &selected_list) { + class_list = class_list_arg; + for (const QString &object_class : class_list) { const QString class_string = g_adconfig->get_class_display_name(object_class); auto checkbox = new QCheckBox(class_string); @@ -54,26 +57,33 @@ void ClassFilterWidget::set_classes(const QList &class_list, const QLis checkbox->setChecked(is_selected); ui->classes_layout->addWidget(checkbox); + + connect( + checkbox, &QCheckBox::toggled, + this, &ClassFilterWidget::changed); } } QString ClassFilterWidget::get_filter() const { - const QList class_filter_list = [&] { + const QList selected_list = [&] { QList out; - for (const QString &object_class : checkbox_map.keys()) { + // NOTE: important iterate over class list + // because iterating over map keys causes + // undefined order, which breaks tests and + // creates other problems + for (const QString &object_class : class_list) { QCheckBox *checkbox = checkbox_map[object_class]; if (checkbox->isChecked()) { - const QString class_filter = filter_CONDITION(Condition_Equals, ATTRIBUTE_OBJECT_CLASS, object_class); - out.append(class_filter); + out.append(object_class); } } return out; }(); - const QString filter = filter_OR(class_filter_list); + const QString filter = get_classes_filter(selected_list); return filter; } @@ -81,7 +91,7 @@ QString ClassFilterWidget::get_filter() const { QList ClassFilterWidget::get_selected_classes() const { QList out; - for (const QString &object_class : checkbox_map.keys()) { + for (const QString &object_class : class_list) { const QCheckBox *check = checkbox_map[object_class]; if (check->isChecked()) { @@ -107,7 +117,11 @@ void ClassFilterWidget::clear_selection() { QVariant ClassFilterWidget::save_state() const { QHash state; - for (const QString &object_class : checkbox_map.keys()) { + // NOTE: important iterate over class list + // because iterating over map keys causes + // undefined order, which breaks tests and + // creates other problems + for (const QString &object_class : class_list) { QCheckBox *checkbox = checkbox_map[object_class]; const bool checked = checkbox->isChecked(); @@ -120,7 +134,7 @@ QVariant ClassFilterWidget::save_state() const { void ClassFilterWidget::restore_state(const QVariant &state_variant) { const QHash state = state_variant.toHash(); - for (const QString &object_class : checkbox_map.keys()) { + for (const QString &object_class : class_list) { const bool checked = [&]() { if (state.contains(object_class)) { const bool out = state[object_class].toBool(); diff --git a/src/admc/filter_widget/class_filter_widget.h b/src/admc/filter_widget/class_filter_widget.h index 2d625da1..d2336e57 100644 --- a/src/admc/filter_widget/class_filter_widget.h +++ b/src/admc/filter_widget/class_filter_widget.h @@ -51,8 +51,13 @@ public: QVariant save_state() const; void restore_state(const QVariant &state); +signals: + // Emitted when any of the checkboxes are touched + void changed(); + private: QHash checkbox_map; + QList class_list; void clear_selection(); void select_all(); diff --git a/src/admc/filter_widget/class_filter_widget.ui b/src/admc/filter_widget/class_filter_widget.ui index 7c4d7cb9..049058d3 100644 --- a/src/admc/filter_widget/class_filter_widget.ui +++ b/src/admc/filter_widget/class_filter_widget.ui @@ -6,8 +6,8 @@ 0 0 - 400 - 300 + 245 + 147 @@ -24,8 +24,8 @@ 0 0 - 376 - 236 + 221 + 83 diff --git a/src/admc/filter_widget/filter_dialog.cpp b/src/admc/filter_widget/filter_dialog.cpp index 1fb59ffe..1adaa285 100644 --- a/src/admc/filter_widget/filter_dialog.cpp +++ b/src/admc/filter_widget/filter_dialog.cpp @@ -27,13 +27,15 @@ #include -FilterDialog::FilterDialog(QWidget *parent) +FilterDialog::FilterDialog(const QList &class_list, const QList &selected_list, QWidget *parent) : QDialog(parent) { ui = new Ui::FilterDialog(); ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); + ui->filter_widget->set_classes(class_list, selected_list); + settings_setup_dialog_geometry(SETTING_filter_dialog_geometry, this); } @@ -41,10 +43,6 @@ FilterDialog::~FilterDialog() { delete ui; } -void FilterDialog::set_classes(const QList &class_list, const QList &selected_list) { - ui->filter_widget->set_classes(class_list, selected_list); -} - QVariant FilterDialog::save_state() const { return ui->filter_widget->save_state(); } @@ -53,6 +51,10 @@ void FilterDialog::restore_state(const QVariant &state) { ui->filter_widget->restore_state(state); } +void FilterDialog::enable_filtering_all_classes() { + ui->filter_widget->enable_filtering_all_classes(); +} + QString FilterDialog::get_filter() const { return ui->filter_widget->get_filter(); } diff --git a/src/admc/filter_widget/filter_dialog.h b/src/admc/filter_widget/filter_dialog.h index 4f43af3b..1395add9 100644 --- a/src/admc/filter_widget/filter_dialog.h +++ b/src/admc/filter_widget/filter_dialog.h @@ -39,13 +39,12 @@ class FilterDialog final : public QDialog { public: Ui::FilterDialog *ui; - FilterDialog(QWidget *parent); + FilterDialog(const QList &class_list, const QList &selected_list, QWidget *parent); ~FilterDialog(); - void set_classes(const QList &class_list, const QList &selected_list); - QVariant save_state() const; void restore_state(const QVariant &state); + void enable_filtering_all_classes(); QString get_filter() const; diff --git a/src/admc/filter_widget/filter_widget.cpp b/src/admc/filter_widget/filter_widget.cpp index 9fbb6b78..f4bf1912 100644 --- a/src/admc/filter_widget/filter_widget.cpp +++ b/src/admc/filter_widget/filter_widget.cpp @@ -42,6 +42,11 @@ void FilterWidget::set_classes(const QList &class_list, const QListnormal_tab->set_classes(class_list, selected_list); } +void FilterWidget::enable_filtering_all_classes() { + ui->simple_tab->enable_filtering_all_classes(); + ui->normal_tab->enable_filtering_all_classes(); +} + QString FilterWidget::get_filter() const { const FilterWidgetTab *current_tab = dynamic_cast(ui->tab_widget->currentWidget()); diff --git a/src/admc/filter_widget/filter_widget.h b/src/admc/filter_widget/filter_widget.h index 6aee1b36..a6438b8c 100644 --- a/src/admc/filter_widget/filter_widget.h +++ b/src/admc/filter_widget/filter_widget.h @@ -52,6 +52,8 @@ public: void restore_state(const QVariant &state); void clear(); + + void enable_filtering_all_classes(); }; class FilterWidgetTab : public QWidget { diff --git a/src/admc/filter_widget/filter_widget_normal_tab.cpp b/src/admc/filter_widget/filter_widget_normal_tab.cpp index d66c7f6c..6ab6b8a0 100644 --- a/src/admc/filter_widget/filter_widget_normal_tab.cpp +++ b/src/admc/filter_widget/filter_widget_normal_tab.cpp @@ -65,6 +65,10 @@ void FilterWidgetNormalTab::set_classes(const QList &class_list, const } +void FilterWidgetNormalTab::enable_filtering_all_classes() { + ui->select_classes_widget->enable_filtering_all_classes(); +} + QString FilterWidgetNormalTab::get_filter() const { const QString attribute_filter = [this]() { QList filters; diff --git a/src/admc/filter_widget/filter_widget_normal_tab.h b/src/admc/filter_widget/filter_widget_normal_tab.h index 1733fb68..0e6d3c7a 100644 --- a/src/admc/filter_widget/filter_widget_normal_tab.h +++ b/src/admc/filter_widget/filter_widget_normal_tab.h @@ -46,6 +46,7 @@ public: ~FilterWidgetNormalTab(); void set_classes(const QList &class_list, const QList &selected_list); + void enable_filtering_all_classes(); QString get_filter() const; void clear(); diff --git a/src/admc/filter_widget/filter_widget_simple_tab.cpp b/src/admc/filter_widget/filter_widget_simple_tab.cpp index bf7011c9..346ed481 100644 --- a/src/admc/filter_widget/filter_widget_simple_tab.cpp +++ b/src/admc/filter_widget/filter_widget_simple_tab.cpp @@ -37,6 +37,10 @@ void FilterWidgetSimpleTab::set_classes(const QList &class_list, const ui->select_classes_widget->set_classes(class_list, selected_list); } +void FilterWidgetSimpleTab::enable_filtering_all_classes() { + ui->select_classes_widget->enable_filtering_all_classes(); +} + QString FilterWidgetSimpleTab::get_filter() const { const QString name_filter = [this]() { const QString name = ui->name_edit->text(); diff --git a/src/admc/filter_widget/filter_widget_simple_tab.h b/src/admc/filter_widget/filter_widget_simple_tab.h index 69053fe3..0ca3ef06 100644 --- a/src/admc/filter_widget/filter_widget_simple_tab.h +++ b/src/admc/filter_widget/filter_widget_simple_tab.h @@ -41,6 +41,7 @@ public: ~FilterWidgetSimpleTab(); void set_classes(const QList &class_list, const QList &selected_list); + void enable_filtering_all_classes(); QString get_filter() const; void clear(); diff --git a/src/admc/filter_widget/select_base_widget.cpp b/src/admc/filter_widget/select_base_widget.cpp index 15bcca10..826a291b 100644 --- a/src/admc/filter_widget/select_base_widget.cpp +++ b/src/admc/filter_widget/select_base_widget.cpp @@ -23,6 +23,7 @@ #include "adldap.h" #include "globals.h" +#include "utils.h" #include "select_container_dialog.h" SelectBaseWidget::SelectBaseWidget(QWidget *parent) @@ -30,7 +31,7 @@ SelectBaseWidget::SelectBaseWidget(QWidget *parent) ui = new Ui::SelectBaseWidget(); ui->setupUi(this); - const QString domain_dn = g_adconfig->domain_head(); + const QString domain_dn = g_adconfig->domain_dn(); const QString domain_name = dn_get_name(domain_dn); ui->combo->addItem(domain_name, domain_dn); @@ -60,20 +61,33 @@ QString SelectBaseWidget::get_base() const { } void SelectBaseWidget::open_browse_dialog() { - auto browse_dialog = new SelectContainerDialog(this); + AdInterface ad; + if (ad_failed(ad, this)) { + return; + } + + auto browse_dialog = new SelectContainerDialog(ad, this); browse_dialog->open(); connect( browse_dialog, &QDialog::accepted, + this, [this, browse_dialog]() { const QString selected = browse_dialog->get_selected(); const QString name = dn_get_name(selected); - ui->combo->addItem(name, selected); + const int added_base_index = ui->combo->findText(name); + const bool base_already_added = (added_base_index != -1); - // Select newly added search base in combobox - const int new_base_index = ui->combo->count() - 1; - ui->combo->setCurrentIndex(new_base_index); + if (base_already_added) { + ui->combo->setCurrentIndex(added_base_index); + } else { + ui->combo->addItem(name, selected); + + // Select newly added search base in combobox + const int new_base_index = ui->combo->count() - 1; + ui->combo->setCurrentIndex(new_base_index); + } }); } diff --git a/src/admc/filter_widget/select_classes_widget.cpp b/src/admc/filter_widget/select_classes_widget.cpp index 11e6b228..6da3c123 100644 --- a/src/admc/filter_widget/select_classes_widget.cpp +++ b/src/admc/filter_widget/select_classes_widget.cpp @@ -23,6 +23,7 @@ #include "adldap.h" #include "globals.h" +#include "utils.h" #include "filter_widget/class_filter_dialog.h" SelectClassesWidget::SelectClassesWidget(QWidget *parent) @@ -30,6 +31,9 @@ SelectClassesWidget::SelectClassesWidget(QWidget *parent) ui = new Ui::SelectClassesWidget(); ui->setupUi(this); + filtering_all_classes_is_enabled = false; + m_all_is_checked = false; + connect( ui->select_button, &QAbstractButton::clicked, this, &SelectClassesWidget::open_dialog); @@ -42,20 +46,31 @@ SelectClassesWidget::~SelectClassesWidget() { void SelectClassesWidget::set_classes(const QList &class_list_arg, const QList &selected_list_arg) { class_list = class_list_arg; - set_selected_list(selected_list_arg); + m_selected_list = selected_list_arg; + update_class_display(); +} + +void SelectClassesWidget::enable_filtering_all_classes() { + filtering_all_classes_is_enabled = true; } QString SelectClassesWidget::get_filter() const { - return filter; + if (m_all_is_checked) { + return QString(); + } else { + const QString out = get_classes_filter(m_selected_list); + + return out; + } } QVariant SelectClassesWidget::save_state() const { QHash state; - state["dialog_state"] = dialog_state; + const QList selected_list_variant = string_list_to_variant_list(m_selected_list); + state["selected_list"] = selected_list_variant; - const QString classes_display_text = ui->classes_display->text(); - state["classes_display_text"] = classes_display_text; + state["m_all_is_checked"] = m_all_is_checked; return state; } @@ -64,48 +79,52 @@ void SelectClassesWidget::restore_state(const QVariant &state_variant) { QHash state = state_variant.toHash(); const QList saved_selected_list_variant = state["selected_list"].toList(); - QList saved_selected_list; - for (const QVariant &v : saved_selected_list_variant) { - saved_selected_list.append(v.toString()); - } + const QList saved_selected_list = variant_list_to_string_list(saved_selected_list_variant); - set_selected_list(saved_selected_list); + m_selected_list = saved_selected_list; + + m_all_is_checked = state["m_all_is_checked"].toBool(); + + update_class_display(); } void SelectClassesWidget::open_dialog() { - auto dialog = new ClassFilterDialog(this); - dialog->set_classes(class_list, selected_list); + auto dialog = new ClassFilterDialog(class_list, m_selected_list, filtering_all_classes_is_enabled, m_all_is_checked, this); dialog->open(); connect( dialog, &QDialog::accepted, + this, [this, dialog]() { const QList new_selected_list = dialog->get_selected_classes(); - set_selected_list(new_selected_list); - - filter = dialog->get_filter(); + m_selected_list = new_selected_list; + m_all_is_checked = dialog->get_all_is_checked(); + update_class_display(); }); } -void SelectClassesWidget::set_selected_list(const QList &new_selected_list) { - selected_list = new_selected_list; - +// Use current state to generate class display string +void SelectClassesWidget::update_class_display() { // Convert class list to list of class display strings, // then sort it and finally join by comma's const QString display_string = [&]() { - QList class_display_list; + if (m_all_is_checked) { + return tr("All"); + } else { + QList class_display_list; - for (const QString &object_class : selected_list) { - const QString class_display = g_adconfig->get_class_display_name(object_class); + for (const QString &object_class : m_selected_list) { + const QString class_display = g_adconfig->get_class_display_name(object_class); - class_display_list.append(class_display); + class_display_list.append(class_display); + } + + std::sort(class_display_list.begin(), class_display_list.end()); + + const QString joined = class_display_list.join(", "); + + return joined; } - - std::sort(class_display_list.begin(), class_display_list.end()); - - const QString joined = class_display_list.join(", "); - - return joined; }(); ui->classes_display->setText(display_string); diff --git a/src/admc/filter_widget/select_classes_widget.h b/src/admc/filter_widget/select_classes_widget.h index 802727c5..d62175de 100644 --- a/src/admc/filter_widget/select_classes_widget.h +++ b/src/admc/filter_widget/select_classes_widget.h @@ -45,6 +45,7 @@ public: ~SelectClassesWidget(); void set_classes(const QList &class_list, const QList &selected_list); + void enable_filtering_all_classes(); QString get_filter() const; @@ -53,12 +54,12 @@ public: private: QList class_list; - QList selected_list; - QString filter; - QVariant dialog_state; + QList m_selected_list; + bool filtering_all_classes_is_enabled; + bool m_all_is_checked; void open_dialog(); - void set_selected_list(const QList &new_selected_list); + void update_class_display(); }; #endif /* SELECT_CLASSES_WIDGET_H */ diff --git a/src/admc/find_object_dialog.cpp b/src/admc/find_object_dialog.cpp index 3960e90c..d0d19803 100644 --- a/src/admc/find_object_dialog.cpp +++ b/src/admc/find_object_dialog.cpp @@ -29,7 +29,7 @@ #include -FindObjectDialog::FindObjectDialog(const QString &default_base, QWidget *parent) +FindObjectDialog::FindObjectDialog(ConsoleWidget *buddy_console, const QString &default_base, QWidget *parent) : QDialog(parent) { ui = new Ui::FindObjectDialog(); ui->setupUi(this); @@ -48,10 +48,12 @@ FindObjectDialog::FindObjectDialog(const QString &default_base, QWidget *parent) CLASS_GROUP, }; + ui->find_widget->set_buddy_console(buddy_console); ui->find_widget->set_classes(class_list, selected_list); ui->find_widget->set_default_base(default_base); ui->find_widget->setup_action_menu(action_menu); ui->find_widget->setup_view_menu(view_menu); + ui->find_widget->enable_filtering_all_classes(); settings_setup_dialog_geometry(SETTING_find_object_dialog_geometry, this); @@ -65,7 +67,3 @@ FindObjectDialog::~FindObjectDialog() { delete ui; } - -void FindObjectDialog::set_buddy_console(ConsoleWidget *buddy_console) { - ui->find_widget->set_buddy_console(buddy_console); -} diff --git a/src/admc/find_object_dialog.h b/src/admc/find_object_dialog.h index d7129fc0..3fb115e2 100644 --- a/src/admc/find_object_dialog.h +++ b/src/admc/find_object_dialog.h @@ -39,10 +39,8 @@ class FindObjectDialog final : public QDialog { public: Ui::FindObjectDialog *ui; - FindObjectDialog(const QString &default_base, QWidget *parent); + FindObjectDialog(ConsoleWidget *buddy_console, const QString &default_base, QWidget *parent); ~FindObjectDialog(); - - void set_buddy_console(ConsoleWidget *buddy_console); }; #endif /* FIND_OBJECT_DIALOG_H */ diff --git a/src/admc/find_widget.cpp b/src/admc/find_widget.cpp index 50a35118..6e08a84f 100644 --- a/src/admc/find_widget.cpp +++ b/src/admc/find_widget.cpp @@ -24,11 +24,11 @@ #include "adldap.h" #include "console_impls/item_type.h" #include "console_impls/object_impl.h" -#include "console_impls/query_folder_impl.h" -#include "console_impls/query_item_impl.h" +#include "console_impls/find_root_impl.h" #include "console_widget/results_view.h" #include "search_thread.h" #include "settings.h" +#include "status.h" #include "utils.h" #include "globals.h" @@ -40,14 +40,14 @@ FindWidget::FindWidget(QWidget *parent) ui = new Ui::FindWidget(); ui->setupUi(this); - action_view_icons = new QAction(tr("Icons")); + action_view_icons = new QAction(tr("&Icons"), this); action_view_icons->setCheckable(true); - action_view_list = new QAction(tr("List")); + action_view_list = new QAction(tr("&List"), this); action_view_list->setCheckable(true); - action_view_detail = new QAction(tr("Detail")); + action_view_detail = new QAction(tr("&Detail"), this); action_view_detail->setCheckable(true); - action_customize_columns = new QAction(tr("Customize Columns")); - action_toggle_description_bar = new QAction(tr("Description Bar")); + action_customize_columns = new QAction(tr("&Customize Columns"), this); + action_toggle_description_bar = new QAction(tr("&Description Bar"), this); action_toggle_description_bar->setCheckable(true); const ConsoleWidgetActions console_actions = [&]() { @@ -57,14 +57,14 @@ FindWidget::FindWidget(QWidget *parent) out.view_list = action_view_list; out.view_detail = action_view_detail; out.toggle_description_bar = action_toggle_description_bar; + out.customize_columns = action_customize_columns; // Use placeholders for unused actions - out.navigate_up = new QAction(); - out.navigate_back = new QAction(); - out.navigate_forward = new QAction(); - out.refresh = new QAction(); - out.customize_columns = new QAction(); - out.toggle_console_tree = new QAction(); + out.navigate_up = new QAction(this); + out.navigate_back = new QAction(this); + out.navigate_forward = new QAction(this); + out.refresh = new QAction(this); + out.toggle_console_tree = new QAction(this); return out; }(); @@ -77,22 +77,13 @@ FindWidget::FindWidget(QWidget *parent) object_impl->set_find_action_enabled(false); object_impl->set_refresh_action_enabled(false); - // NOTE: registering impl so that it supplies text to - // the description bar - auto query_item_impl = new QueryItemImpl(ui->console); - ui->console->register_impl(ItemType_QueryItem, query_item_impl); + auto find_root_impl = new FindRootImpl(ui->console); + ui->console->register_impl(ItemType_FindRoot, find_root_impl); - auto query_folder_impl = new QueryFolderImpl(ui->console); - ui->console->register_impl(ItemType_QueryFolder, query_folder_impl); + ResultsView *find_root_view = find_root_impl->view(); + find_root_view->set_drag_drop_enabled(false); - ResultsView *query_results = query_item_impl->view(); - query_results->set_drag_drop_enabled(false); - - const QList root_row = ui->console->add_scope_item(ItemType_QueryFolder, QModelIndex()); - QStandardItem *root_item = root_row[0]; - root_item->setData(true, QueryItemRole_IsRoot); - - const QList row = ui->console->add_scope_item(ItemType_QueryItem, root_item->index()); + const QList row = ui->console->add_scope_item(ItemType_FindRoot, QModelIndex()); head_item = row[0]; head_item->setText(tr("Find results")); @@ -113,6 +104,7 @@ FindWidget::FindWidget(QWidget *parent) // indicator stays on. connect( this, &QObject::destroyed, + this, []() { hide_busy_indicator(); }); @@ -126,6 +118,10 @@ void FindWidget::set_classes(const QList &class_list, const QListfilter_widget->set_classes(class_list, selected_list); } +void FindWidget::enable_filtering_all_classes() { + ui->filter_widget->enable_filtering_all_classes(); +} + void FindWidget::set_default_base(const QString &default_base) { ui->select_base_widget->set_default_base(default_base); } @@ -145,11 +141,7 @@ void FindWidget::restore_console_state(const QVariant &state) { } void FindWidget::setup_action_menu(QMenu *menu) { - ui->console->add_actions(menu); - - connect( - menu, &QMenu::aboutToShow, - ui->console, &ConsoleWidget::update_actions); + ui->console->setup_menubar_action_menu(menu); } void FindWidget::setup_view_menu(QMenu *menu) { @@ -187,14 +179,15 @@ void FindWidget::find() { find_thread, &SearchThread::finished, this, [this, find_thread]() { - if (find_thread->failed_to_connect()) { - search_thread_error_log(this); - } - + g_status->display_ad_messages(find_thread->get_ad_messages(), this); + search_thread_display_errors(find_thread, this); + ui->find_button->setEnabled(true); ui->clear_button->setEnabled(true); hide_busy_indicator(); + + find_thread->deleteLater(); }); show_busy_indicator(); @@ -221,7 +214,8 @@ void FindWidget::handle_find_thread_results(const QHash &resu } QList FindWidget::get_selected_dns() const { - const QList out = get_selected_dn_list(ui->console, ItemType_Object, ObjectRole_DN); + const QList indexes = ui->console->get_selected_items(ItemType_Object); + const QList out = index_list_to_dn_list(indexes, ObjectRole_DN); return out; } diff --git a/src/admc/find_widget.h b/src/admc/find_widget.h index 5ba517e6..8a712495 100644 --- a/src/admc/find_widget.h +++ b/src/admc/find_widget.h @@ -64,6 +64,8 @@ public: // NOTE: returned items need to be re-parented or deleted! QList get_selected_dns() const; + void enable_filtering_all_classes(); + private slots: void find(); void handle_find_thread_results(const QHash &results); diff --git a/src/admc/fsmo_dialog.cpp b/src/admc/fsmo_dialog.cpp new file mode 100644 index 00000000..f217efdc --- /dev/null +++ b/src/admc/fsmo_dialog.cpp @@ -0,0 +1,97 @@ +/* + * 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 "fsmo_dialog.h" +#include "ui_fsmo_dialog.h" + +#include "fsmo_tab.h" +#include "settings.h" +#include "adldap.h" +#include "globals.h" + +enum FSMORole { + FSMORole_DomainDNS, + FSMORole_ForestDNS, + FSMORole_PDCEmulation, + FSMORole_Schema, + FSMORole_DomainNaming, + FSMORole_Infrastructure, + FSMORole_RidAllocation, + + FSMORole_COUNT, +}; + +FSMODialog::FSMODialog(AdInterface &ad, QWidget *parent) +: QDialog(parent) { + ui = new Ui::FSMODialog(); + ui->setupUi(this); + + setAttribute(Qt::WA_DeleteOnClose); + + for (int role_i = 0; role_i < FSMORole_COUNT; role_i++) { + const FSMORole role = (FSMORole) role_i; + + const QString title = [&]() { + switch (role) { + case FSMORole_DomainDNS: return tr("Domain DNS"); + case FSMORole_ForestDNS: return tr("Forest DNS"); + case FSMORole_PDCEmulation: return tr("PDC Emulation"); + case FSMORole_Schema: return tr("Schema"); + case FSMORole_DomainNaming: return tr("Domain Naming"); + case FSMORole_Infrastructure: return tr("Infrastructure"); + case FSMORole_RidAllocation: return tr("Rid Allocation"); + + case FSMORole_COUNT: break; + }; + + return QString(); + }(); + + // NOTE: this is the DN of the object that + // store's role's master in it's attributes + const QString role_dn = [&]() { + const QString domain_dn = g_adconfig->domain_dn(); + + switch (role) { + case FSMORole_DomainDNS: return QString("CN=Infrastructure,DC=DomainDnsZones,%1").arg(domain_dn); + case FSMORole_ForestDNS: return QString("CN=Infrastructure,DC=ForestDnsZones,%1").arg(domain_dn); + case FSMORole_PDCEmulation: return domain_dn; + case FSMORole_Schema: return g_adconfig->schema_dn(); + case FSMORole_DomainNaming: return g_adconfig->partitions_dn(); + case FSMORole_Infrastructure: return QString("CN=Infrastructure,%1").arg(domain_dn); + case FSMORole_RidAllocation: return QString("CN=RID Manager$,CN=System,%1").arg(domain_dn); + + case FSMORole_COUNT: break; + }; + + return QString(); + }(); + + auto tab = new FSMOTab(title, role_dn); + ui->tab_widget->add_tab(tab, title); + tab->load(ad); + } + + settings_setup_dialog_geometry(SETTING_fsmo_dialog_geometry, this); +} + +FSMODialog::~FSMODialog() { + delete ui; +} diff --git a/src/admc/multi_tabs/properties_multi_tab.h b/src/admc/fsmo_dialog.h similarity index 65% rename from src/admc/multi_tabs/properties_multi_tab.h rename to src/admc/fsmo_dialog.h index e24ccf00..c95ed28d 100644 --- a/src/admc/multi_tabs/properties_multi_tab.h +++ b/src/admc/fsmo_dialog.h @@ -18,32 +18,29 @@ * along with this program. If not, see . */ -#ifndef PROPERTIES_MULTI_TAB_H -#define PROPERTIES_MULTI_TAB_H - -#include - -class AdInterface; -class AttributeMultiEdit; +#ifndef FSMO_DIALOG_H +#define FSMO_DIALOG_H /** - * Base class for properties multi tabs. + * Dialog for viewing and editing FSMO roles. */ -class PropertiesMultiTab : public QWidget { +#include + +class AdInterface; + +namespace Ui { +class FSMODialog; +} + +class FSMODialog : public QDialog { Q_OBJECT public: - virtual bool apply(AdInterface &ad, const QList &target_list); - virtual void reset(); + Ui::FSMODialog *ui; - void on_edit_edited(); - -signals: - void edited(); - -protected: - QList edit_list; + FSMODialog(AdInterface &ad, QWidget *parent); + ~FSMODialog(); }; -#endif /* PROPERTIES_MULTI_TAB_H */ +#endif /* FSMO_DIALOG_H */ diff --git a/src/admc/fsmo_dialog.ui b/src/admc/fsmo_dialog.ui new file mode 100644 index 00000000..67b17ee1 --- /dev/null +++ b/src/admc/fsmo_dialog.ui @@ -0,0 +1,75 @@ + + + FSMODialog + + + + 0 + 0 + 400 + 300 + + + + Operations Masters + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + TabWidget + QWidget +
tab_widget.h
+ 1 +
+
+ + + + buttonBox + accepted() + FSMODialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + FSMODialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + +
diff --git a/src/admc/fsmo_tab.cpp b/src/admc/fsmo_tab.cpp new file mode 100644 index 00000000..c13b8ce9 --- /dev/null +++ b/src/admc/fsmo_tab.cpp @@ -0,0 +1,98 @@ +/* + * 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 "fsmo_tab.h" +#include "ui_fsmo_tab.h" + +#include "adldap.h" +#include "utils.h" +#include "globals.h" +#include "status.h" + +FSMOTab::FSMOTab(const QString &title, const QString &role_dn_arg) { + ui = new Ui::FSMOTab(); + ui->setupUi(this); + + ui->title_label->setText(title); + + role_dn = role_dn_arg; + + connect( + ui->change_button, &QPushButton::clicked, + this, &FSMOTab::change_master); +} + +FSMOTab::~FSMOTab() { + delete ui; +} + +void FSMOTab::load(AdInterface &ad) { + const QString current_master = [&]() { + const AdObject role_object = ad.search_object(role_dn); + const QString master_settings_dn = role_object.get_string(ATTRIBUTE_FSMO_ROLE_OWNER); + const QString master_dn = dn_get_parent(master_settings_dn); + const AdObject master_object = ad.search_object(master_dn); + const QString out = master_object.get_string(ATTRIBUTE_DNS_HOST_NAME); + + return out; + }(); + + const QString new_master = [&]() { + const AdObject rootDSE = ad.search_object(""); + const QString server_name = rootDSE.get_string(ATTRIBUTE_SERVER_NAME); + const AdObject server = ad.search_object(server_name); + const QString out = server.get_string(ATTRIBUTE_DNS_HOST_NAME); + + return out; + }(); + + ui->current_edit->setText(current_master); + ui->new_edit->setText(new_master); +} + +void FSMOTab::change_master() { + AdInterface ad; + if (ad_failed(ad, this)) { + return; + } + + const QString current_master = ui->current_edit->text(); + const QString new_master = ui->new_edit->text(); + + if (current_master == new_master) { + message_box_warning(this, tr("Error"), tr("This machine is already a master for this role. Switch to a different machine in Connection Options to change master.")); + return; + } + + const QString new_master_service = [&]() { + const AdObject rootDSE = ad.search_object(""); + const QString out = rootDSE.get_string(ATTRIBUTE_DS_SERVICE_NAME); + + return out; + }(); + + const bool success = ad.attribute_replace_string(role_dn, ATTRIBUTE_FSMO_ROLE_OWNER, new_master_service); + + g_status->display_ad_messages(ad, this); + + if (success) { + load(ad); + } +} diff --git a/src/admc/widget_state.h b/src/admc/fsmo_tab.h similarity index 66% rename from src/admc/widget_state.h rename to src/admc/fsmo_tab.h index dc0a278e..7630751d 100644 --- a/src/admc/widget_state.h +++ b/src/admc/fsmo_tab.h @@ -18,29 +18,32 @@ * along with this program. If not, see . */ -#ifndef WIDGET_STATE_H -#define WIDGET_STATE_H +#ifndef FSMO_TAB_H +#define FSMO_TAB_H -/** - * Saves/restores state of all the widgets in the list. - */ +#include -class WidgetStatePrivate; -template -class QList; -class QWidget; +class AdInterface; + +namespace Ui { +class FSMOTab; +} + +class FSMOTab final : public QWidget { + Q_OBJECT -class WidgetState final { public: - WidgetState(); - ~WidgetState(); + Ui::FSMOTab *ui; - void set_widget_list(const QList &widget_list); - void save(); - void restore(); + FSMOTab(const QString &title, const QString &role_dn); + ~FSMOTab(); + + void load(AdInterface &ad); private: - WidgetStatePrivate *d; + QString role_dn; + + void change_master(); }; -#endif /* WIDGET_STATE_H */ +#endif /* FSMO_TAB_H */ diff --git a/src/admc/fsmo_tab.ui b/src/admc/fsmo_tab.ui new file mode 100644 index 00000000..9844e954 --- /dev/null +++ b/src/admc/fsmo_tab.ui @@ -0,0 +1,93 @@ + + + FSMOTab + + + + 0 + 0 + 400 + 300 + + + + Form + + + + + + PLACEHOLDER + + + + + + + Current master: + + + + + + + true + + + + + + + Change to: + + + + + + + true + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Change + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/admc/main.cpp b/src/admc/main.cpp index f6e896ee..04f86edc 100644 --- a/src/admc/main.cpp +++ b/src/admc/main.cpp @@ -24,6 +24,7 @@ #include "main_window.h" #include "main_window_connection_error.h" #include "settings.h" +#include "status.h" #include "utils.h" #include "connection_options_dialog.h" @@ -47,6 +48,7 @@ int main(int argc, char **argv) { app.setApplicationVersion(ADMC_VERSION); app.setOrganizationName(ADMC_ORGANIZATION); app.setOrganizationDomain(ADMC_ORGANIZATION_DOMAIN); + app.setWindowIcon(QIcon(":/admc/admc.svg")); const QLocale saved_locale = settings_get_variant(SETTING_locale).toLocale(); @@ -68,13 +70,21 @@ int main(int argc, char **argv) { // NOTE: these translations are for qt-defined text, like standard dialog buttons QTranslator qt_translator; - const bool loaded_qt_translation = qt_translator.load("qt_" + saved_locale.name(), QLibraryInfo::location(QLibraryInfo::TranslationsPath)); + const bool loaded_qt_translation = qt_translator.load(saved_locale, "qt", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath)); app.installTranslator(&qt_translator); if (!loaded_qt_translation) { qDebug() << "Failed to load qt translation"; } + QTranslator qtbase_translator; + const bool loaded_qtbase_translation = qtbase_translator.load(saved_locale, "qtbase", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath)); + app.installTranslator(&qtbase_translator); + + if (!loaded_qtbase_translation) { + qDebug() << "Failed to load qt base translation"; + } + load_connection_options(); // In case of failure to connect to AD and load @@ -89,19 +99,31 @@ int main(int argc, char **argv) { // is needed because many child widgets used by // MainWindow require adconfig to load their UI // elements. - QMainWindow *first_main_window = [&]() -> QMainWindow * { + // + // Control flow here is awkward for multiple + // reasons. First, the ad error log has to be + // displayed *after* main window is shown and + // parented to it to be modal. Secondly, we need + // adinterface to stay in this scope so that it is + // destroyed when scope is over. If it's created + // outside the scope, then it will stay alive for + // the whole duration of the app which is bad. + QMainWindow *first_main_window = nullptr; + { AdInterface ad; - if (ad_connected(ad)) { + if (ad.is_connected()) { load_g_adconfig(ad); - return new MainWindow(ad); + first_main_window = new MainWindow(ad); + first_main_window->show(); } else { - return new MainWindowConnectionError(); - } - }(); + first_main_window = new MainWindowConnectionError(); + first_main_window->show(); - first_main_window->show(); + ad_error_log(ad, first_main_window); + } + } const int retval = app.exec(); diff --git a/src/admc/main_window.cpp b/src/admc/main_window.cpp index 14a9e63d..1d95c8ec 100644 --- a/src/admc/main_window.cpp +++ b/src/admc/main_window.cpp @@ -39,6 +39,7 @@ #include "settings.h" #include "status.h" #include "utils.h" +#include "fsmo_dialog.h" #include #include @@ -103,11 +104,7 @@ MainWindow::MainWindow(AdInterface &ad, QWidget *parent) // NOTE: "Action" menu actions need to be filled by the // console - ui->console->add_actions(ui->menu_action); - -#ifndef QT_DEBUG - ui->action_dev_mode->setVisible(false); -#endif + ui->console->setup_menubar_action_menu(ui->menu_action); // NOTE: toolbar and message log(dock widget) have built // in toggle actions, but there's no way to add them @@ -193,7 +190,7 @@ MainWindow::MainWindow(AdInterface &ad, QWidget *parent) const bool is_checked = [=]() { const QLocale current_locale = settings_get_variant(SETTING_locale).toLocale(); - return (current_locale == locale); + return (current_locale.language() == locale.language()); }(); action->setChecked(is_checked); @@ -201,6 +198,7 @@ MainWindow::MainWindow(AdInterface &ad, QWidget *parent) connect( action, &QAction::triggered, + this, [this, language](bool checked) { if (checked) { settings_set_variant(SETTING_locale, QLocale(language)); @@ -216,6 +214,9 @@ MainWindow::MainWindow(AdInterface &ad, QWidget *parent) connect( ui->action_connection_options, &QAction::triggered, this, &MainWindow::open_connection_options); + connect( + ui->action_edit_fsmo_roles, &QAction::triggered, + this, &MainWindow::edit_fsmo_roles); connect( ui->action_quit, &QAction::triggered, this, &MainWindow::close); @@ -231,9 +232,6 @@ MainWindow::MainWindow(AdInterface &ad, QWidget *parent) connect( ui->action_filter_objects, &QAction::triggered, object_impl, &ObjectImpl::open_console_filter_dialog); - connect( - ui->menu_action, &QMenu::aboutToShow, - ui->console, &ConsoleWidget::update_actions); const QHash bool_action_map = { {SETTING_confirm_actions, ui->action_confirm_actions}, @@ -243,7 +241,6 @@ MainWindow::MainWindow(AdInterface &ad, QWidget *parent) {SETTING_show_login, ui->action_show_login}, {SETTING_show_non_containers_in_console_tree, ui->action_show_noncontainers}, {SETTING_advanced_features, ui->action_advanced_features}, - {SETTING_dev_mode, ui->action_dev_mode}, }; const QList simple_setting_list = { @@ -257,7 +254,7 @@ MainWindow::MainWindow(AdInterface &ad, QWidget *parent) for (const QString &setting : bool_action_map.keys()) { QAction *action = bool_action_map[setting]; - const bool setting_enabled = settings_get_bool(setting); + const bool setting_enabled = settings_get_variant(setting).toBool(); action->setChecked(setting_enabled); } @@ -268,8 +265,9 @@ MainWindow::MainWindow(AdInterface &ad, QWidget *parent) connect( action, &QAction::toggled, + this, [setting](bool checked) { - settings_set_bool(setting, checked); + settings_set_variant(setting, checked); }); } @@ -280,7 +278,6 @@ MainWindow::MainWindow(AdInterface &ad, QWidget *parent) const QList complex_setting_list = { SETTING_show_non_containers_in_console_tree, SETTING_advanced_features, - SETTING_dev_mode, }; for (const QString &setting : complex_setting_list) { @@ -288,8 +285,9 @@ MainWindow::MainWindow(AdInterface &ad, QWidget *parent) connect( action, &QAction::toggled, + this, [setting, object_impl](bool checked) { - settings_set_bool(setting, checked); + settings_set_variant(setting, checked); object_impl->refresh_tree(); }); @@ -352,3 +350,13 @@ void MainWindow::open_about() { auto about_dialog = new AboutDialog(this); about_dialog->open(); } + +void MainWindow::edit_fsmo_roles() { + AdInterface ad; + if (ad_failed(ad, this)) { + return; + } + + auto dialog = new FSMODialog(ad, this); + dialog->open(); +} diff --git a/src/admc/main_window.h b/src/admc/main_window.h index b479e6fb..4faf8bbd 100644 --- a/src/admc/main_window.h +++ b/src/admc/main_window.h @@ -51,6 +51,7 @@ private: void open_connection_options(); void open_changelog(); void open_about(); + void edit_fsmo_roles(); }; #endif /* MAIN_WINDOW_H */ diff --git a/src/admc/main_window.ui b/src/admc/main_window.ui index fe79fc81..2e9bbf0d 100644 --- a/src/admc/main_window.ui +++ b/src/admc/main_window.ui @@ -25,19 +25,22 @@ - File + &File + + + - Action + &Action - View + &View @@ -53,11 +56,11 @@ - Preferences + &Preferences - Language + &Language @@ -67,11 +70,10 @@ - - Help + &Help @@ -120,12 +122,12 @@ - Connection Options + &Connection Options - Quit + &Quit Ctrl+Q @@ -137,17 +139,23 @@ .. - Manual + &Manual + + + Manual (Alt + 8) + + + Alt+8 - Changelog + &Changelog - About ADMC + &About ADMC @@ -155,7 +163,7 @@ true - Icons + &Icons @@ -163,7 +171,7 @@ true - List + &List @@ -171,7 +179,7 @@ true - Detail + &Detail @@ -195,7 +203,7 @@ true - Console Tree + &Console Tree @@ -203,17 +211,17 @@ true - Description Bar + Description &Bar - Customize Columns... + C&ustomize Columns... - Filter Objects... + &Filter Objects... @@ -221,7 +229,7 @@ true - Advanced Features + &Advanced Features @@ -229,7 +237,7 @@ true - Confirm Actions + &Confirm Actions @@ -237,7 +245,7 @@ true - Put Last Name Before First Name + &Put Last Name Before First Name @@ -245,7 +253,7 @@ true - Log Searches + &Log Searches @@ -253,7 +261,7 @@ true - Timestamps in Message Log + &Timestamps in Message Log @@ -261,7 +269,7 @@ true - Show Non-Container Objects in Console Tree + &Show Non-Container Objects in Console Tree @@ -284,6 +292,9 @@ Navigate Back + + Navigate Back (Alt + -) + Alt+- @@ -296,6 +307,9 @@ Navigate Forward + + Navigate Forward (Alt + =) + Alt+= @@ -308,10 +322,11 @@ Refresh - - - - Dev mode + + Refresh (Alt + 9) + + + Alt+9 @@ -322,6 +337,11 @@ Show Login + + + &Operations Masters + +
diff --git a/src/admc/main_window_connection_error.cpp b/src/admc/main_window_connection_error.cpp index 9429b19d..ac164fe5 100644 --- a/src/admc/main_window_connection_error.cpp +++ b/src/admc/main_window_connection_error.cpp @@ -53,7 +53,7 @@ MainWindowConnectionError::~MainWindowConnectionError() { void MainWindowConnectionError::on_retry_button() { AdInterface ad; - if (ad_connected(ad)) { + if (ad_connected(ad, this)) { load_g_adconfig(ad); MainWindow *real_main_window = new MainWindow(ad, this); diff --git a/src/admc/main_window_connection_error.ui b/src/admc/main_window_connection_error.ui index 044593b8..49e818e3 100644 --- a/src/admc/main_window_connection_error.ui +++ b/src/admc/main_window_connection_error.ui @@ -11,7 +11,7 @@ - MainWindow + Connection Error diff --git a/src/admc/multi_tabs/account_multi_tab.cpp b/src/admc/multi_tabs/account_multi_tab.cpp index f5616ce8..e843d685 100644 --- a/src/admc/multi_tabs/account_multi_tab.cpp +++ b/src/admc/multi_tabs/account_multi_tab.cpp @@ -22,17 +22,20 @@ #include "multi_tabs/ui_account_multi_tab.h" #include "adldap.h" -#include "attribute_multi_edits/account_option_multi_edit.h" -#include "attribute_multi_edits/expiry_multi_edit.h" -#include "attribute_multi_edits/string_multi_edit.h" -#include "attribute_multi_edits/upn_multi_edit.h" +#include "attribute_edits/account_option_multi_edit.h" +#include "attribute_edits/expiry_edit.h" +#include "attribute_edits/string_edit.h" +#include "attribute_edits/upn_multi_edit.h" -AccountMultiTab::AccountMultiTab(AdInterface &ad) { +#include + +AccountMultiTab::AccountMultiTab(AdInterface &ad, QList *edit_list, QHash *check_map, QWidget *parent) +: QWidget(parent) { ui = new Ui::AccountMultiTab(); ui->setupUi(this); - new UpnMultiEdit(ui->upn_edit, ui->upn_check, edit_list, ad, this); - const QHash check_map = { + auto upn_edit = new UpnMultiEdit(ui->upn_edit, ad, this); + const QHash option_to_check_map = { {AccountOption_Disabled, ui->option_disabled}, {AccountOption_PasswordExpired, ui->option_pass_expired}, {AccountOption_DontExpirePassword, ui->option_dont_expire_pass}, @@ -41,10 +44,18 @@ AccountMultiTab::AccountMultiTab(AdInterface &ad) { {AccountOption_CantDelegate, ui->option_cant_delegate}, {AccountOption_DontRequirePreauth, ui->option_dont_require_kerb}, }; - new AccountOptionMultiEdit(check_map, ui->options_check, edit_list, this); - new ExpiryMultiEdit(ui->expiry_edit, ui->expiry_check, edit_list, this); + auto options_edit = new AccountOptionMultiEdit(option_to_check_map, this); + auto expiry_edit = new ExpiryEdit(ui->expiry_edit, this); - multi_edits_connect_to_tab(edit_list, this); + edit_list->append({ + upn_edit, + options_edit, + expiry_edit, + }); + + check_map->insert(upn_edit, ui->upn_check); + check_map->insert(options_edit, ui->options_check); + check_map->insert(expiry_edit, ui->expiry_check); } AccountMultiTab::~AccountMultiTab() { diff --git a/src/admc/multi_tabs/account_multi_tab.h b/src/admc/multi_tabs/account_multi_tab.h index 4f4bedae..37986e06 100644 --- a/src/admc/multi_tabs/account_multi_tab.h +++ b/src/admc/multi_tabs/account_multi_tab.h @@ -26,19 +26,23 @@ * attributes. */ -#include "multi_tabs/properties_multi_tab.h" +#include + +class AttributeEdit; +class AdInterface; +class QCheckBox; namespace Ui { class AccountMultiTab; } -class AccountMultiTab final : public PropertiesMultiTab { +class AccountMultiTab final : public QWidget { Q_OBJECT public: Ui::AccountMultiTab *ui; - AccountMultiTab(AdInterface &ad); + AccountMultiTab(AdInterface &ad, QList *edit_list, QHash *check_map, QWidget *parent); ~AccountMultiTab(); }; diff --git a/src/admc/multi_tabs/address_multi_tab.cpp b/src/admc/multi_tabs/address_multi_tab.cpp index 9a7e580a..5c2e0ef8 100644 --- a/src/admc/multi_tabs/address_multi_tab.cpp +++ b/src/admc/multi_tabs/address_multi_tab.cpp @@ -21,21 +21,36 @@ #include "multi_tabs/address_multi_tab.h" #include "ui_address_multi_tab.h" -#include "adldap.h" -#include "attribute_multi_edits/country_multi_edit.h" -#include "attribute_multi_edits/string_multi_edit.h" +#include "ad_defines.h" +#include "attribute_edits/country_edit.h" +#include "attribute_edits/string_edit.h" -AddressMultiTab::AddressMultiTab() { +#include + +AddressMultiTab::AddressMultiTab(QList *edit_list, QHash *check_map, QWidget *parent) +: QWidget(parent) { ui = new Ui::AddressMultiTab(); ui->setupUi(this); - new StringMultiEdit(ui->po_edit, ui->po_check, ATTRIBUTE_PO_BOX, edit_list, this); - new StringMultiEdit(ui->city_edit, ui->city_check, ATTRIBUTE_CITY, edit_list, this); - new StringMultiEdit(ui->state_edit, ui->state_check, ATTRIBUTE_STATE, edit_list, this); - new StringMultiEdit(ui->postal_edit, ui->postal_check, ATTRIBUTE_POSTAL_CODE, edit_list, this); - new CountryMultiEdit(ui->country_combo, ui->country_check, edit_list, this); + auto po_edit = new StringEdit(ui->po_edit, ATTRIBUTE_PO_BOX, this); + auto city_edit = new StringEdit(ui->city_edit, ATTRIBUTE_CITY, this); + auto state_edit = new StringEdit(ui->state_edit, ATTRIBUTE_STATE, this); + auto postal_edit = new StringEdit(ui->postal_edit, ATTRIBUTE_POSTAL_CODE, this); + auto country_edit = new CountryEdit(ui->country_combo, this); - multi_edits_connect_to_tab(edit_list, this); + edit_list->append({ + po_edit, + city_edit, + state_edit, + postal_edit, + country_edit, + }); + + check_map->insert(po_edit, ui->po_check); + check_map->insert(city_edit, ui->city_check); + check_map->insert(state_edit, ui->state_check); + check_map->insert(postal_edit, ui->postal_check); + check_map->insert(country_edit, ui->country_check); } AddressMultiTab::~AddressMultiTab() { diff --git a/src/admc/multi_tabs/address_multi_tab.h b/src/admc/multi_tabs/address_multi_tab.h index 5ed0d058..acbd94aa 100644 --- a/src/admc/multi_tabs/address_multi_tab.h +++ b/src/admc/multi_tabs/address_multi_tab.h @@ -26,19 +26,22 @@ * attributes. */ -#include "multi_tabs/properties_multi_tab.h" +#include + +class AttributeEdit; +class QCheckBox; namespace Ui { class AddressMultiTab; } -class AddressMultiTab final : public PropertiesMultiTab { +class AddressMultiTab final : public QWidget { Q_OBJECT public: Ui::AddressMultiTab *ui; - AddressMultiTab(); + AddressMultiTab(QList *edit_list, QHash *check_map, QWidget *parent); ~AddressMultiTab(); }; diff --git a/src/admc/multi_tabs/general_other_multi_tab.cpp b/src/admc/multi_tabs/general_other_multi_tab.cpp index c3bf3485..aea64354 100644 --- a/src/admc/multi_tabs/general_other_multi_tab.cpp +++ b/src/admc/multi_tabs/general_other_multi_tab.cpp @@ -21,14 +21,21 @@ #include "multi_tabs/general_other_multi_tab.h" #include "multi_tabs/ui_general_other_multi_tab.h" -#include "adldap.h" -#include "attribute_multi_edits/string_multi_edit.h" +#include "ad_defines.h" +#include "attribute_edits/string_edit.h" -GeneralOtherMultiTab::GeneralOtherMultiTab() { +#include + +GeneralOtherMultiTab::GeneralOtherMultiTab(QList *edit_list, QHash *check_map, QWidget *parent) +: QWidget(parent) { ui = new Ui::GeneralOtherMultiTab(); ui->setupUi(this); - new StringMultiEdit(ui->description_edit, ui->description_check, ATTRIBUTE_DESCRIPTION, edit_list, this); + auto description_edit = new StringEdit(ui->description_edit, ATTRIBUTE_DESCRIPTION, this); - multi_edits_connect_to_tab(edit_list, this); + edit_list->append({ + description_edit, + }); + + check_map->insert(description_edit, ui->description_check); } diff --git a/src/admc/multi_tabs/general_other_multi_tab.h b/src/admc/multi_tabs/general_other_multi_tab.h index b8da3d39..86b22592 100644 --- a/src/admc/multi_tabs/general_other_multi_tab.h +++ b/src/admc/multi_tabs/general_other_multi_tab.h @@ -25,19 +25,22 @@ * General multi tab for all non-user objects. */ -#include "multi_tabs/properties_multi_tab.h" +#include + +class AttributeEdit; +class QCheckBox; namespace Ui { class GeneralOtherMultiTab; } -class GeneralOtherMultiTab final : public PropertiesMultiTab { +class GeneralOtherMultiTab final : public QWidget { Q_OBJECT public: Ui::GeneralOtherMultiTab *ui; - GeneralOtherMultiTab(); + GeneralOtherMultiTab(QList *edit_list, QHash *check_map, QWidget *parent); }; #endif /* GENERAL_OTHER_MULTI_TAB_H */ diff --git a/src/admc/multi_tabs/general_user_multi_tab.cpp b/src/admc/multi_tabs/general_user_multi_tab.cpp index cd4ecf6b..50659c78 100644 --- a/src/admc/multi_tabs/general_user_multi_tab.cpp +++ b/src/admc/multi_tabs/general_user_multi_tab.cpp @@ -21,21 +21,38 @@ #include "multi_tabs/general_user_multi_tab.h" #include "multi_tabs/ui_general_user_multi_tab.h" -#include "adldap.h" -#include "attribute_multi_edits/string_multi_edit.h" +#include "ad_defines.h" +#include "attribute_edits/string_edit.h" -GeneralUserMultiTab::GeneralUserMultiTab() { +#include + +GeneralUserMultiTab::GeneralUserMultiTab(QList *edit_list, QHash *check_map, QWidget *parent) +: QWidget(parent) { ui = new Ui::GeneralUserMultiTab(); ui->setupUi(this); - new StringMultiEdit(ui->description_edit, ui->description_check, ATTRIBUTE_DESCRIPTION, edit_list, this); - new StringMultiEdit(ui->office_edit, ui->office_check, ATTRIBUTE_OFFICE, edit_list, this); - new StringMultiEdit(ui->mobile_edit, ui->mobile_check, ATTRIBUTE_MOBILE, edit_list, this); - new StringMultiEdit(ui->fax_edit, ui->fax_check, ATTRIBUTE_FAX_NUMBER, edit_list, this); - new StringMultiEdit(ui->web_edit, ui->web_check, ATTRIBUTE_WWW_HOMEPAGE, edit_list, this); - new StringMultiEdit(ui->email_edit, ui->email_check, ATTRIBUTE_MAIL, edit_list, this); + auto description_edit = new StringEdit(ui->description_edit, ATTRIBUTE_DESCRIPTION, this); + auto office_edit = new StringEdit(ui->office_edit, ATTRIBUTE_OFFICE, this); + auto mobile_edit = new StringEdit(ui->mobile_edit, ATTRIBUTE_MOBILE, this); + auto fax_edit = new StringEdit(ui->fax_edit, ATTRIBUTE_FAX_NUMBER, this); + auto homepage_edit = new StringEdit(ui->web_edit, ATTRIBUTE_WWW_HOMEPAGE, this); + auto mail_edit = new StringEdit(ui->email_edit, ATTRIBUTE_MAIL, this); - multi_edits_connect_to_tab(edit_list, this); + edit_list->append({ + description_edit, + office_edit, + mobile_edit, + fax_edit, + homepage_edit, + mail_edit, + }); + + check_map->insert(description_edit, ui->description_check); + check_map->insert(office_edit, ui->office_check); + check_map->insert(mobile_edit, ui->mobile_check); + check_map->insert(fax_edit, ui->fax_check); + check_map->insert(homepage_edit, ui->web_check); + check_map->insert(mail_edit, ui->email_check); } GeneralUserMultiTab::~GeneralUserMultiTab() { diff --git a/src/admc/multi_tabs/general_user_multi_tab.h b/src/admc/multi_tabs/general_user_multi_tab.h index c9d17441..69fe0ba0 100644 --- a/src/admc/multi_tabs/general_user_multi_tab.h +++ b/src/admc/multi_tabs/general_user_multi_tab.h @@ -25,19 +25,22 @@ * General multi tab for users. */ -#include "multi_tabs/properties_multi_tab.h" +#include + +class AttributeEdit; +class QCheckBox; namespace Ui { class GeneralUserMultiTab; } -class GeneralUserMultiTab final : public PropertiesMultiTab { +class GeneralUserMultiTab final : public QWidget { Q_OBJECT public: Ui::GeneralUserMultiTab *ui; - GeneralUserMultiTab(); + GeneralUserMultiTab(QList *edit_list, QHash *check_map, QWidget *parent); ~GeneralUserMultiTab(); }; diff --git a/src/admc/multi_tabs/organization_multi_tab.cpp b/src/admc/multi_tabs/organization_multi_tab.cpp index 8d4a86c4..45f40afe 100644 --- a/src/admc/multi_tabs/organization_multi_tab.cpp +++ b/src/admc/multi_tabs/organization_multi_tab.cpp @@ -21,20 +21,31 @@ #include "multi_tabs/organization_multi_tab.h" #include "multi_tabs/ui_organization_multi_tab.h" -#include "adldap.h" -#include "attribute_multi_edits/manager_multi_edit.h" -#include "attribute_multi_edits/string_multi_edit.h" +#include "ad_defines.h" +#include "attribute_edits/manager_edit.h" +#include "attribute_edits/string_edit.h" -OrganizationMultiTab::OrganizationMultiTab() { +OrganizationMultiTab::OrganizationMultiTab(QList *edit_list, QHash *check_map, QWidget *parent) +: QWidget(parent) { ui = new Ui::OrganizationMultiTab(); ui->setupUi(this); - new StringMultiEdit(ui->title_edit, ui->title_check, ATTRIBUTE_TITLE, edit_list, this); - new StringMultiEdit(ui->department_edit, ui->department_check, ATTRIBUTE_DEPARTMENT, edit_list, this); - new StringMultiEdit(ui->company_edit, ui->company_check, ATTRIBUTE_COMPANY, edit_list, this); - new ManagerMultiEdit(ui->manager_edit, ui->manager_check, edit_list, this); + auto title_edit = new StringEdit(ui->title_edit, ATTRIBUTE_TITLE, this); + auto department_edit = new StringEdit(ui->department_edit, ATTRIBUTE_DEPARTMENT, this); + auto company_edit = new StringEdit(ui->company_edit, ATTRIBUTE_COMPANY, this); + auto manager_edit = new ManagerEdit(ui->manager_edit, ATTRIBUTE_MANAGER, this); - multi_edits_connect_to_tab(edit_list, this); + edit_list->append({ + title_edit, + department_edit, + company_edit, + manager_edit, + }); + + check_map->insert(title_edit, ui->title_check); + check_map->insert(department_edit, ui->department_check); + check_map->insert(company_edit, ui->company_check); + check_map->insert(manager_edit, ui->manager_check); } OrganizationMultiTab::~OrganizationMultiTab() { diff --git a/src/admc/multi_tabs/organization_multi_tab.h b/src/admc/multi_tabs/organization_multi_tab.h index f6296937..4fe6f279 100644 --- a/src/admc/multi_tabs/organization_multi_tab.h +++ b/src/admc/multi_tabs/organization_multi_tab.h @@ -21,19 +21,22 @@ #ifndef ORGANIZATION_MULTI_TAB_H #define ORGANIZATION_MULTI_TAB_H -#include "multi_tabs/properties_multi_tab.h" +#include + +class AttributeEdit; +class QCheckBox; namespace Ui { class OrganizationMultiTab; } -class OrganizationMultiTab final : public PropertiesMultiTab { +class OrganizationMultiTab final : public QWidget { Q_OBJECT public: Ui::OrganizationMultiTab *ui; - OrganizationMultiTab(); + OrganizationMultiTab(QList *edit_list, QHash *check_map, QWidget *parent); ~OrganizationMultiTab(); }; diff --git a/src/admc/multi_tabs/profile_multi_tab.cpp b/src/admc/multi_tabs/profile_multi_tab.cpp index e89a2944..7be75070 100644 --- a/src/admc/multi_tabs/profile_multi_tab.cpp +++ b/src/admc/multi_tabs/profile_multi_tab.cpp @@ -21,18 +21,29 @@ #include "multi_tabs/profile_multi_tab.h" #include "ui_profile_multi_tab.h" -#include "adldap.h" -#include "attribute_multi_edits/string_multi_edit.h" +#include "ad_defines.h" +#include "attribute_edits/string_edit.h" -ProfileMultiTab::ProfileMultiTab() { +#include + +ProfileMultiTab::ProfileMultiTab(QList *edit_list, QHash *check_map, QWidget *parent) +: QWidget(parent) { ui = new Ui::ProfileMultiTab(); ui->setupUi(this); - new StringMultiEdit(ui->profile_edit, ui->profile_check, ATTRIBUTE_PROFILE_PATH, edit_list, this); - new StringMultiEdit(ui->script_edit, ui->script_check, ATTRIBUTE_SCRIPT_PATH, edit_list, this); - new StringMultiEdit(ui->home_edit, ui->home_check, ATTRIBUTE_HOME_DIRECTORY, edit_list, this); + auto profile_edit = new StringEdit(ui->profile_edit, ATTRIBUTE_PROFILE_PATH, this); + auto script_edit = new StringEdit(ui->script_edit, ATTRIBUTE_SCRIPT_PATH, this); + auto home_edit = new StringEdit(ui->home_edit, ATTRIBUTE_HOME_DIRECTORY, this); - multi_edits_connect_to_tab(edit_list, this); + edit_list->append({ + profile_edit, + script_edit, + home_edit, + }); + + check_map->insert(profile_edit, ui->profile_check); + check_map->insert(script_edit, ui->script_check); + check_map->insert(home_edit, ui->home_check); } ProfileMultiTab::~ProfileMultiTab() { diff --git a/src/admc/multi_tabs/profile_multi_tab.h b/src/admc/multi_tabs/profile_multi_tab.h index ca8df7a6..e7812959 100644 --- a/src/admc/multi_tabs/profile_multi_tab.h +++ b/src/admc/multi_tabs/profile_multi_tab.h @@ -21,19 +21,22 @@ #ifndef PROFILE_MULTI_TAB_H #define PROFILE_MULTI_TAB_H -#include "multi_tabs/properties_multi_tab.h" +#include + +class AttributeEdit; +class QCheckBox; namespace Ui { class ProfileMultiTab; } -class ProfileMultiTab final : public PropertiesMultiTab { +class ProfileMultiTab final : public QWidget { Q_OBJECT public: Ui::ProfileMultiTab *ui; - ProfileMultiTab(); + ProfileMultiTab(QList *edit_list, QHash *check_map, QWidget *parent); ~ProfileMultiTab(); }; diff --git a/src/admc/password_dialog.cpp b/src/admc/password_dialog.cpp index 94b7b563..723fd586 100644 --- a/src/admc/password_dialog.cpp +++ b/src/admc/password_dialog.cpp @@ -30,37 +30,32 @@ #include "status.h" #include "utils.h" -PasswordDialog::PasswordDialog(QWidget *parent) +#include + +PasswordDialog::PasswordDialog(AdInterface &ad, const QString &target_arg, QWidget *parent) : QDialog(parent) { ui = new Ui::PasswordDialog(); ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - new PasswordEdit(ui->password_main_edit, ui->password_confirm_edit, &edits, this); + auto password_main_edit = new PasswordEdit(ui->password_main_edit, ui->password_confirm_edit, ui->show_password_check, this); - pass_expired_edit = new AccountOptionEdit(ui->expired_check, AccountOption_PasswordExpired, &edits, this); + pass_expired_edit = new AccountOptionEdit(ui->expired_check, AccountOption_PasswordExpired, this); - new UnlockEdit(ui->unlock_check, &edits, this); - - settings_setup_dialog_geometry(SETTING_password_dialog_geometry, this); -} - -PasswordDialog::~PasswordDialog() { - delete ui; -} - -void PasswordDialog::set_target(const QString &target_arg) { + auto unlock_edit = new UnlockEdit(ui->unlock_check, this); + target = target_arg; - AdInterface ad; - if (ad_failed(ad)) { - return; - } + edits = { + password_main_edit, + pass_expired_edit, + unlock_edit, + }; const AdObject object = ad.search_object(target); - edits_load(edits, ad, object); + AttributeEdit::load(edits, ad, object); const bool expired_check_enabled = [&]() { const bool dont_expire_pass = object.get_account_option(AccountOption_DontExpirePassword, g_adconfig); @@ -72,34 +67,44 @@ void PasswordDialog::set_target(const QString &target_arg) { if (expired_check_enabled) { ui->expired_check->setChecked(true); - - // NOTE: always set expired option to modified, so that - // it always applies, even if this option is already - // turned on. This is for consistent and understandable - // messaging to user. - pass_expired_edit->set_modified(true); } else { ui->expired_check->setEnabled(false); ui->expired_check->setToolTip(tr("Option is unavailable because a conflicting account option is currently enabled.")); } - g_status->display_ad_messages(ad, this); + required_list = { + ui->password_main_edit, + ui->password_confirm_edit, + }; + + for (QLineEdit *edit : required_list) { + connect( + edit, &QLineEdit::textChanged, + this, &PasswordDialog::on_edited); + } + on_edited(); + + settings_setup_dialog_geometry(SETTING_password_dialog_geometry, this); +} + +PasswordDialog::~PasswordDialog() { + delete ui; } void PasswordDialog::accept() { AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, this)) { return; } show_busy_indicator(); - const bool verify_success = edits_verify(ad, edits, target); + const bool verify_success = AttributeEdit::verify(edits, ad, target); if (!verify_success) { return; } - const bool apply_success = edits_apply(ad, edits, target); + const bool apply_success = AttributeEdit::apply(edits, ad, target); hide_busy_indicator(); @@ -109,3 +114,18 @@ void PasswordDialog::accept() { QDialog::accept(); } } + +void PasswordDialog::on_edited() { + const bool all_required_filled = [this]() { + for (QLineEdit *edit : required_list) { + if (edit->text().isEmpty()) { + return false; + } + } + + return true; + }(); + + auto ok_button = ui->button_box->button(QDialogButtonBox::Ok); + ok_button->setEnabled(all_required_filled); +} diff --git a/src/admc/password_dialog.h b/src/admc/password_dialog.h index 6126476a..32091c93 100644 --- a/src/admc/password_dialog.h +++ b/src/admc/password_dialog.h @@ -24,6 +24,8 @@ #include class AttributeEdit; +class AdInterface; +class QLineEdit; namespace Ui { class PasswordDialog; @@ -36,11 +38,9 @@ class PasswordDialog final : public QDialog { public: Ui::PasswordDialog *ui; - PasswordDialog(QWidget *parent); + PasswordDialog(AdInterface &ad, const QString &dn, QWidget *parent); ~PasswordDialog(); - void set_target(const QString &dn); - public slots: void accept(); @@ -48,6 +48,9 @@ private: QString target; QList edits; AttributeEdit *pass_expired_edit; + QList required_list; + + void on_edited(); }; #endif /* PASSWORD_DIALOG_H */ diff --git a/src/admc/password_dialog.ui b/src/admc/password_dialog.ui index a2893086..d4e62232 100644 --- a/src/admc/password_dialog.ui +++ b/src/admc/password_dialog.ui @@ -7,7 +7,7 @@ 0 0 400 - 197 + 232 @@ -47,7 +47,7 @@
- + User must change password on next logon @@ -57,17 +57,27 @@ - + Unlock account + + + + Show password + + + false + + +
- + Qt::Horizontal @@ -81,7 +91,7 @@ - buttonBox + button_box accepted() PasswordDialog accept() @@ -97,7 +107,7 @@ - buttonBox + button_box rejected() PasswordDialog reject() diff --git a/src/admc/policy_results_widget.cpp b/src/admc/policy_results_widget.cpp index ad8c60c6..69e9cd31 100644 --- a/src/admc/policy_results_widget.cpp +++ b/src/admc/policy_results_widget.cpp @@ -135,7 +135,7 @@ void PolicyResultsWidget::update(const QString &new_gpo) { gpo = new_gpo; AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, this)) { return; } @@ -148,13 +148,20 @@ void PolicyResultsWidget::update(const QString &new_gpo) { if (!perms_ok && ok) { const QString title = tr("Incorrect permissions detected"); const QString text = tr("Permissions for this policy's GPT don't match the permissions for it's GPC object. Would you like to update GPT permissions?"); - QMessageBox *sync_warning_dialog =message_box_warning(this, title, text); + + auto sync_warning_dialog = new QMessageBox(this); + sync_warning_dialog->setAttribute(Qt::WA_DeleteOnClose); + sync_warning_dialog->setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + sync_warning_dialog->setWindowTitle(title); + sync_warning_dialog->setText(text); + sync_warning_dialog->setIcon(QMessageBox::Warning); connect( sync_warning_dialog, &QDialog::accepted, + this, [this]() { AdInterface ad_inner; - if (ad_failed(ad_inner)) { + if (ad_failed(ad_inner, this)) { return; } @@ -168,7 +175,7 @@ void PolicyResultsWidget::update(const QString &new_gpo) { model->removeRows(0, model->rowCount()); - const QString base = g_adconfig->domain_head(); + const QString base = g_adconfig->domain_dn(); const SearchScope scope = SearchScope_All; const QList attributes = {ATTRIBUTE_NAME, ATTRIBUTE_GPLINK}; const QString filter = filter_CONDITION(Condition_Contains, ATTRIBUTE_GPLINK, gpo); @@ -248,7 +255,7 @@ void PolicyResultsWidget::on_item_changed(QStandardItem *item) { } AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, this)) { return; } @@ -286,7 +293,7 @@ void PolicyResultsWidget::open_context_menu(const QPoint &pos) { void PolicyResultsWidget::delete_link() { AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, this)) { return; } diff --git a/src/admc/properties_dialog.cpp b/src/admc/properties_dialog.cpp index c8d10988..45d8fdb2 100644 --- a/src/admc/properties_dialog.cpp +++ b/src/admc/properties_dialog.cpp @@ -25,6 +25,7 @@ #include "console_impls/object_impl.h" #include "globals.h" #include "properties_warning_dialog.h" +#include "security_sort_warning_dialog.h" #include "settings.h" #include "status.h" #include "tab_widget.h" @@ -39,7 +40,7 @@ #include "tabs/general_policy_tab.h" #include "tabs/general_user_tab.h" #include "tabs/general_computer_tab.h" -#include "tabs/gpo_links_tab.h" +#include "tabs/general_shared_folder_tab.h" #include "tabs/group_policy_tab.h" #include "tabs/managed_by_tab.h" #include "tabs/membership_tab.h" @@ -47,9 +48,10 @@ #include "tabs/organization_tab.h" #include "tabs/os_tab.h" #include "tabs/profile_tab.h" -#include "tabs/properties_tab.h" #include "tabs/security_tab.h" #include "tabs/telephones_tab.h" +#include "tabs/laps_tab.h" +#include "tabs/error_tab.h" #include "utils.h" #include @@ -60,7 +62,7 @@ QHash PropertiesDialog::instances; -PropertiesDialog *PropertiesDialog::open_for_target(const QString &target, bool *dialog_is_new) { +PropertiesDialog *PropertiesDialog::open_for_target(AdInterface &ad, const QString &target, bool *dialog_is_new) { if (target.isEmpty()) { return nullptr; } @@ -78,7 +80,7 @@ PropertiesDialog *PropertiesDialog::open_for_target(const QString &target, bool dialog->setFocus(); } else { // Make new dialog for this target - dialog = new PropertiesDialog(target); + dialog = new PropertiesDialog(ad, target); dialog->open(); } @@ -94,14 +96,20 @@ PropertiesDialog *PropertiesDialog::open_for_target(const QString &target, bool void PropertiesDialog::open_when_view_item_activated(QAbstractItemView *view, const int dn_role) { connect( view, &QAbstractItemView::doubleClicked, - [dn_role](const QModelIndex &index) { + view, + [view, dn_role](const QModelIndex &index) { + AdInterface ad; + if (ad_failed(ad, view)) { + return; + } + const QString dn = index.data(dn_role).toString(); - open_for_target(dn); + open_for_target(ad, dn); }); } -PropertiesDialog::PropertiesDialog(const QString &target_arg) +PropertiesDialog::PropertiesDialog(AdInterface &ad, const QString &target_arg) : QDialog() { ui = new Ui::PropertiesDialog(); ui->setupUi(this); @@ -109,7 +117,11 @@ PropertiesDialog::PropertiesDialog(const QString &target_arg) setAttribute(Qt::WA_DeleteOnClose); target = target_arg; - is_modified = false; + + security_warning_was_rejected = false; + security_tab = nullptr; + + ui->tab_widget->enable_auto_switch_tab(false); PropertiesDialog::instances[target] = this; @@ -128,97 +140,134 @@ PropertiesDialog::PropertiesDialog(const QString &target_arg) }(); setWindowTitle(title); - AdObject object; + const AdObject object = ad.search_object(target); - AdInterface ad; - if (!ad_failed(ad)) { - object = ad.search_object(target); - } + const bool is_person = (object.is_class(CLASS_USER) || object.is_class(CLASS_INET_ORG_PERSON)); - // Create new tabs - const auto add_tab = [this](PropertiesTab *tab, const QString &tab_title) { - tabs.append(tab); - ui->tab_widget->add_tab(tab, tab_title); - }; - - PropertiesTab *general_tab = [&]() -> PropertiesTab * { - if (object.is_class(CLASS_USER)) { - return new GeneralUserTab(object); + // + // Create tabs + // + QWidget *general_tab = [&]() -> QWidget * { + if (is_person || object.is_class(CLASS_CONTACT)) { + return new GeneralUserTab(&edit_list, this); } else if (object.is_class(CLASS_GROUP)) { - return new GeneralGroupTab(object); + return new GeneralGroupTab(&edit_list, this); } else if (object.is_class(CLASS_OU)) { - return new GeneralOUTab(object); - } else if (object.is_class(CLASS_GROUP)) { - return new GeneralGroupTab(object); + return new GeneralOUTab(&edit_list, this); } else if (object.is_class(CLASS_COMPUTER)) { - return new GeneralComputerTab(object); + return new GeneralComputerTab(&edit_list, this); } else if (object.is_class(CLASS_GP_CONTAINER)) { - return new GeneralPolicyTab(); + return new GeneralPolicyTab(&edit_list, this); + } else if (object.is_class(CLASS_SHARED_FOLDER)) { + return new GeneralSharedFolderTab(&edit_list, this); + } else if (!object.is_empty()) { + return new GeneralOtherTab(&edit_list, this); } else { - return new GeneralOtherTab(object); + return new ErrorTab(this); } }(); - add_tab(general_tab, tr("General")); + ui->tab_widget->add_tab(general_tab, tr("General")); - const bool advanced_view_ON = settings_get_bool(SETTING_advanced_features); + const bool advanced_view_ON = settings_get_variant(SETTING_advanced_features).toBool(); - if (advanced_view_ON && !object.is_class(CLASS_GP_CONTAINER)) { - add_tab(new ObjectTab(), tr("Object")); + if (advanced_view_ON && !object.is_empty()) { + auto object_tab = new ObjectTab(&edit_list, this); + attributes_tab = new AttributesTab(&edit_list, this); - attributes_tab = new AttributesTab(); - add_tab(attributes_tab, tr("Attributes")); + ui->tab_widget->add_tab(object_tab, tr("Object")); + ui->tab_widget->add_tab(attributes_tab, tr("Attributes")); } else { attributes_tab = nullptr; } - if (object.is_class(CLASS_USER)) { - add_tab(new AccountTab(ad), tr("Account")); - add_tab(new AddressTab(), tr("Address")); - add_tab(new OrganizationTab(), tr("Organization")); - add_tab(new TelephonesTab(), tr("Telephones")); - add_tab(new ProfileTab(), tr("Profile")); - } - if (object.is_class(CLASS_GROUP)) { - add_tab(new MembersTab(), tr("Members")); - } - if (object.is_class(CLASS_USER)) { - add_tab(new MemberOfTab(), tr("Member of")); + if (is_person || object.is_class(CLASS_CONTACT)) { + auto address_tab = new AddressTab(&edit_list, this); + auto organization_tab = new OrganizationTab(&edit_list, this); + auto telephones_tab = new TelephonesTab(&edit_list, this); + + ui->tab_widget->add_tab(address_tab, tr("Address")); + ui->tab_widget->add_tab(organization_tab, tr("Organization")); + ui->tab_widget->add_tab(telephones_tab, tr("Telephones")); } - if (object.is_class(CLASS_OU)) { - add_tab(new ManagedByTab(), tr("Managed by")); + if (is_person) { + auto account_tab = new AccountTab(ad, &edit_list, this); + + ui->tab_widget->add_tab(account_tab, tr("Account")); + + const bool profile_tab_enabled = settings_get_variant(SETTING_feature_profile_tab).toBool(); + if (profile_tab_enabled) { + auto profile_tab = new ProfileTab(&edit_list, this); + ui->tab_widget->add_tab(profile_tab, tr("Profile")); + } + } + + if (object.is_class(CLASS_GROUP)) { + auto members_tab = new MembershipTab(&edit_list, MembershipTabType_Members, this); + ui->tab_widget->add_tab(members_tab, tr("Members")); + } + + if (is_person || object.is_class(CLASS_COMPUTER) || object.is_class(CLASS_CONTACT)) { + auto member_of_tab = new MembershipTab(&edit_list, MembershipTabType_MemberOf, this); + ui->tab_widget->add_tab(member_of_tab, tr("Member of")); + } + + if (is_person || object.is_class(CLASS_COMPUTER)) { + auto delegation_tab = new DelegationTab(&edit_list, this); + ui->tab_widget->add_tab(delegation_tab, tr("Delegation")); + } + + if (object.is_class(CLASS_OU) || object.is_class(CLASS_COMPUTER) || object.is_class(CLASS_SHARED_FOLDER)) { + auto managed_by_tab = new ManagedByTab(&edit_list, this); + ui->tab_widget->add_tab(managed_by_tab, tr("Managed by")); } if (object.is_class(CLASS_OU) || object.is_class(CLASS_DOMAIN)) { - add_tab(new GroupPolicyTab(), tr("Group policy")); - } - - if (object.is_class(CLASS_GP_CONTAINER)) { - add_tab(new GpoLinksTab(), tr("Links to")); + auto group_policy_tab = new GroupPolicyTab(&edit_list, this); + ui->tab_widget->add_tab(group_policy_tab, tr("Group policy")); } if (object.is_class(CLASS_COMPUTER)) { - add_tab(new OSTab(), tr("Operating System")); - add_tab(new DelegationTab(), tr("Delegation")); - add_tab(new MemberOfTab(), tr("Member of")); - add_tab(new ManagedByTab(), tr("Managed by")); + auto os_tab = new OSTab(&edit_list, this); + + ui->tab_widget->add_tab(os_tab, tr("Operating System")); + + const bool laps_enabled = [&]() { + const QList attribute_list = object.attributes(); + const bool out = (attribute_list.contains(ATTRIBUTE_LAPS_PASSWORD) && attribute_list.contains(ATTRIBUTE_LAPS_EXPIRATION)); + + return out; + }(); + + if (laps_enabled) { + auto laps_tab = new LAPSTab(&edit_list, this); + ui->tab_widget->add_tab(laps_tab, tr("LAPS")); + } } const bool need_security_tab = object.attributes().contains(ATTRIBUTE_SECURITY_DESCRIPTOR); if (need_security_tab && advanced_view_ON) { - add_tab(new SecurityTab(), tr("Security")); + security_tab = new SecurityTab(&edit_list, this); + ui->tab_widget->add_tab(security_tab, tr("Security")); } - for (auto tab : tabs) { + for (AttributeEdit *edit : edit_list) { connect( - tab, &PropertiesTab::edited, - this, &PropertiesDialog::on_edited); + edit, &AttributeEdit::edited, + [this, edit]() { + const bool already_added = apply_list.contains(edit); + + if (!already_added) { + apply_list.append(edit); + } + + apply_button->setEnabled(true); + reset_button->setEnabled(true); + }); } - if (ad.is_connected()) { - reset_internal(ad); - } + reset_internal(ad, object); settings_setup_dialog_geometry(SETTING_properties_dialog_geometry, this); @@ -240,10 +289,23 @@ PropertiesDialog::~PropertiesDialog() { delete ui; } -void PropertiesDialog::on_current_tab_changed(QWidget *prev_tab, QWidget *new_tab) { +// NOTE: order here is important. Attributes warning +// can apply/discard changes, while security warning +// can add a modification to security tab. Therefore +// attributes warning needs to go first so that it +// won't discard security warning modification. +void PropertiesDialog::on_current_tab_changed(const int prev, const int current) { + QWidget *prev_tab = ui->tab_widget->get_tab(prev); + QWidget *new_tab = ui->tab_widget->get_tab(current); + const bool switching_to_or_from_attributes = (prev_tab == attributes_tab || new_tab == attributes_tab); - const bool need_to_open_dialog = (switching_to_or_from_attributes && is_modified); - if (!need_to_open_dialog) { + const bool is_modified = !apply_list.isEmpty(); + const bool need_attributes_warning = (switching_to_or_from_attributes && is_modified); + if (!need_attributes_warning) { + ui->tab_widget->set_current_tab(current); + + open_security_warning(); + return; } @@ -255,14 +317,15 @@ void PropertiesDialog::on_current_tab_changed(QWidget *prev_tab, QWidget *new_ta } }(); - auto dialog = new PropertiesWarningDialog(warning_type, this); - dialog->open(); + auto attributes_warning_dialog = new PropertiesWarningDialog(warning_type, this); + attributes_warning_dialog->open(); connect( - dialog, &QDialog::accepted, - [this]() { + attributes_warning_dialog, &PropertiesWarningDialog::applied, + this, + [this, current]() { AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, this)) { return; } @@ -270,19 +333,86 @@ void PropertiesDialog::on_current_tab_changed(QWidget *prev_tab, QWidget *new_ta // NOTE: have to reset for attributes tab and other tabs // to load updates - reset_internal(ad); + const AdObject object = ad.search_object(target); + reset_internal(ad, object); + + ui->tab_widget->set_current_tab(current); }); connect( - dialog, &QDialog::rejected, - [this]() { + attributes_warning_dialog, &PropertiesWarningDialog::discarded, + [this, current]() { reset(); + + ui->tab_widget->set_current_tab(current); }); + + connect( + attributes_warning_dialog, &PropertiesWarningDialog::rejected, + [this, prev]() { + ui->tab_widget->set_current_tab(prev); + }); + + // Open security warning after attributes warning + // is finished + connect( + attributes_warning_dialog, &QDialog::finished, + this, &PropertiesDialog::open_security_warning); +} + +void PropertiesDialog::open_security_warning() { + const bool security_tab_exists = (security_tab != nullptr); + if (!security_tab_exists) { + return; + } + + // NOTE: if security warning was rejected once, + // then security tab is forever read only and we + // don't show the warning again (for this dialog + // instance). + if (security_warning_was_rejected) { + return; + } + + const bool switched_to_security_tab = [&]() { + const QWidget *current_tab = ui->tab_widget->get_current_tab(); + const bool out = (current_tab == security_tab); + + return out; + }(); + if (!switched_to_security_tab) { + return; + } + + const bool order_is_correct = security_tab->verify_acl_order(); + if (order_is_correct) { + return; + } + + auto security_warning_dialog = new SecuritySortWarningDialog(this); + security_warning_dialog->open(); + + connect( + security_warning_dialog, &QDialog::accepted, + this, &PropertiesDialog::on_security_warning_accepted); + connect( + security_warning_dialog, &QDialog::rejected, + this, &PropertiesDialog::on_security_warning_rejected); +} + +void PropertiesDialog::on_security_warning_accepted() { + security_tab->fix_acl_order(); +} + +void PropertiesDialog::on_security_warning_rejected() { + security_tab->set_read_only(); + + security_warning_was_rejected = true; } void PropertiesDialog::accept() { AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, this)) { return; } @@ -301,7 +431,7 @@ void PropertiesDialog::done(int r) { void PropertiesDialog::apply() { AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, this)) { return; } @@ -310,34 +440,35 @@ void PropertiesDialog::apply() { ad.clear_messages(); if (apply_success) { - reset_internal(ad); + const AdObject object = ad.search_object(target); + reset_internal(ad, object); } } void PropertiesDialog::reset() { AdInterface ad; - if (ad_connected(ad)) { - reset_internal(ad); + if (ad_connected(ad, this)) { + const AdObject object = ad.search_object(target); + reset_internal(ad, object); } } bool PropertiesDialog::apply_internal(AdInterface &ad) { - for (auto tab : tabs) { - const bool verify_success = tab->verify(ad, target); - if (!verify_success) { - return false; - } + // NOTE: only verify and apply edits in the "apply + // list", aka the edits that were edited + const bool edits_verify_success = AttributeEdit::verify(apply_list, ad, target); + + if (!edits_verify_success) { + return false; } show_busy_indicator(); bool total_apply_success = true; - for (auto tab : tabs) { - const bool apply_success = tab->apply(ad, target); - if (!apply_success) { - total_apply_success = false; - } + const bool edits_apply_success = AttributeEdit::apply(apply_list, ad, target); + if (!edits_apply_success) { + total_apply_success = false; } g_status->display_ad_messages(ad, this); @@ -345,7 +476,6 @@ bool PropertiesDialog::apply_internal(AdInterface &ad) { if (total_apply_success) { apply_button->setEnabled(false); reset_button->setEnabled(false); - is_modified = false; } hide_busy_indicator(); @@ -355,22 +485,12 @@ bool PropertiesDialog::apply_internal(AdInterface &ad) { return total_apply_success; } -void PropertiesDialog::reset_internal(AdInterface &ad) { - const AdObject object = ad.search_object(target); - - for (auto tab : tabs) { - tab->load(ad, object); - } +void PropertiesDialog::reset_internal(AdInterface &ad, const AdObject &object) { + AttributeEdit::load(edit_list, ad, object); apply_button->setEnabled(false); reset_button->setEnabled(false); - is_modified = false; + apply_list.clear(); g_status->display_ad_messages(ad, this); } - -void PropertiesDialog::on_edited() { - apply_button->setEnabled(true); - reset_button->setEnabled(true); - is_modified = true; -} diff --git a/src/admc/properties_dialog.h b/src/admc/properties_dialog.h index 79ef369a..33b66ed4 100644 --- a/src/admc/properties_dialog.h +++ b/src/admc/properties_dialog.h @@ -35,7 +35,10 @@ class QAbstractItemView; class QPushButton; class AttributesTab; class AdInterface; +class AdObject; class PropertiesWarningDialog; +class AttributeEdit; +class SecurityTab; namespace Ui { class PropertiesDialog; @@ -53,7 +56,7 @@ public: // newly created dialog and false if a dialog was // already open for given target and reused. Use to know // whether to connect to applied() signal - static PropertiesDialog *open_for_target(const QString &target, bool *dialog_is_new = nullptr); + static PropertiesDialog *open_for_target(AdInterface &ad, const QString &target, bool *dialog_is_new = nullptr); static void open_when_view_item_activated(QAbstractItemView *view, const int dn_role); ~PropertiesDialog(); @@ -68,23 +71,27 @@ private slots: void done(int r) override; void apply(); void reset(); - void on_edited(); private: - QList tabs; + QList edit_list; + QList apply_list; QString target; QPushButton *apply_button; QPushButton *reset_button; AttributesTab *attributes_tab; PropertiesWarningDialog *warning_dialog; - bool is_modified; + bool security_warning_was_rejected; + SecurityTab *security_tab; // NOTE: ctor is private, use open_for_target() instead - PropertiesDialog(const QString &target_arg); + PropertiesDialog(AdInterface &ad, const QString &target_arg); bool apply_internal(AdInterface &ad); - void reset_internal(AdInterface &ad); + void reset_internal(AdInterface &ad, const AdObject &object); - void on_current_tab_changed(QWidget *prev_tab, QWidget *new_tab); + void on_current_tab_changed(const int prev, const int current); + void open_security_warning(); + void on_security_warning_accepted(); + void on_security_warning_rejected(); }; #endif /* PROPERTIES_DIALOG_H */ diff --git a/src/admc/properties_multi_dialog.cpp b/src/admc/properties_multi_dialog.cpp index f1080095..67a99f68 100644 --- a/src/admc/properties_multi_dialog.cpp +++ b/src/admc/properties_multi_dialog.cpp @@ -29,6 +29,7 @@ #include "multi_tabs/general_user_multi_tab.h" #include "multi_tabs/organization_multi_tab.h" #include "multi_tabs/profile_multi_tab.h" +#include "attribute_edits/attribute_edit.h" #include "settings.h" #include "status.h" #include "tab_widget.h" @@ -37,8 +38,9 @@ #include #include #include +#include -PropertiesMultiDialog::PropertiesMultiDialog(const QList &target_list_arg, const QList &class_list) +PropertiesMultiDialog::PropertiesMultiDialog(AdInterface &ad, const QList &target_list_arg, const QList &class_list) : QDialog() { ui = new Ui::PropertiesMultiDialog(); ui->setupUi(this); @@ -47,29 +49,43 @@ PropertiesMultiDialog::PropertiesMultiDialog(const QList &target_list_a target_list = target_list_arg; - AdInterface ad; - if (ad_failed(ad)) { - close(); - - return; - } - - auto apply_button = ui->button_box->button(QDialogButtonBox::Apply); - auto reset_button = ui->button_box->button(QDialogButtonBox::Reset); - - auto add_tab = [&](PropertiesMultiTab *tab, const QString &title) { - ui->tab_widget->add_tab(tab, title); - tab_list.append(tab); - }; + apply_button = ui->button_box->button(QDialogButtonBox::Apply); if (class_list == QList({CLASS_USER})) { - add_tab(new GeneralUserMultiTab(), tr("General")); - add_tab(new AccountMultiTab(ad), tr("Account")); - add_tab(new AddressMultiTab(), tr("Address")); - add_tab(new ProfileMultiTab(), tr("Profile")); - add_tab(new OrganizationMultiTab(), tr("Organization")); + auto general_user_tab = new GeneralUserMultiTab(&edit_list, &check_map, this); + auto account_tab = new AccountMultiTab(ad, &edit_list, &check_map, this); + auto address_tab = new AddressMultiTab(&edit_list, &check_map, this); + auto profile_tab = new ProfileMultiTab(&edit_list, &check_map, this); + auto organization_tab = new OrganizationMultiTab(&edit_list, &check_map, this); + + ui->tab_widget->add_tab(general_user_tab, tr("General")); + ui->tab_widget->add_tab(account_tab, tr("Account")); + ui->tab_widget->add_tab(address_tab, tr("Address")); + ui->tab_widget->add_tab(profile_tab, tr("Profile")); + ui->tab_widget->add_tab(organization_tab, tr("Organization")); } else { - add_tab(new GeneralOtherMultiTab(), tr("General")); + auto general_other_tab = new GeneralOtherMultiTab(&edit_list, &check_map, this); + + ui->tab_widget->add_tab(general_other_tab, tr("General")); + } + + for (AttributeEdit *edit : check_map.keys()) { + QCheckBox *apply_check = check_map[edit]; + + connect( + apply_check, &QAbstractButton::toggled, + this, &PropertiesMultiDialog::on_edited); + connect( + apply_check, &QAbstractButton::toggled, + edit, + [edit, apply_check]() { + const bool enabled = apply_check->isChecked(); + edit->set_enabled(enabled); + }); + } + + for (AttributeEdit *edit : edit_list) { + edit->set_enabled(false); } settings_setup_dialog_geometry(SETTING_object_multi_dialog_geometry, this); @@ -77,11 +93,6 @@ PropertiesMultiDialog::PropertiesMultiDialog(const QList &target_list_a connect( apply_button, &QPushButton::clicked, this, &PropertiesMultiDialog::apply); - connect( - reset_button, &QPushButton::clicked, - this, &PropertiesMultiDialog::reset); - - reset(); } PropertiesMultiDialog::~PropertiesMultiDialog() { @@ -98,20 +109,42 @@ void PropertiesMultiDialog::accept() { bool PropertiesMultiDialog::apply() { AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, this)) { return false; } show_busy_indicator(); - bool total_apply_success = true; - for (PropertiesMultiTab *tab : tab_list) { - const bool success = tab->apply(ad, target_list); + const bool apply_success = [&]() { + bool out = true; - if (!success) { - total_apply_success = false; + for (AttributeEdit *edit : edit_list) { + QCheckBox *apply_check = check_map[edit]; + const bool need_to_apply = apply_check->isChecked(); + + if (need_to_apply) { + const bool success = [&]() { + bool success_out = true; + + for (const QString &target : target_list) { + const bool this_success = edit->apply(ad, target); + + success_out = (success_out && this_success); + } + + return success_out; + }(); + + if (success) { + apply_check->setChecked(false); + } + + out = (out && success); + } } - } + + return out; + }(); g_status->display_ad_messages(ad, this); @@ -119,11 +152,9 @@ bool PropertiesMultiDialog::apply() { emit applied(); - return total_apply_success; + return apply_success; } -void PropertiesMultiDialog::reset() { - for (PropertiesMultiTab *tab : tab_list) { - tab->reset(); - } +void PropertiesMultiDialog::on_edited() { + apply_button->setEnabled(true); } diff --git a/src/admc/properties_multi_dialog.h b/src/admc/properties_multi_dialog.h index 490becf3..36a64a35 100644 --- a/src/admc/properties_multi_dialog.h +++ b/src/admc/properties_multi_dialog.h @@ -30,7 +30,9 @@ #include -class PropertiesMultiTab; +class AttributeEdit; +class AdInterface; +class QCheckBox; namespace Ui { class PropertiesMultiDialog; @@ -42,7 +44,7 @@ class PropertiesMultiDialog final : public QDialog { public: Ui::PropertiesMultiDialog *ui; - PropertiesMultiDialog(const QList &target_list_arg, const QList &class_list); + PropertiesMultiDialog(AdInterface &ad, const QList &target_list_arg, const QList &class_list); ~PropertiesMultiDialog(); signals: @@ -50,11 +52,13 @@ signals: private slots: void accept() override; - void reset(); + void on_edited(); private: QList target_list; - QList tab_list; + QList edit_list; + QHash check_map; + QPushButton *apply_button; bool apply(); }; diff --git a/src/admc/properties_multi_dialog.ui b/src/admc/properties_multi_dialog.ui index 1e7ccfaa..74739972 100644 --- a/src/admc/properties_multi_dialog.ui +++ b/src/admc/properties_multi_dialog.ui @@ -23,7 +23,7 @@ Qt::Horizontal - QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Ok diff --git a/src/admc/properties_warning_dialog.cpp b/src/admc/properties_warning_dialog.cpp index 1524a8db..edb72b5d 100644 --- a/src/admc/properties_warning_dialog.cpp +++ b/src/admc/properties_warning_dialog.cpp @@ -21,6 +21,8 @@ #include "properties_warning_dialog.h" #include "ui_properties_warning_dialog.h" +#include + PropertiesWarningDialog::PropertiesWarningDialog(const PropertiesWarningType type, QWidget *parent) : QDialog(parent) { ui = new Ui::PropertiesWarningDialog(); @@ -38,10 +40,29 @@ PropertiesWarningDialog::PropertiesWarningDialog(const PropertiesWarningType typ ui->label->setText(label_text); - ui->button_box->addButton(tr("Apply current changes"), QDialogButtonBox::AcceptRole); - ui->button_box->addButton(tr("Discard changes"), QDialogButtonBox::RejectRole); + auto apply_button = ui->button_box->button(QDialogButtonBox::Apply); + auto discard_button = ui->button_box->button(QDialogButtonBox::Discard); + + connect( + apply_button, &QPushButton::clicked, + this, &PropertiesWarningDialog::on_apply_button); + connect( + discard_button, &QPushButton::clicked, + this, &PropertiesWarningDialog::on_discard_button); } PropertiesWarningDialog::~PropertiesWarningDialog() { delete ui; } + +void PropertiesWarningDialog::on_apply_button() { + emit applied(); + + QDialog::accept(); +} + +void PropertiesWarningDialog::on_discard_button() { + emit discarded(); + + QDialog::accept(); +} diff --git a/src/admc/properties_warning_dialog.h b/src/admc/properties_warning_dialog.h index 6649d880..14aa8121 100644 --- a/src/admc/properties_warning_dialog.h +++ b/src/admc/properties_warning_dialog.h @@ -47,6 +47,14 @@ public: PropertiesWarningDialog(const PropertiesWarningType type, QWidget *parent); ~PropertiesWarningDialog(); + +signals: + void applied(); + void discarded(); + +private: + void on_apply_button(); + void on_discard_button(); }; #endif /* PROPERTIES_WARNING_DIALOG_H */ diff --git a/src/admc/properties_warning_dialog.ui b/src/admc/properties_warning_dialog.ui index 36fae590..6d97859d 100644 --- a/src/admc/properties_warning_dialog.ui +++ b/src/admc/properties_warning_dialog.ui @@ -19,6 +19,9 @@ PLACEHOLDER (actual text depends on context) + + true + @@ -27,7 +30,7 @@ Qt::Horizontal - QDialogButtonBox::NoButton + QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Discard diff --git a/src/admc/rename_group_dialog.cpp b/src/admc/rename_group_dialog.cpp index 62155a91..c0e58cd3 100644 --- a/src/admc/rename_group_dialog.cpp +++ b/src/admc/rename_group_dialog.cpp @@ -21,28 +21,39 @@ #include "rename_group_dialog.h" #include "ui_rename_group_dialog.h" +#include "rename_object_helper.h" #include "attribute_edits/sam_name_edit.h" #include "settings.h" +#include "ad_defines.h" -RenameGroupDialog::RenameGroupDialog(QWidget *parent) +RenameGroupDialog::RenameGroupDialog(AdInterface &ad, const QString &target_arg, QWidget *parent) : RenameObjectDialog(parent) { ui = new Ui::RenameGroupDialog(); ui->setupUi(this); - QList edit_list; - sam_name_edit = new SamNameEdit(ui->sam_name_edit, ui->sam_name_domain_edit, &edit_list, this); + auto sam_name_edit = new SamNameEdit(ui->sam_name_edit, ui->sam_name_domain_edit, this); - init(ui->name_edit, ui->button_box, edit_list); + const QList edit_list = { + sam_name_edit, + }; + + helper = new RenameObjectHelper(ad, target_arg, ui->name_edit, edit_list, this); settings_setup_dialog_geometry(SETTING_rename_group_dialog_geometry, this); } +void RenameGroupDialog::accept() { + const bool accepted = helper->accept(); + + if (accepted) { + QDialog::accept(); + } +} + +QString RenameGroupDialog::get_new_dn() const { + return helper->get_new_dn(); +} + RenameGroupDialog::~RenameGroupDialog() { delete ui; } - -void RenameGroupDialog::open() { - sam_name_edit->load_domain(); - - RenameObjectDialog::open(); -} diff --git a/src/admc/rename_group_dialog.h b/src/admc/rename_group_dialog.h index 295ddf3c..3d492645 100644 --- a/src/admc/rename_group_dialog.h +++ b/src/admc/rename_group_dialog.h @@ -23,7 +23,8 @@ #include "rename_object_dialog.h" -class SamNameEdit; +class AdInterface; +class RenameObjectHelper; namespace Ui { class RenameGroupDialog; @@ -35,13 +36,14 @@ class RenameGroupDialog final : public RenameObjectDialog { public: Ui::RenameGroupDialog *ui; - RenameGroupDialog(QWidget *parent); + RenameGroupDialog(AdInterface &ad, const QString &target, QWidget *parent); ~RenameGroupDialog(); - void open() override; + void accept() override; + QString get_new_dn() const override; private: - SamNameEdit *sam_name_edit; + RenameObjectHelper *helper; }; #endif /* RENAME_GROUP_DIALOG_H */ diff --git a/src/admc/rename_group_dialog.ui b/src/admc/rename_group_dialog.ui index 82e802e8..3c4e1a18 100644 --- a/src/admc/rename_group_dialog.ui +++ b/src/admc/rename_group_dialog.ui @@ -29,7 +29,7 @@ - Logon name (pre-Windows 2000): + Group name (pre-Windows 2000): @@ -55,7 +55,7 @@ Qt::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset + QDialogButtonBox::Cancel|QDialogButtonBox::Ok diff --git a/src/admc/rename_object_dialog.h b/src/admc/rename_object_dialog.h index 2466e0e1..a611345b 100644 --- a/src/admc/rename_object_dialog.h +++ b/src/admc/rename_object_dialog.h @@ -23,35 +23,12 @@ #include -class QLineEdit; -class QDialogButtonBox; -class AttributeEdit; -class AdInterface; - class RenameObjectDialog : public QDialog { - Q_OBJECT public: using QDialog::QDialog; - static void success_msg(const QString &old_name); - static void fail_msg(const QString &old_name); - - void init(QLineEdit *name_edit, QDialogButtonBox *button_box, const QList &edits); - - void set_target(const QString &dn); - void reset(); - void open() override; - void accept() override; - - QString get_target() const; - QString get_new_name() const; - QString get_new_dn() const; - -private: - QString target; - QLineEdit *name_edit; - QList edits; + virtual QString get_new_dn() const = 0; }; #endif /* RENAME_OBJECT_DIALOG_H */ diff --git a/src/admc/rename_object_dialog.cpp b/src/admc/rename_object_helper.cpp similarity index 61% rename from src/admc/rename_object_dialog.cpp rename to src/admc/rename_object_helper.cpp index cf39330a..8187c465 100644 --- a/src/admc/rename_object_dialog.cpp +++ b/src/admc/rename_object_helper.cpp @@ -18,7 +18,7 @@ * along with this program. If not, see . */ -#include "rename_object_dialog.h" +#include "rename_object_helper.h" #include "adldap.h" #include "attribute_edits/attribute_edit.h" @@ -26,78 +26,65 @@ #include "status.h" #include "utils.h" -#include +#include #include -#include -void RenameObjectDialog::success_msg(const QString &old_name) { +void RenameObjectHelper::success_msg(const QString &old_name) { const QString message = QString(tr("Object %1 was renamed.")).arg(old_name); g_status->add_message(message, StatusType_Success); } -void RenameObjectDialog::fail_msg(const QString &old_name) { +void RenameObjectHelper::fail_msg(const QString &old_name) { const QString message = QString(tr("Failed to rename object %1")).arg(old_name); g_status->add_message(message, StatusType_Error); } -void RenameObjectDialog::init(QLineEdit *name_edit_arg, QDialogButtonBox *button_box, const QList &edits_arg) { +RenameObjectHelper::RenameObjectHelper(AdInterface &ad, const QString &target_arg, QLineEdit *name_edit_arg, const QList &edits_arg, QDialog *parent_dialog_arg) +: QObject(parent_dialog_arg) { name_edit = name_edit_arg; edits = edits_arg; - - QPushButton *reset_button = button_box->button(QDialogButtonBox::Reset); - - connect( - reset_button, &QPushButton::clicked, - this, &RenameObjectDialog::reset); -} - -void RenameObjectDialog::set_target(const QString &dn) { - target = dn; -} - -void RenameObjectDialog::reset() { - AdInterface ad; - if (ad_failed(ad)) { - return; - } + target = target_arg; + parent_dialog = parent_dialog_arg; const QString name = dn_get_name(target); name_edit->setText(name); + limit_edit(name_edit, ATTRIBUTE_CN); + const AdObject object = ad.search_object(target); - edits_load(edits, ad, object); + AttributeEdit::load(edits, ad, object); } -void RenameObjectDialog::open() { - reset(); - - QDialog::open(); -} - -void RenameObjectDialog::accept() { +bool RenameObjectHelper::accept() const { AdInterface ad; - if (ad_failed(ad)) { - return; + if (ad_failed(ad, parent_dialog)) { + return false; } const QString old_dn = target; const QString old_name = dn_get_name(target); - const bool verify_success = edits_verify(ad, edits, target); + const QString new_name = get_new_name(); + + const bool verify_name_success = verify_object_name(new_name, parent_dialog); + if (!verify_name_success) { + return false; + } + + const bool verify_success = AttributeEdit::verify(edits, ad, target); if (!verify_success) { - return; + return false; } show_busy_indicator(); - const QString new_name = get_new_name(); const QString new_dn = get_new_dn(); const bool rename_success = ad.object_rename(target, new_name); bool final_success = false; if (rename_success) { - const bool apply_success = edits_apply(ad, edits, new_dn); + const bool apply_success = AttributeEdit::apply(edits, ad, new_dn); if (apply_success) { final_success = true; @@ -106,28 +93,24 @@ void RenameObjectDialog::accept() { hide_busy_indicator(); - g_status->display_ad_messages(ad, this); + g_status->display_ad_messages(ad, parent_dialog); if (final_success) { - RenameObjectDialog::success_msg(old_name); - - QDialog::accept(); + RenameObjectHelper::success_msg(old_name); } else { - RenameObjectDialog::fail_msg(old_name); + RenameObjectHelper::fail_msg(old_name); } + + return final_success; } -QString RenameObjectDialog::get_target() const { - return target; -} - -QString RenameObjectDialog::get_new_name() const { +QString RenameObjectHelper::get_new_name() const { const QString new_name = name_edit->text().trimmed(); return new_name; } -QString RenameObjectDialog::get_new_dn() const { +QString RenameObjectHelper::get_new_dn() const { const QString new_name = get_new_name(); const QString new_dn = dn_rename(target, new_name); diff --git a/src/admc/rename_object_helper.h b/src/admc/rename_object_helper.h new file mode 100644 index 00000000..4347552e --- /dev/null +++ b/src/admc/rename_object_helper.h @@ -0,0 +1,51 @@ +/* + * 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 RENAME_OBJECT_HELPER_H +#define RENAME_OBJECT_HELPER_H + +#include + +class QLineEdit; +class QDialog; +class AttributeEdit; +class AdInterface; + +class RenameObjectHelper : public QObject { + Q_OBJECT + +public: + RenameObjectHelper(AdInterface &ad, const QString &target_arg, QLineEdit *name_edit_arg, const QList &edits_arg, QDialog *parent_dialog); + + static void success_msg(const QString &old_name); + static void fail_msg(const QString &old_name); + + bool accept() const; + QString get_new_name() const; + QString get_new_dn() const; + +private: + QString target; + QLineEdit *name_edit; + QList edits; + QDialog *parent_dialog; +}; + +#endif /* RENAME_OBJECT_HELPER_H */ diff --git a/src/admc/rename_other_dialog.cpp b/src/admc/rename_other_dialog.cpp index 9e9694e4..71f05577 100644 --- a/src/admc/rename_other_dialog.cpp +++ b/src/admc/rename_other_dialog.cpp @@ -21,18 +21,31 @@ #include "rename_other_dialog.h" #include "ui_rename_other_dialog.h" +#include "rename_object_helper.h" #include "settings.h" -RenameOtherDialog::RenameOtherDialog(QWidget *parent) +RenameOtherDialog::RenameOtherDialog(AdInterface &ad, const QString &target_arg, QWidget *parent) : RenameObjectDialog(parent) { ui = new Ui::RenameOtherDialog(); ui->setupUi(this); - init(ui->name_edit, ui->button_box, {}); + helper = new RenameObjectHelper(ad, target_arg, ui->name_edit, {}, this); settings_setup_dialog_geometry(SETTING_rename_other_dialog_geometry, this); } +void RenameOtherDialog::accept() { + const bool accepted = helper->accept(); + + if (accepted) { + QDialog::accept(); + } +} + +QString RenameOtherDialog::get_new_dn() const { + return helper->get_new_dn(); +} + RenameOtherDialog::~RenameOtherDialog() { delete ui; } diff --git a/src/admc/rename_other_dialog.h b/src/admc/rename_other_dialog.h index bcd0c934..be45722f 100644 --- a/src/admc/rename_other_dialog.h +++ b/src/admc/rename_other_dialog.h @@ -23,6 +23,9 @@ #include "rename_object_dialog.h" +class AdInterface; +class RenameObjectHelper; + namespace Ui { class RenameOtherDialog; } @@ -33,8 +36,14 @@ class RenameOtherDialog final : public RenameObjectDialog { public: Ui::RenameOtherDialog *ui; - RenameOtherDialog(QWidget *parent); + RenameOtherDialog(AdInterface &ad, const QString &target, QWidget *parent); ~RenameOtherDialog(); + + void accept() override; + QString get_new_dn() const override; + +private: + RenameObjectHelper *helper; }; #endif /* RENAME_OTHER_DIALOG_H */ diff --git a/src/admc/rename_other_dialog.ui b/src/admc/rename_other_dialog.ui index 251938e5..3d9722b7 100644 --- a/src/admc/rename_other_dialog.ui +++ b/src/admc/rename_other_dialog.ui @@ -34,7 +34,7 @@ Qt::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset + QDialogButtonBox::Cancel|QDialogButtonBox::Ok diff --git a/src/admc/rename_policy_dialog.cpp b/src/admc/rename_policy_dialog.cpp index 4fb81311..465ec29e 100644 --- a/src/admc/rename_policy_dialog.cpp +++ b/src/admc/rename_policy_dialog.cpp @@ -23,30 +23,28 @@ #include "adldap.h" #include "globals.h" -#include "rename_object_dialog.h" +#include "rename_object_helper.h" #include "settings.h" #include "status.h" #include "utils.h" -#include - -RenamePolicyDialog::RenamePolicyDialog(QWidget *parent) +RenamePolicyDialog::RenamePolicyDialog(AdInterface &ad, const QString &target_dn_arg, QWidget *parent) : QDialog(parent) { ui = new Ui::RenamePolicyDialog(); ui->setupUi(this); setAttribute(Qt::WA_DeleteOnClose); - ok_button = ui->button_box->button(QDialogButtonBox::Ok); - reset_button = ui->button_box->button(QDialogButtonBox::Reset); + target_dn = target_dn_arg; - connect( - reset_button, &QPushButton::clicked, - this, &RenamePolicyDialog::reset); - connect( - ui->name_edit, &QLineEdit::textChanged, - this, &RenamePolicyDialog::on_edited); - on_edited(); + target_name = [&]() { + const AdObject object = ad.search_object(target_dn); + + return object.get_string(ATTRIBUTE_DISPLAY_NAME); + }(); + + ui->name_edit->setText(target_name); + limit_edit(ui->name_edit, ATTRIBUTE_DISPLAY_NAME); settings_setup_dialog_geometry(SETTING_rename_policy_dialog_geometry, this); } @@ -55,15 +53,9 @@ RenamePolicyDialog::~RenamePolicyDialog() { delete ui; } -void RenamePolicyDialog::open() { - reset(); - - QDialog::open(); -} - void RenamePolicyDialog::accept() { AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, this)) { return; } @@ -71,9 +63,9 @@ void RenamePolicyDialog::accept() { const bool apply_success = ad.attribute_replace_string(target_dn, ATTRIBUTE_DISPLAY_NAME, new_name); if (apply_success) { - RenameObjectDialog::success_msg(target_old_name); + RenameObjectHelper::success_msg(target_name); } else { - RenameObjectDialog::fail_msg(target_old_name); + RenameObjectHelper::fail_msg(target_name); } g_status->display_ad_messages(ad, this); @@ -82,30 +74,3 @@ void RenamePolicyDialog::accept() { QDialog::accept(); } } - -void RenamePolicyDialog::set_target(const QString &dn) { - target_dn = dn; - - target_old_name = [&]() { - AdInterface ad; - if (ad_failed(ad)) { - return QString(); - } - - const AdObject object = ad.search_object(target_dn); - - return object.get_string(ATTRIBUTE_DISPLAY_NAME); - }(); -} - -void RenamePolicyDialog::on_edited() { - reset_button->setEnabled(true); - ok_button->setEnabled(true); -} - -void RenamePolicyDialog::reset() { - reset_button->setEnabled(false); - ok_button->setEnabled(false); - - ui->name_edit->setText(target_old_name); -} diff --git a/src/admc/rename_policy_dialog.h b/src/admc/rename_policy_dialog.h index fc201782..b58a0dcc 100644 --- a/src/admc/rename_policy_dialog.h +++ b/src/admc/rename_policy_dialog.h @@ -30,6 +30,7 @@ #include class QPushButton; +class AdInterface; namespace Ui { class RenamePolicyDialog; @@ -41,22 +42,14 @@ class RenamePolicyDialog final : public QDialog { public: Ui::RenamePolicyDialog *ui; - RenamePolicyDialog(QWidget *parent); + RenamePolicyDialog(AdInterface &ad, const QString &target_dn, QWidget *parent); ~RenamePolicyDialog(); - void open() override; void accept() override; - void set_target(const QString &dn); - private: QString target_dn; - QString target_old_name; - QPushButton *ok_button; - QPushButton *reset_button; - - void on_edited(); - void reset(); + QString target_name; }; #endif /* RENAME_DIALOG_POLICY_H */ diff --git a/src/admc/rename_policy_dialog.ui b/src/admc/rename_policy_dialog.ui index bff47c8d..07c66e3f 100644 --- a/src/admc/rename_policy_dialog.ui +++ b/src/admc/rename_policy_dialog.ui @@ -34,7 +34,7 @@ Qt::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset + QDialogButtonBox::Cancel|QDialogButtonBox::Ok diff --git a/src/admc/rename_user_dialog.cpp b/src/admc/rename_user_dialog.cpp index 4b9856ba..eefbc3e9 100644 --- a/src/admc/rename_user_dialog.cpp +++ b/src/admc/rename_user_dialog.cpp @@ -25,38 +25,51 @@ #include "attribute_edits/sam_name_edit.h" #include "attribute_edits/string_edit.h" #include "attribute_edits/upn_edit.h" +#include "rename_object_helper.h" #include "utils.h" #include "settings.h" -RenameUserDialog::RenameUserDialog(QWidget *parent) +RenameUserDialog::RenameUserDialog(AdInterface &ad, const QString &target_arg, QWidget *parent) : RenameObjectDialog(parent) { ui = new Ui::RenameUserDialog(); ui->setupUi(this); - QList edit_list; - new StringEdit(ui->first_name_edit, ATTRIBUTE_FIRST_NAME, &edit_list, this); - new StringEdit(ui->last_name_edit, ATTRIBUTE_LAST_NAME, &edit_list, this); - new StringEdit(ui->full_name_edit, ATTRIBUTE_DISPLAY_NAME, &edit_list, this); - upn_edit = new UpnEdit(ui->upn_prefix_edit, ui->upn_suffix_edit, &edit_list, this); - sam_name_edit = new SamNameEdit(ui->sam_name_edit, ui->sam_name_domain_edit, &edit_list, this); + auto first_name_edit = new StringEdit(ui->first_name_edit, ATTRIBUTE_FIRST_NAME, this); + auto last_name_edit = new StringEdit(ui->last_name_edit, ATTRIBUTE_LAST_NAME, this); + auto display_name_edit = new StringEdit(ui->full_name_edit, ATTRIBUTE_DISPLAY_NAME, this); - init(ui->name_edit, ui->button_box, edit_list); + auto upn_edit = new UpnEdit(ui->upn_prefix_edit, ui->upn_suffix_edit, this); + upn_edit->init_suffixes(ad); + + auto sam_name_edit = new SamNameEdit(ui->sam_name_edit, ui->sam_name_domain_edit, this); + + const QList edit_list = { + first_name_edit, + last_name_edit, + display_name_edit, + upn_edit, + sam_name_edit, + }; + + helper = new RenameObjectHelper(ad, target_arg, ui->name_edit, edit_list, this); + + setup_lineedit_autofill(ui->upn_prefix_edit, ui->sam_name_edit); settings_setup_dialog_geometry(SETTING_rename_user_dialog_geometry, this); } +void RenameUserDialog::accept() { + const bool accepted = helper->accept(); + + if (accepted) { + QDialog::accept(); + } +} + +QString RenameUserDialog::get_new_dn() const { + return helper->get_new_dn(); +} + RenameUserDialog::~RenameUserDialog() { delete ui; } - -void RenameUserDialog::open() { - AdInterface ad; - if (ad_failed(ad)) { - return; - } - - upn_edit->init_suffixes(ad); - sam_name_edit->load_domain(); - - RenameObjectDialog::open(); -} diff --git a/src/admc/rename_user_dialog.h b/src/admc/rename_user_dialog.h index 093dc13e..cc9f48b1 100644 --- a/src/admc/rename_user_dialog.h +++ b/src/admc/rename_user_dialog.h @@ -23,8 +23,8 @@ #include "rename_object_dialog.h" -class UpnEdit; -class SamNameEdit; +class AdInterface; +class RenameObjectHelper; namespace Ui { class RenameUserDialog; @@ -36,15 +36,14 @@ class RenameUserDialog final : public RenameObjectDialog { public: Ui::RenameUserDialog *ui; - RenameUserDialog(QWidget *parent); + RenameUserDialog(AdInterface &ad, const QString &target, QWidget *parent); ~RenameUserDialog(); -public slots: - void open() override; + void accept() override; + QString get_new_dn() const override; private: - UpnEdit *upn_edit; - SamNameEdit *sam_name_edit; + RenameObjectHelper *helper; }; #endif /* RENAME_USER_DIALOG_H */ diff --git a/src/admc/rename_user_dialog.ui b/src/admc/rename_user_dialog.ui index 6b2de0ad..14574f44 100644 --- a/src/admc/rename_user_dialog.ui +++ b/src/admc/rename_user_dialog.ui @@ -19,7 +19,7 @@ - Display name: + Full name: @@ -49,7 +49,7 @@ - Full name: + Display name: @@ -102,7 +102,7 @@ Qt::Horizontal - QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::Reset + QDialogButtonBox::Cancel|QDialogButtonBox::Ok diff --git a/src/admc/search_thread.cpp b/src/admc/search_thread.cpp index 1e3dcd1a..b2149a7a 100644 --- a/src/admc/search_thread.cpp +++ b/src/admc/search_thread.cpp @@ -23,6 +23,7 @@ #include "adldap.h" #include "utils.h" #include "status.h" +#include "settings.h" #include @@ -33,14 +34,11 @@ SearchThread::SearchThread(const QString base_arg, const SearchScope scope_arg, filter = filter_arg; attributes = attributes_arg; m_failed_to_connect = false; + m_hit_object_display_limit = false; static int id_max = 0; id = id_max; id_max++; - - connect( - this, &SearchThread::finished, - this, &QObject::deleteLater); } void SearchThread::stop() { @@ -57,11 +55,25 @@ void SearchThread::run() { AdCookie cookie; + const int object_display_limit = settings_get_variant(SETTING_object_display_limit).toInt(); + + int total_results_count = 0; + while (true) { QHash results; const bool success = ad.search_paged(base, scope, filter, attributes, &results, &cookie); + total_results_count += results.count(); + + if (total_results_count > object_display_limit) { + m_hit_object_display_limit = true; + + break; + } + + ad_messages = ad.messages(); + emit results_ready(results); const bool search_interrupted = (!success || stop_flag); @@ -83,6 +95,18 @@ bool SearchThread::failed_to_connect() const { return m_failed_to_connect; } -void search_thread_error_log(QWidget *parent) { - error_log({QCoreApplication::translate("object_impl.cpp", "Failed to connect to server while searching for objects.")}, parent); +bool SearchThread::hit_object_display_limit() const { + return m_hit_object_display_limit; +} + +QList SearchThread::get_ad_messages() const { + return ad_messages; +} + +void search_thread_display_errors(SearchThread *thread, QWidget *parent) { + if (thread->failed_to_connect()) { + error_log({QCoreApplication::translate("object_impl.cpp", "Failed to connect to server while searching for objects.")}, parent); + } else if (thread->hit_object_display_limit()) { + error_log({QCoreApplication::translate("object_impl.cpp", "Could not load all objects. Increase object display limit in Filter Options or reduce number of objects by applying a filter. Filter Options is accessible from main window's menubar via the \"View\" menu.")}, parent); + } } diff --git a/src/admc/search_thread.h b/src/admc/search_thread.h index 82f868b9..6fe11b32 100644 --- a/src/admc/search_thread.h +++ b/src/admc/search_thread.h @@ -29,8 +29,8 @@ * has multiple pages, then results_ready() will be emitted * multiple times. Use stop() to stop search. Note that search is * not stopped immediately but when current results page is - * done processing. SearchThread deletes itself when it's - * finished. + * done processing. Note that creator of thread should call + * thread's deleteLater() in the finished() slot. */ #include @@ -38,6 +38,7 @@ #include "ad_defines.h" class AdObject; +class AdMessage; class SearchThread final : public QThread { Q_OBJECT @@ -48,9 +49,12 @@ public: void stop(); int get_id() const; bool failed_to_connect() const; + bool hit_object_display_limit() const; + QList get_ad_messages() const; signals: void results_ready(const QHash &results); + void over_object_display_limit(); private: bool stop_flag; @@ -60,10 +64,15 @@ private: QList attributes; int id; bool m_failed_to_connect; + bool m_hit_object_display_limit; + QList ad_messages; void run() override; }; -void search_thread_error_log(QWidget *parent); +// Call this in your finished() slot to display any +// error dialogs. Search thread can't display them +// because it is run in non-GUI thread. +void search_thread_display_errors(SearchThread *thread, QWidget *parent); #endif /* SEARCH_THREAD_H */ diff --git a/src/admc/multi_tabs/properties_multi_tab.cpp b/src/admc/security_sort_warning_dialog.cpp similarity index 52% rename from src/admc/multi_tabs/properties_multi_tab.cpp rename to src/admc/security_sort_warning_dialog.cpp index 36cc1869..ed24f7bc 100644 --- a/src/admc/multi_tabs/properties_multi_tab.cpp +++ b/src/admc/security_sort_warning_dialog.cpp @@ -18,29 +18,22 @@ * along with this program. If not, see . */ -#include "multi_tabs/properties_multi_tab.h" +#include "security_sort_warning_dialog.h" +#include "ui_security_sort_warning_dialog.h" -#include "attribute_multi_edits/attribute_multi_edit.h" +SecuritySortWarningDialog::SecuritySortWarningDialog(QWidget *parent) +: QDialog(parent) { + ui = new Ui::SecuritySortWarningDialog(); + ui->setupUi(this); -bool PropertiesMultiTab::apply(AdInterface &ad, const QList &target_list) { - bool total_success = true; - for (AttributeMultiEdit *edit : edit_list) { - const bool success = edit->apply(ad, target_list); + setAttribute(Qt::WA_DeleteOnClose); - if (!success) { - total_success = false; - } - } + ui->label->setText(tr("This object's security descriptor contains ACL that has incorrect order. Fix order to proceed with editing. If order is not fixed, Security tab will be read only.")); - return total_success; + ui->button_box->addButton(tr("Fix order"), QDialogButtonBox::AcceptRole); + ui->button_box->addButton(tr("Cancel"), QDialogButtonBox::RejectRole); } -void PropertiesMultiTab::on_edit_edited() { - emit edited(); -} - -void PropertiesMultiTab::reset() { - for (AttributeMultiEdit *edit : edit_list) { - edit->reset(); - } +SecuritySortWarningDialog::~SecuritySortWarningDialog() { + delete ui; } diff --git a/src/admc/security_sort_warning_dialog.h b/src/admc/security_sort_warning_dialog.h new file mode 100644 index 00000000..097c30e3 --- /dev/null +++ b/src/admc/security_sort_warning_dialog.h @@ -0,0 +1,46 @@ +/* + * 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 SECURITY_SORT_WARNING_DIALOG +#define SECURITY_SORT_WARNING_DIALOG + +/** + * Dialog that warns about incorrect ACL sort order and + * offers to fix it. Opens when switching to security + * tab and incorrect order is detected. + */ + +#include + +namespace Ui { +class SecuritySortWarningDialog; +} + +class SecuritySortWarningDialog final : public QDialog { + Q_OBJECT + +public: + Ui::SecuritySortWarningDialog *ui; + + SecuritySortWarningDialog(QWidget *parent); + ~SecuritySortWarningDialog(); +}; + +#endif /* SECURITY_SORT_WARNING_DIALOG */ diff --git a/src/admc/security_sort_warning_dialog.ui b/src/admc/security_sort_warning_dialog.ui new file mode 100644 index 00000000..7efd0b1e --- /dev/null +++ b/src/admc/security_sort_warning_dialog.ui @@ -0,0 +1,74 @@ + + + SecuritySortWarningDialog + + + + 0 + 0 + 405 + 300 + + + + Warning + + + + + + PLACEHOLDER (actual text depends on context) + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::NoButton + + + + + + + + + button_box + accepted() + SecuritySortWarningDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + button_box + rejected() + SecuritySortWarningDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/admc/select_container_dialog.cpp b/src/admc/select_container_dialog.cpp index 5666b0b4..8d639413 100644 --- a/src/admc/select_container_dialog.cpp +++ b/src/admc/select_container_dialog.cpp @@ -37,7 +37,7 @@ QStandardItem *make_container_node(const AdObject &object); -SelectContainerDialog::SelectContainerDialog(QWidget *parent) +SelectContainerDialog::SelectContainerDialog(AdInterface &ad, QWidget *parent) : QDialog(parent) { ui = new Ui::SelectContainerDialog(); ui->setupUi(this); @@ -64,6 +64,12 @@ SelectContainerDialog::SelectContainerDialog(QWidget *parent) QPushButton *ok_button = ui->button_box->button(QDialogButtonBox::Ok); enable_widget_on_selection(ok_button, ui->view); + // Load head object + const QString head_dn = g_adconfig->domain_dn(); + const AdObject head_object = ad.search_object(head_dn); + QStandardItem *item = make_container_node(head_object); + model->appendRow(item); + // NOTE: geometry is shared with the subclass // MoveObjectDialog but that is intended. settings_setup_dialog_geometry(SETTING_select_container_dialog_geometry, this); @@ -77,27 +83,6 @@ SelectContainerDialog::~SelectContainerDialog() { delete ui; } -void SelectContainerDialog::open() { - AdInterface ad; - if (ad_failed(ad)) { - close(); - - return; - } - - model->removeRows(0, model->rowCount()); - - // Load head object - const QString head_dn = g_adconfig->domain_head(); - const AdObject head_object = ad.search_object(head_dn); - auto item = make_container_node(head_object); - model->appendRow(item); - - g_status->display_ad_messages(ad, this); - - QDialog::open(); -} - QString SelectContainerDialog::get_selected() const { const QModelIndex selected_index = ui->view->selectionModel()->currentIndex(); const QString dn = selected_index.data(ContainerRole_DN).toString(); @@ -109,7 +94,7 @@ void SelectContainerDialog::fetch_node(const QModelIndex &proxy_index) { const QModelIndex index = proxy_model->mapToSource(proxy_index); AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, this)) { return; } @@ -124,9 +109,7 @@ void SelectContainerDialog::fetch_node(const QModelIndex &proxy_index) { QString out; out = is_container_filter(); - - advanced_features_filter(out); - dev_mode_filter(out); + out = advanced_features_filter(out); return out; }(); diff --git a/src/admc/select_container_dialog.h b/src/admc/select_container_dialog.h index d0b7f442..d1858ec9 100644 --- a/src/admc/select_container_dialog.h +++ b/src/admc/select_container_dialog.h @@ -31,6 +31,7 @@ class QTreeView; class QStandardItemModel; class QSortFilterProxyModel; +class AdInterface; namespace Ui { class SelectContainerDialog; @@ -47,11 +48,9 @@ class SelectContainerDialog : public QDialog { public: Ui::SelectContainerDialog *ui; - SelectContainerDialog(QWidget *parent); + SelectContainerDialog(AdInterface &ad, QWidget *parent); ~SelectContainerDialog(); - void open() override; - QString get_selected() const; private: diff --git a/src/admc/select_object_dialog.cpp b/src/admc/select_object_dialog.cpp index 2bfad707..9bac41a7 100644 --- a/src/admc/select_object_dialog.cpp +++ b/src/admc/select_object_dialog.cpp @@ -57,6 +57,8 @@ SelectObjectDialog::SelectObjectDialog(const QList class_list_arg, cons ui->view->setModel(model); + enable_widget_on_selection(ui->remove_button, ui->view); + settings_setup_dialog_geometry(SETTING_select_object_dialog_geometry, this); settings_restore_header_state(SETTING_select_object_header_state, ui->view->header()); @@ -107,6 +109,13 @@ void SelectObjectDialog::accept() { const bool selected_multiple_when_single_selection = (multi_selection == SelectObjectDialogMultiSelection_No && selected.size() > 1); if (selected_multiple_when_single_selection) { message_box_warning(this, tr("Error"), tr("This selection accepts only one object. Remove extra objects to proceed.")); + } else if (selected.isEmpty()) { + // TODO: replace with "ok" button turning off + // if selection is empty. but the + // "selected_multiple_when_single_selection" + // should still be done via warning, otherwise + // would be confusing + message_box_warning(this, tr("Error"), tr("You must select at least one object.")); } else { QDialog::accept(); } @@ -118,7 +127,7 @@ void SelectObjectDialog::on_add_button() { } AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, this)) { return; } @@ -151,12 +160,12 @@ void SelectObjectDialog::on_add_button() { add_objects_to_list({dn}, ad); } else if (search_results.size() > 1) { // Open dialog where you can select one of the matches - auto dialog = new SelectObjectMatchDialog(this); - dialog->set_search_results(search_results); + auto dialog = new SelectObjectMatchDialog(search_results, this); dialog->open(); connect( dialog, &QDialog::accepted, + this, [this, dialog]() { const QList selected_matches = dialog->get_selected(); @@ -182,6 +191,7 @@ void SelectObjectDialog::open_advanced_dialog() { connect( dialog, &QDialog::accepted, + this, [this, dialog]() { const QList selected = dialog->get_selected_dns(); @@ -191,7 +201,7 @@ void SelectObjectDialog::open_advanced_dialog() { void SelectObjectDialog::add_objects_to_list(const QList &dn_list) { AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, this)) { return; } diff --git a/src/admc/select_object_match_dialog.cpp b/src/admc/select_object_match_dialog.cpp index ea3dc0e5..9719f730 100644 --- a/src/admc/select_object_match_dialog.cpp +++ b/src/admc/select_object_match_dialog.cpp @@ -23,11 +23,13 @@ #include "console_impls/object_impl.h" #include "select_object_dialog.h" +#include "utils.h" #include "settings.h" #include +#include -SelectObjectMatchDialog::SelectObjectMatchDialog(QWidget *parent) +SelectObjectMatchDialog::SelectObjectMatchDialog(const QHash &search_results, QWidget *parent) : QDialog(parent) { ui = new Ui::SelectObjectMatchDialog(); ui->setupUi(this); @@ -41,6 +43,13 @@ SelectObjectMatchDialog::SelectObjectMatchDialog(QWidget *parent) ui->view->sortByColumn(0, Qt::AscendingOrder); ui->view->setModel(model); + for (const AdObject &object : search_results) { + add_select_object_to_model(model, object); + } + + QPushButton *ok_button = ui->button_box->button(QDialogButtonBox::Ok); + enable_widget_on_selection(ok_button, ui->view); + settings_setup_dialog_geometry(SETTING_select_object_match_dialog_geometry, this); settings_restore_header_state(SETTING_select_object_match_header_state, ui->view->header()); } @@ -51,14 +60,6 @@ SelectObjectMatchDialog::~SelectObjectMatchDialog() { delete ui; } -void SelectObjectMatchDialog::set_search_results(const QHash &search_results) { - model->removeRows(0, model->rowCount()); - - for (const AdObject &object : search_results) { - add_select_object_to_model(model, object); - } -} - QList SelectObjectMatchDialog::get_selected() const { QList out; diff --git a/src/admc/select_object_match_dialog.h b/src/admc/select_object_match_dialog.h index 4789f95b..85c40cc9 100644 --- a/src/admc/select_object_match_dialog.h +++ b/src/admc/select_object_match_dialog.h @@ -41,10 +41,9 @@ class SelectObjectMatchDialog final : public QDialog { public: Ui::SelectObjectMatchDialog *ui; - SelectObjectMatchDialog(QWidget *parent); + SelectObjectMatchDialog(const QHash &search_results, QWidget *parent); ~SelectObjectMatchDialog(); - void set_search_results(const QHash &search_results); QList get_selected() const; private: diff --git a/src/admc/select_object_match_dialog.ui b/src/admc/select_object_match_dialog.ui index 9a304aac..0eb01ea4 100644 --- a/src/admc/select_object_match_dialog.ui +++ b/src/admc/select_object_match_dialog.ui @@ -35,7 +35,7 @@ - + Qt::Horizontal @@ -49,7 +49,7 @@ - buttonBox + button_box accepted() SelectObjectMatchDialog accept() @@ -65,7 +65,7 @@ - buttonBox + button_box rejected() SelectObjectMatchDialog reject() diff --git a/src/admc/select_policy_dialog.cpp b/src/admc/select_policy_dialog.cpp index c691bc5c..014d7ee0 100644 --- a/src/admc/select_policy_dialog.cpp +++ b/src/admc/select_policy_dialog.cpp @@ -31,7 +31,7 @@ #include #include -SelectPolicyDialog::SelectPolicyDialog(QWidget *parent) +SelectPolicyDialog::SelectPolicyDialog(AdInterface &ad, QWidget *parent) : QDialog(parent) { ui = new Ui::SelectPolicyDialog(); ui->setupUi(this); @@ -46,23 +46,7 @@ SelectPolicyDialog::SelectPolicyDialog(QWidget *parent) QPushButton *ok_button = ui->button_box->button(QDialogButtonBox::Ok); enable_widget_on_selection(ok_button, ui->view); - settings_setup_dialog_geometry(SETTING_select_policy_dialog_geometry, this); -} - -SelectPolicyDialog::~SelectPolicyDialog() { - delete ui; -} - -void SelectPolicyDialog::open() { - AdInterface ad; - if (ad_failed(ad)) { - close(); - return; - } - - model->removeRows(0, model->rowCount()); - - const QString base = g_adconfig->domain_head(); + const QString base = g_adconfig->domain_dn(); const SearchScope scope = SearchScope_All; const QString filter = filter_CONDITION(Condition_Equals, ATTRIBUTE_OBJECT_CLASS, CLASS_GP_CONTAINER); const QList attributes = console_policy_search_attributes(); @@ -76,7 +60,11 @@ void SelectPolicyDialog::open() { console_policy_load_item(item, object); } - QDialog::open(); + settings_setup_dialog_geometry(SETTING_select_policy_dialog_geometry, this); +} + +SelectPolicyDialog::~SelectPolicyDialog() { + delete ui; } QList SelectPolicyDialog::get_selected_dns() const { diff --git a/src/admc/select_policy_dialog.h b/src/admc/select_policy_dialog.h index ea2efb3a..5bb25c6f 100644 --- a/src/admc/select_policy_dialog.h +++ b/src/admc/select_policy_dialog.h @@ -24,6 +24,7 @@ #include class QStandardItemModel; +class AdInterface; namespace Ui { class SelectPolicyDialog; @@ -35,11 +36,9 @@ class SelectPolicyDialog final : public QDialog { public: Ui::SelectPolicyDialog *ui; - SelectPolicyDialog(QWidget *parent); + SelectPolicyDialog(AdInterface &ad, QWidget *parent); ~SelectPolicyDialog(); - void open() override; - QList get_selected_dns() const; private: diff --git a/src/admc/select_policy_dialog.ui b/src/admc/select_policy_dialog.ui index e4082a6d..49378851 100644 --- a/src/admc/select_policy_dialog.ui +++ b/src/admc/select_policy_dialog.ui @@ -15,7 +15,11 @@ - + + + QAbstractItemView::NoEditTriggers + + diff --git a/src/admc/settings.cpp b/src/admc/settings.cpp index 18382e48..1eb2ce0e 100644 --- a/src/admc/settings.cpp +++ b/src/admc/settings.cpp @@ -21,6 +21,7 @@ #include "settings.h" #include "config.h" +#include "connection_options_dialog.h" #include #include @@ -28,10 +29,9 @@ #include #include -const QHash bool_setting_default_map = { +const QHash setting_default_map = { {SETTING_advanced_features, false}, {SETTING_confirm_actions, true}, - {SETTING_dev_mode, false}, {SETTING_show_non_containers_in_console_tree, false}, {SETTING_last_name_before_first_name, []() { @@ -42,35 +42,29 @@ const QHash bool_setting_default_map = { return false; } }()}, - {SETTING_show_console_tree, true}, - {SETTING_show_results_header, true}, {SETTING_log_searches, false}, {SETTING_timestamp_log, true}, {SETTING_sasl_nocanon, true}, {SETTING_show_login, true}, + {SETTING_host, QString()}, + {SETTING_object_filter, QString()}, + {SETTING_object_filter_enabled, false}, + {SETTING_cert_strategy, CERT_STRATEGY_NEVER_define}, + {SETTING_object_display_limit, 1000}, + + {SETTING_feature_logon_computers, false}, + {SETTING_feature_profile_tab, false}, + {SETTING_feature_dev_mode, false}, + {SETTING_feature_current_locale_first, false}, }; -bool settings_get_bool(const QString setting) { - QSettings settings; - - const bool default_value = bool_setting_default_map.value(setting, false); - const bool value = settings.value(setting, default_value).toBool(); - - return value; -} - -void settings_set_bool(const QString setting, const bool value) { - QSettings settings; - - settings.setValue(setting, value); -} - void settings_setup_dialog_geometry(const QString setting, QDialog *dialog) { settings_restore_geometry(setting, dialog); QObject::connect( dialog, &QDialog::finished, - [=]() { + dialog, + [setting, dialog]() { const QByteArray geometry = dialog->saveGeometry(); settings_set_variant(setting, geometry); }); @@ -103,8 +97,11 @@ bool settings_restore_header_state(const QString setting, QHeaderView *header) { } } -QVariant settings_get_variant(const QString setting, const QVariant &default_value) { +QVariant settings_get_variant(const QString setting) { QSettings settings; + + const QVariant default_value = setting_default_map.value(setting, QVariant()); + const QVariant value = settings.value(setting, default_value); return value; diff --git a/src/admc/settings.h b/src/admc/settings.h index ee8d4f0a..c92132a1 100644 --- a/src/admc/settings.h +++ b/src/admc/settings.h @@ -89,6 +89,9 @@ DEFINE_SETTING(SETTING_datetime_attribute_dialog_geometry); DEFINE_SETTING(SETTING_list_attribute_dialog_geometry); DEFINE_SETTING(SETTING_octet_attribute_dialog_geometry); DEFINE_SETTING(SETTING_string_attribute_dialog_geometry); +DEFINE_SETTING(SETTING_fsmo_dialog_geometry); +DEFINE_SETTING(SETTING_create_shared_folder_dialog_geometry); +DEFINE_SETTING(SETTING_create_contact_dialog_geometry); // Header state DEFINE_SETTING(SETTING_results_header); @@ -97,7 +100,6 @@ DEFINE_SETTING(SETTING_attributes_tab_header_state); DEFINE_SETTING(SETTING_select_object_header_state); DEFINE_SETTING(SETTING_membership_tab_header_state); DEFINE_SETTING(SETTING_organization_tab_header_state); -DEFINE_SETTING(SETTING_gpo_links_tab_header_state); DEFINE_SETTING(SETTING_group_policy_tab_header_state); DEFINE_SETTING(SETTING_security_tab_header_state); DEFINE_SETTING(SETTING_select_object_match_header_state); @@ -105,15 +107,13 @@ DEFINE_SETTING(SETTING_select_object_match_header_state); // Bool DEFINE_SETTING(SETTING_advanced_features); DEFINE_SETTING(SETTING_confirm_actions); -DEFINE_SETTING(SETTING_dev_mode); DEFINE_SETTING(SETTING_show_non_containers_in_console_tree); DEFINE_SETTING(SETTING_last_name_before_first_name); -DEFINE_SETTING(SETTING_show_console_tree); -DEFINE_SETTING(SETTING_show_results_header); DEFINE_SETTING(SETTING_log_searches); DEFINE_SETTING(SETTING_timestamp_log); DEFINE_SETTING(SETTING_sasl_nocanon); DEFINE_SETTING(SETTING_show_login); +DEFINE_SETTING(SETTING_show_password); // Other DEFINE_SETTING(SETTING_host); @@ -125,15 +125,24 @@ DEFINE_SETTING(SETTING_cert_strategy); DEFINE_SETTING(SETTING_last_opened_version); DEFINE_SETTING(SETTING_object_filter); DEFINE_SETTING(SETTING_object_filter_enabled); +DEFINE_SETTING(SETTING_object_display_limit); -QVariant settings_get_variant(const QString setting, const QVariant &default_value = QVariant()); +// Feature flags +// +// NOTE: this set of settings is not editable anywhere +// within the app. Instead it should be manually edited +// by hand in ADMC.conf. If you want to forcefully +// enable it for a new version, set it manually in +// main.cpp so that it overwrites default value as well +// as any values defined in .conf file. +DEFINE_SETTING(SETTING_feature_logon_computers); +DEFINE_SETTING(SETTING_feature_profile_tab); +DEFINE_SETTING(SETTING_feature_dev_mode); +DEFINE_SETTING(SETTING_feature_current_locale_first); + +QVariant settings_get_variant(const QString setting); void settings_set_variant(const QString setting, const QVariant &value); -// NOTE: returns default value if it's defined in -// settings.cpp -bool settings_get_bool(const QString setting); -void settings_set_bool(const QString setting, const bool value); - // Does two things. First it restores previously saved // geometry, if it exists. Then it connects to dialogs // finished() signal so that it's geometry is saved when diff --git a/src/admc/status.cpp b/src/admc/status.cpp index 6f57685e..82e5bcc5 100644 --- a/src/admc/status.cpp +++ b/src/admc/status.cpp @@ -56,7 +56,7 @@ void Status::add_message(const QString &msg, const StatusType &type) { const QString timestamped_msg = QString("%1 %2").arg(timestamp, msg); - const bool timestamps_ON = settings_get_bool(SETTING_timestamp_log); + const bool timestamps_ON = settings_get_variant(SETTING_timestamp_log).toBool(); const QColor color = [type]() { switch (type) { @@ -93,14 +93,13 @@ void Status::add_message(const QString &msg, const StatusType &type) { m_message_log->setTextCursor(end_cursor); } -void Status::display_ad_messages(const AdInterface &ad, QWidget *parent) { +void Status::display_ad_messages(const QList &messages, QWidget *parent) { if (m_status_bar == nullptr || m_message_log == nullptr) { return; } // // Display all messages in status log // - const QList messages = ad.messages(); for (const AdMessage &message : messages) { const StatusType status_type = [message]() { switch (message.type()) { @@ -113,15 +112,19 @@ void Status::display_ad_messages(const AdInterface &ad, QWidget *parent) { add_message(message.text(), status_type); } - ad_error_log(ad, parent); + ad_error_log(messages, parent); } -void ad_error_log(const AdInterface &ad, QWidget *parent) { +void Status::display_ad_messages(const AdInterface &ad, QWidget *parent) { + const QList messages = ad.messages(); + + display_ad_messages(messages, parent); +} + +void ad_error_log(const QList &messages, QWidget *parent) { const QList error_list = [&]() { QList out; - const QList messages = ad.messages(); - for (const auto &message : messages) { if (message.type() == AdMessageType_Error) { out.append(message.text()); @@ -134,6 +137,12 @@ void ad_error_log(const AdInterface &ad, QWidget *parent) { error_log(error_list, parent); } +void ad_error_log(const AdInterface &ad, QWidget *parent) { + const QList messages = ad.messages(); + + ad_error_log(messages, parent); +} + void error_log(const QList error_list, QWidget *parent) { if (error_list.isEmpty()) { return; diff --git a/src/admc/status.h b/src/admc/status.h index 2874808a..d4605978 100644 --- a/src/admc/status.h +++ b/src/admc/status.h @@ -32,6 +32,7 @@ class QStatusBar; class QString; class QWidget; class AdInterface; +class AdMessage; template class QList; @@ -47,6 +48,7 @@ public: void add_message(const QString &msg, const StatusType &type); + void display_ad_messages(const QList &messages, QWidget *parent); void display_ad_messages(const AdInterface &ad, QWidget *parent); private: @@ -56,6 +58,7 @@ private: // Opens a dialog containing ad error messages in a // scrollable list. Nothing is done if no errors occured. +void ad_error_log(const QList &messages, QWidget *parent); void ad_error_log(const AdInterface &ad, QWidget *parent); void error_log(const QList error_list, QWidget *parent); diff --git a/src/admc/tab_widget.cpp b/src/admc/tab_widget.cpp index f46edf64..59348edb 100644 --- a/src/admc/tab_widget.cpp +++ b/src/admc/tab_widget.cpp @@ -21,11 +21,23 @@ #include "tab_widget.h" #include "ui_tab_widget.h" +// TODO: a lot of code has grown here that is used only +// by properties dialog. Other places(fsmo and multi +// properties) only use add_tab() and connection of +// list widget to stacked widget. Should separate those +// two into free f-ns. Properties dialog should +// implement all of the extra stuff inside it. UI files +// for users of this file will need to be updated to +// contain list/stacked widget. + TabWidget::TabWidget(QWidget *parent) : QWidget(parent) { ui = new Ui::TabWidget(); ui->setupUi(this); + auto_switch_tab = true; + ignore_current_row_signal = false; + connect( ui->list_widget, &QListWidget::currentRowChanged, this, &TabWidget::on_list_current_row_changed); @@ -35,17 +47,53 @@ TabWidget::~TabWidget() { delete ui; } +QWidget *TabWidget::get_tab(const int index) const { + QWidget *out = ui->stacked_widget->widget(index); + + return out; +} + +QWidget *TabWidget::get_current_tab() const { + QWidget *out = ui->stacked_widget->currentWidget(); + + return out; +} + +void TabWidget::set_current_tab(const int index) { + ignore_current_row_signal = true; + ui->list_widget->setCurrentRow(index, QItemSelectionModel::Clear | QItemSelectionModel::SelectCurrent); + ignore_current_row_signal = false; + ui->stacked_widget->setCurrentIndex(index); +} + void TabWidget::add_tab(QWidget *tab, const QString &title) { ui->list_widget->addItem(title); + ui->stacked_widget->addWidget(tab); } -void TabWidget::on_list_current_row_changed(int index) { - QWidget *prev_tab = ui->stacked_widget->currentWidget(); - - ui->stacked_widget->setCurrentIndex(index); - - QWidget *new_tab = ui->stacked_widget->currentWidget(); - - emit current_changed(prev_tab, new_tab); +void TabWidget::enable_auto_switch_tab(const bool enabled) { + auto_switch_tab = enabled; +} + +void TabWidget::on_list_current_row_changed(int index) { + if (ignore_current_row_signal) { + return; + } + + // NOTE: since tab wasn't change yet in stacked + // widget, we may use it to get previous tab index + const int prev = ui->stacked_widget->currentIndex(); + + if (auto_switch_tab) { + ui->stacked_widget->setCurrentIndex(index); + } else { + // Restore prev index so current in list widget + // is the same as in stacked widget + ignore_current_row_signal = true; + ui->list_widget->setCurrentRow(prev, QItemSelectionModel::Clear | QItemSelectionModel::SelectCurrent); + ignore_current_row_signal = false; + } + + emit current_changed(prev, index); } diff --git a/src/admc/tab_widget.h b/src/admc/tab_widget.h index 4da9c833..d5b44221 100644 --- a/src/admc/tab_widget.h +++ b/src/admc/tab_widget.h @@ -42,13 +42,26 @@ public: TabWidget(QWidget *parent = nullptr); ~TabWidget(); + QWidget *get_tab(const int index) const; + QWidget *get_current_tab() const; + void set_current_tab(const int index); void add_tab(QWidget *tab, const QString &title); + // By default tab is automatically switches when + // user selects different tab in tab list. If this + // is disabled, then parent widget needs to connect + // to current_changed() signal and manually change + // tab using set_current_tab(). This is useful in + // cases where you want to forbid switching tabs. + void enable_auto_switch_tab(const bool enabled); + signals: - void current_changed(QWidget *prev_tab, QWidget *new_tab); + void current_changed(const int prev, const int current); private: void on_list_current_row_changed(int index); + bool auto_switch_tab; + bool ignore_current_row_signal; }; #endif /* TAB_WIDGET_H */ diff --git a/src/admc/tabs/account_tab.cpp b/src/admc/tabs/account_tab.cpp index 325da19d..fc302be0 100644 --- a/src/admc/tabs/account_tab.cpp +++ b/src/admc/tabs/account_tab.cpp @@ -28,44 +28,59 @@ #include "attribute_edits/string_edit.h" #include "attribute_edits/unlock_edit.h" #include "attribute_edits/upn_edit.h" +#include "settings.h" // NOTE: the "can't change password" checkbox does not // affect the permission in the security tab, even though // they control the same thing. And vice versa. Too // complicated to implement so this is a WONTFIX. -AccountTab::AccountTab(AdInterface &ad) { +AccountTab::AccountTab(AdInterface &ad, QList *edit_list, QWidget *parent) +: QWidget(parent) { ui = new Ui::AccountTab(); ui->setupUi(this); - auto upn_edit = new UpnEdit(ui->upn_prefix_edit, ui->upn_suffix_edit, &edits, this); + auto upn_edit = new UpnEdit(ui->upn_prefix_edit, ui->upn_suffix_edit, this); upn_edit->init_suffixes(ad); - new UnlockEdit(ui->unlock_check, &edits, this); - new ExpiryEdit(ui->expiry_widget, &edits, this); - new LogonHoursEdit(ui->logon_hours_button, &edits, this); - new LogonComputersEdit(ui->logon_computers_button, &edits, this); + auto unlock_edit = new UnlockEdit(ui->unlock_check, this); + auto expiry_widget_edit = new ExpiryEdit(ui->expiry_widget, this); + auto logon_hours_edit = new LogonHoursEdit(ui->logon_hours_button, this); + auto logon_computers_edit = new LogonComputersEdit(ui->logon_computers_button, this); const QHash check_map = { {AccountOption_Disabled, ui->disabled_check}, {AccountOption_CantChangePassword, ui->cant_change_pass_check}, {AccountOption_PasswordExpired, ui->pass_expired_check}, {AccountOption_DontExpirePassword, ui->dont_expire_pass_check}, - {AccountOption_UseDesKey, ui->des_key_check}, + {AccountOption_AllowReversibleEncryption, ui->reversible_encrypt_check}, {AccountOption_SmartcardRequired, ui->smartcard_check}, {AccountOption_CantDelegate, ui->cant_delegate_check}, + {AccountOption_UseDesKey, ui->des_key_check}, {AccountOption_DontRequirePreauth, ui->require_preauth_check}, - {AccountOption_TrustedForDelegation, ui->trusted_check}, }; for (const AccountOption &option : check_map.keys()) { QCheckBox *check = check_map[option]; - new AccountOptionEdit(check, option, &edits, this); + auto edit = new AccountOptionEdit(check, option, this); + + edit_list->append(edit); } account_option_setup_conflicts(check_map); - edits_connect_to_tab(edits, this); + edit_list->append({ + upn_edit, + unlock_edit, + expiry_widget_edit, + logon_hours_edit, + logon_computers_edit, + }); + + const bool logon_computers_enabled = settings_get_variant(SETTING_feature_logon_computers).toBool(); + if (!logon_computers_enabled) { + ui->logon_computers_button->hide(); + } } AccountTab::~AccountTab() { diff --git a/src/admc/tabs/account_tab.h b/src/admc/tabs/account_tab.h index 6459e730..5ec49a33 100644 --- a/src/admc/tabs/account_tab.h +++ b/src/admc/tabs/account_tab.h @@ -21,21 +21,22 @@ #ifndef ACCOUNT_TAB_H #define ACCOUNT_TAB_H -#include "tabs/properties_tab.h" +#include class AdInterface; +class AttributeEdit; namespace Ui { class AccountTab; } -class AccountTab final : public PropertiesTab { +class AccountTab final : public QWidget { Q_OBJECT public: Ui::AccountTab *ui; - AccountTab(AdInterface &ad); + AccountTab(AdInterface &ad, QList *edit_list, QWidget *parent); ~AccountTab(); }; diff --git a/src/admc/tabs/account_tab.ui b/src/admc/tabs/account_tab.ui index 412dd700..48ca4b86 100644 --- a/src/admc/tabs/account_tab.ui +++ b/src/admc/tabs/account_tab.ui @@ -6,8 +6,8 @@ 0 0 - 431 - 619 + 486 + 581 @@ -168,7 +168,7 @@ - + Store password using reversible encryption @@ -189,16 +189,16 @@ - + - Don't require Kerberos pre-authentication + Use Kerberos DES encryption types for this account - + - Trusted for delegation + Don't require Kerberos pre-authentication diff --git a/src/admc/tabs/address_tab.cpp b/src/admc/tabs/address_tab.cpp index e8406e5d..7fd15f97 100644 --- a/src/admc/tabs/address_tab.cpp +++ b/src/admc/tabs/address_tab.cpp @@ -26,20 +26,29 @@ #include "attribute_edits/string_edit.h" #include "attribute_edits/string_large_edit.h" -AddressTab::AddressTab() { +AddressTab::AddressTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { ui = new Ui::AddressTab(); ui->setupUi(this); - new StringLargeEdit(ui->street_edit, ATTRIBUTE_STREET, &edits, this); + auto street_edit = new StringLargeEdit(ui->street_edit, ATTRIBUTE_STREET, this); - new StringEdit(ui->po_box_edit, ATTRIBUTE_PO_BOX, &edits, this); - new StringEdit(ui->city_edit, ATTRIBUTE_CITY, &edits, this); - new StringEdit(ui->state_edit, ATTRIBUTE_STATE, &edits, this); - new StringEdit(ui->postal_code_edit, ATTRIBUTE_POSTAL_CODE, &edits, this); + auto po_box_edit = new StringEdit(ui->po_box_edit, ATTRIBUTE_PO_BOX, this); + auto city_edit = new StringEdit(ui->city_edit, ATTRIBUTE_CITY, this); + auto state_edit = new StringEdit(ui->state_edit, ATTRIBUTE_STATE, this); + auto postal_code_edit = new StringEdit(ui->postal_code_edit, ATTRIBUTE_POSTAL_CODE, this); - new CountryEdit(ui->country_combo, &edits, this); + auto country_edit = new CountryEdit(ui->country_combo, this); - edits_connect_to_tab(edits, this); + edit_list->append({ + street_edit, + po_box_edit, + city_edit, + state_edit, + street_edit, + postal_code_edit, + country_edit, + }); } AddressTab::~AddressTab() { diff --git a/src/admc/tabs/address_tab.h b/src/admc/tabs/address_tab.h index e3133a06..57255960 100644 --- a/src/admc/tabs/address_tab.h +++ b/src/admc/tabs/address_tab.h @@ -21,20 +21,22 @@ #ifndef ADDRESS_TAB_H #define ADDRESS_TAB_H -#include "tabs/properties_tab.h" +#include + +class AttributeEdit; namespace Ui { class AddressTab; } // Address related attributes -class AddressTab final : public PropertiesTab { +class AddressTab final : public QWidget { Q_OBJECT public: Ui::AddressTab *ui; - AddressTab(); + AddressTab(QList *edit_list, QWidget *parent); ~AddressTab(); }; diff --git a/src/admc/tabs/attributes_tab.cpp b/src/admc/tabs/attributes_tab.cpp index 41017b59..45e091d5 100644 --- a/src/admc/tabs/attributes_tab.cpp +++ b/src/admc/tabs/attributes_tab.cpp @@ -41,10 +41,25 @@ QString attribute_type_display_string(const AttributeType type); -AttributesTab::AttributesTab() { +AttributesTab::AttributesTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { ui = new Ui::AttributesTab(); ui->setupUi(this); + auto tab_edit = new AttributesTabEdit(ui->view, ui->filter_button, ui->edit_button, ui->view_button, this); + + edit_list->append({ + tab_edit, + }); +} + +AttributesTabEdit::AttributesTabEdit(QTreeView *view_arg, QPushButton *filter_button_arg, QPushButton *edit_button_arg, QPushButton *view_button_arg, QObject *parent) +: AttributeEdit(parent) { + view = view_arg; + filter_button = filter_button_arg; + edit_button = edit_button_arg; + view_button = view_button_arg; + model = new QStandardItemModel(0, AttributesColumn_COUNT, this); set_horizontal_header_labels_from_map(model, { @@ -53,39 +68,43 @@ AttributesTab::AttributesTab() { {AttributesColumn_Type, tr("Type")} }); - auto filter_menu = new AttributesTabFilterMenu(this); + auto filter_menu = new AttributesTabFilterMenu(view); proxy = new AttributesTabProxy(filter_menu, this); proxy->setSourceModel(model); - ui->view->setModel(proxy); + view->setModel(proxy); - ui->filter_button->setMenu(filter_menu); + filter_button->setMenu(filter_menu); - enable_widget_on_selection(ui->edit_button, ui->view); + enable_widget_on_selection(edit_button, view); - settings_restore_header_state(SETTING_attributes_tab_header_state, ui->view->header()); + settings_restore_header_state(SETTING_attributes_tab_header_state, view->header()); const QHash state = settings_get_variant(SETTING_attributes_tab_header_state).toHash(); - ui->view->header()->restoreState(state["header"].toByteArray()); + // This is the default sort, overriden by saved + // sort when state is restored + view->sortByColumn(AttributesColumn_Name, Qt::AscendingOrder); - QItemSelectionModel *selection_model = ui->view->selectionModel(); + view->header()->restoreState(state["header"].toByteArray()); + + QItemSelectionModel *selection_model = view->selectionModel(); connect( selection_model, &QItemSelectionModel::selectionChanged, - this, &AttributesTab::update_edit_and_view_buttons); + this, &AttributesTabEdit::update_edit_and_view_buttons); update_edit_and_view_buttons(); connect( - ui->view, &QAbstractItemView::doubleClicked, - this, &AttributesTab::on_double_click); + view, &QAbstractItemView::doubleClicked, + this, &AttributesTabEdit::on_double_click); connect( - ui->edit_button, &QAbstractButton::clicked, - this, &AttributesTab::edit_attribute); + edit_button, &QAbstractButton::clicked, + this, &AttributesTabEdit::edit_attribute); connect( - ui->view_button, &QAbstractButton::clicked, - this, &AttributesTab::view_attribute); + view_button, &QAbstractButton::clicked, + this, &AttributesTabEdit::view_attribute); connect( filter_menu, &AttributesTabFilterMenu::filter_changed, proxy, &AttributesTabProxy::invalidate); @@ -97,8 +116,8 @@ AttributesTab::~AttributesTab() { delete ui; } -QList AttributesTab::get_selected_row() const { - const QItemSelectionModel *selection_model = ui->view->selectionModel(); +QList AttributesTabEdit::get_selected_row() const { + const QItemSelectionModel *selection_model = view->selectionModel(); const QList selecteds = selection_model->selectedRows(); if (selecteds.isEmpty()) { @@ -121,37 +140,37 @@ QList AttributesTab::get_selected_row() const { return row; } -void AttributesTab::update_edit_and_view_buttons() { +void AttributesTabEdit::update_edit_and_view_buttons() { const QList selected_row = get_selected_row(); const bool no_selection = selected_row.isEmpty(); if (no_selection) { - ui->edit_button->setVisible(true); - ui->edit_button->setEnabled(false); + edit_button->setVisible(true); + edit_button->setEnabled(false); - ui->view_button->setVisible(false); - ui->view_button->setEnabled(false); + view_button->setVisible(false); + view_button->setEnabled(false); } else { const QString attribute = selected_row[AttributesColumn_Name]->text(); const bool read_only = g_adconfig->get_attribute_is_system_only(attribute); if (read_only) { - ui->edit_button->setVisible(false); - ui->edit_button->setEnabled(false); + edit_button->setVisible(false); + edit_button->setEnabled(false); - ui->view_button->setVisible(true); - ui->view_button->setEnabled(true); + view_button->setVisible(true); + view_button->setEnabled(true); } else { - ui->edit_button->setVisible(true); - ui->edit_button->setEnabled(true); + edit_button->setVisible(true); + edit_button->setEnabled(true); - ui->view_button->setVisible(false); - ui->view_button->setEnabled(false); + view_button->setVisible(false); + view_button->setEnabled(false); } } } -void AttributesTab::on_double_click() { +void AttributesTabEdit::on_double_click() { const QList selected_row = get_selected_row(); const QString attribute = selected_row[AttributesColumn_Name]->text(); const bool read_only = g_adconfig->get_attribute_is_system_only(attribute); @@ -163,7 +182,7 @@ void AttributesTab::on_double_click() { } } -void AttributesTab::view_attribute() { +void AttributesTabEdit::view_attribute() { const bool read_only = true; AttributeDialog *dialog = get_attribute_dialog(read_only); if (dialog == nullptr) { @@ -173,7 +192,7 @@ void AttributesTab::view_attribute() { dialog->open(); } -void AttributesTab::edit_attribute() { +void AttributesTabEdit::edit_attribute() { const bool read_only = false; AttributeDialog *dialog = get_attribute_dialog(read_only); if (dialog == nullptr) { @@ -184,6 +203,7 @@ void AttributesTab::edit_attribute() { connect( dialog, &QDialog::accepted, + this, [this, dialog]() { const QList row = get_selected_row(); @@ -197,11 +217,12 @@ void AttributesTab::edit_attribute() { current[attribute] = new_value_list; load_row(row, attribute, new_value_list); + // TODO: fix emit edited(); }); } -void AttributesTab::load(AdInterface &ad, const AdObject &object) { +void AttributesTabEdit::load(AdInterface &ad, const AdObject &object) { UNUSED_ARG(ad); original.clear(); @@ -234,7 +255,7 @@ void AttributesTab::load(AdInterface &ad, const AdObject &object) { } } -bool AttributesTab::apply(AdInterface &ad, const QString &target) { +bool AttributesTabEdit::apply(AdInterface &ad, const QString &target) const { bool total_success = true; for (const QString &attribute : current.keys()) { @@ -243,9 +264,7 @@ bool AttributesTab::apply(AdInterface &ad, const QString &target) { if (current_values != original_values) { const bool success = ad.attribute_replace_values(target, attribute, current_values); - if (success) { - original[attribute] = current_values; - } else { + if (!success) { total_success = false; } } @@ -254,7 +273,7 @@ bool AttributesTab::apply(AdInterface &ad, const QString &target) { return total_success; } -void AttributesTab::load_row(const QList &row, const QString &attribute, const QList &values) { +void AttributesTabEdit::load_row(const QList &row, const QString &attribute, const QList &values) { const QString display_values = attribute_display_values(attribute, values, g_adconfig); const AttributeType type = g_adconfig->get_attribute_type(attribute); const QString type_display = attribute_type_display_string(type); @@ -266,7 +285,7 @@ void AttributesTab::load_row(const QList &row, const QString &a // Return an appropriate attribute dialog for currently // selected attribute row. -AttributeDialog *AttributesTab::get_attribute_dialog(const bool read_only) { +AttributeDialog *AttributesTabEdit::get_attribute_dialog(const bool read_only) { const QList row = get_selected_row(); if (row.isEmpty()) { @@ -281,31 +300,31 @@ AttributeDialog *AttributesTab::get_attribute_dialog(const bool read_only) { // switch statement for better flow auto octet_attribute_dialog = [&]() -> AttributeDialog * { if (single_valued) { - return new OctetAttributeDialog(value_list, attribute, read_only, this); + return new OctetAttributeDialog(value_list, attribute, read_only, view); } else { - return new ListAttributeDialog(value_list, attribute, read_only, this); + return new ListAttributeDialog(value_list, attribute, read_only, view); } }; auto string_attribute_dialog = [&]() -> AttributeDialog * { if (single_valued) { - return new StringAttributeDialog(value_list, attribute, read_only, this); + return new StringAttributeDialog(value_list, attribute, read_only, view); } else { - return new ListAttributeDialog(value_list, attribute, read_only, this); + return new ListAttributeDialog(value_list, attribute, read_only, view); } }; auto bool_attribute_dialog = [&]() -> AttributeDialog * { if (single_valued) { - return new BoolAttributeDialog(value_list, attribute, read_only, this); + return new BoolAttributeDialog(value_list, attribute, read_only, view); } else { - return new ListAttributeDialog(value_list, attribute, read_only, this); + return new ListAttributeDialog(value_list, attribute, read_only, view); } }; auto datetime_attribute_dialog = [&]() -> AttributeDialog * { if (single_valued) { - return new DatetimeAttributeDialog(value_list, attribute, read_only, this); + return new DatetimeAttributeDialog(value_list, attribute, read_only, view); } else { return nullptr; } @@ -313,32 +332,58 @@ AttributeDialog *AttributesTab::get_attribute_dialog(const bool read_only) { const AttributeType type = g_adconfig->get_attribute_type(attribute); - switch (type) { - case AttributeType_Octet: return octet_attribute_dialog(); - case AttributeType_Sid: return octet_attribute_dialog(); + AttributeDialog *dialog = [&]() -> AttributeDialog * { + switch (type) { + case AttributeType_Octet: return octet_attribute_dialog(); + case AttributeType_Sid: return octet_attribute_dialog(); - case AttributeType_Boolean: return bool_attribute_dialog(); + case AttributeType_Boolean: return bool_attribute_dialog(); - case AttributeType_Unicode: return string_attribute_dialog(); - case AttributeType_StringCase: return string_attribute_dialog(); - case AttributeType_DSDN: return string_attribute_dialog(); - case AttributeType_IA5: return string_attribute_dialog(); - case AttributeType_Teletex: return string_attribute_dialog(); - case AttributeType_ObjectIdentifier: return string_attribute_dialog(); - case AttributeType_Integer: return string_attribute_dialog(); - case AttributeType_Enumeration: return string_attribute_dialog(); - case AttributeType_LargeInteger: return string_attribute_dialog(); - case AttributeType_UTCTime: return datetime_attribute_dialog(); - case AttributeType_GeneralizedTime: return datetime_attribute_dialog(); - case AttributeType_NTSecDesc: return string_attribute_dialog(); - case AttributeType_Numeric: return string_attribute_dialog(); - case AttributeType_Printable: return string_attribute_dialog(); - case AttributeType_DNString: return string_attribute_dialog(); + case AttributeType_Unicode: return string_attribute_dialog(); + case AttributeType_StringCase: return string_attribute_dialog(); + case AttributeType_DSDN: return string_attribute_dialog(); + case AttributeType_IA5: return string_attribute_dialog(); + case AttributeType_Teletex: return string_attribute_dialog(); + case AttributeType_ObjectIdentifier: return string_attribute_dialog(); + case AttributeType_Integer: return string_attribute_dialog(); + case AttributeType_Enumeration: return string_attribute_dialog(); + case AttributeType_LargeInteger: return string_attribute_dialog(); + case AttributeType_UTCTime: return datetime_attribute_dialog(); + case AttributeType_GeneralizedTime: return datetime_attribute_dialog(); + case AttributeType_NTSecDesc: return octet_attribute_dialog(); + case AttributeType_Numeric: return string_attribute_dialog(); + case AttributeType_Printable: return string_attribute_dialog(); + case AttributeType_DNString: return string_attribute_dialog(); - // NOTE: putting these here as confirmed to be unsupported - case AttributeType_ReplicaLink: return nullptr; - case AttributeType_DNBinary: return nullptr; + // NOTE: putting these here as confirmed to be unsupported + case AttributeType_ReplicaLink: return nullptr; + case AttributeType_DNBinary: return nullptr; + } + + return nullptr; + }(); + + const QString title = [&]() { + const QString title_action = [&]() { + if (read_only) { + return tr("View"); + } else { + return tr("Edit"); + } + }(); + + const QString title_attribute = attribute_type_display_string(type); + + if (single_valued) { + return QString("%1 %2").arg(title_action, title_attribute); + } else { + return QString(tr("%1 Multi-Valued %2", "This is a dialog title for attribute editors. Example: \"Edit Multi-Valued String\"")).arg(title_action, title_attribute); + } + }(); + + if (dialog != nullptr) { + dialog->setWindowTitle(title); } - return nullptr; + return dialog; } diff --git a/src/admc/tabs/attributes_tab.h b/src/admc/tabs/attributes_tab.h index f61d2fd7..6665749a 100644 --- a/src/admc/tabs/attributes_tab.h +++ b/src/admc/tabs/attributes_tab.h @@ -26,7 +26,8 @@ * viewing/editing if possible via attribute dialogs. */ -#include "tabs/properties_tab.h" +#include +#include "attribute_edits/attribute_edit.h" enum AttributesColumn { AttributesColumn_Name, @@ -40,24 +41,38 @@ class QStandardItem; class AttributesTabProxy; class AttributesFilterDialog; class AttributeDialog; +class AttributesTabEdit; +class QTreeView; +class QPushButton; namespace Ui { class AttributesTab; } -class AttributesTab final : public PropertiesTab { +class AttributesTab final : public QWidget { Q_OBJECT public: Ui::AttributesTab *ui; - AttributesTab(); + AttributesTab(QList *edit_list, QWidget *parent); ~AttributesTab(); +}; + +class AttributesTabEdit final : public AttributeEdit { + Q_OBJECT + +public: + AttributesTabEdit(QTreeView *view, QPushButton *filter_button, QPushButton *edit_button, QPushButton *view_button, QObject *parent); void load(AdInterface &ad, const AdObject &object) override; - bool apply(AdInterface &ad, const QString &target) override; + bool apply(AdInterface &ad, const QString &dn) const override; private: + QTreeView *view; + QPushButton *filter_button; + QPushButton *edit_button; + QPushButton *view_button; AttributesFilterDialog *filter_dialog; QStandardItemModel *model; AttributesTabProxy *proxy; diff --git a/src/admc/tabs/delegation_tab.cpp b/src/admc/tabs/delegation_tab.cpp index a34e324b..a5975a13 100644 --- a/src/admc/tabs/delegation_tab.cpp +++ b/src/admc/tabs/delegation_tab.cpp @@ -23,15 +23,17 @@ #include "adldap.h" #include "attribute_edits/delegation_edit.h" -#include "globals.h" -DelegationTab::DelegationTab() { +DelegationTab::DelegationTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { ui = new Ui::DelegationTab(); ui->setupUi(this); - new DelegationEdit(ui->off_button, ui->on_button, &edits, this); + auto tab_edit = new DelegationEdit(ui->off_button, ui->on_button, this); - edits_connect_to_tab(edits, this); + edit_list->append({ + tab_edit, + }); } DelegationTab::~DelegationTab() { diff --git a/src/admc/tabs/delegation_tab.h b/src/admc/tabs/delegation_tab.h index c120493f..1b61e5c0 100644 --- a/src/admc/tabs/delegation_tab.h +++ b/src/admc/tabs/delegation_tab.h @@ -21,19 +21,21 @@ #ifndef DELEGATION_TAB_H #define DELEGATION_TAB_H -#include "tabs/properties_tab.h" +#include + +class AttributeEdit; namespace Ui { class DelegationTab; } -class DelegationTab final : public PropertiesTab { +class DelegationTab final : public QWidget { Q_OBJECT public: Ui::DelegationTab *ui; - DelegationTab(); + DelegationTab(QList *edit_list, QWidget *parent); ~DelegationTab(); }; diff --git a/src/admc/tabs/error_tab.cpp b/src/admc/tabs/error_tab.cpp new file mode 100644 index 00000000..3e13b086 --- /dev/null +++ b/src/admc/tabs/error_tab.cpp @@ -0,0 +1,32 @@ +/* + * 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 "tabs/error_tab.h" +#include "tabs/ui_error_tab.h" + +ErrorTab::ErrorTab(QWidget *parent) +: QWidget(parent) { + ui = new Ui::ErrorTab(); + ui->setupUi(this); +} + +ErrorTab::~ErrorTab() { + delete ui; +} diff --git a/src/admc/attribute_multi_edits/expiry_multi_edit.h b/src/admc/tabs/error_tab.h similarity index 64% rename from src/admc/attribute_multi_edits/expiry_multi_edit.h rename to src/admc/tabs/error_tab.h index f16889c8..c6ecf250 100644 --- a/src/admc/attribute_multi_edits/expiry_multi_edit.h +++ b/src/admc/tabs/error_tab.h @@ -18,23 +18,23 @@ * along with this program. If not, see . */ -#ifndef EXPIRY_MULTI_EDIT_H -#define EXPIRY_MULTI_EDIT_H +#ifndef ERROR_TAB_H +#define ERROR_TAB_H -#include "attribute_multi_edits/attribute_multi_edit.h" +#include -class ExpiryWidget; -class QCheckBox; +namespace Ui { +class ErrorTab; +} -class ExpiryMultiEdit final : public AttributeMultiEdit { +class ErrorTab final : public QWidget { Q_OBJECT + public: - ExpiryMultiEdit(ExpiryWidget *widget, QCheckBox *check, QList &edits_out, QObject *parent); + Ui::ErrorTab *ui; - DECL_ATTRIBUTE_MULTI_EDIT_VIRTUALS(); - -private: - ExpiryWidget *edit_widget; + ErrorTab(QWidget *parent); + ~ErrorTab(); }; -#endif /* EXPIRY_MULTI_EDIT_H */ +#endif /* ERROR_TAB_H */ diff --git a/src/admc/tabs/gpo_links_tab.ui b/src/admc/tabs/error_tab.ui similarity index 53% rename from src/admc/tabs/gpo_links_tab.ui rename to src/admc/tabs/error_tab.ui index 35935ca2..1593c8e4 100644 --- a/src/admc/tabs/gpo_links_tab.ui +++ b/src/admc/tabs/error_tab.ui @@ -1,7 +1,7 @@ - GpoLinksTab - + ErrorTab + 0 @@ -15,17 +15,11 @@ - - - Qt::CustomContextMenu + + + Failed to load object information. Check your connection. - - QAbstractItemView::NoEditTriggers - - - true - - + true diff --git a/src/admc/tabs/general_computer_tab.cpp b/src/admc/tabs/general_computer_tab.cpp index 483304c5..5f0f4e8a 100644 --- a/src/admc/tabs/general_computer_tab.cpp +++ b/src/admc/tabs/general_computer_tab.cpp @@ -22,25 +22,31 @@ #include "tabs/ui_general_computer_tab.h" #include "adldap.h" -#include "attribute_edits/sam_name_edit.h" +#include "attribute_edits/general_name_edit.h" +#include "attribute_edits/computer_sam_name_edit.h" #include "attribute_edits/string_edit.h" -#include "tabs/general_other_tab.h" -GeneralComputerTab::GeneralComputerTab(const AdObject &object) { +GeneralComputerTab::GeneralComputerTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { ui = new Ui::GeneralComputerTab(); ui->setupUi(this); - load_name_label(ui->name_label, object); + auto name_edit = new GeneralNameEdit(ui->name_label, this); + auto sam_name_edit = new ComputerSamNameEdit(ui->sam_name_edit, ui->sam_name_domain_edit, this); + auto dns_edit = new StringEdit(ui->dns_host_name_edit, ATTRIBUTE_DNS_HOST_NAME, this); + auto description_edit = new StringEdit(ui->description_edit, ATTRIBUTE_DESCRIPTION, this); + auto location_edit = new StringEdit(ui->location_edit, ATTRIBUTE_LOCATION, this); - auto sam_name_edit = new SamNameEdit(ui->sam_name_edit, ui->sam_name_domain_edit, &edits, this); - auto dns_edit = new StringEdit(ui->dns_host_name_edit, ATTRIBUTE_DNS_HOST_NAME, &edits, this); - new StringEdit(ui->description_edit, ATTRIBUTE_DESCRIPTION, &edits, this); - new StringEdit(ui->location_edit, ATTRIBUTE_LOCATION, &edits, this); + sam_name_edit->set_enabled(false); + dns_edit->set_enabled(false); - sam_name_edit->set_read_only(true); - dns_edit->set_read_only(true); - - edits_connect_to_tab(edits, this); + edit_list->append({ + name_edit, + sam_name_edit, + dns_edit, + description_edit, + location_edit, + }); } GeneralComputerTab::~GeneralComputerTab() { diff --git a/src/admc/tabs/general_computer_tab.h b/src/admc/tabs/general_computer_tab.h index 82bca12a..c7adb45a 100644 --- a/src/admc/tabs/general_computer_tab.h +++ b/src/admc/tabs/general_computer_tab.h @@ -21,19 +21,22 @@ #ifndef GENERAL_COMPUTER_TAB_H #define GENERAL_COMPUTER_TAB_H -#include "tabs/properties_tab.h" +#include + +class AttributeEdit; +class AdObject; namespace Ui { class GeneralComputerTab; } -class GeneralComputerTab final : public PropertiesTab { +class GeneralComputerTab final : public QWidget { Q_OBJECT public: Ui::GeneralComputerTab *ui; - GeneralComputerTab(const AdObject &object); + GeneralComputerTab(QList *edit_list, QWidget *parent); ~GeneralComputerTab(); }; diff --git a/src/admc/tabs/general_computer_tab.ui b/src/admc/tabs/general_computer_tab.ui index 7a36cb45..eaf58386 100644 --- a/src/admc/tabs/general_computer_tab.ui +++ b/src/admc/tabs/general_computer_tab.ui @@ -45,7 +45,11 @@ - + + + true + + @@ -77,7 +81,11 @@ - + + + true + + diff --git a/src/admc/tabs/general_group_tab.cpp b/src/admc/tabs/general_group_tab.cpp index f6b04c6e..30e150b9 100644 --- a/src/admc/tabs/general_group_tab.cpp +++ b/src/admc/tabs/general_group_tab.cpp @@ -22,33 +22,35 @@ #include "tabs/ui_general_group_tab.h" #include "adldap.h" +#include "attribute_edits/general_name_edit.h" #include "attribute_edits/group_scope_edit.h" #include "attribute_edits/group_type_edit.h" #include "attribute_edits/sam_name_edit.h" #include "attribute_edits/string_edit.h" -#include "tabs/general_other_tab.h" -GeneralGroupTab::GeneralGroupTab(const AdObject &object) { +GeneralGroupTab::GeneralGroupTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { ui = new Ui::GeneralGroupTab(); ui->setupUi(this); - load_name_label(ui->name_label, object); + auto name_edit = new GeneralNameEdit(ui->name_label, this); + auto sam_name_edit = new SamNameEdit(ui->sam_name_edit, ui->sam_name_domain_edit, this); + auto description_edit = new StringEdit(ui->description_edit, ATTRIBUTE_DESCRIPTION, this); + auto email_edit = new StringEdit(ui->email_edit, ATTRIBUTE_MAIL, this); + auto notes_edit = new StringEdit(ui->notes_edit, ATTRIBUTE_INFO, this); - new SamNameEdit(ui->sam_name_edit, ui->sam_name_domain_edit, &edits, this); - new StringEdit(ui->description_edit, ATTRIBUTE_DESCRIPTION, &edits, this); - new StringEdit(ui->email_edit, ATTRIBUTE_MAIL, &edits, this); - new StringEdit(ui->notes_edit, ATTRIBUTE_INFO, &edits, this); + auto scope_edit = new GroupScopeEdit(ui->scope_combo, this); + auto type_edit = new GroupTypeEdit(ui->type_combo, this); - auto scope_edit = new GroupScopeEdit(ui->scope_combo, &edits, this); - auto type_edit = new GroupTypeEdit(ui->type_combo, &edits, this); - - const bool is_critical_system_object = object.get_bool(ATTRIBUTE_IS_CRITICAL_SYSTEM_OBJECT); - if (is_critical_system_object) { - scope_edit->set_read_only(true); - type_edit->set_read_only(true); - } - - edits_connect_to_tab(edits, this); + edit_list->append({ + name_edit, + sam_name_edit, + description_edit, + email_edit, + notes_edit, + scope_edit, + type_edit, + }); } GeneralGroupTab::~GeneralGroupTab() { diff --git a/src/admc/tabs/general_group_tab.h b/src/admc/tabs/general_group_tab.h index 54eafb7a..9e4b29ee 100644 --- a/src/admc/tabs/general_group_tab.h +++ b/src/admc/tabs/general_group_tab.h @@ -21,19 +21,22 @@ #ifndef GENERAL_GROUP_TAB_H #define GENERAL_GROUP_TAB_H -#include "tabs/properties_tab.h" +#include + +class AdObject; +class AttributeEdit; namespace Ui { class GeneralGroupTab; } -class GeneralGroupTab final : public PropertiesTab { +class GeneralGroupTab final : public QWidget { Q_OBJECT public: Ui::GeneralGroupTab *ui; - GeneralGroupTab(const AdObject &object); + GeneralGroupTab(QList *edit_list, QWidget *parent); ~GeneralGroupTab(); }; diff --git a/src/admc/tabs/general_group_tab.ui b/src/admc/tabs/general_group_tab.ui index 24ca83ad..a14e61cc 100644 --- a/src/admc/tabs/general_group_tab.ui +++ b/src/admc/tabs/general_group_tab.ui @@ -33,7 +33,7 @@ - Logon name (pre-Windows 2000): + Group name (pre-Windows 2000): diff --git a/src/admc/tabs/general_other_tab.cpp b/src/admc/tabs/general_other_tab.cpp index 34db717e..49b69e57 100644 --- a/src/admc/tabs/general_other_tab.cpp +++ b/src/admc/tabs/general_other_tab.cpp @@ -22,29 +22,23 @@ #include "tabs/ui_general_other_tab.h" #include "adldap.h" +#include "attribute_edits/general_name_edit.h" #include "attribute_edits/string_edit.h" -#include "tabs/general_other_tab.h" -GeneralOtherTab::GeneralOtherTab(const AdObject &object) { +GeneralOtherTab::GeneralOtherTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { ui = new Ui::GeneralOtherTab(); ui->setupUi(this); - load_name_label(ui->name_label, object); + auto name_edit = new GeneralNameEdit(ui->name_label, this); + auto description_edit = new StringEdit(ui->description_edit, ATTRIBUTE_DESCRIPTION, this); - new StringEdit(ui->description_edit, ATTRIBUTE_DESCRIPTION, &edits, this); - - edits_connect_to_tab(edits, this); + edit_list->append({ + name_edit, + description_edit, + }); } GeneralOtherTab::~GeneralOtherTab() { delete ui; } - -void load_name_label(QLabel *name_label, const AdObject &object) { - const QString name = object.get_string(ATTRIBUTE_NAME); - name_label->setText(name); - - if (object.is_empty()) { - name_label->setText(QCoreApplication::translate("general_other_tab.cpp", "Failed to load object information. Check your connection.")); - } -} diff --git a/src/admc/tabs/general_other_tab.h b/src/admc/tabs/general_other_tab.h index 7b0c609b..bc0d35b5 100644 --- a/src/admc/tabs/general_other_tab.h +++ b/src/admc/tabs/general_other_tab.h @@ -21,25 +21,24 @@ #ifndef GENERAL_OTHER_TAB_H #define GENERAL_OTHER_TAB_H -#include "tabs/properties_tab.h" +#include class QLabel; class AdObject; +class AttributeEdit; namespace Ui { class GeneralOtherTab; } -class GeneralOtherTab final : public PropertiesTab { +class GeneralOtherTab final : public QWidget { Q_OBJECT public: Ui::GeneralOtherTab *ui; - GeneralOtherTab(const AdObject &object); + GeneralOtherTab(QList *edit_list, QWidget *parent); ~GeneralOtherTab(); }; -void load_name_label(QLabel *name_label, const AdObject &object); - #endif /* GENERAL_OTHER_TAB_H */ diff --git a/src/admc/tabs/general_other_tab.ui b/src/admc/tabs/general_other_tab.ui index b24a245b..ee3865dc 100644 --- a/src/admc/tabs/general_other_tab.ui +++ b/src/admc/tabs/general_other_tab.ui @@ -15,7 +15,7 @@ - + PLACEHOLDER FOR NAME @@ -31,7 +31,7 @@ - + Description: diff --git a/src/admc/tabs/general_ou_tab.cpp b/src/admc/tabs/general_ou_tab.cpp index 60bf664c..808ace25 100644 --- a/src/admc/tabs/general_ou_tab.cpp +++ b/src/admc/tabs/general_ou_tab.cpp @@ -22,25 +22,33 @@ #include "tabs/ui_general_ou_tab.h" #include "adldap.h" +#include "attribute_edits/general_name_edit.h" #include "attribute_edits/country_edit.h" #include "attribute_edits/string_edit.h" -#include "tabs/general_other_tab.h" -GeneralOUTab::GeneralOUTab(const AdObject &object) { +GeneralOUTab::GeneralOUTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { ui = new Ui::GeneralOUTab(); ui->setupUi(this); - load_name_label(ui->name_label, object); + auto name_edit = new GeneralNameEdit(ui->name_label, this); + auto description_edit = new StringEdit(ui->description_edit, ATTRIBUTE_DESCRIPTION, this); + auto street_edit = new StringEdit(ui->street_edit, ATTRIBUTE_STREET_OU, this); + auto city_edit = new StringEdit(ui->city_edit, ATTRIBUTE_CITY, this); + auto state_edit = new StringEdit(ui->state_edit, ATTRIBUTE_STATE, this); + auto postal_code_edit = new StringEdit(ui->postal_code_edit, ATTRIBUTE_POSTAL_CODE, this); - new StringEdit(ui->description_edit, ATTRIBUTE_DESCRIPTION, &edits, this); - new StringEdit(ui->street_edit, ATTRIBUTE_STREET, &edits, this); - new StringEdit(ui->city_edit, ATTRIBUTE_CITY, &edits, this); - new StringEdit(ui->state_edit, ATTRIBUTE_STATE, &edits, this); - new StringEdit(ui->postal_code_edit, ATTRIBUTE_POSTAL_CODE, &edits, this); + auto country_edit = new CountryEdit(ui->country_combo, this); - new CountryEdit(ui->country_combo, &edits, this); - - edits_connect_to_tab(edits, this); + edit_list->append({ + name_edit, + description_edit, + street_edit, + city_edit, + state_edit, + postal_code_edit, + country_edit, + }); } GeneralOUTab::~GeneralOUTab() { diff --git a/src/admc/tabs/general_ou_tab.h b/src/admc/tabs/general_ou_tab.h index 9044fbf9..6065c25f 100644 --- a/src/admc/tabs/general_ou_tab.h +++ b/src/admc/tabs/general_ou_tab.h @@ -21,19 +21,22 @@ #ifndef GENERAL_OU_TAB_H #define GENERAL_OU_TAB_H -#include "tabs/properties_tab.h" +#include + +class AdObject; +class AttributeEdit; namespace Ui { class GeneralOUTab; } -class GeneralOUTab final : public PropertiesTab { +class GeneralOUTab final : public QWidget { Q_OBJECT public: Ui::GeneralOUTab *ui; - GeneralOUTab(const AdObject &object); + GeneralOUTab(QList *edit_list, QWidget *parent); ~GeneralOUTab(); }; diff --git a/src/admc/tabs/general_policy_tab.cpp b/src/admc/tabs/general_policy_tab.cpp index 7c71a443..62113f95 100644 --- a/src/admc/tabs/general_policy_tab.cpp +++ b/src/admc/tabs/general_policy_tab.cpp @@ -22,6 +22,7 @@ #include "tabs/ui_general_policy_tab.h" #include "adldap.h" +#include "attribute_edits/general_name_edit.h" #include "attribute_edits/datetime_edit.h" #include "utils.h" @@ -34,24 +35,34 @@ // is stored somewhere on sysvol. Owner, not sure, might be // in security descriptor? -GeneralPolicyTab::GeneralPolicyTab() { +GeneralPolicyTab::GeneralPolicyTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { ui = new Ui::GeneralPolicyTab(); ui->setupUi(this); - auto created_edit = new DateTimeEdit(ui->created_edit, ATTRIBUTE_WHEN_CREATED, &edits, this); - auto modified_edit = new DateTimeEdit(ui->modified_edit, ATTRIBUTE_WHEN_CHANGED, &edits, this); + auto name_edit = new GeneralNameEdit(ui->name_label, this); + auto created_edit = new DateTimeEdit(ui->created_edit, ATTRIBUTE_WHEN_CREATED, this); + auto modified_edit = new DateTimeEdit(ui->modified_edit, ATTRIBUTE_WHEN_CHANGED, this); + auto tab_edit = new GeneralPolicyTabEdit(ui, this); - created_edit->set_read_only(true); - modified_edit->set_read_only(true); - - edits_connect_to_tab(edits, this); + edit_list->append({ + name_edit, + created_edit, + modified_edit, + tab_edit, + }); } GeneralPolicyTab::~GeneralPolicyTab() { delete ui; } -void GeneralPolicyTab::load(AdInterface &ad, const AdObject &object) { +GeneralPolicyTabEdit::GeneralPolicyTabEdit(Ui::GeneralPolicyTab *ui_arg, QObject *parent) +: AttributeEdit(parent) { + ui = ui_arg; +} + +void GeneralPolicyTabEdit::load(AdInterface &ad, const AdObject &object) { // Load version strings const int ad_version = object.get_int(ATTRIBUTE_VERSION_NUMBER); @@ -105,6 +116,4 @@ void GeneralPolicyTab::load(AdInterface &ad, const AdObject &object) { const QString id = object.get_string(ATTRIBUTE_CN); ui->unique_id_label->setText(id); - - PropertiesTab::load(ad, object); } diff --git a/src/admc/tabs/general_policy_tab.h b/src/admc/tabs/general_policy_tab.h index 9819e1a8..b03fdeb2 100644 --- a/src/admc/tabs/general_policy_tab.h +++ b/src/admc/tabs/general_policy_tab.h @@ -21,24 +21,37 @@ #ifndef GENERAL_POLICY_TAB_H #define GENERAL_POLICY_TAB_H -#include "tabs/properties_tab.h" +#include +#include "attribute_edits/attribute_edit.h" class AdObject; +class GeneralPolicyTabEdit; +class AttributeEdit; namespace Ui { class GeneralPolicyTab; } -class GeneralPolicyTab final : public PropertiesTab { +class GeneralPolicyTab final : public QWidget { Q_OBJECT public: Ui::GeneralPolicyTab *ui; - GeneralPolicyTab(); + GeneralPolicyTab(QList *edit_list, QWidget *parent); ~GeneralPolicyTab(); +}; + +class GeneralPolicyTabEdit final : public AttributeEdit { + Q_OBJECT + +public: + GeneralPolicyTabEdit(Ui::GeneralPolicyTab *ui, QObject *parent); void load(AdInterface &ad, const AdObject &object) override; + +private: + Ui::GeneralPolicyTab *ui; }; #endif /* GENERAL_POLICY_TAB_H */ diff --git a/src/admc/tabs/general_policy_tab.ui b/src/admc/tabs/general_policy_tab.ui index 032b5f7a..fa49c517 100644 --- a/src/admc/tabs/general_policy_tab.ui +++ b/src/admc/tabs/general_policy_tab.ui @@ -38,7 +38,11 @@ - + + + true + + @@ -48,7 +52,11 @@ - + + + true + + diff --git a/src/admc/tabs/general_shared_folder_tab.cpp b/src/admc/tabs/general_shared_folder_tab.cpp new file mode 100644 index 00000000..6ed1152c --- /dev/null +++ b/src/admc/tabs/general_shared_folder_tab.cpp @@ -0,0 +1,47 @@ +/* + * 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 "tabs/general_shared_folder_tab.h" +#include "tabs/ui_general_shared_folder_tab.h" + +#include "adldap.h" +#include "attribute_edits/general_name_edit.h" +#include "attribute_edits/string_edit.h" +#include "attribute_edits/string_list_edit.h" + +GeneralSharedFolderTab::GeneralSharedFolderTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { + ui = new Ui::GeneralSharedFolderTab(); + ui->setupUi(this); + + auto name_edit = new GeneralNameEdit(ui->name_label, this); + auto description_edit = new StringEdit(ui->description_edit, ATTRIBUTE_DESCRIPTION, this); + auto keywords_edit = new StringListEdit(ui->keywords_button, ATTRIBUTE_KEYWORDS, this); + + edit_list->append({ + name_edit, + description_edit, + keywords_edit, + }); +} + +GeneralSharedFolderTab::~GeneralSharedFolderTab() { + delete ui; +} diff --git a/src/admc/tabs/general_shared_folder_tab.h b/src/admc/tabs/general_shared_folder_tab.h new file mode 100644 index 00000000..01afb705 --- /dev/null +++ b/src/admc/tabs/general_shared_folder_tab.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 GENERAL_SHARED_FOLDER_TAB_H +#define GENERAL_SHARED_FOLDER_TAB_H + +#include + +class QLabel; +class AdObject; +class AttributeEdit; + +namespace Ui { +class GeneralSharedFolderTab; +} + +class GeneralSharedFolderTab final : public QWidget { + Q_OBJECT + +public: + Ui::GeneralSharedFolderTab *ui; + + GeneralSharedFolderTab(QList *edit_list, QWidget *parent); + ~GeneralSharedFolderTab(); +}; + +#endif /* GENERAL_SHARED_FOLDER_TAB_H */ diff --git a/src/admc/tabs/general_shared_folder_tab.ui b/src/admc/tabs/general_shared_folder_tab.ui new file mode 100644 index 00000000..eb6e1c0b --- /dev/null +++ b/src/admc/tabs/general_shared_folder_tab.ui @@ -0,0 +1,56 @@ + + + GeneralSharedFolderTab + + + + 0 + 0 + 400 + 131 + + + + Form + + + + + + PLACEHOLDER + + + + + + + Qt::Horizontal + + + + + + + + + Description: + + + + + + + + + + Keywords + + + + + + + + + + diff --git a/src/admc/tabs/general_user_tab.cpp b/src/admc/tabs/general_user_tab.cpp index 6aef2f87..b1f5bd7a 100644 --- a/src/admc/tabs/general_user_tab.cpp +++ b/src/admc/tabs/general_user_tab.cpp @@ -22,28 +22,39 @@ #include "tabs/ui_general_user_tab.h" #include "adldap.h" +#include "attribute_edits/general_name_edit.h" #include "attribute_edits/string_edit.h" #include "attribute_edits/string_other_edit.h" -#include "tabs/general_other_tab.h" -GeneralUserTab::GeneralUserTab(const AdObject &object) { +GeneralUserTab::GeneralUserTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { ui = new Ui::GeneralUserTab(); ui->setupUi(this); - load_name_label(ui->name_label, object); + auto name_edit = new GeneralNameEdit(ui->name_label, this); + auto description_edit = new StringEdit(ui->description_edit, ATTRIBUTE_DESCRIPTION, this); + auto first_name_edit = new StringEdit(ui->first_name_edit, ATTRIBUTE_FIRST_NAME, this); + auto last_name_edit = new StringEdit(ui->last_name_edit, ATTRIBUTE_LAST_NAME, this); + auto display_name_edit = new StringEdit(ui->display_name_edit, ATTRIBUTE_DISPLAY_NAME, this); + auto initials_edit = new StringEdit(ui->initials_edit, ATTRIBUTE_INITIALS, this); + auto mail_edit = new StringEdit(ui->email_edit, ATTRIBUTE_MAIL, this); + auto office_edit = new StringEdit(ui->office_edit, ATTRIBUTE_OFFICE, this); - new StringEdit(ui->description_edit, ATTRIBUTE_DESCRIPTION, &edits, this); - new StringEdit(ui->first_name_edit, ATTRIBUTE_FIRST_NAME, &edits, this); - new StringEdit(ui->last_name_edit, ATTRIBUTE_LAST_NAME, &edits, this); - new StringEdit(ui->display_name_edit, ATTRIBUTE_DISPLAY_NAME, &edits, this); - new StringEdit(ui->initials_edit, ATTRIBUTE_INITIALS, &edits, this); - new StringEdit(ui->email_edit, ATTRIBUTE_MAIL, &edits, this); - new StringEdit(ui->office_edit, ATTRIBUTE_OFFICE, &edits, this); + auto telephone_edit = new StringOtherEdit(ui->telephone_edit, ui->telephone_button, ATTRIBUTE_TELEPHONE_NUMBER, ATTRIBUTE_TELEPHONE_NUMBER_OTHER, this); + auto web_page_edit = new StringOtherEdit(ui->web_page_edit, ui->web_page_button, ATTRIBUTE_WWW_HOMEPAGE, ATTRIBUTE_WWW_HOMEPAGE_OTHER, this); - new StringOtherEdit(ui->telephone_edit, ui->telephone_button, ATTRIBUTE_TELEPHONE_NUMBER, ATTRIBUTE_TELEPHONE_NUMBER_OTHER, &edits, this); - new StringOtherEdit(ui->web_page_edit, ui->web_page_button, ATTRIBUTE_WWW_HOMEPAGE, ATTRIBUTE_WWW_HOMEPAGE_OTHER, &edits, this); - - edits_connect_to_tab(edits, this); + edit_list->append({ + name_edit, + description_edit, + first_name_edit, + last_name_edit, + display_name_edit, + initials_edit, + mail_edit, + office_edit, + telephone_edit, + web_page_edit, + }); } GeneralUserTab::~GeneralUserTab() { diff --git a/src/admc/tabs/general_user_tab.h b/src/admc/tabs/general_user_tab.h index e878ee17..5ff08265 100644 --- a/src/admc/tabs/general_user_tab.h +++ b/src/admc/tabs/general_user_tab.h @@ -21,19 +21,22 @@ #ifndef GENERAL_USER_TAB_H #define GENERAL_USER_TAB_H -#include "tabs/properties_tab.h" +#include + +class AdObject; +class AttributeEdit; namespace Ui { class GeneralUserTab; } -class GeneralUserTab final : public PropertiesTab { +class GeneralUserTab final : public QWidget { Q_OBJECT public: Ui::GeneralUserTab *ui; - GeneralUserTab(const AdObject &object); + GeneralUserTab(QList *edit_list, QWidget *parent); ~GeneralUserTab(); }; diff --git a/src/admc/tabs/gpo_links_tab.cpp b/src/admc/tabs/gpo_links_tab.cpp deleted file mode 100644 index 902dbb0e..00000000 --- a/src/admc/tabs/gpo_links_tab.cpp +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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 "tabs/gpo_links_tab.h" -#include "tabs/ui_gpo_links_tab.h" - -#include "adldap.h" -#include "globals.h" -#include "properties_dialog.h" -#include "settings.h" -#include "utils.h" - -#include - -#include - -enum GpoLinksColumn { - GpoLinksColumn_Name, - GpoLinksColumn_COUNT, -}; - -enum GpoLinksRole { - GpoLinksRole_DN = Qt::UserRole + 1, -}; - -GpoLinksTab::GpoLinksTab() { - ui = new Ui::GpoLinksTab(); - ui->setupUi(this); - - model = new QStandardItemModel(0, GpoLinksColumn_COUNT, this); - set_horizontal_header_labels_from_map(model, - { - {GpoLinksColumn_Name, tr("Name")}, - }); - - ui->view->setModel(model); - - PropertiesDialog::open_when_view_item_activated(ui->view, GpoLinksRole_DN); - - settings_restore_header_state(SETTING_gpo_links_tab_header_state, ui->view->header()); -} - -GpoLinksTab::~GpoLinksTab() { - settings_save_header_state(SETTING_gpo_links_tab_header_state, ui->view->header()); - - delete ui; -} - -void GpoLinksTab::load(AdInterface &ad, const AdObject &object) { - const QString base = g_adconfig->domain_head(); - const SearchScope scope = SearchScope_All; - const QList attributes = {ATTRIBUTE_NAME}; - const QString filter = filter_CONDITION(Condition_Contains, ATTRIBUTE_GPLINK, object.get_dn()); - const QHash results = ad.search(base, scope, filter, attributes); - - // Sort objects by dn(identical to sorting by name) - QList dns = results.keys(); - std::sort(dns.begin(), dns.end()); - - for (auto dn : dns) { - const AdObject linked_object = results[dn]; - const QString name = linked_object.get_string(ATTRIBUTE_NAME); - - const QList row = make_item_row(GpoLinksColumn_COUNT); - row[GpoLinksColumn_Name]->setText(name); - - set_data_for_row(row, dn, GpoLinksRole_DN); - - model->appendRow(row); - } - - model->sort(GpoLinksColumn_Name); -} diff --git a/src/admc/tabs/group_policy_tab.cpp b/src/admc/tabs/group_policy_tab.cpp index 21115b95..660a4ca9 100644 --- a/src/admc/tabs/group_policy_tab.cpp +++ b/src/admc/tabs/group_policy_tab.cpp @@ -59,10 +59,24 @@ const QHash column_to_option = { QString gplink_option_to_display_string(const QString &option); -GroupPolicyTab::GroupPolicyTab() { +GroupPolicyTab::GroupPolicyTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { ui = new Ui::GroupPolicyTab(); ui->setupUi(this); + auto tab_edit = new GroupPolicyTabEdit(ui, this); + auto options_edit = new GpoptionsEdit(ui->gpo_options_check, this); + + edit_list->append({ + tab_edit, + options_edit, + }); +} + +GroupPolicyTabEdit::GroupPolicyTabEdit(Ui::GroupPolicyTab *ui_arg, QObject *parent) +: AttributeEdit(parent) { + ui = ui_arg; + model = new QStandardItemModel(0, GplinkColumn_COUNT, this); set_horizontal_header_labels_from_map(model, { @@ -73,25 +87,22 @@ GroupPolicyTab::GroupPolicyTab() { ui->view->setModel(model); - new GpoptionsEdit(ui->gpo_options_check, &edits, this); - edits_connect_to_tab(edits, this); - settings_restore_header_state(SETTING_group_policy_tab_header_state, ui->view->header()); enable_widget_on_selection(ui->remove_button, ui->view); connect( ui->remove_button, &QAbstractButton::clicked, - this, &GroupPolicyTab::on_remove_button); + this, &GroupPolicyTabEdit::on_remove_button); connect( ui->add_button, &QAbstractButton::clicked, - this, &GroupPolicyTab::on_add_button); + this, &GroupPolicyTabEdit::on_add_button); connect( ui->view, &QWidget::customContextMenuRequested, - this, &GroupPolicyTab::on_context_menu); + this, &GroupPolicyTabEdit::on_context_menu); connect( model, &QStandardItemModel::itemChanged, - this, &GroupPolicyTab::on_item_changed); + this, &GroupPolicyTabEdit::on_item_changed); } GroupPolicyTab::~GroupPolicyTab() { @@ -100,17 +111,17 @@ GroupPolicyTab::~GroupPolicyTab() { delete ui; } -void GroupPolicyTab::load(AdInterface &ad, const AdObject &object) { +void GroupPolicyTabEdit::load(AdInterface &ad, const AdObject &object) { + UNUSED_ARG(ad); + const QString gplink_string = object.get_string(ATTRIBUTE_GPLINK); gplink = Gplink(gplink_string); original_gplink_string = gplink_string; reload_gplink(); - - PropertiesTab::load(ad, object); } -bool GroupPolicyTab::apply(AdInterface &ad, const QString &target) { +bool GroupPolicyTabEdit::apply(AdInterface &ad, const QString &target) const { bool total_success = true; const bool gplink_changed = !gplink.equals(original_gplink_string); @@ -122,28 +133,19 @@ bool GroupPolicyTab::apply(AdInterface &ad, const QString &target) { if (!replace_success) { total_success = false; } - - if (replace_success) { - original_gplink_string = gplink_string; - } - } - - const bool apply_success = PropertiesTab::apply(ad, target); - if (!apply_success) { - total_success = false; } return total_success; } -void GroupPolicyTab::on_context_menu(const QPoint pos) { +void GroupPolicyTabEdit::on_context_menu(const QPoint pos) { const QModelIndex index = ui->view->indexAt(pos); const QString gpo = index.data(GplinkRole_DN).toString(); if (gpo.isEmpty()) { return; } - auto menu = new QMenu(this); + auto menu = new QMenu(ui->view); menu->setAttribute(Qt::WA_DeleteOnClose); menu->addAction(tr("Remove link"), [this, gpo]() { @@ -160,12 +162,18 @@ void GroupPolicyTab::on_context_menu(const QPoint pos) { menu->popup(pos); } -void GroupPolicyTab::on_add_button() { - auto dialog = new SelectPolicyDialog(this); +void GroupPolicyTabEdit::on_add_button() { + AdInterface ad; + if (ad_failed(ad, ui->view)) { + return; + } + + auto dialog = new SelectPolicyDialog(ad, ui->view); dialog->open(); connect( dialog, &QDialog::accepted, + this, [this, dialog]() { const QList selected = dialog->get_selected_dns(); @@ -173,7 +181,7 @@ void GroupPolicyTab::on_add_button() { }); } -void GroupPolicyTab::on_remove_button() { +void GroupPolicyTabEdit::on_remove_button() { const QItemSelectionModel *selection_model = ui->view->selectionModel(); const QList selected_raw = selection_model->selectedRows(); @@ -187,7 +195,7 @@ void GroupPolicyTab::on_remove_button() { remove_link(selected); } -void GroupPolicyTab::add_link(QList gps) { +void GroupPolicyTabEdit::add_link(QList gps) { for (auto gp : gps) { gplink.add(gp); } @@ -197,7 +205,7 @@ void GroupPolicyTab::add_link(QList gps) { emit edited(); } -void GroupPolicyTab::remove_link(QList gps) { +void GroupPolicyTabEdit::remove_link(QList gps) { for (auto gp : gps) { gplink.remove(gp); } @@ -207,7 +215,7 @@ void GroupPolicyTab::remove_link(QList gps) { emit edited(); } -void GroupPolicyTab::move_link_up(const QString &gpo) { +void GroupPolicyTabEdit::move_link_up(const QString &gpo) { gplink.move_up(gpo); reload_gplink(); @@ -215,7 +223,7 @@ void GroupPolicyTab::move_link_up(const QString &gpo) { emit edited(); } -void GroupPolicyTab::move_link_down(const QString &gpo) { +void GroupPolicyTabEdit::move_link_down(const QString &gpo) { gplink.move_down(gpo); reload_gplink(); @@ -223,15 +231,15 @@ void GroupPolicyTab::move_link_down(const QString &gpo) { emit edited(); } -void GroupPolicyTab::reload_gplink() { +void GroupPolicyTabEdit::reload_gplink() { model->removeRows(0, model->rowCount()); AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, ui->view)) { return; } - const QString base = g_adconfig->domain_head(); + const QString base = g_adconfig->domain_dn(); const SearchScope scope = SearchScope_All; const QString filter = filter_CONDITION(Condition_Equals, ATTRIBUTE_OBJECT_CLASS, CLASS_GP_CONTAINER); const QList attributes = {ATTRIBUTE_DISPLAY_NAME}; @@ -303,7 +311,7 @@ void GroupPolicyTab::reload_gplink() { model->sort(GplinkColumn_Name); } -void GroupPolicyTab::on_item_changed(QStandardItem *item) { +void GroupPolicyTabEdit::on_item_changed(QStandardItem *item) { const GplinkColumn column = (GplinkColumn) item->column(); if (option_columns.contains(column)) { diff --git a/src/admc/tabs/group_policy_tab.h b/src/admc/tabs/group_policy_tab.h index 8dc7c9f3..8f9d8989 100644 --- a/src/admc/tabs/group_policy_tab.h +++ b/src/admc/tabs/group_policy_tab.h @@ -21,7 +21,8 @@ #ifndef GROUP_POLICY_TAB_H #define GROUP_POLICY_TAB_H -#include "tabs/properties_tab.h" +#include +#include "attribute_edits/attribute_edit.h" #include "gplink.h" @@ -29,6 +30,7 @@ class QString; class QStandardItemModel; class QStandardItem; class QPoint; +class GroupPolicyTabEdit; /** * Tab for displaying, modifying group policy related @@ -40,19 +42,27 @@ namespace Ui { class GroupPolicyTab; } -class GroupPolicyTab final : public PropertiesTab { +class GroupPolicyTab final : public QWidget { Q_OBJECT public: Ui::GroupPolicyTab *ui; - GroupPolicyTab(); + GroupPolicyTab(QList *edit_list, QWidget *parent); ~GroupPolicyTab(); +}; + +class GroupPolicyTabEdit final : public AttributeEdit { + Q_OBJECT + +public: + GroupPolicyTabEdit(Ui::GroupPolicyTab *ui, QObject *parent); void load(AdInterface &ad, const AdObject &object) override; - bool apply(AdInterface &ad, const QString &target) override; + bool apply(AdInterface &ad, const QString &dn) const override; private: + Ui::GroupPolicyTab *ui; QStandardItemModel *model; Gplink gplink; QString original_gplink_string; diff --git a/src/admc/tabs/laps_tab.cpp b/src/admc/tabs/laps_tab.cpp new file mode 100644 index 00000000..3a792439 --- /dev/null +++ b/src/admc/tabs/laps_tab.cpp @@ -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 . + */ + +#include "tabs/laps_tab.h" +#include "tabs/ui_laps_tab.h" + +#include "adldap.h" +#include "attribute_edits/string_edit.h" +#include "attribute_edits/laps_expiry_edit.h" + +LAPSTab::LAPSTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { + ui = new Ui::LAPSTab(); + ui->setupUi(this); + + auto pass_edit = new StringEdit(ui->pass_lineedit, ATTRIBUTE_LAPS_PASSWORD, this); + auto laps_edit = new LAPSExpiryEdit(ui->expiry_datetimeedit, ui->reset_expiry_button, this); + + edit_list->append({ + pass_edit, + laps_edit, + }); +} + +LAPSTab::~LAPSTab() { + delete ui; +} diff --git a/src/admc/tabs/laps_tab.h b/src/admc/tabs/laps_tab.h new file mode 100644 index 00000000..7aabdc84 --- /dev/null +++ b/src/admc/tabs/laps_tab.h @@ -0,0 +1,42 @@ +/* + * 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 LAPS_TAB_H +#define LAPS_TAB_H + +#include + +class AttributeEdit; + +namespace Ui { +class LAPSTab; +} + +class LAPSTab final : public QWidget { + Q_OBJECT + +public: + Ui::LAPSTab *ui; + + LAPSTab(QList *edit_list, QWidget *parent); + ~LAPSTab(); +}; + +#endif /* LAPS_TAB_H */ diff --git a/src/admc/tabs/laps_tab.ui b/src/admc/tabs/laps_tab.ui new file mode 100644 index 00000000..ba8157c8 --- /dev/null +++ b/src/admc/tabs/laps_tab.ui @@ -0,0 +1,86 @@ + + + LAPSTab + + + + 0 + 0 + 400 + 140 + + + + Form + + + + + + + + Password: + + + + + + + true + + + + + + + Password expires: + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Reset expiry + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/admc/tabs/managed_by_tab.cpp b/src/admc/tabs/managed_by_tab.cpp index 832998b2..c61b9865 100644 --- a/src/admc/tabs/managed_by_tab.cpp +++ b/src/admc/tabs/managed_by_tab.cpp @@ -33,46 +33,68 @@ // NOTE: store manager's edits in separate list because they // don't apply to the target of properties. -ManagedByTab::ManagedByTab() { +ManagedByTab::ManagedByTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { ui = new Ui::ManagedByTab(); ui->setupUi(this); - manager_edit = new ManagerEdit(ui->manager_widget, ATTRIBUTE_MANAGED_BY, &edits, this); + auto tab_edit = new ManagedByTabEdit(ui, this); - new StringEdit(ui->office_edit, ATTRIBUTE_OFFICE, &manager_edits, this); - new StringEdit(ui->street_edit, ATTRIBUTE_STREET, &manager_edits, this); - new StringEdit(ui->city_edit, ATTRIBUTE_CITY, &manager_edits, this); - new StringEdit(ui->state_edit, ATTRIBUTE_STATE, &manager_edits, this); + edit_list->append({ + tab_edit, + }); +} - new CountryEdit(ui->country_combo, &manager_edits, this); +ManagedByTabEdit::ManagedByTabEdit(Ui::ManagedByTab *ui_arg, QObject *parent) +: AttributeEdit(parent) { + ui = ui_arg; - new StringOtherEdit(ui->telephone_edit, ui->telephone_button, ATTRIBUTE_TELEPHONE_NUMBER, ATTRIBUTE_TELEPHONE_NUMBER_OTHER, &manager_edits, this); - new StringOtherEdit(ui->fax_edit, ui->fax_button, ATTRIBUTE_FAX_NUMBER, ATTRIBUTE_OTHER_FAX_NUMBER, &manager_edits, this); + manager_edit = new ManagerEdit(ui->manager_widget, ATTRIBUTE_MANAGED_BY, this); - edits_set_read_only(manager_edits, true); + auto office_edit = new StringEdit(ui->office_edit, ATTRIBUTE_OFFICE, this); + auto street_edit = new StringEdit(ui->street_edit, ATTRIBUTE_STREET, this); + auto city_edit = new StringEdit(ui->city_edit, ATTRIBUTE_CITY, this); + auto state_edit = new StringEdit(ui->state_edit, ATTRIBUTE_STATE, this); - edits_connect_to_tab(edits, this); - edits_connect_to_tab(manager_edits, this); + auto country_edit = new CountryEdit(ui->country_combo, this); + + auto telephone_edit = new StringOtherEdit(ui->telephone_edit, ui->telephone_button, ATTRIBUTE_TELEPHONE_NUMBER, ATTRIBUTE_TELEPHONE_NUMBER_OTHER, this); + auto fax_edit = new StringOtherEdit(ui->fax_edit, ui->fax_button, ATTRIBUTE_FAX_NUMBER, ATTRIBUTE_OTHER_FAX_NUMBER, this); + + manager_edits = { + office_edit, + street_edit, + city_edit, + state_edit, + country_edit, + telephone_edit, + fax_edit, + }; + + telephone_edit->set_read_only(true); + fax_edit->set_read_only(true); connect( manager_edit, &ManagerEdit::edited, - this, &ManagedByTab::on_manager_edited); + this, &ManagedByTabEdit::on_manager_edited); } ManagedByTab::~ManagedByTab() { delete ui; } -void ManagedByTab::on_manager_edited() { +void ManagedByTabEdit::on_manager_edited() { AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, ui->manager_widget)) { return; } load_manager_edits(ad); + + emit edited(); } -void ManagedByTab::load(AdInterface &ad, const AdObject &object) { +void ManagedByTabEdit::load(AdInterface &ad, const AdObject &object) { manager_edit->load(ad, object); // NOTE: load AFTER loading manager! because manager @@ -80,14 +102,20 @@ void ManagedByTab::load(AdInterface &ad, const AdObject &object) { load_manager_edits(ad); } -void ManagedByTab::load_manager_edits(AdInterface &ad) { +bool ManagedByTabEdit::apply(AdInterface &ad, const QString &dn) const { + const bool success = manager_edit->apply(ad, dn); + + return success; +} + +void ManagedByTabEdit::load_manager_edits(AdInterface &ad) { const QString manager = manager_edit->get_manager(); if (!manager.isEmpty()) { const AdObject manager_object = ad.search_object(manager); - edits_load(manager_edits, ad, manager_object); + AttributeEdit::load(manager_edits, ad, manager_object); } else { AdObject empty_object; - edits_load(manager_edits, ad, empty_object); + AttributeEdit::load(manager_edits, ad, empty_object); } } diff --git a/src/admc/tabs/managed_by_tab.h b/src/admc/tabs/managed_by_tab.h index b5ab8645..50c49d8c 100644 --- a/src/admc/tabs/managed_by_tab.h +++ b/src/admc/tabs/managed_by_tab.h @@ -21,26 +21,37 @@ #ifndef MANAGED_BY_TAB_H #define MANAGED_BY_TAB_H -#include "tabs/properties_tab.h" +#include +#include "attribute_edits/attribute_edit.h" class ManagerEdit; +class ManagedByTabEdit; namespace Ui { class ManagedByTab; } -class ManagedByTab final : public PropertiesTab { +class ManagedByTab final : public QWidget { Q_OBJECT public: Ui::ManagedByTab *ui; - ManagedByTab(); + ManagedByTab(QList *edit_list, QWidget *parent); ~ManagedByTab(); +}; + +class ManagedByTabEdit final : public AttributeEdit { + Q_OBJECT + +public: + ManagedByTabEdit(Ui::ManagedByTab *ui, QObject *parent); void load(AdInterface &ad, const AdObject &object) override; + bool apply(AdInterface &ad, const QString &dn) const override; private: + Ui::ManagedByTab *ui; QList manager_edits; ManagerEdit *manager_edit; diff --git a/src/admc/tabs/managed_by_tab.ui b/src/admc/tabs/managed_by_tab.ui index 0ed642bf..3aaa5e65 100644 --- a/src/admc/tabs/managed_by_tab.ui +++ b/src/admc/tabs/managed_by_tab.ui @@ -32,7 +32,11 @@ - + + + true + + @@ -42,7 +46,11 @@ - + + + true + + @@ -52,7 +60,11 @@ - + + + true + + @@ -62,7 +74,11 @@ - + + + true + + @@ -72,7 +88,11 @@ - + + + false + + @@ -91,7 +111,11 @@ - + + + true + + @@ -105,7 +129,11 @@ - + + + true + + diff --git a/src/admc/tabs/membership_tab.cpp b/src/admc/tabs/membership_tab.cpp index 8c635137..68dcd243 100644 --- a/src/admc/tabs/membership_tab.cpp +++ b/src/admc/tabs/membership_tab.cpp @@ -48,18 +48,26 @@ enum MembersRole { MembersRole_Primary = Qt::UserRole + 2, }; -MembersTab::MembersTab() -: MembershipTab(MembershipTabType_Members) { -} - -MemberOfTab::MemberOfTab() -: MembershipTab(MembershipTabType_MemberOf) { -} - -MembershipTab::MembershipTab(const MembershipTabType type_arg) { +MembershipTab::MembershipTab(QList *edit_list, const MembershipTabType &type, QWidget *parent) +: QWidget(parent) { ui = new Ui::MembershipTab(); ui->setupUi(this); + auto tab_edit = new MembershipTabEdit(ui->view, ui->primary_button, ui->add_button, ui->remove_button, ui->properties_button, ui->primary_group_label, type, this); + + edit_list->append({ + tab_edit, + }); +} + +MembershipTabEdit::MembershipTabEdit(QTreeView *view_arg, QPushButton *primary_button_arg, QPushButton *add_button_arg, QPushButton *remove_button_arg, QPushButton *properties_button_arg, QLabel *primary_group_label_arg, const MembershipTabType &type_arg, QObject *parent) +: AttributeEdit(parent) { + view = view_arg; + primary_button = primary_button_arg; + add_button = add_button_arg; + remove_button = remove_button_arg; + properties_button = properties_button_arg; + primary_group_label = primary_group_label_arg; type = type_arg; model = new QStandardItemModel(0, MembersColumn_COUNT, this); @@ -69,39 +77,39 @@ MembershipTab::MembershipTab(const MembershipTabType type_arg) { {MembersColumn_Parent, tr("Folder")}, }); - ui->view->setModel(model); + view->setModel(model); // Primary group widgets are visible only in Member of version if (type == MembershipTabType_Members) { - ui->primary_button->hide(); - ui->primary_group_label->hide(); + primary_button->hide(); + primary_group_label->hide(); } - settings_restore_header_state(SETTING_membership_tab_header_state, ui->view->header()); + settings_restore_header_state(SETTING_membership_tab_header_state, view->header()); - enable_widget_on_selection(ui->remove_button, ui->view); - enable_widget_on_selection(ui->properties_button, ui->view); + enable_widget_on_selection(remove_button, view); + enable_widget_on_selection(properties_button, view); - const QItemSelectionModel *selection_model = ui->view->selectionModel(); + const QItemSelectionModel *selection_model = view->selectionModel(); connect( selection_model, &QItemSelectionModel::selectionChanged, - this, &MembershipTab::enable_primary_button_on_valid_selection); + this, &MembershipTabEdit::enable_primary_button_on_valid_selection); enable_primary_button_on_valid_selection(); connect( - ui->remove_button, &QAbstractButton::clicked, - this, &MembershipTab::on_remove_button); + remove_button, &QAbstractButton::clicked, + this, &MembershipTabEdit::on_remove_button); connect( - ui->add_button, &QAbstractButton::clicked, - this, &MembershipTab::on_add_button); + add_button, &QAbstractButton::clicked, + this, &MembershipTabEdit::on_add_button); connect( - ui->properties_button, &QAbstractButton::clicked, - this, &MembershipTab::on_properties_button); + properties_button, &QAbstractButton::clicked, + this, &MembershipTabEdit::on_properties_button); connect( - ui->primary_button, &QAbstractButton::clicked, - this, &MembershipTab::on_primary_button); + primary_button, &QAbstractButton::clicked, + this, &MembershipTabEdit::on_primary_button); - PropertiesDialog::open_when_view_item_activated(ui->view, MembersRole_DN); + PropertiesDialog::open_when_view_item_activated(view, MembersRole_DN); } MembershipTab::~MembershipTab() { @@ -110,7 +118,7 @@ MembershipTab::~MembershipTab() { delete ui; } -void MembershipTab::load(AdInterface &ad, const AdObject &object) { +void MembershipTabEdit::load(AdInterface &ad, const AdObject &object) { const QList values = object.get_strings(get_membership_attribute()); original_values = values.toSet(); current_values = original_values; @@ -123,7 +131,7 @@ void MembershipTab::load(AdInterface &ad, const AdObject &object) { const QByteArray group_sid = object.get_value(ATTRIBUTE_OBJECT_SID); const QString group_rid = extract_rid_from_sid(group_sid, g_adconfig); - const QString base = g_adconfig->domain_head(); + const QString base = g_adconfig->domain_dn(); const SearchScope scope = SearchScope_All; const QString filter = filter_CONDITION(Condition_Equals, ATTRIBUTE_PRIMARY_GROUP_ID, group_rid); const QList attributes = QList(); @@ -150,7 +158,7 @@ void MembershipTab::load(AdInterface &ad, const AdObject &object) { const int cut_index = user_sid_string.lastIndexOf("-") + 1; const QString group_sid = user_sid_string.left(cut_index) + group_rid; - const QString base = g_adconfig->domain_head(); + const QString base = g_adconfig->domain_dn(); const SearchScope scope = SearchScope_All; const QString filter = filter_CONDITION(Condition_Equals, ATTRIBUTE_OBJECT_SID, group_sid); const QList attributes = QList(); @@ -170,14 +178,9 @@ void MembershipTab::load(AdInterface &ad, const AdObject &object) { reload_model(); } -bool MembershipTab::apply(AdInterface &ad, const QString &target) { +bool MembershipTabEdit::apply(AdInterface &ad, const QString &target) const { bool total_success = true; - // NOTE: need temp copy because can't edit the set - // during iteration - QSet new_original_values = original_values; - QSet new_original_primary_values = original_primary_values; - // NOTE: logic is kinda duplicated but switching on behavior within iterations would be very confusing switch (type) { case MembershipTabType_Members: { @@ -187,9 +190,7 @@ bool MembershipTab::apply(AdInterface &ad, const QString &target) { const bool removed = !current_values.contains(user); if (removed) { const bool success = ad.group_remove_member(group, user); - if (success) { - new_original_values.remove(user); - } else { + if (!success) { total_success = false; } } @@ -199,9 +200,7 @@ bool MembershipTab::apply(AdInterface &ad, const QString &target) { const bool added = !original_values.contains(user); if (added) { const bool success = ad.group_add_member(group, user); - if (success) { - new_original_values.insert(user); - } else { + if (!success) { total_success = false; } } @@ -222,17 +221,7 @@ bool MembershipTab::apply(AdInterface &ad, const QString &target) { const QString group_dn = current_primary_values.values()[0]; const bool success = ad.user_set_primary_group(group_dn, target); - if (success) { - new_original_primary_values = {group_dn}; - - // Server adds old primary group to - // normal membership - new_original_values.insert(original_primary_group); - - // Server removes new primary group from - // normal membership - new_original_values.remove(group_dn); - } else { + if (!success) { total_success = false; } } @@ -254,9 +243,7 @@ bool MembershipTab::apply(AdInterface &ad, const QString &target) { const bool removed = !current_values.contains(group); if (removed) { const bool success = ad.group_remove_member(group, user); - if (success) { - new_original_values.remove(group); - } else { + if (!success) { total_success = false; } } @@ -271,9 +258,7 @@ bool MembershipTab::apply(AdInterface &ad, const QString &target) { const bool added = !original_values.contains(group); if (added) { const bool success = ad.group_add_member(group, user); - if (success) { - new_original_values.insert(group); - } else { + if (!success) { total_success = false; } } @@ -283,13 +268,10 @@ bool MembershipTab::apply(AdInterface &ad, const QString &target) { } } - original_values = new_original_values; - original_primary_values = new_original_primary_values; - return total_success; } -void MembershipTab::on_add_button() { +void MembershipTabEdit::on_add_button() { const QList classes = [this]() -> QList { switch (type) { case MembershipTabType_Members: return { @@ -303,7 +285,7 @@ void MembershipTab::on_add_button() { return QList(); }(); - auto dialog = new SelectObjectDialog(classes, SelectObjectDialogMultiSelection_Yes, this); + auto dialog = new SelectObjectDialog(classes, SelectObjectDialogMultiSelection_Yes, view); const QString title = [&]() { switch (type) { @@ -318,6 +300,7 @@ void MembershipTab::on_add_button() { connect( dialog, &SelectObjectDialog::accepted, + this, [this, dialog]() { const QList selected = dialog->get_selected(); @@ -325,8 +308,8 @@ void MembershipTab::on_add_button() { }); } -void MembershipTab::on_remove_button() { - const QItemSelectionModel *selection_model = ui->view->selectionModel(); +void MembershipTabEdit::on_remove_button() { + const QItemSelectionModel *selection_model = view->selectionModel(); const QList selected = selection_model->selectedRows(); QList removed_values; @@ -355,15 +338,15 @@ void MembershipTab::on_remove_button() { return QString(); }(); - message_box_warning(this, tr("Error"), error_text); + message_box_warning(view, tr("Error"), error_text); } else { remove_values(removed_values); } } -void MembershipTab::on_primary_button() { +void MembershipTabEdit::on_primary_button() { // Make selected group primary - const QItemSelectionModel *selection_model = ui->view->selectionModel(); + const QItemSelectionModel *selection_model = view->selectionModel(); const QList selecteds = selection_model->selectedRows(); const QModelIndex selected = selecteds[0]; const QString group_dn = selected.data(MembersRole_DN).toString(); @@ -380,19 +363,24 @@ void MembershipTab::on_primary_button() { emit edited(); } -void MembershipTab::on_properties_button() { - const QModelIndex current = ui->view->selectionModel()->currentIndex(); - const QString dn = current.data(MembersRole_DN).toString(); - - PropertiesDialog::open_for_target(dn); -} - -void MembershipTab::enable_primary_button_on_valid_selection() { - if (ui->primary_button == nullptr) { +void MembershipTabEdit::on_properties_button() { + AdInterface ad; + if (ad_failed(ad, view)) { return; } - const QItemSelectionModel *selection_model = ui->view->selectionModel(); + const QModelIndex current = view->selectionModel()->currentIndex(); + const QString dn = current.data(MembersRole_DN).toString(); + + PropertiesDialog::open_for_target(ad, dn); +} + +void MembershipTabEdit::enable_primary_button_on_valid_selection() { + if (primary_button == nullptr) { + return; + } + + const QItemSelectionModel *selection_model = view->selectionModel(); const QList selecteds = selection_model->selectedRows(); // Enable "set primary group" button if @@ -411,13 +399,13 @@ void MembershipTab::enable_primary_button_on_valid_selection() { const QString dn = selected_dns.values()[0]; const bool is_primary = current_primary_values.contains(dn); - ui->primary_button->setEnabled(!is_primary); + primary_button->setEnabled(!is_primary); } else { - ui->primary_button->setEnabled(false); + primary_button->setEnabled(false); } } -void MembershipTab::reload_model() { +void MembershipTabEdit::reload_model() { // Load primary group name into label if (type == MembershipTabType_MemberOf) { const QString primary_group_label_text = [this]() { @@ -433,7 +421,7 @@ void MembershipTab::reload_model() { return out; }(); - ui->primary_group_label->setText(primary_group_label_text); + primary_group_label->setText(primary_group_label_text); } model->removeRows(0, model->rowCount()); @@ -456,7 +444,7 @@ void MembershipTab::reload_model() { model->sort(MembersColumn_Name); } -void MembershipTab::add_values(QList values) { +void MembershipTabEdit::add_values(QList values) { for (auto value : values) { current_values.insert(value); } @@ -466,7 +454,7 @@ void MembershipTab::add_values(QList values) { emit edited(); } -void MembershipTab::remove_values(QList values) { +void MembershipTabEdit::remove_values(QList values) { for (auto value : values) { current_values.remove(value); } @@ -476,7 +464,7 @@ void MembershipTab::remove_values(QList values) { emit edited(); } -QString MembershipTab::get_membership_attribute() { +QString MembershipTabEdit::get_membership_attribute() { switch (type) { case MembershipTabType_Members: return ATTRIBUTE_MEMBER; case MembershipTabType_MemberOf: return ATTRIBUTE_MEMBER_OF; diff --git a/src/admc/tabs/membership_tab.h b/src/admc/tabs/membership_tab.h index 67a5c80a..2e49527a 100644 --- a/src/admc/tabs/membership_tab.h +++ b/src/admc/tabs/membership_tab.h @@ -21,40 +21,56 @@ #ifndef MEMBERS_TAB_H #define MEMBERS_TAB_H -#include "tabs/properties_tab.h" +#include +#include "attribute_edits/attribute_edit.h" #include class QStandardItemModel; +class QTreeView; +class QPushButton; +class QLabel; // Displays and edits membership info which can go both ways // 1. users that are members of group // 2. groups of user is member of // MembersTab and MemberOfTab implement both of those +enum MembershipTabType { + MembershipTabType_Members, + MembershipTabType_MemberOf +}; + namespace Ui { class MembershipTab; } -class MembershipTab : public PropertiesTab { +class MembershipTab final : public QWidget { Q_OBJECT public: Ui::MembershipTab *ui; - void load(AdInterface &ad, const AdObject &object) override; - bool apply(AdInterface &ad, const QString &target) override; - -protected: - enum MembershipTabType { - MembershipTabType_Members, - MembershipTabType_MemberOf - }; - - MembershipTab(const MembershipTabType type_arg); + MembershipTab(QList *edit_list, const MembershipTabType &type, QWidget *parent); ~MembershipTab(); +}; + +class MembershipTabEdit final : public AttributeEdit { + Q_OBJECT + +public: + MembershipTabEdit(QTreeView *view, QPushButton *primary_button, QPushButton *add_button, QPushButton *remove_button, QPushButton *properties_button, QLabel *primary_group_label, const MembershipTabType &type, QObject *parent); + + void load(AdInterface &ad, const AdObject &object) override; + bool apply(AdInterface &ad, const QString &dn) const override; private: + QTreeView *view; + QPushButton *primary_button; + QPushButton *add_button; + QPushButton *remove_button; + QPushButton *properties_button; + QLabel *primary_group_label; MembershipTabType type; QStandardItemModel *model; @@ -74,14 +90,4 @@ private: QString get_membership_attribute(); }; -class MembersTab final : public MembershipTab { -public: - MembersTab(); -}; - -class MemberOfTab final : public MembershipTab { -public: - MemberOfTab(); -}; - #endif /* MEMBERS_TAB_H */ diff --git a/src/admc/tabs/object_tab.cpp b/src/admc/tabs/object_tab.cpp index 7467cca0..c021bfd5 100644 --- a/src/admc/tabs/object_tab.cpp +++ b/src/admc/tabs/object_tab.cpp @@ -28,25 +28,31 @@ #include -ObjectTab::ObjectTab() { +ObjectTab::ObjectTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { ui = new Ui::ObjectTab(); ui->setupUi(this); - new StringEdit(ui->dn_edit, ATTRIBUTE_DN, &edits, this); - new StringEdit(ui->class_edit, ATTRIBUTE_OBJECT_CLASS, &edits, this); + auto dn_edit = new StringEdit(ui->dn_edit, ATTRIBUTE_DN, this); + auto class_edit = new StringEdit(ui->class_edit, ATTRIBUTE_OBJECT_CLASS, this); - new DateTimeEdit(ui->created_edit, ATTRIBUTE_WHEN_CREATED, &edits, this); - new DateTimeEdit(ui->changed_edit, ATTRIBUTE_WHEN_CHANGED, &edits, this); + auto when_created_edit = new DateTimeEdit(ui->created_edit, ATTRIBUTE_WHEN_CREATED, this); + auto when_changed_edit = new DateTimeEdit(ui->changed_edit, ATTRIBUTE_WHEN_CHANGED, this); - new StringEdit(ui->usn_created_edit, ATTRIBUTE_USN_CREATED, &edits, this); - new StringEdit(ui->usn_changed_edit, ATTRIBUTE_USN_CHANGED, &edits, this); + auto usn_created_edit = new StringEdit(ui->usn_created_edit, ATTRIBUTE_USN_CREATED, this); + auto usn_changed_edit = new StringEdit(ui->usn_changed_edit, ATTRIBUTE_USN_CHANGED, this); - auto deletion_edit = new ProtectDeletionEdit(ui->deletion_check, &edits, this); + auto deletion_edit = new ProtectDeletionEdit(ui->deletion_check, this); - edits_set_read_only(edits, true); - deletion_edit->set_read_only(false); - - edits_connect_to_tab(edits, this); + edit_list->append({ + dn_edit, + class_edit, + when_created_edit, + when_changed_edit, + usn_created_edit, + usn_changed_edit, + deletion_edit, + }); } ObjectTab::~ObjectTab() { diff --git a/src/admc/tabs/object_tab.h b/src/admc/tabs/object_tab.h index af42e4a9..cac4e64b 100644 --- a/src/admc/tabs/object_tab.h +++ b/src/admc/tabs/object_tab.h @@ -21,20 +21,22 @@ #ifndef OBJECT_TAB_H #define OBJECT_TAB_H -#include "tabs/properties_tab.h" +#include + +class AttributeEdit; namespace Ui { class ObjectTab; } // Object attributes, same for all types -class ObjectTab final : public PropertiesTab { +class ObjectTab final : public QWidget { Q_OBJECT public: Ui::ObjectTab *ui; - ObjectTab(); + ObjectTab(QList *edit_list, QWidget *parent); ~ObjectTab(); }; diff --git a/src/admc/tabs/object_tab.ui b/src/admc/tabs/object_tab.ui index 1d772300..be0d0bfb 100644 --- a/src/admc/tabs/object_tab.ui +++ b/src/admc/tabs/object_tab.ui @@ -24,7 +24,11 @@ - + + + true + + @@ -34,7 +38,11 @@ - + + + true + + @@ -44,7 +52,11 @@ - + + + true + + @@ -54,7 +66,11 @@ - + + + true + + @@ -64,7 +80,11 @@ - + + + true + + @@ -74,7 +94,11 @@ - + + + true + + diff --git a/src/admc/tabs/organization_tab.cpp b/src/admc/tabs/organization_tab.cpp index 667e0a68..c9002a30 100644 --- a/src/admc/tabs/organization_tab.cpp +++ b/src/admc/tabs/organization_tab.cpp @@ -41,17 +41,31 @@ enum ReportsRole { ReportsRole_DN = Qt::UserRole + 1, }; -OrganizationTab::OrganizationTab() { +OrganizationTab::OrganizationTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { ui = new Ui::OrganizationTab(); ui->setupUi(this); - new StringEdit(ui->job_title_edit, ATTRIBUTE_TITLE, &edits, this); - new StringEdit(ui->department_edit, ATTRIBUTE_DEPARTMENT, &edits, this); - new StringEdit(ui->company_edit, ATTRIBUTE_COMPANY, &edits, this); + auto title_edit = new StringEdit(ui->job_title_edit, ATTRIBUTE_TITLE, this); + auto department_edit = new StringEdit(ui->department_edit, ATTRIBUTE_DEPARTMENT, this); + auto company_edit = new StringEdit(ui->company_edit, ATTRIBUTE_COMPANY, this); + auto manager_edit = new ManagerEdit(ui->manager_widget, ATTRIBUTE_MANAGER, this); - new ManagerEdit(ui->manager_widget, ATTRIBUTE_MANAGER, &edits, this); + auto tab_edit = new OrganizationTabEdit(ui, this); - edits_connect_to_tab(edits, this); + edit_list->append({ + title_edit, + department_edit, + company_edit, + manager_edit, + + tab_edit, + }); +} + +OrganizationTabEdit::OrganizationTabEdit(Ui::OrganizationTab *ui_arg, QObject *parent) +: AttributeEdit(parent) { + ui = ui_arg; reports_model = new QStandardItemModel(0, ReportsColumn_COUNT, this); set_horizontal_header_labels_from_map(reports_model, @@ -73,7 +87,9 @@ OrganizationTab::~OrganizationTab() { delete ui; } -void OrganizationTab::load(AdInterface &ad, const AdObject &object) { +void OrganizationTabEdit::load(AdInterface &ad, const AdObject &object) { + UNUSED_ARG(ad); + const QList reports = object.get_strings(ATTRIBUTE_DIRECT_REPORTS); reports_model->removeRows(0, reports_model->rowCount()); @@ -89,6 +105,4 @@ void OrganizationTab::load(AdInterface &ad, const AdObject &object) { reports_model->appendRow(row); } - - PropertiesTab::load(ad, object); } diff --git a/src/admc/tabs/organization_tab.h b/src/admc/tabs/organization_tab.h index 256271f5..27d146b5 100644 --- a/src/admc/tabs/organization_tab.h +++ b/src/admc/tabs/organization_tab.h @@ -21,7 +21,8 @@ #ifndef ORGANIZATION_TAB_H #define ORGANIZATION_TAB_H -#include "tabs/properties_tab.h" +#include +#include "attribute_edits/attribute_edit.h" class QStandardItemModel; @@ -29,18 +30,26 @@ namespace Ui { class OrganizationTab; } -class OrganizationTab final : public PropertiesTab { +class OrganizationTab final : public QWidget { Q_OBJECT public: Ui::OrganizationTab *ui; - OrganizationTab(); + OrganizationTab(QList *edit_list, QWidget *parent); ~OrganizationTab(); +}; + +class OrganizationTabEdit final : public AttributeEdit { + Q_OBJECT + +public: + OrganizationTabEdit(Ui::OrganizationTab *ui, QObject *parent); void load(AdInterface &ad, const AdObject &object) override; private: + Ui::OrganizationTab *ui; QStandardItemModel *reports_model; }; diff --git a/src/admc/tabs/os_tab.cpp b/src/admc/tabs/os_tab.cpp index 9e1a52e2..7ffe221e 100644 --- a/src/admc/tabs/os_tab.cpp +++ b/src/admc/tabs/os_tab.cpp @@ -24,16 +24,20 @@ #include "adldap.h" #include "attribute_edits/string_edit.h" -OSTab::OSTab() { +OSTab::OSTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { ui = new Ui::OSTab(); ui->setupUi(this); - new StringEdit(ui->os_edit, ATTRIBUTE_OS, &edits, this); - new StringEdit(ui->version_edit, ATTRIBUTE_OS_VERSION, &edits, this); - new StringEdit(ui->pack_edit, ATTRIBUTE_OS_SERVICE_PACK, &edits, this); + auto os_edit = new StringEdit(ui->os_edit, ATTRIBUTE_OS, this); + auto version_edit = new StringEdit(ui->version_edit, ATTRIBUTE_OS_VERSION, this); + auto pack_edit = new StringEdit(ui->pack_edit, ATTRIBUTE_OS_SERVICE_PACK, this); - edits_set_read_only(edits, true); - edits_connect_to_tab(edits, this); + edit_list->append({ + os_edit, + version_edit, + pack_edit, + }); } OSTab::~OSTab() { diff --git a/src/admc/tabs/os_tab.h b/src/admc/tabs/os_tab.h index 236ce5f1..13dc5e5f 100644 --- a/src/admc/tabs/os_tab.h +++ b/src/admc/tabs/os_tab.h @@ -21,19 +21,21 @@ #ifndef OS_TAB_H #define OS_TAB_H -#include "tabs/properties_tab.h" +#include + +class AttributeEdit; namespace Ui { class OSTab; } -class OSTab final : public PropertiesTab { +class OSTab final : public QWidget { Q_OBJECT public: Ui::OSTab *ui; - OSTab(); + OSTab(QList *edit_list, QWidget *parent); ~OSTab(); }; diff --git a/src/admc/tabs/os_tab.ui b/src/admc/tabs/os_tab.ui index cbee95ec..800b3f34 100644 --- a/src/admc/tabs/os_tab.ui +++ b/src/admc/tabs/os_tab.ui @@ -22,7 +22,11 @@ - + + + true + + @@ -32,7 +36,11 @@ - + + + true + + @@ -42,7 +50,11 @@ - + + + true + + diff --git a/src/admc/tabs/profile_tab.cpp b/src/admc/tabs/profile_tab.cpp index aed183ce..8ce3a78f 100644 --- a/src/admc/tabs/profile_tab.cpp +++ b/src/admc/tabs/profile_tab.cpp @@ -24,15 +24,20 @@ #include "adldap.h" #include "attribute_edits/string_edit.h" -ProfileTab::ProfileTab() { +ProfileTab::ProfileTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { ui = new Ui::ProfileTab(); ui->setupUi(this); - new StringEdit(ui->profile_path_edit, ATTRIBUTE_PROFILE_PATH, &edits, this); - new StringEdit(ui->script_path_edit, ATTRIBUTE_SCRIPT_PATH, &edits, this); - new StringEdit(ui->home_dir_edit, ATTRIBUTE_HOME_DIRECTORY, &edits, this); + auto profile_path_edit = new StringEdit(ui->profile_path_edit, ATTRIBUTE_PROFILE_PATH, this); + auto script_path_edit = new StringEdit(ui->script_path_edit, ATTRIBUTE_SCRIPT_PATH, this); + auto home_dir_edit = new StringEdit(ui->home_dir_edit, ATTRIBUTE_HOME_DIRECTORY, this); - edits_connect_to_tab(edits, this); + edit_list->append({ + profile_path_edit, + script_path_edit, + home_dir_edit, + }); } ProfileTab::~ProfileTab() { diff --git a/src/admc/tabs/profile_tab.h b/src/admc/tabs/profile_tab.h index 4cd4c994..a8415a92 100644 --- a/src/admc/tabs/profile_tab.h +++ b/src/admc/tabs/profile_tab.h @@ -21,19 +21,21 @@ #ifndef PROFILE_TAB_H #define PROFILE_TAB_H -#include "tabs/properties_tab.h" +#include + +class AttributeEdit; namespace Ui { class ProfileTab; } -class ProfileTab final : public PropertiesTab { +class ProfileTab final : public QWidget { Q_OBJECT public: Ui::ProfileTab *ui; - ProfileTab(); + ProfileTab(QList *edit_list, QWidget *parent); ~ProfileTab(); }; diff --git a/src/admc/tabs/properties_tab.h b/src/admc/tabs/properties_tab.h deleted file mode 100644 index d6bf3595..00000000 --- a/src/admc/tabs/properties_tab.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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 PROPERTIES_TAB_H -#define PROPERTIES_TAB_H - -#include - -/** - * PropertiesTab are used in PropertiesDialog. They are usually - * a collection of AttributeEdit's and so mirror their - * behavior. All tab f-ns call f-ns of tab's edits but edits - * aren't required. If needed, a tab can implement custom - * functionality without edits. - */ - -class AttributeEdit; -class AdInterface; -class AdObject; - -class PropertiesTab : public QWidget { - Q_OBJECT - -public: - virtual void load(AdInterface &ad, const AdObject &object); - virtual bool verify(AdInterface &ad, const QString &target) const; - virtual bool apply(AdInterface &ad, const QString &target); - - void on_edit_edited(); - -signals: - // Emitted to notify details dialog that tab was edited. - void edited(); - -protected: - QList edits; -}; - -#endif /* PROPERTIES_TAB_H */ diff --git a/src/admc/tabs/security_tab.cpp b/src/admc/tabs/security_tab.cpp index f12d4e2d..71322c49 100644 --- a/src/admc/tabs/security_tab.cpp +++ b/src/admc/tabs/security_tab.cpp @@ -37,169 +37,380 @@ #include #include #include +#include + +struct security_descriptor; enum TrusteeItemRole { TrusteeItemRole_Sid = Qt::UserRole, }; -enum AcePermissionItemRole { - AcePermissionItemRole_Permission = Qt::UserRole, +enum EditAction { + EditAction_None, + EditAction_Add, + EditAction_Remove, }; -QHash SecurityTab::ace_permission_to_name_map() { - return { - {AcePermission_FullControl, tr("Full control")}, - {AcePermission_Read, tr("Read")}, - {AcePermission_Write, tr("Write")}, - {AcePermission_Delete, tr("Delete")}, - {AcePermission_DeleteSubtree, tr("Delete subtree")}, - {AcePermission_CreateChild, tr("Create child")}, - {AcePermission_DeleteChild, tr("Delete child")}, - {AcePermission_AllowedToAuthenticate, tr("Allowed to authenticate")}, - {AcePermission_ChangePassword, tr("Change password")}, - {AcePermission_ReceiveAs, tr("Receive as")}, - {AcePermission_ResetPassword, tr("Reset password")}, - {AcePermission_SendAs, tr("Send as")}, - {AcePermission_ReadAccountRestrictions, tr("Read Account restrictions")}, - {AcePermission_WriteAccountRestrictions, tr("Write Account restrictions")}, - {AcePermission_ReadGeneralInfo, tr("Read general info")}, - {AcePermission_WriteGeneralInfo, tr("Write general info")}, - {AcePermission_ReadGroupMembership, tr("Read group membership")}, - {AcePermission_ReadLogonInfo, tr("Read logon info")}, - {AcePermission_WriteLogonInfo, tr("Write logon info")}, - {AcePermission_ReadPersonalInfo, tr("Read personal info")}, - {AcePermission_WritePersonalInfo, tr("Write personal info")}, - {AcePermission_ReadPhoneAndMailOptions, tr("Read phone and mail options")}, - {AcePermission_WritePhoneAndMailOptions, tr("Write phone and mail options")}, - {AcePermission_ReadPrivateInfo, tr("Read private info")}, - {AcePermission_WritePrivateInfo, tr("Write private info")}, - {AcePermission_ReadPublicInfo, tr("Read public info")}, - {AcePermission_WritePublicInfo, tr("Write public info")}, - {AcePermission_ReadRemoteAccessInfo, tr("Read remote access info")}, - {AcePermission_WriteRemoteAccessInfo, tr("Write remote access info")}, - {AcePermission_ReadTerminalServerLicenseServer, tr("Read terminal server license server")}, - {AcePermission_WriteTerminalServerLicenseServer, tr("Write terminal server license server")}, - {AcePermission_ReadWebInfo, tr("Read web info")}, - {AcePermission_WriteWebInfo, tr("Write web info")}, - }; -} +enum RightsItemRole { + RightsItemRole_AccessMask = Qt::UserRole, + RightsItemRole_ObjectType, + RightsItemRole_ObjectTypeName, +}; -SecurityTab::SecurityTab() { +class RightsSortModel final : public QSortFilterProxyModel { +public: + using QSortFilterProxyModel::QSortFilterProxyModel; + + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override { + const QString name_left = source_left.data(Qt::DisplayRole).toString(); + const QString name_right = source_right.data(Qt::DisplayRole).toString(); + const QString object_type_name_left = source_left.data(RightsItemRole_ObjectTypeName).toString(); + const QString object_type_name_right = source_right.data(RightsItemRole_ObjectTypeName).toString(); + const QByteArray object_type_left = source_left.data(RightsItemRole_ObjectType).toByteArray(); + const QByteArray object_type_right = source_right.data(RightsItemRole_ObjectType).toByteArray(); + const uint32_t access_mask_left = source_left.data(RightsItemRole_AccessMask).toUInt(); + const uint32_t access_mask_right = source_right.data(RightsItemRole_AccessMask).toUInt(); + const bool is_common_left = object_type_left.isEmpty(); + const bool is_common_right = object_type_right.isEmpty(); + const bool is_control_left = (access_mask_left == SEC_ADS_CONTROL_ACCESS); + const bool is_control_right = (access_mask_right == SEC_ADS_CONTROL_ACCESS); + const bool is_read_left = (access_mask_left == SEC_ADS_READ_PROP); + const bool is_read_right = (access_mask_right == SEC_ADS_READ_PROP); + + // Generic are before non-generic + if (is_common_left != is_common_right) { + return is_common_left; + } + + // Generic among generic are in pre-defined order + if (is_common_left && is_common_right) { + const int common_index_left = common_rights_list.indexOf(access_mask_left); + const int common_index_right = common_rights_list.indexOf(access_mask_right); + + return (common_index_left < common_index_right); + } + + // Control rights are before read/write rights + if (is_control_left != is_control_right) { + return is_control_left; + } + + // Control rights are sorted by name + if (is_control_left && is_control_right) { + return name_left < name_right; + } + + // Read/write rights are sorted by name + if (object_type_left != object_type_right) { + return object_type_name_left < object_type_name_right; + } + + // Read rights are before write rights + if (is_read_left != is_read_right) { + return is_read_left; + } + + return name_left < name_right; + } +}; + +SecurityTab::SecurityTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { ui = new Ui::SecurityTab(); ui->setupUi(this); + tab_edit = new SecurityTabEdit(ui, this); + + edit_list->append({ + tab_edit, + }); +} + +void SecurityTab::fix_acl_order() { + tab_edit->fix_acl_order(); +} + +void SecurityTab::set_read_only() { + tab_edit->set_read_only(); +} + +bool SecurityTab::verify_acl_order() const { + return tab_edit->verify_acl_order(); +} + +SecurityTabEdit::SecurityTabEdit(Ui::SecurityTab *ui_arg, QObject *parent) +: AttributeEdit(parent) { + ui = ui_arg; + + sd = nullptr; + ignore_item_changed_signal = false; + read_only = false; trustee_model = new QStandardItemModel(0, 1, this); ui->trustee_view->setModel(trustee_model); - ui->trustee_view->sortByColumn(0, Qt::AscendingOrder); - - ace_model = new QStandardItemModel(0, AceColumn_COUNT, this); - set_horizontal_header_labels_from_map(ace_model, + + rights_model = new QStandardItemModel(0, AceColumn_COUNT, this); + set_horizontal_header_labels_from_map(rights_model, { {AceColumn_Name, tr("Name")}, {AceColumn_Allowed, tr("Allowed")}, {AceColumn_Denied, tr("Denied")}, }); - // Fill ace model - for (const AcePermission &permission : all_permissions_list) { - const QList row = make_item_row(AceColumn_COUNT); + rights_sort_model = new RightsSortModel(this); + rights_sort_model->setSourceModel(rights_model); - const QString mask_string = ace_permission_to_name_map()[permission]; - row[AceColumn_Name]->setText(mask_string); + ui->rights_view->setModel(rights_sort_model); + ui->rights_view->setColumnWidth(AceColumn_Name, 400); - row[AceColumn_Allowed]->setCheckable(true); - row[AceColumn_Denied]->setCheckable(true); - - row[0]->setData(permission, AcePermissionItemRole_Permission); - - ace_model->appendRow(row); - } - - permission_item_map = [&]() { - QHash> out; - - for (int row = 0; row < ace_model->rowCount(); row++) { - QStandardItem *main_item = ace_model->item(row, 0); - const AcePermission permission = (AcePermission) main_item->data(AcePermissionItemRole_Permission).toInt(); - - const QList column_list = { - AceColumn_Allowed, - AceColumn_Denied, - }; - - for (const AceColumn &column : column_list) { - QStandardItem *item = ace_model->item(row, column); - out[permission][column] = item; - } - } - - return out; - }(); - - ui->ace_view->setModel(ace_model); - ui->ace_view->setColumnWidth(AceColumn_Name, 400); - - settings_restore_header_state(SETTING_security_tab_header_state, ui->ace_view->header()); + settings_restore_header_state(SETTING_security_tab_header_state, ui->rights_view->header()); connect( ui->trustee_view->selectionModel(), &QItemSelectionModel::currentChanged, - this, &SecurityTab::load_trustee_acl); + this, &SecurityTabEdit::load_rights_model); connect( - ace_model, &QStandardItemModel::itemChanged, - this, &SecurityTab::on_item_changed); + rights_model, &QStandardItemModel::itemChanged, + this, &SecurityTabEdit::on_item_changed); connect( ui->add_trustee_button, &QAbstractButton::clicked, - this, &SecurityTab::on_add_trustee_button); + this, &SecurityTabEdit::on_add_trustee_button); connect( ui->add_well_known_trustee_button, &QAbstractButton::clicked, - this, &SecurityTab::on_add_well_known_trustee); + this, &SecurityTabEdit::on_add_well_known_trustee); connect( ui->remove_trustee_button, &QAbstractButton::clicked, - this, &SecurityTab::on_remove_trustee_button); + this, &SecurityTabEdit::on_remove_trustee_button); +} + +SecurityTabEdit::~SecurityTabEdit() { + if (sd != nullptr) { + security_descriptor_free(sd); + } +} + +void SecurityTabEdit::fix_acl_order() { + security_descriptor_sort_dacl(sd); + + emit edited(); +} + +void SecurityTabEdit::set_read_only() { + ui->add_trustee_button->setEnabled(false); + ui->add_well_known_trustee_button->setEnabled(false); + ui->remove_trustee_button->setEnabled(false); + + make_rights_model_read_only(); + + read_only = true; +} + +bool SecurityTabEdit::verify_acl_order() const { + const bool out = security_descriptor_verify_acl_order(sd); + + return out; } SecurityTab::~SecurityTab() { - settings_save_header_state(SETTING_security_tab_header_state, ui->ace_view->header()); + settings_save_header_state(SETTING_security_tab_header_state, ui->rights_view->header()); delete ui; } -void SecurityTab::load(AdInterface &ad, const AdObject &object) { - trustee_model->removeRows(0, trustee_model->rowCount()); +// Load sd data into trustee model and rights model +// NOTE: load_rights_model() is not explicitly called +// here but it is called implicitly because +// setCurrentIndex() emits currentChanged() signal +// which calls load_rights_model() +void SecurityTabEdit::load_current_sd(AdInterface &ad) { + // Save previous selected trustee before reloading + // trustee model. This is for the case where we + // need to restore selection later. + const QByteArray previous_selected_trustee = [&]() { + const QList selected_list = ui->trustee_view->selectionModel()->selectedRows(); - // Add items to trustee model - const QList trustee_list = ad_security_get_trustee_list_from_object(object); + if (!selected_list.isEmpty()) { + const QModelIndex selected = selected_list[0]; + const QByteArray out = selected.data(TrusteeItemRole_Sid).toByteArray(); + + return out; + } else { + return QByteArray(); + } + }(); + + // Load trustee model + trustee_model->removeRows(0, trustee_model->rowCount()); + const QList trustee_list = security_descriptor_get_trustee_list(sd); add_trustees(trustee_list, ad); - trustee_model->sort(0, Qt::AscendingOrder); + // Select a trustee + // + // Note that trustee view must always have a + // selection so that rights view displays + // something. We also restore + const QModelIndex selected_trustee = [&]() { + const QModelIndex first_index = trustee_model->index(0,0); - permission_state_map = object.get_security_state(g_adconfig); + // Restore previously selected trustee + const QList match_list = trustee_model->match(first_index, TrusteeItemRole_Sid, previous_selected_trustee, -1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive)); - original_permission_state_map = permission_state_map; + if (!match_list.isEmpty()) { + return match_list[0]; + } else { + return first_index; + } + }(); - // Select first index - // NOTE: load_trustee_acl() is called because setCurrentIndex - // emits "current change" signal - ui->trustee_view->selectionModel()->setCurrentIndex(trustee_model->index(0, 0), QItemSelectionModel::Current | QItemSelectionModel::ClearAndSelect); - - is_policy = object.is_class(CLASS_GP_CONTAINER); - - PropertiesTab::load(ad, object); + ui->trustee_view->selectionModel()->setCurrentIndex(selected_trustee, QItemSelectionModel::Current | QItemSelectionModel::ClearAndSelect); } -void SecurityTab::load_trustee_acl() { +void SecurityTabEdit::load(AdInterface &ad, const AdObject &object) { + security_descriptor_free(sd); + sd = object.get_security_descriptor(); + + target_class_list = object.get_strings(ATTRIBUTE_OBJECT_CLASS); + + // Create items in rights model. These will not + // change until target object changes. Only the + // state of items changes during editing. + const QList right_list = ad_security_get_right_list_for_class(g_adconfig, target_class_list); + + rights_model->removeRows(0, rights_model->rowCount()); + + const QLocale::Language language = []() { + const QLocale saved_locale = settings_get_variant(SETTING_locale).toLocale(); + const QLocale::Language out = saved_locale.language(); + + return out; + }(); + + for (const SecurityRight &right : right_list) { + const QList row = make_item_row(AceColumn_COUNT); + + // TODO: for russian, probably do "read/write + // property - [property name]" to avoid having + // to do suffixes properties + const QString right_name = ad_security_get_right_name(g_adconfig, right.access_mask, right.object_type, language); + + const QString object_type_name = g_adconfig->get_right_name(right.object_type, language); + + row[AceColumn_Name]->setText(right_name); + row[AceColumn_Allowed]->setCheckable(true); + row[AceColumn_Denied]->setCheckable(true); + + row[0]->setData(right.access_mask, RightsItemRole_AccessMask); + row[0]->setData(right.object_type, RightsItemRole_ObjectType); + row[0]->setData(object_type_name, RightsItemRole_ObjectTypeName); + + rights_model->appendRow(row); + } + + // NOTE: because rights model is dynamically filled + // when trustee switches, we have to make rights + // model read only again after it's reloaded + if (read_only) { + make_rights_model_read_only(); + } + + rights_sort_model->sort(0); + + load_current_sd(ad); + + is_policy = object.is_class(CLASS_GP_CONTAINER); +} + +// Load rights model based on current sd and current +// trustee +void SecurityTabEdit::load_rights_model() { const QModelIndex current_index = ui->trustee_view->currentIndex(); if (!current_index.isValid()) { return; } - apply_current_state_to_items(); + // NOTE: this flag is turned on so that + // on_item_changed() slot doesn't react to us + // changing state of items + ignore_item_changed_signal = true; + + const QByteArray trustee = get_current_trustee(); + + for (int row = 0; row < rights_model->rowCount(); row++) { + const SecurityRightState state = [&]() { + QStandardItem *item = rights_model->item(row, 0); + const uint32_t access_mask = item->data(RightsItemRole_AccessMask).toUInt(); + + const QByteArray object_type = item->data(RightsItemRole_ObjectType).toByteArray(); + const SecurityRightState out = security_descriptor_get_right(sd, trustee, access_mask, object_type); + + return out; + }(); + + const QHash item_map = { + {SecurityRightStateType_Allow, rights_model->item(row, AceColumn_Allowed)}, + {SecurityRightStateType_Deny, rights_model->item(row, AceColumn_Denied)}, + }; + + for (int type_i = 0; type_i < SecurityRightStateType_COUNT; type_i++) { + const SecurityRightStateType type = (SecurityRightStateType) type_i; + + QStandardItem *item = item_map[type]; + + const bool object_state = state.get(SecurityRightStateInherited_No, type); + const bool inherited_state = state.get(SecurityRightStateInherited_Yes, type); + + // Checkboxes become disabled if they + // contain only inherited state. Note that + // if there's both inherited and object + // state for same right, checkbox is + // enabled so that user can remove object + // state. + const bool disabled = (inherited_state && !object_state); + item->setEnabled(!disabled); + + const Qt::CheckState check_state = [&]() { + if (object_state || inherited_state) { + return Qt::Checked; + } else { + return Qt::Unchecked; + } + }(); + item->setCheckState(check_state); + } + } + + // NOTE: need to make read only again because + // during load, items are enabled/disabled based on + // their inheritance state + if (read_only) { + make_rights_model_read_only(); + } + + ignore_item_changed_signal = false; } -void SecurityTab::on_item_changed(QStandardItem *item) { +void SecurityTabEdit::make_rights_model_read_only() { + // NOTE: important to ignore this signal because + // it's slot reloads the rights model + ignore_item_changed_signal = true; + + for (int row = 0; row < rights_model->rowCount(); row++) { + const QList col_list = { + AceColumn_Allowed, + AceColumn_Denied, + }; + + for (const int col : col_list) { + QStandardItem *item = rights_model->item(row, col); + item->setEnabled(false); + } + } + + ignore_item_changed_signal = false; +} + +void SecurityTabEdit::on_item_changed(QStandardItem *item) { // NOTE: in some cases we need to ignore this signal if (ignore_item_changed_signal) { return; @@ -212,65 +423,28 @@ void SecurityTab::on_item_changed(QStandardItem *item) { return; } - const AcePermission permission = [&]() { - QStandardItem *main_item = ace_model->item(item->row(), 0); - const AcePermission out = (AcePermission) main_item->data(AcePermissionItemRole_Permission).toInt(); + QStandardItem *main_item = rights_model->item(item->row(), 0); - return out; - }(); + const bool checked = (item->checkState() == Qt::Checked); - const PermissionState new_state = [&]() { - const bool checked = (item->checkState() == Qt::Checked); - const bool allowed = (column == AceColumn_Allowed); + const QByteArray trustee = get_current_trustee(); + const uint32_t access_mask = main_item->data(RightsItemRole_AccessMask).toUInt(); + const QByteArray object_type = main_item->data(RightsItemRole_ObjectType).toByteArray(); + const bool allow = (column == AceColumn_Allowed); - if (checked) { - if (allowed) { - return PermissionState_Allowed; - } else { - return PermissionState_Denied; - } - } else { - // NOTE: the case of opposite column being - // checked while this one becomes unchecked is - // impossible, so ignore it - return PermissionState_None; - } - }(); + if (checked) { + security_descriptor_add_right(sd, g_adconfig, target_class_list, trustee, access_mask, object_type, allow); + } else { + security_descriptor_remove_right(sd, g_adconfig, target_class_list, trustee, access_mask, object_type, allow); + } - const QByteArray trustee = [&]() { - const QModelIndex current_index = ui->trustee_view->currentIndex(); - QStandardItem *current_item = trustee_model->itemFromIndex(current_index); - const QByteArray out = current_item->data(TrusteeItemRole_Sid).toByteArray(); - - return out; - }(); - - permission_state_map = ad_security_modify(permission_state_map, trustee, permission, new_state); - apply_current_state_to_items(); + load_rights_model(); emit edited(); } -QStandardItem *SecurityTab::get_item(const AcePermission permission, const AceColumn column) { - return permission_item_map[permission][column]; -} - -bool SecurityTab::set_trustee(const QString &trustee_name) { - const QList item_list = trustee_model->findItems(trustee_name); - - if (item_list.isEmpty()) { - return false; - } - - const QStandardItem *item = item_list[0]; - ui->trustee_view->setCurrentIndex(item->index()); - - return true; -} - -bool SecurityTab::verify(AdInterface &ad, const QString &target) const { +bool SecurityTabEdit::verify(AdInterface &ad, const QString &target) const { UNUSED_ARG(target); - if (is_policy) { // To apply security tab for policies we need user // to have admin rights to be able to sync perms of @@ -283,35 +457,29 @@ bool SecurityTab::verify(AdInterface &ad, const QString &target) const { } } -bool SecurityTab::apply(AdInterface &ad, const QString &target) { - const bool modified = (original_permission_state_map != permission_state_map); - if (!modified) { - return true; - } - +bool SecurityTabEdit::apply(AdInterface &ad, const QString &target) const { bool total_success = true; - total_success &= attribute_replace_security_descriptor(&ad, target, permission_state_map); - - original_permission_state_map = permission_state_map; + total_success = (total_success && ad_security_replace_security_descriptor(ad, target, sd)); if (is_policy) { - total_success &= ad.gpo_sync_perms(target); + total_success = (total_success && ad.gpo_sync_perms(target)); } return total_success; } -void SecurityTab::on_add_trustee_button() { - auto dialog = new SelectObjectDialog({CLASS_USER, CLASS_GROUP}, SelectObjectDialogMultiSelection_Yes, this); +void SecurityTabEdit::on_add_trustee_button() { + auto dialog = new SelectObjectDialog({CLASS_USER, CLASS_GROUP}, SelectObjectDialogMultiSelection_Yes, ui->trustee_view); dialog->setWindowTitle(tr("Add Trustee")); dialog->open(); connect( dialog, &SelectObjectDialog::accepted, - [=]() { + this, + [this, dialog]() { AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, ui->trustee_view)) { return; } @@ -334,64 +502,45 @@ void SecurityTab::on_add_trustee_button() { }); } -void SecurityTab::on_remove_trustee_button() { - QItemSelectionModel *selection_model = ui->trustee_view->selectionModel(); - const QList selected_list = persistent_index_list(selection_model->selectedRows()); - - for (const QPersistentModelIndex &index : selected_list) { - const QByteArray sid = index.data(TrusteeItemRole_Sid).toByteArray(); - permission_state_map.remove(sid); - - trustee_model->removeRow(index.row()); +void SecurityTabEdit::on_remove_trustee_button() { + AdInterface ad; + if (ad_failed(ad, ui->remove_trustee_button)) { + return; } - if (!selected_list.isEmpty()) { - emit edited(); - } -} + const QList removed_trustee_list = [&]() { + QList out; -// NOTE: a flag is set to avoid triggering on_item_changed() -// slot due to setCheckState() calls. Otherwise it would -// recurse and do all kinds of bad stuff. -void SecurityTab::apply_current_state_to_items() { - ignore_item_changed_signal = true; + QItemSelectionModel *selection_model = ui->trustee_view->selectionModel(); + const QList selected_list = persistent_index_list(selection_model->selectedRows()); - const QByteArray trustee = [&]() { - const QModelIndex current_index = ui->trustee_view->currentIndex(); - QStandardItem *current_item = trustee_model->itemFromIndex(current_index); - const QByteArray out = current_item->data(TrusteeItemRole_Sid).toByteArray(); + for (const QPersistentModelIndex &index : selected_list) { + const QByteArray sid = index.data(TrusteeItemRole_Sid).toByteArray(); + + out.append(sid); + } return out; }(); - for (const AcePermission &permission : all_permissions) { - QStandardItem *allowed = permission_item_map[permission][AceColumn_Allowed]; - QStandardItem *denied = permission_item_map[permission][AceColumn_Denied]; - const PermissionState state = permission_state_map[trustee][permission]; + // Remove from sd + security_descriptor_remove_trustee(sd, removed_trustee_list); - switch (state) { - case PermissionState_None: { - allowed->setCheckState(Qt::Unchecked); - denied->setCheckState(Qt::Unchecked); - break; - }; - case PermissionState_Allowed: { - allowed->setCheckState(Qt::Checked); - denied->setCheckState(Qt::Unchecked); - break; - } - case PermissionState_Denied: { - allowed->setCheckState(Qt::Unchecked); - denied->setCheckState(Qt::Checked); - break; - } - } + // Reload sd + // + // NOTE: we do this instead of removing selected + // indexes because not all trustee's are guaranteed + // to have been removed + load_current_sd(ad); + + const bool removed_any = !removed_trustee_list.isEmpty(); + + if (removed_any) { + emit edited(); } - - ignore_item_changed_signal = false; } -void SecurityTab::add_trustees(const QList &sid_list, AdInterface &ad) { +void SecurityTabEdit::add_trustees(const QList &sid_list, AdInterface &ad) { const QList current_sid_string_list = [&]() { QList out; @@ -427,24 +576,27 @@ void SecurityTab::add_trustees(const QList &sid_list, AdInterface &a added_anything = true; } + ui->trustee_view->sortByColumn(0, Qt::AscendingOrder); + if (added_anything) { emit edited(); } if (failed_to_add_because_already_exists) { - message_box_warning(this, tr("Error"), tr("Failed to add some trustee's because they are already in the list.")); + message_box_warning(ui->trustee_view, tr("Error"), tr("Failed to add some trustee's because they are already in the list.")); } } -void SecurityTab::on_add_well_known_trustee() { - auto dialog = new SelectWellKnownTrusteeDialog(this); +void SecurityTabEdit::on_add_well_known_trustee() { + auto dialog = new SelectWellKnownTrusteeDialog(ui->trustee_view); dialog->open(); connect( dialog, &QDialog::accepted, + this, [this, dialog]() { AdInterface ad; - if (ad_failed(ad)) { + if (ad_failed(ad, ui->trustee_view)) { return; } @@ -453,3 +605,11 @@ void SecurityTab::on_add_well_known_trustee() { add_trustees(trustee_list, ad); }); } + +QByteArray SecurityTabEdit::get_current_trustee() const { + const QModelIndex current_index = ui->trustee_view->currentIndex(); + QStandardItem *current_item = trustee_model->itemFromIndex(current_index); + const QByteArray out = current_item->data(TrusteeItemRole_Sid).toByteArray(); + + return out; +} diff --git a/src/admc/tabs/security_tab.h b/src/admc/tabs/security_tab.h index 8f663f30..af054b30 100644 --- a/src/admc/tabs/security_tab.h +++ b/src/admc/tabs/security_tab.h @@ -21,14 +21,18 @@ #ifndef SECURITY_TAB_H #define SECURITY_TAB_H -#include "tabs/properties_tab.h" +#include +#include "attribute_edits/attribute_edit.h" #include "ad_defines.h" #include +class RightsSortModel; class QStandardItemModel; class QStandardItem; +class SecurityDescriptor; +struct security_descriptor; enum AceColumn { AceColumn_Name, @@ -38,47 +42,64 @@ enum AceColumn { AceColumn_COUNT, }; +class SecurityTabEdit; + namespace Ui { class SecurityTab; } -class SecurityTab final : public PropertiesTab { +class SecurityTab final : public QWidget { Q_OBJECT public: Ui::SecurityTab *ui; - SecurityTab(); + SecurityTab(QList *edit_list, QWidget *parent); ~SecurityTab(); - static QHash ace_permission_to_name_map(); - - void load(AdInterface &ad, const AdObject &object) override; - bool verify(AdInterface &ad, const QString &target) const override; - bool apply(AdInterface &ad, const QString &target) override; - - // NOTE: f-ns for testings - QStandardItem *get_item(const AcePermission permission, const AceColumn column); - bool set_trustee(const QString &trustee_name); - -private slots: - void load_trustee_acl(); + void fix_acl_order(); + void set_read_only(); + bool verify_acl_order() const; private: + SecurityTabEdit *tab_edit; +}; + +class SecurityTabEdit final : public AttributeEdit { + Q_OBJECT + +public: + SecurityTabEdit(Ui::SecurityTab *ui, QObject *parent); + ~SecurityTabEdit(); + + void load(AdInterface &ad, const AdObject &object) override; + bool verify(AdInterface &ad, const QString &dn) const override; + bool apply(AdInterface &ad, const QString &dn) const override; + + void fix_acl_order(); + void set_read_only(); + bool verify_acl_order() const; + +private: + Ui::SecurityTab *ui; QStandardItemModel *trustee_model; - QStandardItemModel *ace_model; - QHash> permission_item_map; - QHash> original_permission_state_map; - QHash> permission_state_map; - bool ignore_item_changed_signal; + QStandardItemModel *rights_model; + RightsSortModel *rights_sort_model; bool is_policy; + bool ignore_item_changed_signal; + security_descriptor *sd; + QList target_class_list; + bool read_only; void on_item_changed(QStandardItem *item); void on_add_trustee_button(); void on_remove_trustee_button(); - void apply_current_state_to_items(); void add_trustees(const QList &sid_list, AdInterface &ad); void on_add_well_known_trustee(); + void load_current_sd(AdInterface &ad); + void load_rights_model(); + void make_rights_model_read_only(); + QByteArray get_current_trustee() const; }; #endif /* SECURITY_TAB_H */ diff --git a/src/admc/tabs/security_tab.ui b/src/admc/tabs/security_tab.ui index 87aef659..38d5138b 100644 --- a/src/admc/tabs/security_tab.ui +++ b/src/admc/tabs/security_tab.ui @@ -20,7 +20,7 @@ QAbstractItemView::NoEditTriggers - true + false true @@ -76,7 +76,7 @@ - + QAbstractItemView::NoEditTriggers diff --git a/src/admc/tabs/select_well_known_trustee_dialog.cpp b/src/admc/tabs/select_well_known_trustee_dialog.cpp index 73338793..cc8ddff5 100644 --- a/src/admc/tabs/select_well_known_trustee_dialog.cpp +++ b/src/admc/tabs/select_well_known_trustee_dialog.cpp @@ -23,8 +23,11 @@ #include "ad_security.h" #include "ad_utils.h" +#include "utils.h" #include "settings.h" +#include + SelectWellKnownTrusteeDialog::SelectWellKnownTrusteeDialog(QWidget *parent) : QDialog(parent) { ui = new Ui::SelectWellKnownTrusteeDialog(); @@ -44,6 +47,9 @@ SelectWellKnownTrusteeDialog::SelectWellKnownTrusteeDialog(QWidget *parent) ui->list->addItem(item); } + QPushButton *ok_button = ui->button_box->button(QDialogButtonBox::Ok); + enable_widget_on_selection(ok_button, ui->list); + settings_setup_dialog_geometry(SETTING_select_well_known_trustee_dialog_geometry, this); } diff --git a/src/admc/tabs/telephones_tab.cpp b/src/admc/tabs/telephones_tab.cpp index a8217cb1..bc452dc6 100644 --- a/src/admc/tabs/telephones_tab.cpp +++ b/src/admc/tabs/telephones_tab.cpp @@ -25,19 +25,27 @@ #include "attribute_edits/string_large_edit.h" #include "attribute_edits/string_other_edit.h" -TelephonesTab::TelephonesTab() { +TelephonesTab::TelephonesTab(QList *edit_list, QWidget *parent) +: QWidget(parent) { ui = new Ui::TelephonesTab(); ui->setupUi(this); - new StringOtherEdit(ui->home_phone_edit, ui->home_phone_button, ATTRIBUTE_HOME_PHONE, ATTRIBUTE_OTHER_HOME_PHONE, &edits, this); - new StringOtherEdit(ui->pager_edit, ui->pager_button, ATTRIBUTE_PAGER, ATTRIBUTE_OTHER_PAGER, &edits, this); - new StringOtherEdit(ui->mobile_edit, ui->mobile_button, ATTRIBUTE_MOBILE, ATTRIBUTE_OTHER_MOBILE, &edits, this); - new StringOtherEdit(ui->fax_edit, ui->fax_button, ATTRIBUTE_FAX_NUMBER, ATTRIBUTE_OTHER_FAX_NUMBER, &edits, this); - new StringOtherEdit(ui->ip_phone_edit, ui->ip_phone_button, ATTRIBUTE_IP_PHONE, ATTRIBUTE_OTHER_IP_PHONE, &edits, this); + auto home_phone_edit = new StringOtherEdit(ui->home_phone_edit, ui->home_phone_button, ATTRIBUTE_HOME_PHONE, ATTRIBUTE_OTHER_HOME_PHONE, this); + auto pager_edit = new StringOtherEdit(ui->pager_edit, ui->pager_button, ATTRIBUTE_PAGER, ATTRIBUTE_OTHER_PAGER, this); + auto mobile_edit = new StringOtherEdit(ui->mobile_edit, ui->mobile_button, ATTRIBUTE_MOBILE, ATTRIBUTE_OTHER_MOBILE, this); + auto fax_edit = new StringOtherEdit(ui->fax_edit, ui->fax_button, ATTRIBUTE_FAX_NUMBER, ATTRIBUTE_OTHER_FAX_NUMBER, this); + auto ip_phone_edit = new StringOtherEdit(ui->ip_phone_edit, ui->ip_phone_button, ATTRIBUTE_IP_PHONE, ATTRIBUTE_OTHER_IP_PHONE, this); - new StringLargeEdit(ui->notes_edit, ATTRIBUTE_INFO, &edits, this); + auto notes_edit = new StringLargeEdit(ui->notes_edit, ATTRIBUTE_INFO, this); - edits_connect_to_tab(edits, this); + edit_list->append({ + home_phone_edit, + pager_edit, + mobile_edit, + fax_edit, + ip_phone_edit, + notes_edit, + }); } TelephonesTab::~TelephonesTab() { diff --git a/src/admc/tabs/telephones_tab.h b/src/admc/tabs/telephones_tab.h index 986346ff..b3c638f7 100644 --- a/src/admc/tabs/telephones_tab.h +++ b/src/admc/tabs/telephones_tab.h @@ -21,19 +21,21 @@ #ifndef TELEPHONES_TAB_H #define TELEPHONES_TAB_H -#include "tabs/properties_tab.h" +#include + +class AttributeEdit; namespace Ui { class TelephonesTab; } -class TelephonesTab final : public PropertiesTab { +class TelephonesTab final : public QWidget { Q_OBJECT public: Ui::TelephonesTab *ui; - TelephonesTab(); + TelephonesTab(QList *edit_list, QWidget *parent); ~TelephonesTab(); }; diff --git a/src/admc/utils.cpp b/src/admc/utils.cpp index 98083308..c55bcf27 100644 --- a/src/admc/utils.cpp +++ b/src/admc/utils.cpp @@ -101,7 +101,7 @@ void hide_busy_indicator() { } bool confirmation_dialog(const QString &text, QWidget *parent) { - const bool confirm_actions = settings_get_bool(SETTING_confirm_actions); + const bool confirm_actions = settings_get_variant(SETTING_confirm_actions).toBool(); if (!confirm_actions) { return true; } @@ -122,32 +122,40 @@ void set_data_for_row(const QList &row, const QVariant &data, c } } -bool ad_connected_base(const AdInterface &ad) { +bool ad_connected_base(const AdInterface &ad, QWidget *parent) { if (!ad.is_connected()) { - ad_error_log(ad, nullptr); + ad_error_log(ad, parent); } return ad.is_connected(); } -bool ad_connected(const AdInterface &ad) { - return ad_connected_base(ad); +bool ad_connected(const AdInterface &ad, QWidget *parent) { + return ad_connected_base(ad, parent); } -bool ad_failed(const AdInterface &ad) { - return !ad_connected_base(ad); +bool ad_failed(const AdInterface &ad, QWidget *parent) { + return !ad_connected_base(ad, parent); +} + +QString get_classes_filter(const QList &class_list) { + QList class_filters; + for (const QString &object_class : class_list) { + const QString class_filter = filter_CONDITION(Condition_Equals, ATTRIBUTE_OBJECT_CLASS, object_class); + class_filters.append(class_filter); + } + + const QString out = filter_OR(class_filters); + + return out; } QString is_container_filter() { const QList accepted_classes = g_adconfig->get_filter_containers(); - QList class_filters; - for (const QString &object_class : accepted_classes) { - const QString class_filter = filter_CONDITION(Condition_Equals, ATTRIBUTE_OBJECT_CLASS, object_class); - class_filters.append(class_filter); - } + const QString out = get_classes_filter(accepted_classes); - return filter_OR(class_filters); + return out; } void limit_edit(QLineEdit *edit, const QString &attribute) { @@ -158,33 +166,48 @@ void limit_edit(QLineEdit *edit, const QString &attribute) { } QIcon get_object_icon(const AdObject &object) { - static const QMap class_to_icon = { - {CLASS_DOMAIN, "network-server"}, - {CLASS_CONTAINER, "folder"}, - {CLASS_OU, "folder-documents"}, - {CLASS_GROUP, "application-x-smb-workgroup"}, - {CLASS_PERSON, "avatar-default"}, - {CLASS_COMPUTER, "computer"}, - {CLASS_GP_CONTAINER, "folder-templates"}, + // NOTE: use a list of possible icons because + // default icon themes for different DE's don't + // fully intersect + static const QMap> category_to_icon_list = { + {"Domain-DNS", {"network-server"}}, + {"Container", {"folder"}}, + {"Organizational-Unit", {"folder-documents"}}, + {"Group", {"system-users"}}, + {"Person", {"avatar-default", "avatar-default-symbolic"}}, + {"Computer", {"computer"}}, + {"Group-Policy-Container", {"folder-templates"}}, + {"Volume", {"folder-templates"}}, // Some custom icons for one-off objects - {"builtinDomain", "emblem-system"}, - {"configuration", "emblem-system"}, - {"lostAndFound", "emblem-system"}, + {"Builtin-Domain", {"emblem-system", "emblem-system-symbolic"}}, + {"Configuration", {"emblem-system", "emblem-system-symbolic"}}, + {"Lost-And-Found", {"emblem-system", "emblem-system-symbolic"}}, + {"Infrastructure-Update", {"emblem-system", "emblem-system-symbolic"}}, + {"ms-DS-Quota-Container", {"emblem-system", "emblem-system-symbolic"}}, }; - // Iterate over object classes in reverse, starting from most inherited class - QList object_classes = object.get_strings(ATTRIBUTE_OBJECT_CLASS); - std::reverse(object_classes.begin(), object_classes.end()); + // NOTE: This is the icon used when no icon is + // defined for some object category + const QString error_icon = "dialog-question"; - const QString icon_name = [object_classes]() -> QString { - for (auto object_class : object_classes) { - if (class_to_icon.contains(object_class)) { - return class_to_icon[object_class]; + const QString icon_name = [&]() -> QString { + const QString object_category = [&]() { + const QString category_dn = object.get_string(ATTRIBUTE_OBJECT_CATEGORY); + const QString out = dn_get_name(category_dn); + + return out; + }(); + + const QList icon_name_list = category_to_icon_list.value(object_category, {error_icon}); + + for (const QString &icon : icon_name_list) { + if (QIcon::hasThemeIcon(icon)) { + return icon; } } - return "dialog-question"; + return error_icon; }(); const QIcon icon = QIcon::fromTheme(icon_name); @@ -204,52 +227,33 @@ QList persistent_index_list(const QList &ind // Hide advanced view only" objects if advanced view setting // is off -void advanced_features_filter(QString &filter) { - const bool advanced_features_OFF = !settings_get_bool(SETTING_advanced_features); +QString advanced_features_filter(const QString &filter) { + const bool advanced_features_OFF = !settings_get_variant(SETTING_advanced_features).toBool(); if (advanced_features_OFF) { const QString advanced_features = filter_CONDITION(Condition_NotEquals, ATTRIBUTE_SHOW_IN_ADVANCED_VIEW_ONLY, LDAP_BOOL_TRUE); - filter = filter_AND({filter, advanced_features}); + const QString out = filter_AND({filter, advanced_features}); + + return out; + } else { + return filter; } } -// OR filter with some dev mode object classes, so that they -// show up no matter what when dev mode is on -void dev_mode_filter(QString &filter) { - const bool dev_mode = settings_get_bool(SETTING_dev_mode); - if (!dev_mode) { - return; - } - - const QList schema_classes = { - "classSchema", - "attributeSchema", - "displaySpecifier", - }; - - QList class_filters; - for (const QString &object_class : schema_classes) { - const QString class_filter = filter_CONDITION(Condition_Equals, ATTRIBUTE_OBJECT_CLASS, object_class); - class_filters.append(class_filter); - } - - filter = filter_OR({filter, filter_OR(class_filters)}); -} - // NOTE: configuration and schema objects are hidden so that // they don't show up in regular searches. Have to use // search_object() and manually add them to search results. void dev_mode_search_results(QHash &results, AdInterface &ad, const QString &base) { - const bool dev_mode = settings_get_bool(SETTING_dev_mode); + const bool dev_mode = settings_get_variant(SETTING_feature_dev_mode).toBool(); if (!dev_mode) { return; } - const QString domain_head = g_adconfig->domain_head(); + const QString domain_dn = g_adconfig->domain_dn(); const QString configuration_dn = g_adconfig->configuration_dn(); const QString schema_dn = g_adconfig->schema_dn(); - if (base == domain_head) { + if (base == domain_dn) { results[configuration_dn] = ad.search_object(configuration_dn); } else if (base == configuration_dn) { results[schema_dn] = ad.search_object(schema_dn); @@ -285,11 +289,10 @@ QMessageBox *message_box_warning(QWidget *parent, const QString &title, const QS return message_box_generic(QMessageBox::Warning, title, text, parent); } -QList get_selected_dn_list(ConsoleWidget *console, const int type, const int dn_role) { +QList index_list_to_dn_list(const QList &index_list, const int dn_role) { QList out; - const QList indexes = console->get_selected_items(type); - for (const QModelIndex &index : indexes) { + for (const QModelIndex &index : index_list) { const QString dn = index.data(dn_role).toString(); out.append(dn); } @@ -297,8 +300,15 @@ QList get_selected_dn_list(ConsoleWidget *console, const int type, cons return out; } -QString get_selected_dn(ConsoleWidget *console, const int type, const int dn_role) { - const QList dn_list = get_selected_dn_list(console, type, dn_role); +QList get_action_target_dn_list(ConsoleWidget *console, const int type, const int dn_role) { + const QList indexes = console->get_action_target_items(type); + const QList out = index_list_to_dn_list(indexes, dn_role); + + return out; +} + +QString get_action_target_dn(ConsoleWidget *console, const int type, const int dn_role) { + const QList dn_list = get_action_target_dn_list(console, type, dn_role); if (!dn_list.isEmpty()) { return dn_list[0]; @@ -339,3 +349,103 @@ QString generate_new_name(const QList &existing_name_list, const QStrin return get_name(); } + +QList variant_list_to_string_list(const QList &variant_list) { + QList out; + + for (const QVariant &variant : variant_list) { + const QString string = variant.toString(); + out.append(string); + } + + return out; +} + +QList string_list_to_variant_list(const QList &string_list) { + QList out; + + for (const QString &string : string_list) { + const QVariant variant = QVariant(string); + out.append(variant); + } + + return out; +} + +bool string_contains_bad_chars(const QString &string, const QString &bad_chars) { + const QRegularExpression regexp = [&]() { + const QString bad_chars_escaped = QRegularExpression::escape(bad_chars); + const QString regexp_string = QString("[%1]").arg(bad_chars_escaped); + const QRegularExpression out = QRegularExpression(regexp_string); + + return out; + }(); + + const bool out = string.contains(regexp); + + return out; +} + +bool verify_object_name(const QString &name, QWidget *parent) { + const bool contains_bad_chars = [&]() { + const bool some_bad_chars = string_contains_bad_chars(name, NAME_BAD_CHARS); + const bool starts_with_space = name.startsWith(" "); + const bool ends_with_space = name.endsWith(" "); + const bool starts_with_question_mark = name.startsWith("?"); + + const bool out = (some_bad_chars || starts_with_space || ends_with_space || starts_with_question_mark); + + return out; + }(); + + if (contains_bad_chars) { + const QString error_text = QString(QCoreApplication::translate("utils.cpp", "Input field for Name contains one or more of the following illegal characters: # , + \" \\ < > ; = (leading space) (trailing space) (leading question mark)")); + message_box_warning(parent, QCoreApplication::translate("utils.cpp", "Error"), error_text); + + return false; + } + + return true; +} + +void setup_lineedit_autofill(QLineEdit *src, QLineEdit *dest) { + QObject::connect( + src, &QLineEdit::textChanged, + [src, dest]() { + const QString src_input = src->text(); + dest->setText(src_input); + }); +} + +void setup_full_name_autofill(QLineEdit *first_name_edit, QLineEdit *last_name_edit, QLineEdit *full_name_edit) { + auto autofill_full_name = [=]() { + const QString full_name_value = [=]() { + const QString first_name = first_name_edit->text(); + const QString last_name = last_name_edit->text(); + + const bool last_name_first = settings_get_variant(SETTING_last_name_before_first_name).toBool(); + if (!first_name.isEmpty() && !last_name.isEmpty()) { + if (last_name_first) { + return last_name + " " + first_name; + } else { + return first_name + " " + last_name; + } + } else if (!first_name.isEmpty()) { + return first_name; + } else if (!last_name.isEmpty()) { + return last_name; + } else { + return QString(); + } + }(); + + full_name_edit->setText(full_name_value); + }; + + QObject::connect( + first_name_edit, &QLineEdit::textChanged, + first_name_edit, autofill_full_name); + QObject::connect( + last_name_edit, &QLineEdit::textChanged, + last_name_edit, autofill_full_name); +} diff --git a/src/admc/utils.h b/src/admc/utils.h index e79a8791..013d2171 100644 --- a/src/admc/utils.h +++ b/src/admc/utils.h @@ -75,20 +75,24 @@ void set_data_for_row(const QList &row, const QVariant &data, c // Wrappers over is_connected() that also open an error // messagebox if failed to connect. You should generally use // these in GUI code instead of is_connected(). -bool ad_connected(const AdInterface &ad); -bool ad_failed(const AdInterface &ad); +bool ad_connected(const AdInterface &ad, QWidget *parent); +bool ad_failed(const AdInterface &ad, QWidget *parent); +// Filter that accepts only given classes +QString get_classes_filter(const QList &class_list); + +// Filter that accepts only container classes QString is_container_filter(); void limit_edit(QLineEdit *edit, const QString &attribute); +// NOTE: object must contain "objectCategory" attribute QIcon get_object_icon(const AdObject &object); QList persistent_index_list(const QList &indexes); -void advanced_features_filter(QString &filter); +QString advanced_features_filter(const QString &filter); -void dev_mode_filter(QString &filter); void dev_mode_search_results(QHash &results, AdInterface &ad, const QString &base); // NOTE: these f-ns replace QMessageBox static f-ns. The @@ -99,8 +103,9 @@ QMessageBox *message_box_information(QWidget *parent, const QString &title, cons QMessageBox *message_box_question(QWidget *parent, const QString &title, const QString &text); QMessageBox *message_box_warning(QWidget *parent, const QString &title, const QString &text); -QList get_selected_dn_list(ConsoleWidget *console, const int type, const int dn_role); -QString get_selected_dn(ConsoleWidget *console, const int type, const int dn_role); +QList index_list_to_dn_list(const QList &index_list, const int dn_role); +QList get_action_target_dn_list(ConsoleWidget *console, const int type, const int dn_role); +QString get_action_target_dn(ConsoleWidget *console, const int type, const int dn_role); void center_widget(QWidget *widget); @@ -109,4 +114,19 @@ void center_widget(QWidget *widget); // existing names. For example "New Folder (7)" QString generate_new_name(const QList &existing_name_list, const QString &base_name); +QList variant_list_to_string_list(const QList &variant_list); +QList string_list_to_variant_list(const QList &string_list); + +bool string_contains_bad_chars(const QString &string, const QString &bad_chars); + +bool verify_object_name(const QString &name, QWidget *parent); + +// Setup an autofill of from one line edit into +// another, so that when src is edited, input is copied +// into dest. +void setup_lineedit_autofill(QLineEdit *src, QLineEdit *dest); + +// (first name + last name) -> full name +void setup_full_name_autofill(QLineEdit *first_name_edit, QLineEdit *last_name_edit, QLineEdit *full_name_edit); + #endif /* UTILS_H */ diff --git a/src/admc/widget_state.cpp b/src/admc/widget_state.cpp deleted file mode 100644 index 9cdfbe77..00000000 --- a/src/admc/widget_state.cpp +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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 "widget_state.h" - -#include -#include -#include -#include -#include - -class WidgetStatePrivate final { -public: - QList widget_list; - QHash state; -}; - -WidgetState::WidgetState() { - d = new WidgetStatePrivate(); -} - -WidgetState::~WidgetState() { - delete d; -} - -void WidgetState::set_widget_list(const QList &widget_list) { - d->widget_list = widget_list; - - // Clear any previous state since it is now invalid - d->state.clear(); -} - -void WidgetState::save() { - d->state.clear(); - - for (QWidget *widget : d->widget_list) { - QRadioButton *radio_button = qobject_cast(widget); - QLineEdit *line_edit = qobject_cast(widget); - QListWidget *list_widget = qobject_cast(widget); - QCheckBox *check_box = qobject_cast(widget); - QComboBox *combo = qobject_cast(widget); - - if (radio_button != nullptr) { - d->state[widget] = radio_button->isChecked(); - } else if (check_box != nullptr) { - d->state[widget] = check_box->isChecked(); - } else if (line_edit != nullptr) { - d->state[widget] = line_edit->text(); - } else if (list_widget != nullptr) { - d->state[widget] = list_widget->currentRow(); - } else if (combo != nullptr) { - d->state[widget] = combo->currentIndex(); - } - } -} - -void WidgetState::restore() { - for (QWidget *widget : d->widget_list) { - QRadioButton *radio_button = qobject_cast(widget); - QLineEdit *line_edit = qobject_cast(widget); - QListWidget *list_widget = qobject_cast(widget); - QCheckBox *check_box = qobject_cast(widget); - QComboBox *combo = qobject_cast(widget); - - if (radio_button != nullptr) { - radio_button->setChecked(d->state[widget].toBool()); - } else if (check_box != nullptr) { - check_box->setChecked(d->state[widget].toBool()); - } else if (line_edit != nullptr) { - line_edit->setText(d->state[widget].toString()); - } else if (list_widget != nullptr) { - list_widget->setCurrentRow(d->state[widget].toInt()); - } else if (combo != nullptr) { - combo->setCurrentIndex(d->state[widget].toInt()); - } - } -} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 56c3115a..92167ba2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -34,6 +34,7 @@ include_directories( # target + target.cpp set(TEST_TARGETS admc_test_ad_interface + admc_test_ad_security admc_test_unlock_edit admc_test_upn_edit admc_test_string_edit @@ -42,7 +43,6 @@ set(TEST_TARGETS admc_test_gplink admc_test_select_base_widget admc_test_filter_widget - admc_test_security_tab admc_test_attributes_tab admc_test_members_tab admc_test_member_of_tab @@ -70,6 +70,8 @@ set(TEST_TARGETS admc_test_find_object_dialog admc_test_rename_object_dialog admc_test_create_object_dialog + admc_test_select_classes_widget + admc_test_sam_name_edit ) foreach(target ${TEST_TARGETS}) diff --git a/tests/admc_test.cpp b/tests/admc_test.cpp index 193908d0..bd08ec67 100644 --- a/tests/admc_test.cpp +++ b/tests/admc_test.cpp @@ -117,7 +117,7 @@ void ADMCTest::cleanup() { } QString ADMCTest::test_arena_dn() { - const QString head_dn = g_adconfig->domain_head(); + const QString head_dn = g_adconfig->domain_dn(); const QString dn = QString("OU=test-arena,%1").arg(head_dn); return dn; diff --git a/tests/admc_test.h b/tests/admc_test.h index 00193a80..0a54ed62 100644 --- a/tests/admc_test.h +++ b/tests/admc_test.h @@ -42,7 +42,10 @@ class AttributeEdit; #define TEST_PASSWORD "ADMCTEST-pass123!" #define TEST_OU "ADMCTEST-test-ou" #define TEST_GROUP "ADMCTEST-test-group" -#define TEST_COMPUTER "ADMCTEST-test-computer" +// NOTE: use shorter length for computer to fit within +// 16 char length limit for sam account name +#define TEST_COMPUTER "ADMCTEST-pc" +#define TEST_OBJECT "ADMCTEST-object" class ADMCTest : public QObject { Q_OBJECT diff --git a/tests/admc_test_account_option_edit.cpp b/tests/admc_test_account_option_edit.cpp index b6a3909c..70ddd4cf 100644 --- a/tests/admc_test_account_option_edit.cpp +++ b/tests/admc_test_account_option_edit.cpp @@ -41,11 +41,9 @@ void ADMCTestAccountOptionEdit::init() { return out; }(); - QList edit_list; - for (const AccountOption &option : option_list) { auto check = new QCheckBox(parent_widget); - auto edit = new AccountOptionEdit(check, option, &edit_list, parent_widget); + auto edit = new AccountOptionEdit(check, option, parent_widget); check_map[option] = check; edit_map[option] = edit; @@ -66,6 +64,7 @@ void ADMCTestAccountOptionEdit::test_emit_edited_signal() { bool edited_signal_emitted = false; connect( edit, &AttributeEdit::edited, + this, [&edited_signal_emitted]() { edited_signal_emitted = true; }); diff --git a/tests/admc_test_ad_interface.cpp b/tests/admc_test_ad_interface.cpp index b87ec57c..1fe7ea5b 100644 --- a/tests/admc_test_ad_interface.cpp +++ b/tests/admc_test_ad_interface.cpp @@ -29,7 +29,7 @@ void ADMCTestAdInterface::cleanup() { // Delete test gpo, if it was leftover from previous test - const QString base = g_adconfig->domain_head(); + const QString base = g_adconfig->domain_dn(); const QString filter = filter_CONDITION(Condition_Equals, ATTRIBUTE_DISPLAY_NAME, TEST_GPO); const QList attributes = QList(); const QHash search_results = ad.search(base, SearchScope_All, filter, attributes); @@ -101,19 +101,21 @@ void ADMCTestAdInterface::gpo_check_perms() { // Change GPC perms so they don't match with GPT perms { - const AdObject gpc_object = ad.search_object(gpc_dn); - auto permission_state_map = gpc_object.get_security_state(g_adconfig); - const QByteArray trustee = []() { - // NOTE: S-1-1-0 is "WORLD" - const char *sid_string = "S-1-1-0"; - dom_sid sid; - dom_sid_parse(sid_string, &sid); - const QByteArray bytes = dom_sid_to_bytes(sid); + security_descriptor *new_sd = [&]() { + const AdObject gpc_object = ad.search_object(gpc_dn); + security_descriptor *out = gpc_object.get_security_descriptor(); - return bytes; + // NOTE: S-1-1-0 is "WORLD" + const QByteArray trustee_everyone = sid_string_to_bytes("S-1-1-0"); + + const QList class_list = gpc_object.get_strings(ATTRIBUTE_OBJECT_CLASS); + + security_descriptor_add_right(out, ad.adconfig(), class_list, trustee_everyone, SEC_ADS_GENERIC_ALL, QByteArray(), true); + + return out; }(); - permission_state_map = ad_security_modify(permission_state_map, trustee, AcePermission_FullControl, PermissionState_Allowed); - attribute_replace_security_descriptor(&ad, gpc_dn, permission_state_map); + + ad_security_replace_security_descriptor(ad, gpc_dn, new_sd); } bool gpo_check_perms_ok_2 = true; diff --git a/tests/admc_test_ad_security.cpp b/tests/admc_test_ad_security.cpp new file mode 100644 index 00000000..5831ebe8 --- /dev/null +++ b/tests/admc_test_ad_security.cpp @@ -0,0 +1,404 @@ +/* + * ADMC - AD Management Center + * + * Copyright (C) 2020 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_ad_security.h" + +#include "samba/ndr_security.h" +#include "ad_security.h" + +// NOTE: using "int" instead of "uint32_t" for test +// data because "uint32_t" is not supported by +// QTest::addColumn() + +void ADMCTestAdSecurity::initTestCase() { + // NOTE: important to set sd early here, because + // base class initTestCase() calls cleanup() which + // needs to not free sd + sd = NULL; + + ADMCTest::initTestCase(); +} + +void ADMCTestAdSecurity::init() { + ADMCTest::init(); + + // Create test user + test_user_dn = test_object_dn(TEST_USER, CLASS_USER); + const bool create_test_user_success = ad.object_add(test_user_dn, CLASS_USER); + QVERIFY(create_test_user_success); + + // Create test trustee (which is a user) + test_trustee_dn = test_object_dn("test-trustee", CLASS_USER); + const bool create_test_trustee_success = ad.object_add(test_trustee_dn, CLASS_USER); + QVERIFY(create_test_trustee_success); + + test_trustee = [&]() { + const AdObject test_trustee_object = ad.search_object(test_user_dn); + const QByteArray out = test_trustee_object.get_value(ATTRIBUTE_OBJECT_SID); + + return out; + }(); + + load_sd(); +} + +void ADMCTestAdSecurity::cleanup() { + ADMCTest::cleanup(); + + security_descriptor_free(sd); + sd = NULL; +} + +void ADMCTestAdSecurity::add_right() { + security_descriptor_add_right(sd, ad.adconfig(), class_list, test_trustee, SEC_ADS_CREATE_CHILD, QByteArray(), true); + + check_state(test_trustee, SEC_ADS_CREATE_CHILD, QByteArray(), TestAdSecurityType_Allow); +} + +void ADMCTestAdSecurity::remove_right() { + security_descriptor_add_right(sd, ad.adconfig(), class_list, test_trustee, SEC_ADS_CREATE_CHILD, QByteArray(), true); + check_state(test_trustee, SEC_ADS_CREATE_CHILD, QByteArray(), TestAdSecurityType_Allow); + + security_descriptor_remove_right(sd, ad.adconfig(), class_list, test_trustee, SEC_ADS_CREATE_CHILD, QByteArray(), true); + check_state(test_trustee, SEC_ADS_CREATE_CHILD, QByteArray(), TestAdSecurityType_None); +} + +void ADMCTestAdSecurity::remove_trustee() { + const QList mask_list = { + SEC_ADS_CREATE_CHILD, + SEC_STD_DELETE, + }; + + for (const uint32_t &mask : mask_list) { + security_descriptor_add_right(sd, ad.adconfig(), class_list, test_trustee, mask, QByteArray(), true); + } + + for (const uint32_t &mask : mask_list) { + check_state(test_trustee, mask, QByteArray(), TestAdSecurityType_Allow); + } + + security_descriptor_remove_trustee(sd, {test_trustee}); + + for (const uint32_t &mask : mask_list) { + check_state(test_trustee, mask, QByteArray(), TestAdSecurityType_None); + } +} + +// Removing generic read while full control is +// allowed, should leave generic write, even though +// they share a bit, and vice versa +void ADMCTestAdSecurity::handle_generic_read_and_write_sharing_bit() { + const QHash opposite_map = { + {SEC_ADS_GENERIC_READ, SEC_ADS_GENERIC_WRITE}, + {SEC_ADS_GENERIC_WRITE, SEC_ADS_GENERIC_READ}, + }; + + for (const uint32_t &mask : opposite_map.keys()) { + const uint32_t opposite = opposite_map[mask]; + security_descriptor_add_right(sd, ad.adconfig(), class_list, test_trustee, mask, QByteArray(), true); + security_descriptor_add_right(sd, ad.adconfig(), class_list, test_trustee, opposite, QByteArray(), true); + + check_state(test_trustee, mask, QByteArray(), TestAdSecurityType_Allow); + check_state(test_trustee, opposite, QByteArray(), TestAdSecurityType_Allow); + + security_descriptor_remove_right(sd, ad.adconfig(), class_list, test_trustee, mask, QByteArray(), true); + + check_state(test_trustee, opposite, QByteArray(), TestAdSecurityType_Allow); + } +} + +void ADMCTestAdSecurity::protected_against_deletion_data() { + QTest::addColumn("enabled"); + QTest::addColumn("correct_type"); + + QTest::newRow("enabled") << true << TestAdSecurityType_Deny; + QTest::newRow("disabled") << false << TestAdSecurityType_None; +} + +void ADMCTestAdSecurity::protected_against_deletion() { + QFETCH(bool, enabled); + QFETCH(TestAdSecurityType, correct_type); + + ad_security_set_protected_against_deletion(ad, test_user_dn, enabled); + + // NOTE: need to reload sd since this modifies it + // on the server! + load_sd(); + + // Check state ourselves + const QByteArray trustee_everyone = sid_string_to_bytes(SID_WORLD); + const QList protect_deletion_mask_list = { + SEC_STD_DELETE, + SEC_ADS_DELETE_TREE, + }; + for (const uint32_t &mask : protect_deletion_mask_list) { + check_state(trustee_everyone, mask, QByteArray(), correct_type); + } + + // Check using the getter + const AdObject object = ad.search_object(test_user_dn); + const bool actual_from_get = ad_security_get_protected_against_deletion(object); + QCOMPARE(actual_from_get, enabled); +} + +void ADMCTestAdSecurity::cant_change_pass_data() { + QTest::addColumn("enabled"); + QTest::addColumn("correct_type"); + + QTest::newRow("enabled") << true << TestAdSecurityType_Deny; + QTest::newRow("disabled") << false << TestAdSecurityType_Allow; +} + +void ADMCTestAdSecurity::cant_change_pass() { + QFETCH(bool, enabled); + QFETCH(TestAdSecurityType, correct_type); + + ad_security_set_user_cant_change_pass(&ad, test_user_dn, enabled); + + // NOTE: need to reload sd since this modifies it + // on the server! + load_sd(); + + // Check state ourselves + const QList cant_change_pass_trustee_cn_list = { + SID_NT_SELF, + SID_WORLD, + }; + for (const QString &trustee_cn : cant_change_pass_trustee_cn_list) { + const QByteArray trustee = sid_string_to_bytes(trustee_cn); + const QByteArray change_pass_right = ad.adconfig()->get_right_guid("User-Change-Password"); + check_state(trustee, SEC_ADS_CONTROL_ACCESS, change_pass_right, correct_type); + } + + // Check using the getter + const AdObject object = ad.search_object(test_user_dn); + const bool actual_from_get = ad_security_get_user_cant_change_pass(&object, ad.adconfig()); + QCOMPARE(actual_from_get, enabled); +} + +// Setting a right should unset it's opposite. For +// example, set allow for X, then set deny for Y, allow +// for X should go away as a result. +void ADMCTestAdSecurity::add_to_unset_opposite_data() { + QTest::addColumn("first_allow"); + QTest::addColumn("access_mask"); + QTest::addColumn("object_type"); + QTest::addColumn("expected_result"); + + const QByteArray allowed_to_auth_object_type = ad.adconfig()->get_right_guid("Allowed-To-Authenticate"); + + QTest::newRow("allow [create child]") << true << SEC_ADS_CREATE_CHILD << QByteArray() << TestAdSecurityType_Deny; + QTest::newRow("deny [create child]") << false << SEC_ADS_CREATE_CHILD << QByteArray() << TestAdSecurityType_Allow; + QTest::newRow("allow [allowed to authenticate]") << true << SEC_ADS_CONTROL_ACCESS << allowed_to_auth_object_type << TestAdSecurityType_Deny; + QTest::newRow("deny [allowed to authenticate]") << false << SEC_ADS_CONTROL_ACCESS << allowed_to_auth_object_type << TestAdSecurityType_Allow; +} + +void ADMCTestAdSecurity::add_to_unset_opposite() { + QFETCH(bool, first_allow); + QFETCH(int, access_mask); + QFETCH(QByteArray, object_type); + QFETCH(TestAdSecurityType, expected_result); + + security_descriptor_add_right(sd, ad.adconfig(), class_list, test_trustee, access_mask, object_type, first_allow); + security_descriptor_add_right(sd, ad.adconfig(), class_list, test_trustee, access_mask, object_type, !first_allow); + + check_state(test_trustee, access_mask, object_type, expected_result); +} + +// When a right is unset, if it has any subordinates, +// they should become set. Logic here is that +// subordinates are "contained" by the superior's ACE +// but when superior is unset, they need their own +// ACE'S. For example, unsetting "generic read" should +// set all subordinate rights for reading properties. +void ADMCTestAdSecurity::remove_to_set_subordinates_data() { + QTest::addColumn("allow_superior"); + QTest::addColumn("superior_mask"); + QTest::addColumn("subordinate_right_type"); + QTest::addColumn>("subordinate_mask_list"); + QTest::addColumn("exptected_state"); + + const QByteArray type_web_info = ad.adconfig()->get_right_guid("Web-Information"); + const QByteArray type_change_password = ad.adconfig()->get_right_guid("User-Change-Password"); + + QTest::newRow("allow full control") << true << SEC_ADS_GENERIC_ALL << type_web_info << QList({SEC_ADS_WRITE_PROP, SEC_ADS_READ_PROP}) << TestAdSecurityType_Allow; + QTest::newRow("deny full control") << false << SEC_ADS_GENERIC_ALL << type_web_info << QList({SEC_ADS_WRITE_PROP, SEC_ADS_READ_PROP}) << TestAdSecurityType_Deny; + QTest::newRow("allow generic read") << true << SEC_ADS_GENERIC_READ << type_web_info << QList({SEC_ADS_READ_PROP}) << TestAdSecurityType_Allow; + QTest::newRow("deny generic read") << false << SEC_ADS_GENERIC_READ << type_web_info << QList({SEC_ADS_READ_PROP}) << TestAdSecurityType_Deny; + QTest::newRow("allow generic write") << true << SEC_ADS_GENERIC_WRITE << type_web_info << QList({SEC_ADS_WRITE_PROP}) << TestAdSecurityType_Allow; + QTest::newRow("deny generic write") << false << SEC_ADS_GENERIC_WRITE << type_web_info << QList({SEC_ADS_WRITE_PROP}) << TestAdSecurityType_Deny; + QTest::newRow("allow all extended rights") << true << SEC_ADS_CONTROL_ACCESS << type_change_password << QList({SEC_ADS_CONTROL_ACCESS}) << TestAdSecurityType_Allow; + QTest::newRow("allow all extended rights") << true << SEC_ADS_CONTROL_ACCESS << type_change_password << QList({SEC_ADS_CONTROL_ACCESS}) << TestAdSecurityType_Allow; + QTest::newRow("deny all extended rights") << false << SEC_ADS_CONTROL_ACCESS << type_change_password << QList({SEC_ADS_CONTROL_ACCESS}) << TestAdSecurityType_Deny; +} + +void ADMCTestAdSecurity::remove_to_set_subordinates() { + QFETCH(bool, allow_superior); + QFETCH(int, superior_mask); + QFETCH(QByteArray, subordinate_right_type); + QFETCH(QList, subordinate_mask_list); + QFETCH(TestAdSecurityType, exptected_state); + + // Add superior + security_descriptor_add_right(sd, ad.adconfig(), class_list, test_trustee, superior_mask, QByteArray(), allow_superior); + + // Superior should be set + check_state(test_trustee, superior_mask, QByteArray(), exptected_state); + + // Subordinate should be set + for (const int &subordinate_mask : subordinate_mask_list) { + check_state(test_trustee, subordinate_mask, subordinate_right_type, exptected_state); + } + + // Remove superior + security_descriptor_remove_right(sd, ad.adconfig(), class_list, test_trustee, superior_mask, QByteArray(), allow_superior); + + // Superior should be unset + check_state(test_trustee, superior_mask, QByteArray(), TestAdSecurityType_None); + + // But subordinates should remain + for (const int &subordinate_mask : subordinate_mask_list) { + check_state(test_trustee, subordinate_mask, subordinate_right_type, exptected_state); + } +} + +// When a right is unset, if it has any superiors and +// they are set, they should become unset. For example, +// unsetting "allow read some property" should unset +// "generic read" and "full control". +void ADMCTestAdSecurity::remove_to_unset_superior_data() { + QTest::addColumn("allow"); + QTest::addColumn>("superior_mask_list"); + QTest::addColumn("subordinate_mask"); + QTest::addColumn("state_before_remove"); + + QTest::newRow("allow generic read") << true << QList({SEC_ADS_GENERIC_READ}) << SEC_ADS_READ_PROP << TestAdSecurityType_Allow; + QTest::newRow("deny generic read") << false << QList({SEC_ADS_GENERIC_READ}) << SEC_ADS_READ_PROP << TestAdSecurityType_Deny; + QTest::newRow("allow generic write") << true << QList({SEC_ADS_GENERIC_WRITE}) << SEC_ADS_WRITE_PROP << TestAdSecurityType_Allow; + QTest::newRow("deny generic write") << false << QList({SEC_ADS_GENERIC_WRITE}) << SEC_ADS_WRITE_PROP << TestAdSecurityType_Deny; + QTest::newRow("allow generic all and read") << true << QList({SEC_ADS_GENERIC_ALL, SEC_ADS_GENERIC_READ}) << SEC_ADS_READ_PROP << TestAdSecurityType_Allow; + QTest::newRow("deny generic all and read") << false << QList({SEC_ADS_GENERIC_ALL, SEC_ADS_GENERIC_READ}) << SEC_ADS_READ_PROP << TestAdSecurityType_Deny; +} + +void ADMCTestAdSecurity::remove_to_unset_superior() { + QFETCH(bool, allow); + QFETCH(QList, superior_mask_list); + QFETCH(int, subordinate_mask); + QFETCH(TestAdSecurityType, state_before_remove); + + const QByteArray subordinate_right_type = ad.adconfig()->get_right_guid("Web-Information"); + + // Add superiors + // NOTE: there might be overlap but that's ok + for (const int &superior_mask : superior_mask_list) { + security_descriptor_add_right(sd, ad.adconfig(), class_list, test_trustee, superior_mask, QByteArray(), allow); + } + + // Superior should be set + for (const int &superior_mask : superior_mask_list) { + check_state(test_trustee, superior_mask, QByteArray(), state_before_remove); + } + + // Subordinate should be set + check_state(test_trustee, subordinate_mask, subordinate_right_type, state_before_remove); + + // Remove subordinate + security_descriptor_remove_right(sd, ad.adconfig(), class_list, test_trustee, subordinate_mask, subordinate_right_type, allow); + + // Superiors should be unset + for (const int &superior_mask : superior_mask_list) { + check_state(test_trustee, superior_mask, QByteArray(), TestAdSecurityType_None); + } + + // And subordinates also should be unset + check_state(test_trustee, subordinate_mask, subordinate_right_type, TestAdSecurityType_None); +} + +// Setting a right should unset it's opposite superior. +// For example, "Create child" right is subordinate to +// "Full control", so if full control is allowed and +// then you deny "Create child", full control stops +// being allowed. +void ADMCTestAdSecurity::add_to_unset_opposite_superior_data() { + QTest::addColumn("first_allow"); + QTest::addColumn("expected_create_child"); + QTest::addColumn("expected_full_control"); + + QTest::newRow("allow full control, then deny create child") << true << TestAdSecurityType_Deny << TestAdSecurityType_None; + QTest::newRow("deny full control, then allow create child") << false << TestAdSecurityType_Allow << TestAdSecurityType_None; +} + +void ADMCTestAdSecurity::add_to_unset_opposite_superior() { + QFETCH(bool, first_allow); + QFETCH(TestAdSecurityType, expected_create_child); + QFETCH(TestAdSecurityType, expected_full_control); + + security_descriptor_add_right(sd, ad.adconfig(), class_list, test_trustee, SEC_ADS_GENERIC_ALL, QByteArray(), first_allow); + security_descriptor_add_right(sd, ad.adconfig(), class_list, test_trustee, SEC_ADS_CREATE_CHILD, QByteArray(), !first_allow); + + check_state(test_trustee, SEC_ADS_CREATE_CHILD, QByteArray(), expected_create_child); + check_state(test_trustee, SEC_ADS_GENERIC_ALL, QByteArray(), expected_full_control); +} + +void ADMCTestAdSecurity::check_state(const QByteArray &trustee, const uint32_t access_mask, const QByteArray &object_type, const TestAdSecurityType type) const { + const SecurityRightState state = security_descriptor_get_right(sd, trustee, access_mask, object_type); + + const bool inherited_allow = state.get(SecurityRightStateInherited_Yes, SecurityRightStateType_Allow); + const bool inherited_deny = state.get(SecurityRightStateInherited_Yes, SecurityRightStateType_Deny); + const bool object_allow = state.get(SecurityRightStateInherited_No, SecurityRightStateType_Allow); + const bool object_deny = state.get(SecurityRightStateInherited_No, SecurityRightStateType_Deny); + + QCOMPARE(inherited_allow, false); + QCOMPARE(inherited_deny, false); + + switch (type) { + case TestAdSecurityType_Allow: { + QCOMPARE(object_allow, true); + QCOMPARE(object_deny, false); + + break; + } + case TestAdSecurityType_Deny: { + QCOMPARE(object_allow, false); + QCOMPARE(object_deny, true); + + break; + } + case TestAdSecurityType_None: { + QCOMPARE(object_allow, false); + QCOMPARE(object_deny, false); + + break; + } + } +} + +void ADMCTestAdSecurity::load_sd() { + security_descriptor_free(sd); + + sd = [&]() { + const AdObject test_user = ad.search_object(test_user_dn); + security_descriptor *out = test_user.get_security_descriptor(); + + return out; + }(); +} + +QTEST_MAIN(ADMCTestAdSecurity) diff --git a/tests/admc_test_ad_security.h b/tests/admc_test_ad_security.h new file mode 100644 index 00000000..2dc9b0db --- /dev/null +++ b/tests/admc_test_ad_security.h @@ -0,0 +1,71 @@ +/* + * ADMC - AD Management Center + * + * Copyright (C) 2020 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_AD_SECURITY_H +#define ADMC_TEST_AD_SECURITY_H + +#include "admc_test.h" + +struct security_descriptor; + +enum TestAdSecurityType { + TestAdSecurityType_Allow, + TestAdSecurityType_Deny, + TestAdSecurityType_None, +}; +Q_DECLARE_METATYPE(TestAdSecurityType) + +class ADMCTestAdSecurity : public ADMCTest { + Q_OBJECT + +private slots: + void initTestCase() override; + void init() override; + void cleanup() override; + + void add_right(); + void remove_right(); + void remove_trustee(); + void handle_generic_read_and_write_sharing_bit(); + void protected_against_deletion_data(); + void protected_against_deletion(); + void cant_change_pass_data(); + void cant_change_pass(); + void add_to_unset_opposite_data(); + void add_to_unset_opposite(); + void remove_to_set_subordinates_data(); + void remove_to_set_subordinates(); + void remove_to_unset_superior_data(); + void remove_to_unset_superior(); + void add_to_unset_opposite_superior_data(); + void add_to_unset_opposite_superior(); + +private: + QString test_user_dn; + QString test_trustee_dn; + QByteArray test_trustee; + security_descriptor *sd; + const QList class_list = {CLASS_USER}; + + void check_state(const QByteArray &trustee, const uint32_t access_mask, const QByteArray &object_type, const TestAdSecurityType type) const; + void load_sd(); +}; + +#endif /* ADMC_TEST_AD_SECURITY_H */ diff --git a/tests/admc_test_attributes_tab.cpp b/tests/admc_test_attributes_tab.cpp index 408cee48..9da5c6d0 100644 --- a/tests/admc_test_attributes_tab.cpp +++ b/tests/admc_test_attributes_tab.cpp @@ -52,24 +52,22 @@ const QList all_filters = { void ADMCTestAttributesTab::init() { ADMCTest::init(); - attributes_tab = new AttributesTab(); - add_widget(attributes_tab); + view = new QTreeView(parent_widget); + filter_button = new QPushButton(parent_widget); + edit_button = new QPushButton(parent_widget); + auto view_button = new QPushButton(parent_widget); - filter_menu = attributes_tab->findChild(); + edit = new AttributesTabEdit(view, filter_button, edit_button, view_button, parent_widget); + + filter_menu = view->findChild(); QVERIFY(filter_menu); - view = attributes_tab->ui->view; - - model = attributes_tab->findChild(); + model = edit->findChild(); QVERIFY(model); - proxy = attributes_tab->findChild(); + proxy = edit->findChild(); QVERIFY(proxy); - filter_button = attributes_tab->ui->filter_button; - - edit_button = attributes_tab->ui->edit_button; - // Create test user const QString name = TEST_USER; dn = test_object_dn(name, CLASS_USER); @@ -78,13 +76,19 @@ void ADMCTestAttributesTab::init() { // Load it into the tab const AdObject object = ad.search_object(dn); - attributes_tab->load(ad, object); + edit->load(ad, object); // NOTE: filters might be messed up in settings by user // so reset it before tests set_filter(all_filters, true); } +void ADMCTestAttributesTab::cleanup() { + ADMCTest::cleanup(); + + edit_list.clear(); +} + void ADMCTestAttributesTab::load() { // Check that some attributes loaded correctly auto check_value = [&](const QString &name, const QString &value) { @@ -166,7 +170,7 @@ void ADMCTestAttributesTab::apply() { navigate_until_object(view, "description", Qt::DisplayRole); edit_button->click(); - auto list_attribute_dialog = attributes_tab->findChild(); + auto list_attribute_dialog = view->findChild(); QVERIFY(list_attribute_dialog); QVERIFY(QTest::qWaitForWindowExposed(list_attribute_dialog, 1000)); @@ -183,7 +187,7 @@ void ADMCTestAttributesTab::apply() { string_attribute_dialog->accept(); list_attribute_dialog->accept(); - attributes_tab->apply(ad, dn); + edit->apply(ad, dn); const AdObject object = ad.search_object(dn); const QString description_value = object.get_string(ATTRIBUTE_DESCRIPTION); diff --git a/tests/admc_test_attributes_tab.h b/tests/admc_test_attributes_tab.h index e9a28b6c..579a2a95 100644 --- a/tests/admc_test_attributes_tab.h +++ b/tests/admc_test_attributes_tab.h @@ -38,6 +38,7 @@ class ADMCTestAttributesTab : public ADMCTest { private slots: void init() override; + void cleanup() override; void apply(); void load(); @@ -45,7 +46,7 @@ private slots: void filter(); private: - AttributesTab *attributes_tab; + AttributeEdit *edit; AttributesTabFilterMenu *filter_menu; QTreeView *view; QStandardItemModel *model; @@ -53,6 +54,7 @@ private: QPushButton *filter_button; QPushButton *edit_button; QString dn; + QList edit_list; void set_filter(const QList &filter_list, const bool state); }; diff --git a/tests/admc_test_country_edit.cpp b/tests/admc_test_country_edit.cpp index 5339e1f5..b193330b 100644 --- a/tests/admc_test_country_edit.cpp +++ b/tests/admc_test_country_edit.cpp @@ -35,7 +35,7 @@ void ADMCTestCountryEdit::init() { combo = new QComboBox(parent_widget); - edit = new CountryEdit(combo, &edits, parent_widget); + edit = new CountryEdit(combo, parent_widget); // Create test user const QString name = TEST_USER; @@ -55,6 +55,7 @@ void ADMCTestCountryEdit::emit_edited_signal() { bool edited_signal_emitted = false; connect( edit, &AttributeEdit::edited, + this, [&edited_signal_emitted]() { edited_signal_emitted = true; }); diff --git a/tests/admc_test_create_object_dialog.cpp b/tests/admc_test_create_object_dialog.cpp index 85d22058..4aaacbbb 100644 --- a/tests/admc_test_create_object_dialog.cpp +++ b/tests/admc_test_create_object_dialog.cpp @@ -21,16 +21,31 @@ #include "admc_test_create_object_dialog.h" #include "adldap.h" +#include "samba/dom_sid.h" #include "create_computer_dialog.h" #include "create_group_dialog.h" #include "create_ou_dialog.h" #include "create_user_dialog.h" +#include "create_shared_folder_dialog.h" +#include "create_contact_dialog.h" #include "ui_create_computer_dialog.h" #include "ui_create_group_dialog.h" #include "ui_create_ou_dialog.h" #include "ui_create_user_dialog.h" +#include "ui_create_shared_folder_dialog.h" +#include "ui_create_contact_dialog.h" + +void ADMCTestCreateObjectDialog::create_user_data() { + QTest::addColumn("user_class"); + QTest::addColumn("exptected_uac"); + + QTest::newRow("user") << CLASS_USER; + QTest::newRow("inetOrgPerson") << CLASS_INET_ORG_PERSON; +} void ADMCTestCreateObjectDialog::create_user() { + QFETCH(QString, user_class); + const QString name = TEST_USER; const QString logon_name = TEST_USER_LOGON; const QString password = TEST_PASSWORD; @@ -38,8 +53,7 @@ void ADMCTestCreateObjectDialog::create_user() { const QString dn = test_object_dn(name, CLASS_USER); // Create user - auto create_dialog = new CreateUserDialog(parent_widget); - create_dialog->set_parent_dn(parent); + auto create_dialog = new CreateUserDialog(ad, parent, user_class, parent_widget); create_dialog->open(); QVERIFY(QTest::qWaitForWindowExposed(create_dialog, 1000)); @@ -51,8 +65,16 @@ void ADMCTestCreateObjectDialog::create_user() { create_dialog->accept(); QVERIFY2(object_exists(dn), "Created user doesn't exist"); - QCOMPARE(create_dialog->get_created_name(), name); QCOMPARE(create_dialog->get_created_dn(), dn); + + const int actual_uac = [&]() { + const AdObject object = ad.search_object(dn, {ATTRIBUTE_USER_ACCOUNT_CONTROL}); + const int out = object.get_int(ATTRIBUTE_USER_ACCOUNT_CONTROL); + + return out; + }(); + const int expected_uac = UAC_NORMAL_ACCOUNT; + QCOMPARE(actual_uac, expected_uac); } void ADMCTestCreateObjectDialog::create_ou() { @@ -61,8 +83,7 @@ void ADMCTestCreateObjectDialog::create_ou() { const QString dn = test_object_dn(name, CLASS_OU); // Create ou - auto create_dialog = new CreateOUDialog(parent_widget); - create_dialog->set_parent_dn(parent); + auto create_dialog = new CreateOUDialog(parent, parent_widget); create_dialog->open(); QVERIFY(QTest::qWaitForWindowExposed(create_dialog, 1000)); @@ -71,7 +92,6 @@ void ADMCTestCreateObjectDialog::create_ou() { create_dialog->accept(); QVERIFY2(object_exists(dn), "Created OU doesn't exist"); - QCOMPARE(create_dialog->get_created_name(), name); QCOMPARE(create_dialog->get_created_dn(), dn); } @@ -82,8 +102,7 @@ void ADMCTestCreateObjectDialog::create_computer() { const QString dn = test_object_dn(name, object_class); // Open create dialog - auto create_dialog = new CreateComputerDialog(parent_widget); - create_dialog->set_parent_dn(parent); + auto create_dialog = new CreateComputerDialog(parent, parent_widget); create_dialog->open(); QVERIFY(QTest::qWaitForWindowExposed(create_dialog, 1000)); @@ -92,9 +111,28 @@ void ADMCTestCreateObjectDialog::create_computer() { create_dialog->accept(); + const QString actual_dn = create_dialog->get_created_dn(); + QVERIFY2(object_exists(dn), "Created computer doesn't exist"); - QCOMPARE(create_dialog->get_created_name(), name); - QCOMPARE(create_dialog->get_created_dn(), dn); + QCOMPARE(actual_dn, dn); + + const AdObject object = ad.search_object(actual_dn); + + const int actual_sam_type = object.get_int(ATTRIBUTE_SAM_ACCOUNT_TYPE); + const int expected_sam_type = SAM_MACHINE_ACCOUNT; + QCOMPARE(actual_sam_type, expected_sam_type); + + const int actual_primary_group_rid = object.get_int(ATTRIBUTE_PRIMARY_GROUP_ID); + const int expected_primary_group_rid = DOMAIN_RID_DOMAIN_MEMBERS; + QCOMPARE(actual_primary_group_rid, expected_primary_group_rid); + + const int actual_uac = object.get_int(ATTRIBUTE_USER_ACCOUNT_CONTROL); + const int expected_uac = (UAC_PASSWD_NOTREQD | UAC_WORKSTATION_TRUST_ACCOUNT); + QCOMPARE(actual_uac, expected_uac); + + const QString actual_sam_name = object.get_string(ATTRIBUTE_SAM_ACCOUNT_NAME); + const QString expected_sam_name = QString("%1$").arg(name); + QCOMPARE(actual_sam_name, expected_sam_name); } void ADMCTestCreateObjectDialog::create_group() { @@ -104,8 +142,7 @@ void ADMCTestCreateObjectDialog::create_group() { const QString dn = test_object_dn(name, object_class); // Open create dialog - auto create_dialog = new CreateGroupDialog(parent_widget); - create_dialog->set_parent_dn(parent); + auto create_dialog = new CreateGroupDialog(parent, parent_widget); create_dialog->open(); QVERIFY(QTest::qWaitForWindowExposed(create_dialog, 1000)); @@ -115,7 +152,48 @@ void ADMCTestCreateObjectDialog::create_group() { create_dialog->accept(); QVERIFY2(object_exists(dn), "Created group doesn't exist"); - QCOMPARE(create_dialog->get_created_name(), name); + QCOMPARE(create_dialog->get_created_dn(), dn); +} + +void ADMCTestCreateObjectDialog::create_shared_folder() { + const QString object_class = CLASS_SHARED_FOLDER; + const QString name = TEST_OBJECT; + const QString parent = test_arena_dn(); + const QString dn = test_object_dn(name, object_class); + + // Open create dialog + auto create_dialog = new CreateSharedFolderDialog(parent, parent_widget); + create_dialog->open(); + QVERIFY(QTest::qWaitForWindowExposed(create_dialog, 1000)); + + create_dialog->ui->name_edit->setText(name); + create_dialog->ui->path_edit->setText("path"); + + create_dialog->accept(); + + QVERIFY2(object_exists(dn), "Created shared folder doesn't exist"); + QCOMPARE(create_dialog->get_created_dn(), dn); +} + +void ADMCTestCreateObjectDialog::create_contact() { + const QString object_class = CLASS_CONTACT; + const QString name = TEST_OBJECT; + const QString parent = test_arena_dn(); + const QString dn = test_object_dn(name, object_class); + + // Open create dialog + auto create_dialog = new CreateContactDialog(parent, parent_widget); + create_dialog->open(); + QVERIFY(QTest::qWaitForWindowExposed(create_dialog, 1000)); + + create_dialog->ui->first_name_edit->setText("first"); + create_dialog->ui->last_name_edit->setText("last"); + create_dialog->ui->full_name_edit->setText(name); + create_dialog->ui->display_name_edit->setText("display"); + + create_dialog->accept(); + + QVERIFY2(object_exists(dn), "Created contact doesn't exist"); QCOMPARE(create_dialog->get_created_dn(), dn); } diff --git a/tests/admc_test_create_object_dialog.h b/tests/admc_test_create_object_dialog.h index f3677801..2580c854 100644 --- a/tests/admc_test_create_object_dialog.h +++ b/tests/admc_test_create_object_dialog.h @@ -27,10 +27,13 @@ class ADMCTestCreateObjectDialog : public ADMCTest { Q_OBJECT private slots: + void create_user_data(); void create_user(); void create_ou(); void create_computer(); void create_group(); + void create_shared_folder(); + void create_contact(); }; #endif /* ADMC_TEST_CREATE_OBJECT_DIALOG_H */ diff --git a/tests/admc_test_datetime_edit.cpp b/tests/admc_test_datetime_edit.cpp index 5ad9d939..03afb94f 100644 --- a/tests/admc_test_datetime_edit.cpp +++ b/tests/admc_test_datetime_edit.cpp @@ -33,7 +33,7 @@ void ADMCTestDateTimeEdit::init() { qedit = new QDateTimeEdit(parent_widget); - edit = new DateTimeEdit(qedit, ATTRIBUTE_WHEN_CHANGED, &edits, parent_widget); + edit = new DateTimeEdit(qedit, ATTRIBUTE_WHEN_CHANGED, parent_widget); const QString name = TEST_USER; dn = test_object_dn(name, CLASS_USER); diff --git a/tests/admc_test_delegation_edit.cpp b/tests/admc_test_delegation_edit.cpp index 295460f9..c1e2a456 100644 --- a/tests/admc_test_delegation_edit.cpp +++ b/tests/admc_test_delegation_edit.cpp @@ -39,7 +39,7 @@ void ADMCTestDelegationEdit::init() { auto on_button = new QRadioButton(parent_widget); auto off_button = new QRadioButton(parent_widget); - edit = new DelegationEdit(off_button, on_button, &edits, parent_widget); + edit = new DelegationEdit(off_button, on_button, parent_widget); QFETCH_GLOBAL(bool, use_on_button); button = [&]() { @@ -60,6 +60,7 @@ void ADMCTestDelegationEdit::emit_edited_signal() { bool edited_signal_emitted = false; connect( edit, &AttributeEdit::edited, + this, [&edited_signal_emitted]() { edited_signal_emitted = true; }); diff --git a/tests/admc_test_expiry_edit.cpp b/tests/admc_test_expiry_edit.cpp index 19e8e1dd..b8a92386 100644 --- a/tests/admc_test_expiry_edit.cpp +++ b/tests/admc_test_expiry_edit.cpp @@ -25,12 +25,12 @@ #include "attribute_edits/ui_expiry_widget.h" #include "globals.h" -#include +#include #include #include void ADMCTestExpiryEdit::initTestCase_data() { - QTest::addColumn("check_name"); + QTest::addColumn("button_name"); QTest::addColumn("date"); QTest::addColumn("value"); @@ -44,16 +44,16 @@ void ADMCTestExpiryEdit::init() { auto widget = new ExpiryWidget(parent_widget); - edit = new ExpiryEdit(widget, &edits, parent_widget); + edit = new ExpiryEdit(widget, parent_widget); date_edit = widget->ui->date_edit; - QFETCH_GLOBAL(QString, check_name); - const QHash check_map = { + QFETCH_GLOBAL(QString, button_name); + const QHash button_map = { {"end_of_check", widget->ui->end_of_check}, {"never_check", widget->ui->never_check}, }; - check = check_map[check_name]; + button = button_map[button_name]; // Create test user const QString name = TEST_USER; @@ -66,11 +66,12 @@ void ADMCTestExpiryEdit::edited_signal() { bool edited_signal_emitted = false; connect( edit, &AttributeEdit::edited, + this, [&edited_signal_emitted]() { edited_signal_emitted = true; }); - check->setChecked(true); + button->setChecked(true); QVERIFY(edited_signal_emitted); } @@ -82,7 +83,7 @@ void ADMCTestExpiryEdit::load() { const AdObject object = ad.search_object(dn); edit->load(ad, object); - QVERIFY(check->isChecked()); + QVERIFY(button->isChecked()); if (date_edit->isEnabled()) { QCOMPARE(date_edit->date(), date); } @@ -99,7 +100,7 @@ void ADMCTestExpiryEdit::apply() { // Replace value into something different before testing ad.attribute_replace_string(dn, ATTRIBUTE_ACCOUNT_EXPIRES, "129655295400000001"); - check->setChecked(true); + button->setChecked(true); date_edit->setDate(date); const bool apply_success = edit->apply(ad, dn); diff --git a/tests/admc_test_expiry_edit.h b/tests/admc_test_expiry_edit.h index 0f2b6175..4fbf5ed4 100644 --- a/tests/admc_test_expiry_edit.h +++ b/tests/admc_test_expiry_edit.h @@ -24,7 +24,7 @@ #include "admc_test.h" class ExpiryEdit; -class QCheckBox; +class QRadioButton; class QDateEdit; class ADMCTestExpiryEdit : public ADMCTest { @@ -42,7 +42,7 @@ private slots: private: ExpiryEdit *edit; QString dn; - QCheckBox *check; + QRadioButton *button; QDateEdit *date_edit; }; diff --git a/tests/admc_test_find_object_dialog.cpp b/tests/admc_test_find_object_dialog.cpp index 7167f040..10d1d8fa 100644 --- a/tests/admc_test_find_object_dialog.cpp +++ b/tests/admc_test_find_object_dialog.cpp @@ -44,7 +44,7 @@ void ADMCTestFindObjectDialog::simple_tab() { QVERIFY2(create_user_success, "Failed to create user"); QVERIFY2(object_exists(user_dn), "Created user doesn't exist"); - auto find_dialog = new FindObjectDialog(parent, parent_widget); + auto find_dialog = new FindObjectDialog(nullptr, parent, parent_widget); find_dialog->open(); QVERIFY(QTest::qWaitForWindowExposed(find_dialog, 1000)); @@ -77,7 +77,7 @@ void ADMCTestFindObjectDialog::advanced_tab() { QVERIFY2(create_user_success, "Failed to create user"); QVERIFY2(object_exists(user_dn), "Created user doesn't exist"); - auto find_dialog = new FindObjectDialog(parent, parent_widget); + auto find_dialog = new FindObjectDialog(nullptr, parent, parent_widget); find_dialog->open(); QVERIFY(QTest::qWaitForWindowExposed(find_dialog, 1000)); diff --git a/tests/admc_test_gpoptions_edit.cpp b/tests/admc_test_gpoptions_edit.cpp index 1f42ec6a..b0d9237e 100644 --- a/tests/admc_test_gpoptions_edit.cpp +++ b/tests/admc_test_gpoptions_edit.cpp @@ -32,7 +32,7 @@ void ADMCTestGpoptionsEdit::init() { check = new QCheckBox(parent_widget); - edit = new GpoptionsEdit(check, &edits, parent_widget); + edit = new GpoptionsEdit(check, parent_widget); const QString name = TEST_OU; dn = test_object_dn(name, CLASS_OU); diff --git a/tests/admc_test_group_scope_edit.cpp b/tests/admc_test_group_scope_edit.cpp index 5766684f..ef6f0598 100644 --- a/tests/admc_test_group_scope_edit.cpp +++ b/tests/admc_test_group_scope_edit.cpp @@ -30,7 +30,7 @@ void ADMCTestGroupScopeEdit::init() { combo = new QComboBox(parent_widget); - edit = new GroupScopeEdit(combo, &edits, parent_widget); + edit = new GroupScopeEdit(combo, parent_widget); const QString name = TEST_GROUP; dn = test_object_dn(name, CLASS_GROUP); @@ -42,6 +42,7 @@ void ADMCTestGroupScopeEdit::edited_signal() { bool edited_signal_emitted = false; connect( edit, &AttributeEdit::edited, + this, [&edited_signal_emitted]() { edited_signal_emitted = true; }); diff --git a/tests/admc_test_group_type_edit.cpp b/tests/admc_test_group_type_edit.cpp index 9bad6825..7a9ba07b 100644 --- a/tests/admc_test_group_type_edit.cpp +++ b/tests/admc_test_group_type_edit.cpp @@ -30,7 +30,7 @@ void ADMCTestGroupTypeEdit::init() { combo = new QComboBox(parent_widget); - edit = new GroupTypeEdit(combo, &edits, parent_widget); + edit = new GroupTypeEdit(combo, parent_widget); const QString name = TEST_GROUP; dn = test_object_dn(name, CLASS_GROUP); @@ -42,6 +42,7 @@ void ADMCTestGroupTypeEdit::edited_signal() { bool edited_signal_emitted = false; connect( edit, &AttributeEdit::edited, + this, [&edited_signal_emitted]() { edited_signal_emitted = true; }); diff --git a/tests/admc_test_logon_computers_edit.cpp b/tests/admc_test_logon_computers_edit.cpp index 3dabaaae..0abfa826 100644 --- a/tests/admc_test_logon_computers_edit.cpp +++ b/tests/admc_test_logon_computers_edit.cpp @@ -29,46 +29,47 @@ #include #include +// TODO: make this less messy, maybe test dialog +// separately. Should end up without needing to use +// load_and_open_dialog() + void ADMCTestLogonComputersEdit::init() { ADMCTest::init(); - auto open_dialog_button = new QPushButton(parent_widget); + open_dialog_button = new QPushButton(parent_widget); - edit = new LogonComputersEdit(open_dialog_button, &edits, parent_widget); + edit = new LogonComputersEdit(open_dialog_button, parent_widget); const QString name = TEST_USER; dn = test_object_dn(name, CLASS_USER); const bool create_success = ad.object_add(dn, CLASS_USER); QVERIFY(create_success); +} - const QString test_value = "test,value"; - ad.attribute_replace_string(dn, ATTRIBUTE_USER_WORKSTATIONS, test_value); - +void ADMCTestLogonComputersEdit::load_empty() { const AdObject object = ad.search_object(dn); edit->load(ad, object); - open_dialog_button->click(); + open_dialog(); - dialog = parent_widget->findChild(); - QVERIFY(dialog); - QVERIFY(QTest::qWaitForWindowExposed(dialog, 1000)); - - list = dialog->ui->list; - value_edit = dialog->ui->edit; - add_button = dialog->ui->add_button; - remove_button = dialog->ui->remove_button; + QCOMPARE(list->count(), 0); } void ADMCTestLogonComputersEdit::load() { + load_and_open_dialog(); + QCOMPARE(list->count(), 2); test_list_item(0, "test"); test_list_item(1, "value"); } void ADMCTestLogonComputersEdit::emit_edited_signal() { + load_and_open_dialog(); + bool edited_signal_emitted = false; connect( edit, &AttributeEdit::edited, + this, [&edited_signal_emitted]() { edited_signal_emitted = true; }); @@ -79,6 +80,8 @@ void ADMCTestLogonComputersEdit::emit_edited_signal() { } void ADMCTestLogonComputersEdit::add() { + load_and_open_dialog(); + value_edit->setText("new"); add_button->click(); @@ -90,6 +93,8 @@ void ADMCTestLogonComputersEdit::add() { } void ADMCTestLogonComputersEdit::remove() { + load_and_open_dialog(); + list->setCurrentRow(0); remove_button->click(); @@ -99,16 +104,22 @@ void ADMCTestLogonComputersEdit::remove() { } void ADMCTestLogonComputersEdit::test_list_item(const int row, const QString &text) { + load_and_open_dialog(); + auto item = list->item(row); QVERIFY(item); QCOMPARE(item->text(), text); } void ADMCTestLogonComputersEdit::apply_unmodified() { + load_and_open_dialog(); + test_edit_apply_unmodified(edit, dn); } void ADMCTestLogonComputersEdit::apply() { + load_and_open_dialog(); + value_edit->setText("new"); add_button->click(); @@ -122,4 +133,27 @@ void ADMCTestLogonComputersEdit::apply() { QCOMPARE(updated_value, "test,value,new"); } +void ADMCTestLogonComputersEdit::load_and_open_dialog() { + const QString test_value = "test,value"; + ad.attribute_replace_string(dn, ATTRIBUTE_USER_WORKSTATIONS, test_value); + + const AdObject object = ad.search_object(dn); + edit->load(ad, object); + + open_dialog(); +} + +void ADMCTestLogonComputersEdit::open_dialog() { + open_dialog_button->click(); + + dialog = parent_widget->findChild(); + QVERIFY(dialog); + QVERIFY(QTest::qWaitForWindowExposed(dialog, 1000)); + + list = dialog->ui->list; + value_edit = dialog->ui->edit; + add_button = dialog->ui->add_button; + remove_button = dialog->ui->remove_button; +} + QTEST_MAIN(ADMCTestLogonComputersEdit) diff --git a/tests/admc_test_logon_computers_edit.h b/tests/admc_test_logon_computers_edit.h index 73f98701..0c103d1c 100644 --- a/tests/admc_test_logon_computers_edit.h +++ b/tests/admc_test_logon_computers_edit.h @@ -34,6 +34,7 @@ class ADMCTestLogonComputersEdit : public ADMCTest { private slots: void init() override; + void load_empty(); void load(); void emit_edited_signal(); void add(); @@ -46,11 +47,14 @@ private: LogonComputersDialog *dialog; QListWidget *list; QLineEdit *value_edit; + QPushButton *open_dialog_button; QPushButton *add_button; QPushButton *remove_button; QString dn; void test_list_item(const int row, const QString &text); + void load_and_open_dialog(); + void open_dialog(); }; #endif /* ADMC_TEST_LOGON_COMPUTERS_EDIT_H */ diff --git a/tests/admc_test_logon_hours_dialog.cpp b/tests/admc_test_logon_hours_dialog.cpp index f4a84255..ab90362a 100644 --- a/tests/admc_test_logon_hours_dialog.cpp +++ b/tests/admc_test_logon_hours_dialog.cpp @@ -27,10 +27,8 @@ #include #include -void ADMCTestLogonHoursDialog::init() { - ADMCTest::init(); - - dialog = new LogonHoursDialog(parent_widget); +void ADMCTestLogonHoursDialog::open_dialog(const QByteArray &value) { + dialog = new LogonHoursDialog(value, parent_widget); dialog->open(); QVERIFY(QTest::qWaitForWindowExposed(dialog, 1000)); @@ -88,10 +86,28 @@ void ADMCTestLogonHoursDialog::conversion_funs() { QCOMPARE(converted_bytes, test_bytes); } +void ADMCTestLogonHoursDialog::load_data() { + QTest::addColumn("value"); + + QTest::newRow("empty") << empty_bytes; + QTest::newRow("non-empty") << test_bytes; +} + void ADMCTestLogonHoursDialog::load() { + QFETCH(QByteArray, value); + + open_dialog(value); + utc_time_button->setChecked(true); - dialog->load(test_bytes); + const QByteArray actual_value = dialog->get(); + QCOMPARE(actual_value, value); +} + +void ADMCTestLogonHoursDialog::select() { + open_dialog(test_bytes); + + utc_time_button->setChecked(true); const QList selected = selection_model->selectedIndexes(); const QSet selected_set = selected.toSet(); @@ -105,19 +121,12 @@ void ADMCTestLogonHoursDialog::load() { QCOMPARE(selected_set, correct_selected_set); } -// Dialog should handle loading undefined value, where -// bytearray is empty -void ADMCTestLogonHoursDialog::load_undefined() { - dialog->load(QByteArray()); - - const QByteArray out = dialog->get(); - QCOMPARE(out, empty_bytes); -} - -void ADMCTestLogonHoursDialog::get_unchanged() { - dialog->load(test_bytes); - const QByteArray returned_bytes = dialog->get(); - QCOMPARE(returned_bytes, test_bytes); +void ADMCTestLogonHoursDialog::load_empty() { + open_dialog(QByteArray()); + + const QByteArray actual_value = dialog->get(); + const QByteArray expected_value = QByteArray(); + QCOMPARE(actual_value, expected_value); } void ADMCTestLogonHoursDialog::handle_timezone() { @@ -127,7 +136,8 @@ void ADMCTestLogonHoursDialog::handle_timezone() { return out; }(); - dialog->load(bytes); + + open_dialog(bytes); // First do UTC utc_time_button->setChecked(true); diff --git a/tests/admc_test_logon_hours_dialog.h b/tests/admc_test_logon_hours_dialog.h index f91d9b1c..a560ce4a 100644 --- a/tests/admc_test_logon_hours_dialog.h +++ b/tests/admc_test_logon_hours_dialog.h @@ -33,12 +33,11 @@ class ADMCTestLogonHoursDialog : public ADMCTest { Q_OBJECT private slots: - void init() override; - void conversion_funs(); + void load_data(); void load(); - void load_undefined(); - void get_unchanged(); + void load_empty(); + void select(); void handle_timezone(); private: @@ -48,6 +47,8 @@ private: QItemSelectionModel *selection_model; QRadioButton *local_time_button; QRadioButton *utc_time_button; + + void open_dialog(const QByteArray &value); }; #endif /* ADMC_TEST_LOGON_HOURS_DIALOG_H */ diff --git a/tests/admc_test_manager_edit.cpp b/tests/admc_test_manager_edit.cpp index f2baab6b..fa5dc201 100644 --- a/tests/admc_test_manager_edit.cpp +++ b/tests/admc_test_manager_edit.cpp @@ -34,7 +34,7 @@ void ADMCTestManagerEdit::init() { auto manager_widget = new ManagerWidget(parent_widget); - edit = new ManagerEdit(manager_widget, ATTRIBUTE_MANAGER, &edits, parent_widget); + edit = new ManagerEdit(manager_widget, ATTRIBUTE_MANAGER, parent_widget); manager_display = manager_widget->ui->manager_display; change_button = manager_widget->ui->change_button; @@ -66,7 +66,10 @@ void ADMCTestManagerEdit::load() { const AdObject object = ad.search_object(dn); edit->load(ad, object); - QCOMPARE(manager_display->text(), manager_dn); + const QString actual_value = manager_display->text(); + const QString expected_value = dn_get_name(manager_dn); + + QCOMPARE(actual_value, expected_value); } void ADMCTestManagerEdit::apply_unmodified() { diff --git a/tests/admc_test_member_of_tab.cpp b/tests/admc_test_member_of_tab.cpp index 5ce0e231..e9ea5b68 100644 --- a/tests/admc_test_member_of_tab.cpp +++ b/tests/admc_test_member_of_tab.cpp @@ -35,14 +35,17 @@ void ADMCTestMemberOfTab::init() { ADMCTest::init(); - member_of_tab = new MemberOfTab(); - add_widget(member_of_tab); + view = new QTreeView(parent_widget); + auto primary_button = new QPushButton(parent_widget); + add_button = new QPushButton(parent_widget); + remove_button = new QPushButton(parent_widget); + auto properties_button = new QPushButton(parent_widget); + auto primary_group_label = new QLabel(parent_widget); - view = member_of_tab->ui->view; - model = member_of_tab->findChild(); + edit = new MembershipTabEdit(view, primary_button, add_button, remove_button, properties_button, primary_group_label, MembershipTabType_MemberOf, parent_widget); + + model = edit->findChild(); QVERIFY(model); - add_button = member_of_tab->ui->add_button; - remove_button = member_of_tab->ui->remove_button; // Create test user const QString user_name = TEST_USER; @@ -58,7 +61,7 @@ void ADMCTestMemberOfTab::init() { // Load it into the tab const AdObject object = ad.search_object(user_dn); - member_of_tab->load(ad, object); + edit->load(ad, object); } // Loading a group without members should result in empty @@ -74,7 +77,7 @@ void ADMCTestMemberOfTab::load() { QVERIFY(add_success); const AdObject object = ad.search_object(user_dn); - member_of_tab->load(ad, object); + edit->load(ad, object); QCOMPARE(model->rowCount(), 2); @@ -105,7 +108,7 @@ void ADMCTestMemberOfTab::remove() { remove_button->click(); - member_of_tab->apply(ad, user_dn); + edit->apply(ad, user_dn); const AdObject updated_object = ad.search_object(user_dn); const QList member_list = updated_object.get_strings(ATTRIBUTE_MEMBER_OF); @@ -120,7 +123,7 @@ void ADMCTestMemberOfTab::add() { select_object_dialog_select(group_dn); // Apply and check object state - member_of_tab->apply(ad, user_dn); + edit->apply(ad, user_dn); const AdObject object = ad.search_object(user_dn); const QList member_of_list = object.get_strings(ATTRIBUTE_MEMBER_OF); QVERIFY(member_of_list.contains(group_dn)); diff --git a/tests/admc_test_member_of_tab.h b/tests/admc_test_member_of_tab.h index e5922b3a..ecca6b7c 100644 --- a/tests/admc_test_member_of_tab.h +++ b/tests/admc_test_member_of_tab.h @@ -24,7 +24,7 @@ #include "admc_test.h" class QTreeView; -class MemberOfTab; +class AttributeEdit; class QStandardItemModel; class QPushButton; @@ -40,7 +40,7 @@ private slots: void add(); private: - MemberOfTab *member_of_tab; + AttributeEdit *edit; QTreeView *view; QStandardItemModel *model; QString user_dn; diff --git a/tests/admc_test_members_tab.cpp b/tests/admc_test_members_tab.cpp index e861a38f..1e8cce2a 100644 --- a/tests/admc_test_members_tab.cpp +++ b/tests/admc_test_members_tab.cpp @@ -32,14 +32,17 @@ void ADMCTestMembersTab::init() { ADMCTest::init(); - members_tab = new MembersTab(); - add_widget(members_tab); + view = new QTreeView(parent_widget); + auto primary_button = new QPushButton(parent_widget); + add_button = new QPushButton(parent_widget); + remove_button = new QPushButton(parent_widget); + auto properties_button = new QPushButton(parent_widget); + auto primary_group_label = new QLabel(parent_widget); - view = members_tab->ui->view; - model = members_tab->findChild(); + edit = new MembershipTabEdit(view, primary_button, add_button, remove_button, properties_button, primary_group_label, MembershipTabType_Members, parent_widget); + + model = edit->findChild(); QVERIFY(model); - add_button = members_tab->ui->add_button; - remove_button = members_tab->ui->remove_button; // Create test user const QString user_name = TEST_USER; @@ -55,7 +58,7 @@ void ADMCTestMembersTab::init() { // Load it into the tab const AdObject object = ad.search_object(group_dn); - members_tab->load(ad, object); + edit->load(ad, object); } // Loading a group without members should result in empty @@ -71,7 +74,7 @@ void ADMCTestMembersTab::load() { QVERIFY(add_success); const AdObject object = ad.search_object(group_dn); - members_tab->load(ad, object); + edit->load(ad, object); QCOMPARE(model->rowCount(), 1); @@ -88,7 +91,7 @@ void ADMCTestMembersTab::remove() { remove_button->click(); - members_tab->apply(ad, group_dn); + edit->apply(ad, group_dn); const AdObject updated_object = ad.search_object(group_dn); const QList member_list = updated_object.get_strings(ATTRIBUTE_MEMBER); @@ -107,7 +110,7 @@ void ADMCTestMembersTab::add() { QCOMPARE(model->item(0, 0)->text(), dn_get_name(user_dn)); // Apply and check object state - members_tab->apply(ad, group_dn); + edit->apply(ad, group_dn); const AdObject object = ad.search_object(group_dn); const QList member_list = object.get_strings(ATTRIBUTE_MEMBER); QCOMPARE(member_list, QList({user_dn})); diff --git a/tests/admc_test_members_tab.h b/tests/admc_test_members_tab.h index 796e307f..528a0ad7 100644 --- a/tests/admc_test_members_tab.h +++ b/tests/admc_test_members_tab.h @@ -24,7 +24,7 @@ #include "admc_test.h" class QTreeView; -class MembersTab; +class AttributeEdit; class QStandardItemModel; class QPushButton; @@ -40,7 +40,7 @@ private slots: void add(); private: - MembersTab *members_tab; + AttributeEdit *edit; QTreeView *view; QStandardItemModel *model; QString user_dn; diff --git a/tests/admc_test_password_edit.cpp b/tests/admc_test_password_edit.cpp index c7713afc..4f132936 100644 --- a/tests/admc_test_password_edit.cpp +++ b/tests/admc_test_password_edit.cpp @@ -25,6 +25,7 @@ #include #include #include +#include // NOTE: no apply_unmodified() test because password edit // always changes object when applying @@ -34,8 +35,9 @@ void ADMCTestPasswordEdit::init() { main_edit = new QLineEdit(parent_widget); confirm_edit = new QLineEdit(parent_widget); + auto show_password_check = new QCheckBox(parent_widget); - edit = new PasswordEdit(main_edit, confirm_edit, &edits, parent_widget); + edit = new PasswordEdit(main_edit, confirm_edit, show_password_check, parent_widget); // Create test user const QString name = TEST_USER; @@ -48,6 +50,7 @@ void ADMCTestPasswordEdit::edited_signal() { bool edited_signal_emitted = false; connect( edit, &AttributeEdit::edited, + this, [&edited_signal_emitted]() { edited_signal_emitted = true; }); diff --git a/tests/admc_test_policy_results_widget.cpp b/tests/admc_test_policy_results_widget.cpp index a1ddb5cc..6f874daf 100644 --- a/tests/admc_test_policy_results_widget.cpp +++ b/tests/admc_test_policy_results_widget.cpp @@ -48,7 +48,7 @@ void ADMCTestPolicyResultsWidget::cleanupTestCase() { ADMCTest::cleanupTestCase(); // Delete old test-policy, if needed - const QString base = g_adconfig->domain_head(); + const QString base = g_adconfig->domain_dn(); const QString filter = filter_CONDITION(Condition_Equals, ATTRIBUTE_DISPLAY_NAME, gpo_name); const QList attributes = QList(); const QHash search_results = ad.search(base, SearchScope_All, filter, attributes); diff --git a/tests/admc_test_protect_deletion_edit.cpp b/tests/admc_test_protect_deletion_edit.cpp index fdb95555..2853b8db 100644 --- a/tests/admc_test_protect_deletion_edit.cpp +++ b/tests/admc_test_protect_deletion_edit.cpp @@ -23,67 +23,74 @@ #include "attribute_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(); checkbox = new QCheckBox(parent_widget); - edit = new ProtectDeletionEdit(checkbox, &edits, parent_widget); + edit = new ProtectDeletionEdit(checkbox, parent_widget); 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() { +void ADMCTestProtectDeletionEdit::edited_signal_data() { + QTest::addColumn("start_state"); + + QTest::newRow("when checked") << false; + QTest::newRow("when unchecked") << true; +} + +void ADMCTestProtectDeletionEdit::edited_signal() { + QFETCH(bool, start_state); + const bool end_state = !start_state; + + checkbox->setChecked(start_state); + bool edited_signal_emitted = false; connect( edit, &AttributeEdit::edited, + this, [&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); + checkbox->setChecked(end_state); QVERIFY(edited_signal_emitted); } -// Edit should unlock locked user if checkbox is checked +void ADMCTestProtectDeletionEdit::apply_data() { + QTest::addColumn("is_checked"); + QTest::addColumn("expected_delete_success"); + + QTest::newRow("protected") << true << false; + QTest::newRow("not protected") << false << true; +} + void ADMCTestProtectDeletionEdit::apply() { + QFETCH(bool, is_checked); + QFETCH(bool, expected_delete_success); + const AdObject object = ad.search_object(dn); edit->load(ad, object); - // Enable protection against deletion - checkbox->setChecked(true); + checkbox->setChecked(is_checked); 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); + const bool actual_delete_success = ad.object_delete(dn); + QCOMPARE(actual_delete_success, expected_delete_success); - // 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); - QVERIFY(delete_2_success); + // Finally, disable protection so test suite can + // delete this object to clean up + if (!actual_delete_success) { + checkbox->setChecked(false); + const bool apply_2_success = edit->apply(ad, dn); + QVERIFY(apply_2_success); + } } QTEST_MAIN(ADMCTestProtectDeletionEdit) diff --git a/tests/admc_test_protect_deletion_edit.h b/tests/admc_test_protect_deletion_edit.h index 76116d4b..bc6df4b1 100644 --- a/tests/admc_test_protect_deletion_edit.h +++ b/tests/admc_test_protect_deletion_edit.h @@ -32,7 +32,9 @@ class ADMCTestProtectDeletionEdit : public ADMCTest { private slots: void init() override; - void emit_edited_signal(); + void edited_signal_data(); + void edited_signal(); + void apply_data(); void apply(); private: diff --git a/tests/admc_test_rename_object_dialog.cpp b/tests/admc_test_rename_object_dialog.cpp index ff906455..176d0209 100644 --- a/tests/admc_test_rename_object_dialog.cpp +++ b/tests/admc_test_rename_object_dialog.cpp @@ -37,8 +37,7 @@ void ADMCTestRenameObjectDialog::rename() { QVERIFY(object_exists(old_dn)); // Open rename dialog - auto rename_object_dialog = new RenameUserDialog(parent_widget); - rename_object_dialog->set_target(old_dn); + auto rename_object_dialog = new RenameUserDialog(ad, old_dn, parent_widget); rename_object_dialog->open(); QVERIFY(QTest::qWaitForWindowExposed(rename_object_dialog, 1000)); @@ -47,7 +46,6 @@ void ADMCTestRenameObjectDialog::rename() { rename_object_dialog->accept(); QVERIFY(object_exists(new_dn)); - QCOMPARE(rename_object_dialog->get_new_name(), new_name); QCOMPARE(rename_object_dialog->get_new_dn(), new_dn); } diff --git a/tests/admc_test_sam_name_edit.cpp b/tests/admc_test_sam_name_edit.cpp new file mode 100644 index 00000000..9531a0af --- /dev/null +++ b/tests/admc_test_sam_name_edit.cpp @@ -0,0 +1,70 @@ +/* + * 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_sam_name_edit.h" + +#include "attribute_edits/sam_name_edit.h" + +#include + +#define TEST_ATTRIBUTE ATTRIBUTE_FIRST_NAME + +void ADMCTestSamNameEdit::init() { + ADMCTest::init(); + + line_edit = new QLineEdit(parent_widget); + auto domain_edit = new QLineEdit(parent_widget); + + edit = new SamNameEdit(line_edit, domain_edit, parent_widget); +} + +void ADMCTestSamNameEdit::verify_data() { + QTest::addColumn("value"); + QTest::addColumn("correct_result"); + + const QString bad_chars_string = SAM_NAME_BAD_CHARS; + + for (int i = 0; i < bad_chars_string.length(); i++) { + const QChar bad_char = bad_chars_string.at(i); + + const QString bad_char_string = QString(bad_char); + const QByteArray bad_char_bytes = bad_char_string.toUtf8(); + + const QString value = QString("test%1value").arg(bad_char); + + QTest::newRow(bad_char_bytes.constData()) << value << false; + } + + QTest::newRow("ends with dot") << "testvalue." << false; + QTest::newRow("contains dot") << "test.value" << true; +} + +void ADMCTestSamNameEdit::verify() { + QFETCH(QString, value); + QFETCH(bool, correct_result); + + line_edit->setText(value); + + const bool actual_result = edit->verify(ad, QString()); + + QCOMPARE(actual_result, correct_result); +} + +QTEST_MAIN(ADMCTestSamNameEdit) diff --git a/src/admc/attribute_multi_edits/manager_multi_edit.h b/tests/admc_test_sam_name_edit.h similarity index 65% rename from src/admc/attribute_multi_edits/manager_multi_edit.h rename to tests/admc_test_sam_name_edit.h index 2c645f87..339a2491 100644 --- a/src/admc/attribute_multi_edits/manager_multi_edit.h +++ b/tests/admc_test_sam_name_edit.h @@ -18,22 +18,26 @@ * along with this program. If not, see . */ -#ifndef MANAGER_MULTI_EDIT_H -#define MANAGER_MULTI_EDIT_H +#ifndef ADMC_TEST_SAM_NAME_EDIT_H +#define ADMC_TEST_SAM_NAME_EDIT_H -#include "attribute_multi_edits/attribute_multi_edit.h" +#include "admc_test.h" -class ManagerWidget; +class SamNameEdit; +class QLineEdit; -class ManagerMultiEdit : public AttributeMultiEdit { +class ADMCTestSamNameEdit : public ADMCTest { Q_OBJECT -public: - ManagerMultiEdit(ManagerWidget *widget, QCheckBox *check, QList &edits_out, QObject *parent); - DECL_ATTRIBUTE_MULTI_EDIT_VIRTUALS(); +private slots: + void init() override; + + void verify_data(); + void verify(); private: - ManagerWidget *widget; + SamNameEdit *edit; + QLineEdit *line_edit; }; -#endif /* MANAGER_MULTI_EDIT_H */ +#endif /* ADMC_TEST_SAM_NAME_EDIT_H */ diff --git a/tests/admc_test_security_tab.cpp b/tests/admc_test_security_tab.cpp deleted file mode 100644 index f8e6a84f..00000000 --- a/tests/admc_test_security_tab.cpp +++ /dev/null @@ -1,329 +0,0 @@ -/* - * ADMC - AD Management Center - * - * Copyright (C) 2020 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_security_tab.h" - -#include "samba/ndr_security.h" -#include "tabs/security_tab.h" - -#include -#include -#include -#include -#include - -Q_DECLARE_METATYPE(AcePermission) - -void ADMCTestSecurityTab::init() { - ADMCTest::init(); - - security_tab = new SecurityTab(); - add_widget(security_tab); - - // Create test user - const QString name = TEST_USER; - test_user_dn = test_object_dn(name, CLASS_USER); - const bool create_success = ad.object_add(test_user_dn, CLASS_USER); - QVERIFY(create_success); - - const AdObject object = ad.search_object(test_user_dn); - - security_tab->load(ad, object); -} - -void ADMCTestSecurityTab::load_data() { - // NOTE: store trustee name in the data tag to avoid repetition - // QTest::addColumn("trustee_name"); - QTest::addColumn>("allowed_set"); - - QTest::newRow("Account Operators") << all_permissions; - - QTest::newRow("Administrators") << [&]() { - QSet out = all_permissions; - out -= AcePermission_FullControl; - out -= AcePermission_DeleteChild; - out -= AcePermission_DeleteSubtree; - - return out; - }(); - - QTest::newRow("Authenticated Users") << [&]() { - QSet out; - out += AcePermission_ReadGeneralInfo; - out += AcePermission_ReadPersonalInfo; - out += AcePermission_ReadPublicInfo; - out += AcePermission_ReadWebInfo; - - return out; - }(); - - QTest::newRow("Cert Publishers") << QSet(); - - QTest::newRow("Domain Admins") << all_permissions; - - QTest::newRow("ENTERPRISE DOMAIN CONTROLLERS") << QSet(); - - QTest::newRow("Enterprise Admins") << all_permissions; - - QTest::newRow("Everyone") << [&]() { - QSet out; - out += AcePermission_ChangePassword; - - return out; - }(); - - QTest::newRow("Pre-Windows 2000 Compatible Access") << [&]() { - QSet out; - out += AcePermission_ReadAccountRestrictions; - out += AcePermission_ReadGeneralInfo; - out += AcePermission_ReadGroupMembership; - out += AcePermission_ReadLogonInfo; - out += AcePermission_ReadRemoteAccessInfo; - - return out; - }(); - - QTest::newRow("RAS and IAS Servers") << [&]() { - QSet out; - out += AcePermission_ReadAccountRestrictions; - out += AcePermission_ReadGroupMembership; - out += AcePermission_ReadLogonInfo; - out += AcePermission_ReadRemoteAccessInfo; - - return out; - }(); - - QTest::newRow("SELF") << [&]() { - QSet out; - out += AcePermission_Read; - out += read_prop_permissions; - out += AcePermission_ChangePassword; - out += AcePermission_ReceiveAs; - out += AcePermission_SendAs; - out += AcePermission_WritePersonalInfo; - out += AcePermission_WritePhoneAndMailOptions; - out += AcePermission_WritePrivateInfo; - out += AcePermission_WriteWebInfo; - - return out; - }(); - - QTest::newRow("SYSTEM") << all_permissions; - - QTest::newRow("Terminal Server License Servers") << [&]() { - QSet out; - out += AcePermission_ReadTerminalServerLicenseServer; - out += AcePermission_WriteTerminalServerLicenseServer; - - return out; - }(); - - QTest::newRow("Windows Authorization Access Group") << QSet(); -} - -// NOTE: just checking that the default security descriptor -// is laoded correctly. Creating custom security descriptors -// is too complicated at the moment. -void ADMCTestSecurityTab::load() { - const QString trustee_name = QTest::currentDataTag(); - QFETCH(QSet, allowed_set); - - QVERIFY(security_tab->set_trustee(trustee_name)); - - const QSet none_set = all_permissions - allowed_set; - QVERIFY(state_is(allowed_set, PermissionState_Allowed)); - QVERIFY(state_is(none_set, PermissionState_None)); -} - -// When you allow some perm then deny it, the allow checkbox -// should become unchecked, aka they are exclusive. -void ADMCTestSecurityTab::allow_then_deny() { - uncheck_all_permissions(); - - // NOTE: permission doesn't matter, just picked some random one - const AcePermission permission = AcePermission_SendAs; - set_permission_state({permission}, AceColumn_Allowed, Qt::Checked); - QVERIFY(state_is({permission}, PermissionState_Allowed)); - - set_permission_state({permission}, AceColumn_Denied, Qt::Checked); - QVERIFY(state_is({permission}, PermissionState_Denied)); -} - -// Allowing full should allow every permission -void ADMCTestSecurityTab::allow_full() { - uncheck_all_permissions(); - - QVERIFY(state_is(all_permissions, PermissionState_None)); - - set_permission_state({AcePermission_FullControl}, AceColumn_Allowed, Qt::Checked); - - QVERIFY(state_is(all_permissions, PermissionState_Allowed)); -} - -// Allowing full and denying read, should allow everything -// except read permissions which should be denied. -void ADMCTestSecurityTab::allow_full_deny_read() { - uncheck_all_permissions(); - - set_permission_state({AcePermission_FullControl}, AceColumn_Allowed, Qt::Checked); - set_permission_state({AcePermission_Read}, AceColumn_Denied, Qt::Checked); - - QVERIFY(state_is({AcePermission_FullControl}, PermissionState_None)); - QVERIFY(state_is(access_permissions, PermissionState_Allowed)); - QVERIFY(state_is(write_prop_permissions, PermissionState_Allowed)); - - QVERIFY(state_is({AcePermission_Read}, PermissionState_Denied)); - QVERIFY(state_is(read_prop_permissions, PermissionState_Denied)); -} - -// Unchecking read while full is allowed, should uncheck -// full and nothing else. -void ADMCTestSecurityTab::allow_full_uncheck_read() { - uncheck_all_permissions(); - - set_permission_state({AcePermission_FullControl}, AceColumn_Allowed, Qt::Checked); - set_permission_state({AcePermission_Read}, AceColumn_Allowed, Qt::Unchecked); - - QVERIFY(state_is({AcePermission_FullControl}, PermissionState_None)); - QVERIFY(state_is(access_permissions, PermissionState_Allowed)); - QVERIFY(state_is(write_prop_permissions, PermissionState_Allowed)); - QVERIFY(state_is(read_prop_permissions, PermissionState_Allowed)); - - QVERIFY(state_is({AcePermission_Read}, PermissionState_None)); -} - -// Unchecking a read prop while read is allowed, should -// uncheck read and nothing else. -void ADMCTestSecurityTab::allow_read_uncheck_read_prop() { - uncheck_all_permissions(); - - set_permission_state({AcePermission_Read}, AceColumn_Allowed, Qt::Checked); - set_permission_state({AcePermission_ReadWebInfo}, AceColumn_Allowed, Qt::Unchecked); - - QVERIFY(state_is({AcePermission_Read}, PermissionState_None)); - - QVERIFY(state_is((read_prop_permissions - QSet{AcePermission_ReadWebInfo}), PermissionState_Allowed)); -} - -// Denying a read prop while read is allowed, should -// uncheck read and deny that permission. -void ADMCTestSecurityTab::allow_read_deny_read_prop() { - uncheck_all_permissions(); - - set_permission_state({AcePermission_Read}, AceColumn_Allowed, Qt::Checked); - set_permission_state({AcePermission_ReadWebInfo}, AceColumn_Denied, Qt::Checked); - - QVERIFY(state_is({AcePermission_Read}, PermissionState_None)); - QVERIFY(state_is({AcePermission_ReadWebInfo}, PermissionState_Denied)); - - QVERIFY(state_is((read_prop_permissions - QSet{AcePermission_ReadWebInfo}), PermissionState_Allowed)); -} - -void ADMCTestSecurityTab::apply() { - QVERIFY(security_tab->set_trustee("Cert Publishers")); - - // Check/uncheck some permissions in the tab - const QSet allowed_set = { - AcePermission_CreateChild, - AcePermission_ReadPersonalInfo, - }; - const QSet denied_set = { - AcePermission_WriteWebInfo, - AcePermission_WritePersonalInfo, - AcePermission_DeleteChild, - }; - const QSet none_set = all_permissions - allowed_set - denied_set; - uncheck_all_permissions(); - set_permission_state(allowed_set, AceColumn_Allowed, Qt::Checked); - set_permission_state(denied_set, AceColumn_Denied, Qt::Checked); - - // Apply - const bool apply_success = security_tab->apply(ad, test_user_dn); - QVERIFY(apply_success); - - // Reload tab - const AdObject updated_object = ad.search_object(test_user_dn); - security_tab->load(ad, updated_object); - - // Verify that state loaded correctly - QVERIFY(security_tab->set_trustee("Cert Publishers")); - QVERIFY(state_is(allowed_set, PermissionState_Allowed)); - QVERIFY(state_is(denied_set, PermissionState_Denied)); - QVERIFY(state_is(none_set, PermissionState_None)); -} - -void ADMCTestSecurityTab::uncheck_all_permissions() { - set_permission_state(all_permissions, AceColumn_Allowed, Qt::Unchecked); - set_permission_state(all_permissions, AceColumn_Denied, Qt::Unchecked); -} - -bool ADMCTestSecurityTab::state_is(const QSet &permission_set, const PermissionState state) const { - const QSet &checked_columns = [&]() -> QSet { - switch (state) { - case PermissionState_Allowed: return {AceColumn_Allowed}; - case PermissionState_Denied: return {AceColumn_Denied}; - case PermissionState_None: return {}; - } - return {}; - }(); - - for (const AcePermission &permission : permission_set) { - const QList column_list = { - AceColumn_Allowed, - AceColumn_Denied, - }; - - for (const AceColumn &column : column_list) { - QStandardItem *item = security_tab->get_item(permission, column); - - const bool should_be_checked = checked_columns.contains(column); - const bool is_checked = (item->checkState() == Qt::Checked); - const bool state_is_correct = (is_checked == should_be_checked); - - if (!state_is_correct) { - const QString permission_name = SecurityTab::ace_permission_to_name_map()[permission]; - const QString column_name = [&]() { - switch (column) { - case AceColumn_Allowed: return "Allowed"; - case AceColumn_Denied: return "Denied"; - default: break; - } - - return "unknown"; - }(); - - qInfo().noquote() << QString("Incorrect state:\n\tpermission = %1\n\tcolumn = %2\n\tcurrent state = %3\n\tcorrect state = %4").arg(permission_name, column_name, QString::number(is_checked), QString::number(should_be_checked)); - - return false; - } - } - } - - return true; -} - -void ADMCTestSecurityTab::set_permission_state(const QSet &permission_set, const AceColumn column, const Qt::CheckState state) { - for (const AcePermission &permission : permission_set) { - QStandardItem *item = security_tab->get_item(permission, column); - item->setCheckState(state); - } -} - -QTEST_MAIN(ADMCTestSecurityTab) diff --git a/tests/admc_test_security_tab.h b/tests/admc_test_security_tab.h deleted file mode 100644 index 3f321360..00000000 --- a/tests/admc_test_security_tab.h +++ /dev/null @@ -1,55 +0,0 @@ -/* - * ADMC - AD Management Center - * - * Copyright (C) 2020 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_SECURITY_TAB_H -#define ADMC_TEST_SECURITY_TAB_H - -#include "admc_test.h" - -#include "tabs/security_tab.h" - -class SecurityTab; - -class ADMCTestSecurityTab : public ADMCTest { - Q_OBJECT - -private slots: - void init() override; - - void load_data(); - void load(); - void allow_then_deny(); - void allow_full(); - void allow_full_deny_read(); - void allow_full_uncheck_read(); - void allow_read_uncheck_read_prop(); - void allow_read_deny_read_prop(); - void apply(); - -private: - SecurityTab *security_tab; - QString test_user_dn; - - void uncheck_all_permissions(); - void set_permission_state(const QSet &permission_set, const AceColumn column, const Qt::CheckState state); - bool state_is(const QSet &permission_set, const PermissionState state) const; -}; - -#endif /* ADMC_TEST_SECURITY_TAB_H */ diff --git a/tests/admc_test_select_base_widget.cpp b/tests/admc_test_select_base_widget.cpp index d22a929f..4af08d3f 100644 --- a/tests/admc_test_select_base_widget.cpp +++ b/tests/admc_test_select_base_widget.cpp @@ -57,11 +57,11 @@ void ADMCTestSelectBaseWidget::init() { } // By default, domain head should be selected -void ADMCTestSelectBaseWidget::default_to_domain_head() { - const QString domain_head = g_adconfig->domain_head(); +void ADMCTestSelectBaseWidget::default_to_domain_dn() { + const QString domain_dn = g_adconfig->domain_dn(); const QString base = select_base_widget->get_base(); - QCOMPARE(base, domain_head); + QCOMPARE(base, domain_dn); } // After selecting a search base, the widget should return diff --git a/tests/admc_test_select_base_widget.h b/tests/admc_test_select_base_widget.h index 81deb5df..a1a1e558 100644 --- a/tests/admc_test_select_base_widget.h +++ b/tests/admc_test_select_base_widget.h @@ -33,7 +33,7 @@ class ADMCTestSelectBaseWidget : public ADMCTest { private slots: void init() override; - void default_to_domain_head(); + void default_to_domain_dn(); void select_base(); void select_base_multiple(); void save_state(); diff --git a/tests/admc_test_select_classes_widget.cpp b/tests/admc_test_select_classes_widget.cpp new file mode 100644 index 00000000..99092322 --- /dev/null +++ b/tests/admc_test_select_classes_widget.cpp @@ -0,0 +1,112 @@ +/* + * 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_select_classes_widget.h" + +#include "filter_widget/select_classes_widget.h" +#include "filter_widget/ui_select_classes_widget.h" +#include "filter_widget/class_filter_dialog.h" +#include "filter_widget/ui_class_filter_dialog.h" + +void ADMCTestSelectClassesWidget::init() { + ADMCTest::init(); + + select_classes_widget = new SelectClassesWidget(); + select_classes_widget->set_classes(filter_classes, filter_classes); + select_classes_widget->enable_filtering_all_classes(); + add_widget(select_classes_widget); + + select_button = select_classes_widget->ui->select_button; + classes_display = select_classes_widget->ui->classes_display; +} + +void ADMCTestSelectClassesWidget::test_foo_data() { + QTest::addColumn("all_checked"); + QTest::addColumn>("specific_classes_checked"); + QTest::addColumn("correct_display_string"); + QTest::addColumn("correct_filter"); + + QTest::newRow("all only") << true << QList() << "All" << ""; + // NOTE: all should override other checkboxes, even + // if they are checked + QTest::newRow("all + some specific") << true << QList({"User", "Group"}) << "All" << ""; + QTest::newRow("User") << false << QList({"User"}) << "User" << "(objectClass=user)"; + QTest::newRow("User + Group") << false << QList({"User", "Group"}) << "Group, User" << "(|(objectClass=user)(objectClass=group))"; +} + +void ADMCTestSelectClassesWidget::test_foo() { + QFETCH(bool, all_checked); + QFETCH(QList, specific_classes_checked); + QFETCH(QString, correct_display_string); + QFETCH(QString, correct_filter); + + auto check_output_correctness = [&]() { + QCOMPARE(classes_display->text(), correct_display_string); + QCOMPARE(select_classes_widget->get_filter(), correct_filter); + }; + + auto get_class_checkbox_list = [](ClassFilterDialog *dialog) { + ClassFilterWidget * class_filter_widget = dialog->ui->class_filter_widget; + const QList out = class_filter_widget->findChildren(); + + return out; + }; + + // Check correctness of input->output + select_button->click(); + + ClassFilterDialog *class_filter_dialog = select_classes_widget->findChild(); + QVERIFY(class_filter_dialog); + QVERIFY(QTest::qWaitForWindowExposed(class_filter_dialog, 1000)); + + QCheckBox *all_checkbox = class_filter_dialog->ui->all_checkbox; + all_checkbox->setChecked(all_checked); + + const QList class_checkbox_list = get_class_checkbox_list(class_filter_dialog); + + for (QCheckBox *check : class_checkbox_list) { + const bool match = specific_classes_checked.contains(check->text()); + + check->setChecked(match); + } + + class_filter_dialog->accept(); + + check_output_correctness(); + + // Check state saving/restoring + const QVariant state = select_classes_widget->save_state(); + select_classes_widget->restore_state(state); + + check_output_correctness(); + + // Check that reopening dialog retains state + select_button->click(); + + ClassFilterDialog *class_filter_dialog_2 = select_classes_widget->findChild(); + QVERIFY(class_filter_dialog_2); + QVERIFY(QTest::qWaitForWindowExposed(class_filter_dialog_2, 1000)); + + class_filter_dialog_2->accept(); + + check_output_correctness(); +} + +QTEST_MAIN(ADMCTestSelectClassesWidget) diff --git a/src/admc/attribute_multi_edits/country_multi_edit.h b/tests/admc_test_select_classes_widget.h similarity index 62% rename from src/admc/attribute_multi_edits/country_multi_edit.h rename to tests/admc_test_select_classes_widget.h index 9af6dee4..5673bcb5 100644 --- a/src/admc/attribute_multi_edits/country_multi_edit.h +++ b/tests/admc_test_select_classes_widget.h @@ -18,26 +18,28 @@ * along with this program. If not, see . */ -#ifndef COUNTRY_MULTI_EDIT_H -#define COUNTRY_MULTI_EDIT_H +#ifndef ADMC_TEST_SELECT_CLASSES_WIDGET_H +#define ADMC_TEST_SELECT_CLASSES_WIDGET_H -#include "attribute_multi_edits/attribute_multi_edit.h" +#include "admc_test.h" -class QComboBox; +class SelectClassesWidget; +class QPushButton; +class QLineEdit; -/** - * Edit for editing string attributes of multiple objects. - */ - -class CountryMultiEdit : public AttributeMultiEdit { +class ADMCTestSelectClassesWidget : public ADMCTest { Q_OBJECT -public: - CountryMultiEdit(QComboBox *country_combo, QCheckBox *check, QList &edits_out, QObject *parent); - DECL_ATTRIBUTE_MULTI_EDIT_VIRTUALS(); +private slots: + void init() override; + + void test_foo_data(); + void test_foo(); private: - QComboBox *country_combo; + SelectClassesWidget *select_classes_widget; + QPushButton *select_button; + QLineEdit *classes_display; }; -#endif /* COUNTRY_MULTI_EDIT_H */ +#endif /* ADMC_TEST_SELECT_CLASSES_WIDGET_H */ diff --git a/tests/admc_test_string_edit.cpp b/tests/admc_test_string_edit.cpp index a6b4102f..431a2baa 100644 --- a/tests/admc_test_string_edit.cpp +++ b/tests/admc_test_string_edit.cpp @@ -32,7 +32,7 @@ void ADMCTestStringEdit::init() { line_edit = new QLineEdit(parent_widget); - edit = new StringEdit(line_edit, TEST_ATTRIBUTE, &edits, parent_widget); + edit = new StringEdit(line_edit, TEST_ATTRIBUTE, parent_widget); // Create test user const QString name = TEST_USER; @@ -46,6 +46,7 @@ void ADMCTestStringEdit::test_emit_edited_signal() { bool edited_signal_emitted = false; connect( edit, &AttributeEdit::edited, + this, [&edited_signal_emitted]() { edited_signal_emitted = true; }); @@ -88,4 +89,18 @@ void ADMCTestStringEdit::apply_modified() { QCOMPARE(current_value, new_value); } +// Apply should trim leading and trailing spaces +void ADMCTestStringEdit::apply_trim() { + const QString new_value = " new value "; + line_edit->setText(new_value); + + const bool apply_success = edit->apply(ad, dn); + QVERIFY(apply_success); + + const AdObject object = ad.search_object(dn); + const QString current_value = object.get_string(TEST_ATTRIBUTE); + + QCOMPARE(current_value, new_value.trimmed()); +} + QTEST_MAIN(ADMCTestStringEdit) diff --git a/tests/admc_test_string_edit.h b/tests/admc_test_string_edit.h index 189eefc4..7005ee58 100644 --- a/tests/admc_test_string_edit.h +++ b/tests/admc_test_string_edit.h @@ -36,6 +36,7 @@ private slots: void load(); void apply_unmodified(); void apply_modified(); + void apply_trim(); private: StringEdit *edit; diff --git a/tests/admc_test_string_large_edit.cpp b/tests/admc_test_string_large_edit.cpp index 02095a2b..cf9a8a6b 100644 --- a/tests/admc_test_string_large_edit.cpp +++ b/tests/admc_test_string_large_edit.cpp @@ -32,7 +32,7 @@ void ADMCTestStringLargeEdit::init() { text_edit = new QPlainTextEdit(parent_widget); - edit = new StringLargeEdit(text_edit, TEST_ATTRIBUTE, &edits, parent_widget); + edit = new StringLargeEdit(text_edit, TEST_ATTRIBUTE, parent_widget); // Create test user const QString name = TEST_USER; @@ -46,6 +46,7 @@ void ADMCTestStringLargeEdit::test_emit_edited_signal() { bool edited_signal_emitted = false; connect( edit, &AttributeEdit::edited, + this, [&edited_signal_emitted]() { edited_signal_emitted = true; }); diff --git a/tests/admc_test_string_other_edit.cpp b/tests/admc_test_string_other_edit.cpp index 431b441e..3879ff88 100644 --- a/tests/admc_test_string_other_edit.cpp +++ b/tests/admc_test_string_other_edit.cpp @@ -48,7 +48,7 @@ void ADMCTestStringOtherEdit::init() { line_edit = new QLineEdit(parent_widget); other_button = new QPushButton(parent_widget); - edit = new StringOtherEdit(line_edit, other_button, TEST_ATTRIBUTE_MAIN, ATTRIBUTE_WWW_HOMEPAGE_OTHER, &edits, parent_widget); + edit = new StringOtherEdit(line_edit, other_button, TEST_ATTRIBUTE_MAIN, ATTRIBUTE_WWW_HOMEPAGE_OTHER, parent_widget); // Create test user const QString name = TEST_USER; @@ -71,6 +71,7 @@ void ADMCTestStringOtherEdit::test_emit_edited_signal() { bool edited_signal_emitted = false; connect( edit, &AttributeEdit::edited, + this, [&edited_signal_emitted]() { edited_signal_emitted = true; }); diff --git a/tests/admc_test_unlock_edit.cpp b/tests/admc_test_unlock_edit.cpp index a7250ebf..7803eeff 100644 --- a/tests/admc_test_unlock_edit.cpp +++ b/tests/admc_test_unlock_edit.cpp @@ -36,7 +36,7 @@ void ADMCTestUnlockEdit::init() { checkbox = new QCheckBox(parent_widget); - unlock_edit = new UnlockEdit(checkbox, &edits, parent_widget); + unlock_edit = new UnlockEdit(checkbox, parent_widget); // Create test user const QString name = TEST_USER; @@ -50,6 +50,7 @@ void ADMCTestUnlockEdit::test_emit_edited_signal() { bool edited_signal_emitted = false; connect( unlock_edit, &AttributeEdit::edited, + this, [&edited_signal_emitted]() { edited_signal_emitted = true; }); diff --git a/tests/admc_test_upn_edit.cpp b/tests/admc_test_upn_edit.cpp index e0a816ac..8e55baa5 100644 --- a/tests/admc_test_upn_edit.cpp +++ b/tests/admc_test_upn_edit.cpp @@ -26,7 +26,10 @@ #include #include -#define TEST_SUFFIX "test.com" +// NOTE: we have to make sure that test suffix doesn't +// already exist on the test domain, hence the wacky +// name +#define TEST_SUFFIX "totally-unique-test-suffix.zone" void ADMCTestUpnEdit::init() { ADMCTest::init(); @@ -34,7 +37,7 @@ void ADMCTestUpnEdit::init() { prefix_edit = new QLineEdit(); suffix_edit = new QComboBox(); - upn_edit = new UpnEdit(prefix_edit, suffix_edit, &edits, parent_widget); + upn_edit = new UpnEdit(prefix_edit, suffix_edit, parent_widget); upn_edit->init_suffixes(ad); // Create test user @@ -44,19 +47,30 @@ void ADMCTestUpnEdit::init() { QVERIFY(create_success); const QString test_upn = QString("%1@%2").arg(name, TEST_SUFFIX); - ad.attribute_replace_string(dn, ATTRIBUTE_USER_PRINCIPAL_NAME, test_upn); + const bool replace_success = ad.attribute_replace_string(dn, ATTRIBUTE_USER_PRINCIPAL_NAME, test_upn); + QVERIFY(replace_success); const AdObject object = ad.search_object(dn); upn_edit->load(ad, object); } +void ADMCTestUpnEdit::length_limit() { + const int prefix_max_length = prefix_edit->maxLength(); + const int correct_prefix_max_length = [&]() { + const int total_range_upper = ad.adconfig()->get_attribute_range_upper(ATTRIBUTE_USER_PRINCIPAL_NAME); + const int suffix_length = suffix_edit->currentText().length(); + const int out = total_range_upper - 1 - suffix_length; + + return out; + }(); + + QCOMPARE(prefix_max_length, correct_prefix_max_length); +} + // Edit should load prefix and suffix into widgets correctly void ADMCTestUpnEdit::test_load() { const QString prefix = prefix_edit->text(); QCOMPARE(prefix, TEST_USER); - - const QString suffix = suffix_edit->currentText(); - QCOMPARE(suffix, TEST_SUFFIX); } // edited() signal should be emitted when prefix or suffix @@ -65,6 +79,7 @@ void ADMCTestUpnEdit::test_emit_edited() { bool edited_signal_emitted = false; connect( upn_edit, &AttributeEdit::edited, + this, [&edited_signal_emitted]() { edited_signal_emitted = true; }); @@ -168,18 +183,58 @@ void ADMCTestUpnEdit::change_suffix_in_edit() { suffix_edit->setCurrentIndex(new_suffix_index); } + +void ADMCTestUpnEdit::verify_bad_chars_data() { + QTest::addColumn("value"); + QTest::addColumn("correct_result"); + + const QString bad_chars_string = UPN_BAD_CHARS; + + for (int i = 0; i < bad_chars_string.length(); i++) { + const QChar bad_char = bad_chars_string.at(i); + + const QString bad_char_string = QString(bad_char); + const QByteArray bad_char_bytes = bad_char_string.toUtf8(); + + const QString value = QString("test%1value").arg(bad_char); + + QTest::newRow(bad_char_bytes.constData()) << value << false; + } + + QTest::newRow("starts with space") << " testvalue" << false; + QTest::newRow("ends with space") << "testvalue " << false; + QTest::newRow("contains space inside") << "test value" << true; +} + +void ADMCTestUpnEdit::verify_bad_chars() { + QFETCH(QString, value); + QFETCH(bool, correct_result); + + prefix_edit->setText(value); + + const bool actual_result = upn_edit->verify(ad, QString()); + + QCOMPARE(actual_result, correct_result); +} + // verify() must return false if there's a user with the // same upn -void ADMCTestUpnEdit::test_verify() { +void ADMCTestUpnEdit::verify_conflict() { // Create user with conflicting upn const QString conflict_name = "conflicting-upn-test-user"; const QString conflict_dn = test_object_dn(conflict_name, CLASS_USER); const bool create_success = ad.object_add(conflict_dn, CLASS_USER); QVERIFY(create_success); - const QString conflicting_upn = get_current_upn(); - ad.attribute_replace_string(conflict_dn, ATTRIBUTE_USER_PRINCIPAL_NAME, conflicting_upn); - // Verify should fail + const QString conflicting_upn = QString("%1@%2").arg(conflict_name, TEST_SUFFIX); + const bool replace_success = ad.attribute_replace_string(conflict_dn, ATTRIBUTE_USER_PRINCIPAL_NAME, conflicting_upn); + QVERIFY(replace_success); + + // Set input of upn edit so that it conflicts with + // the conflicting user that we have setup. After + // that verify should fail. + prefix_edit->setText(conflict_name); + suffix_edit->setCurrentText(TEST_SUFFIX); const bool verify_success = upn_edit->verify(ad, dn); QVERIFY2(!verify_success, "verify() didn't notice upn conflict"); diff --git a/tests/admc_test_upn_edit.h b/tests/admc_test_upn_edit.h index 24fe5cc1..2bbc8019 100644 --- a/tests/admc_test_upn_edit.h +++ b/tests/admc_test_upn_edit.h @@ -33,6 +33,7 @@ class ADMCTestUpnEdit : public ADMCTest { private slots: void init() override; + void length_limit(); void test_load(); void test_emit_edited(); void apply_unmodified(); @@ -40,7 +41,9 @@ private slots: void test_apply_prefix(); void test_apply_prefix_and_suffix(); void test_reset(); - void test_verify(); + void verify_bad_chars_data(); + void verify_bad_chars(); + void verify_conflict(); private: UpnEdit *upn_edit;