444 lines
16 KiB
Diff
444 lines
16 KiB
Diff
From 0ed5299841733f9e18c5fe8c6a3f9d3fbd49c75a Mon Sep 17 00:00:00 2001
|
|
From: Firstyear <william@blackhats.net.au>
|
|
Date: Fri, 20 Aug 2021 09:18:50 +1000
|
|
Subject: [PATCH 3/4] Issue 4877 - RFE - EntryUUID to validate UUIDs on fixup
|
|
(#4878)
|
|
|
|
Bug Description: Due to changing the syntax of EntryUUID's
|
|
to string, we may have invalid EntryUUID's imported into
|
|
the database.
|
|
|
|
Fix Description: To resolve this during a fixup we validate
|
|
that Uuid's have a valid syntax. If they do not, we regenerate
|
|
them.
|
|
|
|
fixes: https://github.com/389ds/389-ds-base/issues/4877
|
|
|
|
Author: William Brown <william@blackhats.net.au>
|
|
|
|
Review by: @mreynolds389
|
|
---
|
|
.../entryuuid/localhost-userRoot-invalid.ldif | 233 ++++++++++++++++++
|
|
.../tests/suites/entryuuid/basic_test.py | 58 ++++-
|
|
src/plugins/entryuuid/src/lib.rs | 28 ++-
|
|
src/slapi_r_plugin/src/pblock.rs | 4 +-
|
|
src/slapi_r_plugin/src/value.rs | 10 +-
|
|
5 files changed, 322 insertions(+), 11 deletions(-)
|
|
create mode 100644 dirsrvtests/tests/data/entryuuid/localhost-userRoot-invalid.ldif
|
|
|
|
diff --git a/dirsrvtests/tests/data/entryuuid/localhost-userRoot-invalid.ldif b/dirsrvtests/tests/data/entryuuid/localhost-userRoot-invalid.ldif
|
|
new file mode 100644
|
|
index 000000000..9703babed
|
|
--- /dev/null
|
|
+++ b/dirsrvtests/tests/data/entryuuid/localhost-userRoot-invalid.ldif
|
|
@@ -0,0 +1,233 @@
|
|
+version: 1
|
|
+
|
|
+# entry-id: 1
|
|
+dn: dc=example,dc=com
|
|
+objectClass: top
|
|
+objectClass: domain
|
|
+dc: example
|
|
+description: dc=example,dc=com
|
|
+creatorsName: cn=Directory Manager
|
|
+modifiersName: cn=Directory Manager
|
|
+createTimestamp: 20200325015542Z
|
|
+modifyTimestamp: 20200325015542Z
|
|
+nsUniqueId: a2b33229-6e3b11ea-8de0c78c-83e27eda
|
|
+aci: (targetattr="dc || description || objectClass")(targetfilter="(objectClas
|
|
+ s=domain)")(version 3.0; acl "Enable anyone domain read"; allow (read, search
|
|
+ , compare)(userdn="ldap:///anyone");)
|
|
+aci: (targetattr="ou || objectClass")(targetfilter="(objectClass=organizationa
|
|
+ lUnit)")(version 3.0; acl "Enable anyone ou read"; allow (read, search, compa
|
|
+ re)(userdn="ldap:///anyone");)
|
|
+
|
|
+# entry-id: 2
|
|
+dn: cn=389_ds_system,dc=example,dc=com
|
|
+objectClass: top
|
|
+objectClass: nscontainer
|
|
+objectClass: ldapsubentry
|
|
+cn: 389_ds_system
|
|
+creatorsName: cn=Directory Manager
|
|
+modifiersName: cn=Directory Manager
|
|
+createTimestamp: 20200325015542Z
|
|
+modifyTimestamp: 20200325015542Z
|
|
+nsUniqueId: a2b3322a-6e3b11ea-8de0c78c-83e27eda
|
|
+
|
|
+# entry-id: 3
|
|
+dn: ou=groups,dc=example,dc=com
|
|
+objectClass: top
|
|
+objectClass: organizationalunit
|
|
+ou: groups
|
|
+aci: (targetattr="cn || member || gidNumber || nsUniqueId || description || ob
|
|
+ jectClass")(targetfilter="(objectClass=groupOfNames)")(version 3.0; acl "Enab
|
|
+ le anyone group read"; allow (read, search, compare)(userdn="ldap:///anyone")
|
|
+ ;)
|
|
+aci: (targetattr="member")(targetfilter="(objectClass=groupOfNames)")(version
|
|
+ 3.0; acl "Enable group_modify to alter members"; allow (write)(groupdn="ldap:
|
|
+ ///cn=group_modify,ou=permissions,dc=example,dc=com");)
|
|
+aci: (targetattr="cn || member || gidNumber || description || objectClass")(ta
|
|
+ rgetfilter="(objectClass=groupOfNames)")(version 3.0; acl "Enable group_admin
|
|
+ to manage groups"; allow (write, add, delete)(groupdn="ldap:///cn=group_admi
|
|
+ n,ou=permissions,dc=example,dc=com");)
|
|
+creatorsName: cn=Directory Manager
|
|
+modifiersName: cn=Directory Manager
|
|
+createTimestamp: 20200325015543Z
|
|
+modifyTimestamp: 20200325015543Z
|
|
+nsUniqueId: a2b3322b-6e3b11ea-8de0c78c-83e27eda
|
|
+
|
|
+# entry-id: 4
|
|
+dn: ou=people,dc=example,dc=com
|
|
+objectClass: top
|
|
+objectClass: organizationalunit
|
|
+ou: people
|
|
+aci: (targetattr="objectClass || description || nsUniqueId || uid || displayNa
|
|
+ me || loginShell || uidNumber || gidNumber || gecos || homeDirectory || cn ||
|
|
+ memberOf || mail || nsSshPublicKey || nsAccountLock || userCertificate")(tar
|
|
+ getfilter="(objectClass=posixaccount)")(version 3.0; acl "Enable anyone user
|
|
+ read"; allow (read, search, compare)(userdn="ldap:///anyone");)
|
|
+aci: (targetattr="displayName || legalName || userPassword || nsSshPublicKey")
|
|
+ (version 3.0; acl "Enable self partial modify"; allow (write)(userdn="ldap://
|
|
+ /self");)
|
|
+aci: (targetattr="legalName || telephoneNumber || mobile || sn")(targetfilter=
|
|
+ "(|(objectClass=nsPerson)(objectClass=inetOrgPerson))")(version 3.0; acl "Ena
|
|
+ ble self legalname read"; allow (read, search, compare)(userdn="ldap:///self"
|
|
+ );)
|
|
+aci: (targetattr="legalName || telephoneNumber")(targetfilter="(objectClass=ns
|
|
+ Person)")(version 3.0; acl "Enable user legalname read"; allow (read, search,
|
|
+ compare)(groupdn="ldap:///cn=user_private_read,ou=permissions,dc=example,dc=
|
|
+ com");)
|
|
+aci: (targetattr="uid || description || displayName || loginShell || uidNumber
|
|
+ || gidNumber || gecos || homeDirectory || cn || memberOf || mail || legalNam
|
|
+ e || telephoneNumber || mobile")(targetfilter="(&(objectClass=nsPerson)(objec
|
|
+ tClass=nsAccount))")(version 3.0; acl "Enable user admin create"; allow (writ
|
|
+ e, add, delete, read)(groupdn="ldap:///cn=user_admin,ou=permissions,dc=exampl
|
|
+ e,dc=com");)
|
|
+aci: (targetattr="uid || description || displayName || loginShell || uidNumber
|
|
+ || gidNumber || gecos || homeDirectory || cn || memberOf || mail || legalNam
|
|
+ e || telephoneNumber || mobile")(targetfilter="(&(objectClass=nsPerson)(objec
|
|
+ tClass=nsAccount))")(version 3.0; acl "Enable user modify to change users"; a
|
|
+ llow (write, read)(groupdn="ldap:///cn=user_modify,ou=permissions,dc=example,
|
|
+ dc=com");)
|
|
+aci: (targetattr="userPassword || nsAccountLock || userCertificate || nsSshPub
|
|
+ licKey")(targetfilter="(objectClass=nsAccount)")(version 3.0; acl "Enable use
|
|
+ r password reset"; allow (write, read)(groupdn="ldap:///cn=user_passwd_reset,
|
|
+ ou=permissions,dc=example,dc=com");)
|
|
+creatorsName: cn=Directory Manager
|
|
+modifiersName: cn=Directory Manager
|
|
+createTimestamp: 20200325015543Z
|
|
+modifyTimestamp: 20200325015543Z
|
|
+nsUniqueId: a2b3322c-6e3b11ea-8de0c78c-83e27eda
|
|
+
|
|
+# entry-id: 5
|
|
+dn: ou=permissions,dc=example,dc=com
|
|
+objectClass: top
|
|
+objectClass: organizationalunit
|
|
+ou: permissions
|
|
+creatorsName: cn=Directory Manager
|
|
+modifiersName: cn=Directory Manager
|
|
+createTimestamp: 20200325015543Z
|
|
+modifyTimestamp: 20200325015543Z
|
|
+nsUniqueId: a2b3322d-6e3b11ea-8de0c78c-83e27eda
|
|
+
|
|
+# entry-id: 6
|
|
+dn: ou=services,dc=example,dc=com
|
|
+objectClass: top
|
|
+objectClass: organizationalunit
|
|
+ou: services
|
|
+aci: (targetattr="objectClass || description || nsUniqueId || cn || memberOf |
|
|
+ | nsAccountLock ")(targetfilter="(objectClass=netscapeServer)")(version 3.0;
|
|
+ acl "Enable anyone service account read"; allow (read, search, compare)(userd
|
|
+ n="ldap:///anyone");)
|
|
+creatorsName: cn=Directory Manager
|
|
+modifiersName: cn=Directory Manager
|
|
+createTimestamp: 20200325015544Z
|
|
+modifyTimestamp: 20200325015544Z
|
|
+nsUniqueId: a2b3322e-6e3b11ea-8de0c78c-83e27eda
|
|
+
|
|
+# entry-id: 7
|
|
+dn: uid=demo_user,ou=people,dc=example,dc=com
|
|
+objectClass: top
|
|
+objectClass: nsPerson
|
|
+objectClass: nsAccount
|
|
+objectClass: nsOrgPerson
|
|
+objectClass: posixAccount
|
|
+uid: demo_user
|
|
+cn: Demo User
|
|
+displayName: Demo User
|
|
+legalName: Demo User Name
|
|
+uidNumber: 99998
|
|
+gidNumber: 99998
|
|
+homeDirectory: /var/empty
|
|
+loginShell: /bin/false
|
|
+nsAccountLock: true
|
|
+creatorsName: cn=Directory Manager
|
|
+modifiersName: cn=Directory Manager
|
|
+createTimestamp: 20200325015544Z
|
|
+modifyTimestamp: 20200325061615Z
|
|
+nsUniqueId: a2b3322f-6e3b11ea-8de0c78c-83e27eda
|
|
+entryUUID: INVALID_UUID
|
|
+
|
|
+# entry-id: 8
|
|
+dn: cn=demo_group,ou=groups,dc=example,dc=com
|
|
+objectClass: top
|
|
+objectClass: groupOfNames
|
|
+objectClass: posixGroup
|
|
+objectClass: nsMemberOf
|
|
+cn: demo_group
|
|
+gidNumber: 99999
|
|
+creatorsName: cn=Directory Manager
|
|
+modifiersName: cn=Directory Manager
|
|
+createTimestamp: 20200325015544Z
|
|
+modifyTimestamp: 20200325015544Z
|
|
+nsUniqueId: a2b33230-6e3b11ea-8de0c78c-83e27eda
|
|
+entryUUID: f6df8fe9-6b30-46aa-aa13-f0bf755371e8
|
|
+
|
|
+# entry-id: 9
|
|
+dn: cn=group_admin,ou=permissions,dc=example,dc=com
|
|
+objectClass: top
|
|
+objectClass: groupOfNames
|
|
+objectClass: nsMemberOf
|
|
+cn: group_admin
|
|
+creatorsName: cn=Directory Manager
|
|
+modifiersName: cn=Directory Manager
|
|
+createTimestamp: 20200325015545Z
|
|
+modifyTimestamp: 20200325015545Z
|
|
+nsUniqueId: a2b33231-6e3b11ea-8de0c78c-83e27eda
|
|
+
|
|
+# entry-id: 10
|
|
+dn: cn=group_modify,ou=permissions,dc=example,dc=com
|
|
+objectClass: top
|
|
+objectClass: groupOfNames
|
|
+objectClass: nsMemberOf
|
|
+cn: group_modify
|
|
+creatorsName: cn=Directory Manager
|
|
+modifiersName: cn=Directory Manager
|
|
+createTimestamp: 20200325015545Z
|
|
+modifyTimestamp: 20200325015545Z
|
|
+nsUniqueId: a2b33232-6e3b11ea-8de0c78c-83e27eda
|
|
+
|
|
+# entry-id: 11
|
|
+dn: cn=user_admin,ou=permissions,dc=example,dc=com
|
|
+objectClass: top
|
|
+objectClass: groupOfNames
|
|
+objectClass: nsMemberOf
|
|
+cn: user_admin
|
|
+creatorsName: cn=Directory Manager
|
|
+modifiersName: cn=Directory Manager
|
|
+createTimestamp: 20200325015545Z
|
|
+modifyTimestamp: 20200325015545Z
|
|
+nsUniqueId: a2b33233-6e3b11ea-8de0c78c-83e27eda
|
|
+
|
|
+# entry-id: 12
|
|
+dn: cn=user_modify,ou=permissions,dc=example,dc=com
|
|
+objectClass: top
|
|
+objectClass: groupOfNames
|
|
+objectClass: nsMemberOf
|
|
+cn: user_modify
|
|
+creatorsName: cn=Directory Manager
|
|
+modifiersName: cn=Directory Manager
|
|
+createTimestamp: 20200325015546Z
|
|
+modifyTimestamp: 20200325015546Z
|
|
+nsUniqueId: a2b33234-6e3b11ea-8de0c78c-83e27eda
|
|
+
|
|
+# entry-id: 13
|
|
+dn: cn=user_passwd_reset,ou=permissions,dc=example,dc=com
|
|
+objectClass: top
|
|
+objectClass: groupOfNames
|
|
+objectClass: nsMemberOf
|
|
+cn: user_passwd_reset
|
|
+creatorsName: cn=Directory Manager
|
|
+modifiersName: cn=Directory Manager
|
|
+createTimestamp: 20200325015546Z
|
|
+modifyTimestamp: 20200325015546Z
|
|
+nsUniqueId: a2b33235-6e3b11ea-8de0c78c-83e27eda
|
|
+
|
|
+# entry-id: 14
|
|
+dn: cn=user_private_read,ou=permissions,dc=example,dc=com
|
|
+objectClass: top
|
|
+objectClass: groupOfNames
|
|
+objectClass: nsMemberOf
|
|
+cn: user_private_read
|
|
+creatorsName: cn=Directory Manager
|
|
+modifiersName: cn=Directory Manager
|
|
+createTimestamp: 20200325015547Z
|
|
+modifyTimestamp: 20200325015547Z
|
|
+nsUniqueId: a2b33236-6e3b11ea-8de0c78c-83e27eda
|
|
+
|
|
diff --git a/dirsrvtests/tests/suites/entryuuid/basic_test.py b/dirsrvtests/tests/suites/entryuuid/basic_test.py
|
|
index 4d8a40909..41ffbfe10 100644
|
|
--- a/dirsrvtests/tests/suites/entryuuid/basic_test.py
|
|
+++ b/dirsrvtests/tests/suites/entryuuid/basic_test.py
|
|
@@ -10,6 +10,7 @@ import ldap
|
|
import pytest
|
|
import time
|
|
import shutil
|
|
+import uuid
|
|
from lib389.idm.user import nsUserAccounts, UserAccounts
|
|
from lib389.idm.account import Accounts
|
|
from lib389.idm.domain import Domain
|
|
@@ -217,7 +218,7 @@ def test_entryuuid_fixup_task(topology):
|
|
|
|
# 4. run the fix up
|
|
# For now set the log level to high!
|
|
- topology.standalone.config.loglevel(vals=(ErrorLog.DEFAULT,ErrorLog.TRACE))
|
|
+ topology.standalone.config.loglevel(vals=(ErrorLog.DEFAULT,ErrorLog.PLUGIN))
|
|
task = plug.fixup(DEFAULT_SUFFIX)
|
|
task.wait()
|
|
assert(task.is_complete() and task.get_exit_code() == 0)
|
|
@@ -242,3 +243,58 @@ def test_entryuuid_fixup_task(topology):
|
|
euuid_domain_2 = domain.get_attr_val_utf8('entryUUID')
|
|
assert(euuid_domain_2 == euuid_domain)
|
|
|
|
+@pytest.mark.skipif(not default_paths.rust_enabled or ds_is_older('1.4.2.0'), reason="Entryuuid is not available in older versions")
|
|
+def test_entryuuid_import_and_fixup_of_invalid_values(topology):
|
|
+ """ Test that when we import a database with an invalid entryuuid
|
|
+ that it is accepted *and* that subsequently we can fix the invalid
|
|
+ entryuuid during a fixup.
|
|
+
|
|
+ :id: ec8ef3a7-3cd2-4cbd-b6f1-2449fa17be75
|
|
+
|
|
+ :setup: Standalone instance
|
|
+
|
|
+ :steps:
|
|
+ 1. Import the db from the ldif
|
|
+ 2. Check the entryuuid is invalid
|
|
+ 3. Run the fixup
|
|
+ 4. Check the entryuuid is now valid (regenerated)
|
|
+
|
|
+ :expectedresults:
|
|
+ 1. Success
|
|
+ 2. The entryuuid is invalid
|
|
+ 3. Success
|
|
+ 4. The entryuuid is valid
|
|
+ """
|
|
+
|
|
+ # 1. Import the db
|
|
+ ldif_dir = topology.standalone.get_ldif_dir()
|
|
+ target_ldif = os.path.join(ldif_dir, 'localhost-userRoot-invalid.ldif')
|
|
+ import_ldif = os.path.join(DATADIR1, 'localhost-userRoot-invalid.ldif')
|
|
+ shutil.copyfile(import_ldif, target_ldif)
|
|
+ os.chmod(target_ldif, 0o777)
|
|
+
|
|
+ be = Backends(topology.standalone).get('userRoot')
|
|
+ task = be.import_ldif([target_ldif])
|
|
+ task.wait()
|
|
+ assert(task.is_complete() and task.get_exit_code() == 0)
|
|
+
|
|
+ # 2. Check the entryuuid is invalid
|
|
+ account = nsUserAccounts(topology.standalone, DEFAULT_SUFFIX).get("demo_user")
|
|
+ euuid = account.get_attr_val_utf8('entryUUID')
|
|
+ assert(euuid == "INVALID_UUID")
|
|
+
|
|
+ # 3. Run the fixup
|
|
+ topology.standalone.config.loglevel(vals=(ErrorLog.DEFAULT,ErrorLog.PLUGIN))
|
|
+ plug = EntryUUIDPlugin(topology.standalone)
|
|
+ task = plug.fixup(DEFAULT_SUFFIX)
|
|
+ task.wait()
|
|
+ assert(task.is_complete() and task.get_exit_code() == 0)
|
|
+ topology.standalone.config.loglevel(vals=(ErrorLog.DEFAULT,))
|
|
+
|
|
+ # 4. Check the entryuuid is valid
|
|
+ euuid = account.get_attr_val_utf8('entryUUID')
|
|
+ print(f"❄️ account entryUUID -> {euuid}");
|
|
+ assert(euuid != "INVALID_UUID")
|
|
+ # Raises an error if invalid
|
|
+ uuid.UUID(euuid)
|
|
+
|
|
diff --git a/src/plugins/entryuuid/src/lib.rs b/src/plugins/entryuuid/src/lib.rs
|
|
index 29a9f1258..ad3faef4b 100644
|
|
--- a/src/plugins/entryuuid/src/lib.rs
|
|
+++ b/src/plugins/entryuuid/src/lib.rs
|
|
@@ -144,11 +144,17 @@ impl SlapiPlugin3 for EntryUuid {
|
|
// Error if the first filter is empty?
|
|
|
|
// Now, to make things faster, we wrap the filter in a exclude term.
|
|
+
|
|
+ // 2021 - #4877 because we allow entryuuid to be strings, on import these may
|
|
+ // be invalid. As a result, we DO need to allow the fixup to check the entryuuid
|
|
+ // value is correct, so we can not exclude these during the search.
|
|
+ /*
|
|
let raw_filter = if !raw_filter.starts_with('(') && !raw_filter.ends_with('(') {
|
|
format!("(&({})(!(entryuuid=*)))", raw_filter)
|
|
} else {
|
|
format!("(&{}(!(entryuuid=*)))", raw_filter)
|
|
};
|
|
+ */
|
|
|
|
Ok(FixupData { basedn, raw_filter })
|
|
}
|
|
@@ -213,14 +219,20 @@ pub fn entryuuid_fixup_mapfn(e: &EntryRef, _data: &()) -> Result<(), PluginError
|
|
/* Supply a modification to the entry. */
|
|
let sdn = e.get_sdnref();
|
|
|
|
- /* Sanity check that entryuuid doesn't already exist */
|
|
- if e.contains_attr("entryUUID") {
|
|
- log_error!(
|
|
- ErrorLevel::Plugin,
|
|
- "skipping fixup for -> {}",
|
|
- sdn.to_dn_string()
|
|
- );
|
|
- return Ok(());
|
|
+ /* Check that entryuuid doesn't already exist, and is valid */
|
|
+ if let Some(valueset) = e.get_attr("entryUUID") {
|
|
+ if valueset.iter().all(|v| {
|
|
+ let u: Result<Uuid, _> = (&v).try_into();
|
|
+ u.is_ok()
|
|
+ }) {
|
|
+ // All values were valid uuid, move on!
|
|
+ log_error!(
|
|
+ ErrorLevel::Plugin,
|
|
+ "skipping fixup for -> {}",
|
|
+ sdn.to_dn_string()
|
|
+ );
|
|
+ return Ok(());
|
|
+ }
|
|
}
|
|
|
|
// Setup the modifications
|
|
diff --git a/src/slapi_r_plugin/src/pblock.rs b/src/slapi_r_plugin/src/pblock.rs
|
|
index 718ff2ca7..ac1b3c33b 100644
|
|
--- a/src/slapi_r_plugin/src/pblock.rs
|
|
+++ b/src/slapi_r_plugin/src/pblock.rs
|
|
@@ -281,7 +281,9 @@ impl PblockRef {
|
|
}
|
|
|
|
pub fn get_is_replicated_operation(&mut self) -> bool {
|
|
- let i = self.get_value_i32(PblockType::IsReplicationOperation).unwrap_or(0);
|
|
+ let i = self
|
|
+ .get_value_i32(PblockType::IsReplicationOperation)
|
|
+ .unwrap_or(0);
|
|
// Because rust returns the result of the last evaluation, we can
|
|
// just return if not equal 0.
|
|
i != 0
|
|
diff --git a/src/slapi_r_plugin/src/value.rs b/src/slapi_r_plugin/src/value.rs
|
|
index 46246837a..cd565295c 100644
|
|
--- a/src/slapi_r_plugin/src/value.rs
|
|
+++ b/src/slapi_r_plugin/src/value.rs
|
|
@@ -1,6 +1,6 @@
|
|
use crate::ber::{ol_berval, BerValRef};
|
|
use crate::dn::Sdn;
|
|
-use std::convert::{From, TryFrom};
|
|
+use std::convert::{From, TryFrom, TryInto};
|
|
use std::ffi::CString;
|
|
use std::iter::once;
|
|
use std::iter::FromIterator;
|
|
@@ -213,6 +213,14 @@ impl TryFrom<&ValueRef> for String {
|
|
}
|
|
}
|
|
|
|
+impl TryFrom<&ValueRef> for Uuid {
|
|
+ type Error = ();
|
|
+
|
|
+ fn try_from(value: &ValueRef) -> Result<Self, Self::Error> {
|
|
+ (&value.bvr).try_into().map_err(|_| ())
|
|
+ }
|
|
+}
|
|
+
|
|
impl TryFrom<&ValueRef> for Sdn {
|
|
type Error = ();
|
|
|
|
--
|
|
2.31.1
|
|
|