ipa-4.13.1-3.1
- Resolves: RHEL-166865 Include latest fixes in python3-ipatests package [rhel-9.8.z] - Resolves: RHEL-155037 Pagure #9953: Adding a group with 32Bit Idrange fails. [rhel-9.8.z] - Resolves: RHEL-153146 IdM password policy Min lifetime is not enforced when high minlife is set [rhel-9.8.z] - Resolves: RHEL-168047 ipa ca-show ipa --all failing to list RSN version Signed-off-by: David Hanina <dhanina@redhat.com>
This commit is contained in:
parent
33a8859bee
commit
287858bc9e
1627
0021-ipatests-Add-DNS-bugzilla-integration-tests.patch
Normal file
1627
0021-ipatests-Add-DNS-bugzilla-integration-tests.patch
Normal file
File diff suppressed because it is too large
Load Diff
154
0022-Avoid-int-overflow-with-pwpolicy-minlife.patch
Normal file
154
0022-Avoid-int-overflow-with-pwpolicy-minlife.patch
Normal file
@ -0,0 +1,154 @@
|
||||
From 7b0ac4f3dd8febf0c22a11bf2db9b780a242e302 Mon Sep 17 00:00:00 2001
|
||||
From: Rob Crittenden <rcritten@redhat.com>
|
||||
Date: Mon, 26 Jan 2026 19:25:17 -0500
|
||||
Subject: [PATCH] Avoid int overflow with pwpolicy minlife
|
||||
|
||||
This converts the value to an unsigned integer instead and
|
||||
sets the max value to match the maxlife (~54 years).
|
||||
|
||||
A syslog log message was added to explain why the "too young"
|
||||
message is returned
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9929
|
||||
|
||||
Signed-off-by: Rob Crittenden <rcritten@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
|
||||
---
|
||||
.../ipa-slapi-plugins/ipa-pwd-extop/common.c | 2 +-
|
||||
ipaserver/plugins/pwpolicy.py | 1 +
|
||||
ipatests/test_integration/test_pwpolicy.py | 50 ++++++++++++++++---
|
||||
util/ipa_pwd.c | 1 +
|
||||
util/ipa_pwd.h | 2 +-
|
||||
5 files changed, 48 insertions(+), 8 deletions(-)
|
||||
|
||||
diff --git a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
|
||||
index 0e69f3410..71642540a 100644
|
||||
--- a/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
|
||||
+++ b/daemons/ipa-slapi-plugins/ipa-pwd-extop/common.c
|
||||
@@ -407,7 +407,7 @@ int ipapwd_getPolicy(const char *dn,
|
||||
}
|
||||
|
||||
/* read data out of policy object */
|
||||
- policy->min_pwd_life = slapi_entry_attr_get_int(pe, "krbMinPwdLife");
|
||||
+ policy->min_pwd_life = slapi_entry_attr_get_uint(pe, "krbMinPwdLife");
|
||||
|
||||
policy->max_pwd_life = slapi_entry_attr_get_int(pe, "krbMaxPwdLife");
|
||||
|
||||
diff --git a/ipaserver/plugins/pwpolicy.py b/ipaserver/plugins/pwpolicy.py
|
||||
index e49a2e1cd..2d9907d9d 100644
|
||||
--- a/ipaserver/plugins/pwpolicy.py
|
||||
+++ b/ipaserver/plugins/pwpolicy.py
|
||||
@@ -338,6 +338,7 @@ class pwpolicy(LDAPObject):
|
||||
label=_('Min lifetime (hours)'),
|
||||
doc=_('Minimum password lifetime (in hours)'),
|
||||
minvalue=0,
|
||||
+ maxvalue=480000, # about 54 years, same as maxlife
|
||||
),
|
||||
Int('krbpwdhistorylength?',
|
||||
cli_name='history',
|
||||
diff --git a/ipatests/test_integration/test_pwpolicy.py b/ipatests/test_integration/test_pwpolicy.py
|
||||
index a627b66ce..f7fd40ada 100644
|
||||
--- a/ipatests/test_integration/test_pwpolicy.py
|
||||
+++ b/ipatests/test_integration/test_pwpolicy.py
|
||||
@@ -16,17 +16,14 @@ PASSWORD = 'Secret123'
|
||||
POLICY = 'test'
|
||||
|
||||
|
||||
-class TestPWPolicy(IntegrationTest):
|
||||
+class BasePWpolicy(IntegrationTest):
|
||||
"""
|
||||
- Test password policy in action.
|
||||
+ Base class for testing password policies including libpwquality
|
||||
"""
|
||||
- num_replicas = 1
|
||||
-
|
||||
- topology = 'line'
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
- super(TestPWPolicy, cls).install(mh)
|
||||
+ tasks.install_master(cls.master, setup_dns=True)
|
||||
|
||||
tasks.kinit_admin(cls.master)
|
||||
cls.master.run_command(['ipa', 'user-add', USER,
|
||||
@@ -66,6 +63,15 @@ class TestPWPolicy(IntegrationTest):
|
||||
host.run_command(['ipa', 'user-unlock', user])
|
||||
tasks.kdestroy_all(host)
|
||||
|
||||
+
|
||||
+class TestPWquality(BasePWpolicy):
|
||||
+ """
|
||||
+ libpwquality tests
|
||||
+ """
|
||||
+ num_replicas = 1
|
||||
+
|
||||
+ topology = 'line'
|
||||
+
|
||||
def set_pwpolicy(self, minlength=None, maxrepeat=None, maxsequence=None,
|
||||
dictcheck=None, usercheck=None, minclasses=None,
|
||||
dcredit=None, ucredit=None, lcredit=None, ocredit=None):
|
||||
@@ -534,3 +540,35 @@ class TestPWPolicy(IntegrationTest):
|
||||
self.master, dn, ['passwordgraceusertime',],
|
||||
)
|
||||
assert 'passwordgraceusertime: 0' in result.stdout_text.lower()
|
||||
+
|
||||
+
|
||||
+class TestPWpolicy(BasePWpolicy):
|
||||
+ """
|
||||
+ Tests for original/Kerberos password policies. Excludes libpwquality
|
||||
+ """
|
||||
+
|
||||
+ # NOTE: set/reset/clear methods to be added later once there is more
|
||||
+ # than a single test.
|
||||
+
|
||||
+ def test_minlife_overflow(self):
|
||||
+ """Test that a large minlife doesn't overflow an unsigned int."""
|
||||
+ newpassword = "Secret.1234"
|
||||
+
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "pwpolicy-mod", POLICY, "--minlife", "480000",
|
||||
+ "--maxlife", "20000",]
|
||||
+ )
|
||||
+
|
||||
+ self.kinit_as_user(self.master, PASSWORD, PASSWORD)
|
||||
+
|
||||
+ result = self.master.run_command(
|
||||
+ ["ipa", "passwd", USER],
|
||||
+ raiseonerr=False,
|
||||
+ stdin_text='{password}\n{password}\n{newpassword}'.format(
|
||||
+ password=PASSWORD, newpassword=newpassword
|
||||
+ ))
|
||||
+
|
||||
+ assert result.returncode == 1
|
||||
+ assert "Too soon to change password" in result.stderr_text
|
||||
diff --git a/util/ipa_pwd.c b/util/ipa_pwd.c
|
||||
index ba6860106..56135aaff 100644
|
||||
--- a/util/ipa_pwd.c
|
||||
+++ b/util/ipa_pwd.c
|
||||
@@ -561,6 +561,7 @@ int ipapwd_check_policy(struct ipapwd_policy *policy,
|
||||
* policy is set */
|
||||
|
||||
if (cur_time < last_pwd_change + policy->min_pwd_life) {
|
||||
+ syslog(LOG_ERR, "Password too young. %d seconds since last change, policy requires %u seconds.\n", (cur_time - last_pwd_change), policy->min_pwd_life);
|
||||
return IPAPWD_POLICY_PWD_TOO_YOUNG;
|
||||
}
|
||||
}
|
||||
diff --git a/util/ipa_pwd.h b/util/ipa_pwd.h
|
||||
index aa2c6e978..d3c5f8be7 100644
|
||||
--- a/util/ipa_pwd.h
|
||||
+++ b/util/ipa_pwd.h
|
||||
@@ -54,7 +54,7 @@ enum ipapwd_error {
|
||||
};
|
||||
|
||||
struct ipapwd_policy {
|
||||
- int min_pwd_life;
|
||||
+ unsigned int min_pwd_life;
|
||||
int max_pwd_life;
|
||||
int min_pwd_length;
|
||||
int history_length;
|
||||
--
|
||||
2.52.0
|
||||
|
||||
42
0023-ipatests-fix-install-method-for-BasePWpolicy.patch
Normal file
42
0023-ipatests-fix-install-method-for-BasePWpolicy.patch
Normal file
@ -0,0 +1,42 @@
|
||||
From 5cd2639f539ce220c291b00afafa72fd35e1d07e Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Mon, 16 Feb 2026 15:47:32 +0100
|
||||
Subject: [PATCH] ipatests: fix install method for BasePWpolicy
|
||||
|
||||
The test was broken by the previous commit, which creates a
|
||||
new test class TestPWquality that inherits from BasePWpolicy.
|
||||
|
||||
As BasePWpolicy overrides the install method with
|
||||
task.install_master instead of super(TestPWPolicy, cls).install(mh),
|
||||
only the master gets installed.
|
||||
|
||||
Fix the BasePWpolicy class.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9946
|
||||
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_pwpolicy.py | 5 ++++-
|
||||
1 file changed, 4 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_pwpolicy.py b/ipatests/test_integration/test_pwpolicy.py
|
||||
index f7fd40ada..a68252bf9 100644
|
||||
--- a/ipatests/test_integration/test_pwpolicy.py
|
||||
+++ b/ipatests/test_integration/test_pwpolicy.py
|
||||
@@ -21,9 +21,12 @@ class BasePWpolicy(IntegrationTest):
|
||||
Base class for testing password policies including libpwquality
|
||||
"""
|
||||
|
||||
+ num_replicas = 0
|
||||
+ topology = 'line'
|
||||
+
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
- tasks.install_master(cls.master, setup_dns=True)
|
||||
+ super(BasePWpolicy, cls).install(mh)
|
||||
|
||||
tasks.kinit_admin(cls.master)
|
||||
cls.master.run_command(['ipa', 'user-add', USER,
|
||||
--
|
||||
2.52.0
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
From 8a7486bc980b7f9b19e6b15dee6642679198c40f Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Mon, 16 Feb 2026 16:18:06 +0100
|
||||
Subject: [PATCH] webui tests: update expected max value for
|
||||
krbminpwdlife
|
||||
|
||||
The Maximum allowed value for krbminpwdlife has been changed
|
||||
by a recent commit to 480000.
|
||||
Update the test to be consistent.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9947
|
||||
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Carla Martinez <carlmart@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
ipatests/test_webui/test_pwpolicy.py | 4 ++++
|
||||
1 file changed, 4 insertions(+)
|
||||
|
||||
diff --git a/ipatests/test_webui/test_pwpolicy.py b/ipatests/test_webui/test_pwpolicy.py
|
||||
index 74484ab1b..d84bcea41 100644
|
||||
--- a/ipatests/test_webui/test_pwpolicy.py
|
||||
+++ b/ipatests/test_webui/test_pwpolicy.py
|
||||
@@ -151,6 +151,10 @@ class test_pwpolicy(UI_driver):
|
||||
elif field == 'krbpwdmindiffchars':
|
||||
self.check_expected_error(field, 'Maximum value is 5',
|
||||
maximum_value)
|
||||
+ # verifying if field value is more than 480000
|
||||
+ elif field == 'krbminpwdlife':
|
||||
+ self.check_expected_error(field, 'Maximum value is 480000',
|
||||
+ maximum_value)
|
||||
# verifying if field value is more than 2147483647
|
||||
else:
|
||||
self.check_expected_error(field, 'Maximum value is 2147483647',
|
||||
--
|
||||
2.52.0
|
||||
|
||||
1123
0025-ipatests-Add-DNS-integration-tests.patch
Normal file
1123
0025-ipatests-Add-DNS-integration-tests.patch
Normal file
File diff suppressed because it is too large
Load Diff
42
0026-ipatest-make-tests-compatible-with-Pytest-9.patch
Normal file
42
0026-ipatest-make-tests-compatible-with-Pytest-9.patch
Normal file
@ -0,0 +1,42 @@
|
||||
From 0bf2a549a8a858c393ef59487bc1d395e5535c07 Mon Sep 17 00:00:00 2001
|
||||
From: Stanislav Levin <slev@altlinux.org>
|
||||
Date: Tue, 24 Feb 2026 19:44:51 +0300
|
||||
Subject: [PATCH] ipatest: make tests compatible with Pytest 9
|
||||
|
||||
https://docs.pytest.org/en/stable/deprecations.html#applying-a-mark-to-a-fixture-function
|
||||
|
||||
> Applying a mark to a fixture function never had any effect, but it is a common user error.
|
||||
|
||||
Move marks from the fixture to test class.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9950
|
||||
Signed-off-by: Stanislav Levin <slev@altlinux.org>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
---
|
||||
ipatests/test_ipapython/test_ldap_cache.py | 3 +--
|
||||
1 file changed, 1 insertion(+), 2 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_ipapython/test_ldap_cache.py b/ipatests/test_ipapython/test_ldap_cache.py
|
||||
index c960db027..49724ad2f 100644
|
||||
--- a/ipatests/test_ipapython/test_ldap_cache.py
|
||||
+++ b/ipatests/test_ipapython/test_ldap_cache.py
|
||||
@@ -20,8 +20,6 @@ def hits_and_misses(cache, hits, misses):
|
||||
|
||||
|
||||
@pytest.fixture(scope='class')
|
||||
-@pytest.mark.tier1
|
||||
-@pytest.mark.needs_ipaapi
|
||||
def class_cache(request):
|
||||
cache = ipaldap.LDAPCache(api.env.ldap_uri)
|
||||
hits_and_misses(cache, 0, 0)
|
||||
@@ -56,6 +54,7 @@ def class_cache(request):
|
||||
@pytest.mark.usefixtures('class_cache')
|
||||
@pytest.mark.skip_ipaclient_unittest
|
||||
@pytest.mark.needs_ipaapi
|
||||
+@pytest.mark.tier1
|
||||
class TestLDAPCache:
|
||||
|
||||
def test_one(self):
|
||||
--
|
||||
2.52.0
|
||||
|
||||
154
0027-ipatests-Add-ipa-selfservice-BZ-tests-to-xmlrpc.patch
Normal file
154
0027-ipatests-Add-ipa-selfservice-BZ-tests-to-xmlrpc.patch
Normal file
@ -0,0 +1,154 @@
|
||||
From f60df430602db1e3949fa67f3744d40bd9b1d971 Mon Sep 17 00:00:00 2001
|
||||
From: Jay Gondaliya <jgondali@redhat.com>
|
||||
Date: Fri, 27 Feb 2026 15:38:54 +0530
|
||||
Subject: [PATCH] ipatests: Add ipa-selfservice BZ tests to xmlrpc
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
-Convert Bugzilla regression tests (BZ 772106, 772675, 747730, 747741, 747720, 747722) from bash to Python and add them to the existing selfservice test file as the TestSelfserviceMisc Declarative class.
|
||||
-Tests verify that --raw output, empty permissions/attrs, and invalid attrs do not cause internal errors or accidental ACI deletion.
|
||||
-Use a single selfservice rule (selfservice1) across all BZ tests instead of creating and deleting a separate rule per test case, reducing churn and keeping the tests fast.
|
||||
-Drop BZ 747693 (selfservice-find --raw) as it is already covered by the existing "Search for 'testself' with --raw" test in the main test_selfservice CRUD class (test 0011).
|
||||
|
||||
Signed-off-by: Jay Gondaliya jgondali@redhat.com
|
||||
Fixes: https://pagure.io/freeipa/issue/9945
|
||||
Assisted-by: Claude noreply@anthropic.com
|
||||
|
||||
Continuation of PR #8190
|
||||
|
||||
Fixes made:
|
||||
-Fixed lambda expected checkers — replaced defensive .get("result", {}) chains with direct output["result"] key access.
|
||||
-Removed redundant delete test case — dropped explicit selfservice_del test, relying solely on cleanup_commands.
|
||||
-Renamed class TestSelfserviceMisc → test_selfservice_misc.
|
||||
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
.../test_xmlrpc/test_selfservice_plugin.py | 106 +++++++++++++++++-
|
||||
1 file changed, 105 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ipatests/test_xmlrpc/test_selfservice_plugin.py b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
index 02aea35e1..4c5059519 100644
|
||||
--- a/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
+++ b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
@@ -28,7 +28,6 @@ import pytest
|
||||
selfservice1 = u'testself'
|
||||
invalid_selfservice1 = u'bad+name'
|
||||
|
||||
-
|
||||
@pytest.mark.tier1
|
||||
class test_selfservice(Declarative):
|
||||
|
||||
@@ -290,3 +289,108 @@ class test_selfservice(Declarative):
|
||||
),
|
||||
|
||||
]
|
||||
+
|
||||
+
|
||||
+@pytest.mark.tier1
|
||||
+class test_selfservice_misc(Declarative):
|
||||
+ """Bugzilla regression tests for selfservice plugin."""
|
||||
+
|
||||
+ cleanup_commands = [
|
||||
+ ("selfservice_del", [selfservice1], {}),
|
||||
+ ]
|
||||
+
|
||||
+ tests = [
|
||||
+ # BZ 772106: selfservice-add with --raw must not return internal error
|
||||
+ dict(
|
||||
+ desc="Create %r with --raw for BZ 772106" % selfservice1,
|
||||
+ command=(
|
||||
+ "selfservice_add",
|
||||
+ [selfservice1],
|
||||
+ dict(attrs=["l"], raw=True),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=selfservice1,
|
||||
+ summary='Added selfservice "%s"' % selfservice1,
|
||||
+ result={
|
||||
+ "aci": '(targetattr = "l")(version 3.0;acl '
|
||||
+ '"selfservice:%s";allow (write) '
|
||||
+ 'userdn = "ldap:///self";)' % selfservice1,
|
||||
+ },
|
||||
+ ),
|
||||
+ ),
|
||||
+ # BZ 772675: selfservice-mod with --raw must not return internal error
|
||||
+ dict(
|
||||
+ desc="Modify %r with --raw for BZ 772675" % selfservice1,
|
||||
+ command=(
|
||||
+ "selfservice_mod",
|
||||
+ [selfservice1],
|
||||
+ dict(attrs=["mobile"], raw=True),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=selfservice1,
|
||||
+ summary='Modified selfservice "%s"' % selfservice1,
|
||||
+ result={
|
||||
+ "aci": '(targetattr = "mobile")(version 3.0;acl '
|
||||
+ '"selfservice:%s";allow (write) '
|
||||
+ 'userdn = "ldap:///self";)' % selfservice1,
|
||||
+ },
|
||||
+ ),
|
||||
+ ),
|
||||
+ # BZ 747730: selfservice-mod --permissions="" must not delete the entry
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ "Modify %r with empty permissions for BZ 747730"
|
||||
+ % selfservice1
|
||||
+ ),
|
||||
+ command=(
|
||||
+ "selfservice_mod",
|
||||
+ [selfservice1],
|
||||
+ dict(permissions=""),
|
||||
+ ),
|
||||
+ expected=lambda got, output: True,
|
||||
+ ),
|
||||
+ dict(
|
||||
+ desc="Verify %r still exists after BZ 747730" % selfservice1,
|
||||
+ command=("selfservice_show", [selfservice1], {}),
|
||||
+ expected=lambda got, output: (
|
||||
+ got is None
|
||||
+ and output["result"]["aciname"] == selfservice1
|
||||
+ ),
|
||||
+ ),
|
||||
+ # BZ 747741: selfservice-mod --attrs=badattrs must not delete the entry
|
||||
+ dict(
|
||||
+ desc="Modify %r with bad attrs for BZ 747741" % selfservice1,
|
||||
+ command=(
|
||||
+ "selfservice_mod",
|
||||
+ [selfservice1],
|
||||
+ dict(attrs=["badattrs"]),
|
||||
+ ),
|
||||
+ expected=lambda got, output: True,
|
||||
+ ),
|
||||
+ dict(
|
||||
+ desc="Verify %r still exists after BZ 747741" % selfservice1,
|
||||
+ command=("selfservice_show", [selfservice1], {}),
|
||||
+ expected=lambda got, output: (
|
||||
+ got is None
|
||||
+ and output["result"]["aciname"] == selfservice1
|
||||
+ ),
|
||||
+ ),
|
||||
+ # BZ 747720: selfservice-find --permissions="" must not return
|
||||
+ # internal error
|
||||
+ dict(
|
||||
+ desc="BZ 747720: selfservice-find with empty permissions",
|
||||
+ command=("selfservice_find", [], dict(permissions="")),
|
||||
+ expected=lambda got, output: (
|
||||
+ got is None and isinstance(output["result"], (list, tuple))
|
||||
+ ),
|
||||
+ ),
|
||||
+ # BZ 747722: selfservice-find --attrs="" must not return
|
||||
+ # internal error
|
||||
+ dict(
|
||||
+ desc="BZ 747722: selfservice-find with empty attrs",
|
||||
+ command=("selfservice_find", [], dict(attrs="")),
|
||||
+ expected=lambda got, output: (
|
||||
+ got is None and isinstance(output["result"], (list, tuple))
|
||||
+ ),
|
||||
+ ),
|
||||
+ ]
|
||||
--
|
||||
2.52.0
|
||||
|
||||
37
0028-Allow-32bit-gid.patch
Normal file
37
0028-Allow-32bit-gid.patch
Normal file
@ -0,0 +1,37 @@
|
||||
From 58239f9fbe33408b5cb5c52ba5132f6deb6b8f40 Mon Sep 17 00:00:00 2001
|
||||
From: David Hanina <dhanina@redhat.com>
|
||||
Date: Tue, 10 Mar 2026 10:26:33 +0100
|
||||
Subject: [PATCH] Allow 32bit gid
|
||||
|
||||
We should allow 32bit groups, by setting maxvalue we allow that.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9953
|
||||
Signed-off-by: David Hanina <dhanina@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
---
|
||||
ipaserver/plugins/group.py | 2 ++
|
||||
1 file changed, 2 insertions(+)
|
||||
|
||||
diff --git a/ipaserver/plugins/group.py b/ipaserver/plugins/group.py
|
||||
index f05a39f69..308e5458c 100644
|
||||
--- a/ipaserver/plugins/group.py
|
||||
+++ b/ipaserver/plugins/group.py
|
||||
@@ -26,6 +26,7 @@ import re
|
||||
from ipalib import api
|
||||
from ipalib import Int, Str, Flag
|
||||
from ipalib.constants import PATTERN_GROUPUSER_NAME, ERRMSG_GROUPUSER_NAME
|
||||
+from ipalib.parameters import MAX_UINT32
|
||||
from ipalib.plugable import Registry
|
||||
from .baseldap import (
|
||||
add_external_post_callback,
|
||||
@@ -354,6 +355,7 @@ class group(LDAPObject):
|
||||
label=_('GID'),
|
||||
doc=_('GID (use this option to set it manually)'),
|
||||
minvalue=1,
|
||||
+ maxvalue=MAX_UINT32,
|
||||
),
|
||||
ipaexternalmember_param,
|
||||
)
|
||||
--
|
||||
2.52.0
|
||||
|
||||
400
0029-ipatests-Add-ipa-selfservice-users-tests-to-xmlrpc.patch
Normal file
400
0029-ipatests-Add-ipa-selfservice-users-tests-to-xmlrpc.patch
Normal file
@ -0,0 +1,400 @@
|
||||
From ffae7d109b9cc968bb9942da78ea4238eee5497b Mon Sep 17 00:00:00 2001
|
||||
From: Jay Gondaliya <jgondali@redhat.com>
|
||||
Date: Thu, 5 Mar 2026 19:07:06 +0530
|
||||
Subject: [PATCH] ipatests: Add ipa-selfservice users tests to xmlrpc
|
||||
|
||||
Convert self-service users tests from bash to Python and add them to the existing selfservice test file.
|
||||
|
||||
Tests verify that users can modify their own allowed attributes under default and custom selfservice rules,
|
||||
that disallowed attributes are rejected with ACIError, and that cross-user modification is blocked.
|
||||
Also covers atomic failure on mixed permissions, self-password-change, and user-find by phone, fax, and manager
|
||||
(BZ 1188195, 781208, 985016, 967509, 985013).
|
||||
|
||||
Signed-off-by: Jay Gondaliya jgondali@redhat.com
|
||||
Fixes: https://pagure.io/freeipa/issue/9945
|
||||
Assisted-by: Claude noreply@anthropic.com
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
Reviewed-By: PRANAV THUBE <pthube@redhat.com>
|
||||
---
|
||||
.../test_xmlrpc/test_selfservice_plugin.py | 360 +++++++++++++++++-
|
||||
1 file changed, 358 insertions(+), 2 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_xmlrpc/test_selfservice_plugin.py b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
index 4c5059519..e55502a2d 100644
|
||||
--- a/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
+++ b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
@@ -21,8 +21,12 @@
|
||||
Test the `ipaserver/plugins/selfservice.py` module.
|
||||
"""
|
||||
|
||||
-from ipalib import errors
|
||||
-from ipatests.test_xmlrpc.xmlrpc_test import Declarative
|
||||
+from ipalib import api, errors
|
||||
+from ipatests.test_xmlrpc.xmlrpc_test import (
|
||||
+ Declarative, XMLRPC_test, assert_attr_equal,
|
||||
+)
|
||||
+from ipatests.test_xmlrpc.tracker.user_plugin import UserTracker
|
||||
+from ipatests.util import change_principal, unlock_principal_password
|
||||
import pytest
|
||||
|
||||
selfservice1 = u'testself'
|
||||
@@ -394,3 +398,355 @@ class test_selfservice_misc(Declarative):
|
||||
),
|
||||
),
|
||||
]
|
||||
+
|
||||
+
|
||||
+SS_USER1 = 'ssuser0001'
|
||||
+SS_USER1_PASSWORD = 'Passw0rd1'
|
||||
+SS_USER2 = 'ssuser0002'
|
||||
+SS_USER2_PASSWORD = 'Passw0rd2'
|
||||
+SS_GOOD_MANAGER = 'ss_good_manager'
|
||||
+SS_GOOD_MANAGER_PASSWORD = 'Passw0rd3'
|
||||
+
|
||||
+SS_DEFAULT_SELFSERVICE = 'User Self service'
|
||||
+SS_CUSTOM_RULE = 'ss_test_rule0001'
|
||||
+
|
||||
+SS_DEFAULT_SELFSERVICE_ATTRS = [
|
||||
+ 'givenname', 'sn', 'cn', 'displayname', 'title', 'initials',
|
||||
+ 'loginshell', 'gecos', 'homephone', 'mobile', 'pager',
|
||||
+ 'facsimiletelephonenumber', 'telephonenumber', 'street',
|
||||
+ 'roomnumber', 'l', 'st', 'postalcode', 'manager', 'secretary',
|
||||
+ 'description', 'carlicense', 'labeleduri', 'inetuserhttpurl',
|
||||
+ 'seealso', 'employeetype', 'businesscategory', 'ou',
|
||||
+]
|
||||
+
|
||||
+SS_CUSTOM_RULE_ATTRS = [
|
||||
+ 'mobile', 'pager',
|
||||
+ 'facsimiletelephonenumber', 'telephonenumber',
|
||||
+]
|
||||
+
|
||||
+
|
||||
+def _safe_del_selfservice(name):
|
||||
+ """Delete a selfservice rule, ignoring NotFound."""
|
||||
+ try:
|
||||
+ api.Command['selfservice_del'](name)
|
||||
+ except errors.NotFound:
|
||||
+ pass
|
||||
+
|
||||
+
|
||||
+@pytest.fixture
|
||||
+def custom_selfservice_rule(xmlrpc_setup):
|
||||
+ """Replace the default selfservice rule with the narrow custom rule."""
|
||||
+ api.Command['selfservice_del'](SS_DEFAULT_SELFSERVICE)
|
||||
+ api.Command['selfservice_add'](
|
||||
+ SS_CUSTOM_RULE, attrs=SS_CUSTOM_RULE_ATTRS,
|
||||
+ )
|
||||
+ yield
|
||||
+ _safe_del_selfservice(SS_CUSTOM_RULE)
|
||||
+ api.Command['selfservice_add'](
|
||||
+ SS_DEFAULT_SELFSERVICE, attrs=SS_DEFAULT_SELFSERVICE_ATTRS,
|
||||
+ )
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope='class')
|
||||
+def ss_user1(request, xmlrpc_setup):
|
||||
+ tracker = UserTracker(
|
||||
+ name=SS_USER1, givenname='Test', sn='User0001',
|
||||
+ userpassword=SS_USER1_PASSWORD,
|
||||
+ )
|
||||
+ tracker.make_fixture(request)
|
||||
+ tracker.make_create_command()()
|
||||
+ tracker.exists = True
|
||||
+ unlock_principal_password(
|
||||
+ SS_USER1, SS_USER1_PASSWORD, SS_USER1_PASSWORD,
|
||||
+ )
|
||||
+ return tracker
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope='class')
|
||||
+def ss_user2(request, xmlrpc_setup):
|
||||
+ tracker = UserTracker(
|
||||
+ name=SS_USER2, givenname='Test', sn='User0002',
|
||||
+ userpassword=SS_USER2_PASSWORD,
|
||||
+ )
|
||||
+ tracker.make_fixture(request)
|
||||
+ tracker.make_create_command()()
|
||||
+ tracker.exists = True
|
||||
+ unlock_principal_password(
|
||||
+ SS_USER2, SS_USER2_PASSWORD, SS_USER2_PASSWORD,
|
||||
+ )
|
||||
+ return tracker
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope='class')
|
||||
+def ss_good_manager(request, xmlrpc_setup):
|
||||
+ tracker = UserTracker(
|
||||
+ name=SS_GOOD_MANAGER, givenname='Good', sn='Manager',
|
||||
+ userpassword=SS_GOOD_MANAGER_PASSWORD,
|
||||
+ )
|
||||
+ tracker.make_fixture(request)
|
||||
+ tracker.make_create_command()()
|
||||
+ tracker.exists = True
|
||||
+ unlock_principal_password(
|
||||
+ SS_GOOD_MANAGER, SS_GOOD_MANAGER_PASSWORD, SS_GOOD_MANAGER_PASSWORD,
|
||||
+ )
|
||||
+ return tracker
|
||||
+
|
||||
+
|
||||
+@pytest.mark.tier1
|
||||
+@pytest.mark.usefixtures('ss_user1', 'ss_user2', 'ss_good_manager')
|
||||
+class test_selfservice_users(XMLRPC_test):
|
||||
+ """Test self-service user attribute modification permissions."""
|
||||
+
|
||||
+ # usertest_1001: Set all attrs allowed by default self-service rule.
|
||||
+ def test_set_all_default_selfservice_attrs(self):
|
||||
+ """Set all attrs allowed by the default self-service rule."""
|
||||
+ attrs = {
|
||||
+ 'givenname': 'Good',
|
||||
+ 'sn': 'User',
|
||||
+ 'cn': 'gooduser',
|
||||
+ 'displayname': 'gooduser',
|
||||
+ 'initials': 'GU',
|
||||
+ 'gecos': 'gooduser@good.example.com',
|
||||
+ 'loginshell': '/bin/bash',
|
||||
+ 'street': 'Good_Street_Rd',
|
||||
+ 'l': 'Good_City',
|
||||
+ 'st': 'Goodstate',
|
||||
+ 'postalcode': '33333',
|
||||
+ 'telephonenumber': '333-333-3333',
|
||||
+ 'mobile': '333-333-3333',
|
||||
+ 'pager': '333-333-3333',
|
||||
+ 'facsimiletelephonenumber': '333-333-3333',
|
||||
+ 'ou': 'good-org',
|
||||
+ 'title': 'good_admin',
|
||||
+ 'manager': SS_GOOD_MANAGER,
|
||||
+ 'carlicense': 'good-3333',
|
||||
+ }
|
||||
+
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ for attr, value in attrs.items():
|
||||
+ api.Command['user_mod'](SS_USER1, **{attr: value})
|
||||
+
|
||||
+ entry = api.Command['user_show'](SS_USER1, all=True)['result']
|
||||
+ for attr, value in attrs.items():
|
||||
+ assert_attr_equal(entry, attr, value)
|
||||
+
|
||||
+ # usertest_1002: Test that default disallowed attributes are rejected.
|
||||
+ def test_reject_uidnumber_by_default(self):
|
||||
+ """uidnumber change is rejected by default."""
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ with pytest.raises(errors.ACIError):
|
||||
+ api.Command['user_mod'](SS_USER1, uidnumber=9999)
|
||||
+
|
||||
+ def test_reject_gidnumber_by_default(self):
|
||||
+ """gidnumber change is rejected by default."""
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ with pytest.raises(errors.ACIError):
|
||||
+ api.Command['user_mod'](SS_USER1, gidnumber=9999)
|
||||
+
|
||||
+ def test_reject_homedirectory_by_default(self):
|
||||
+ """homedirectory change is rejected by default."""
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ with pytest.raises(errors.ACIError):
|
||||
+ api.Command['user_mod'](
|
||||
+ SS_USER1, homedirectory='/home/gooduser')
|
||||
+
|
||||
+ def test_reject_email_by_default(self):
|
||||
+ """email change is rejected by default."""
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ with pytest.raises(errors.ACIError):
|
||||
+ api.Command['user_mod'](
|
||||
+ SS_USER1, mail='gooduser@good.example.com')
|
||||
+
|
||||
+ # usertest_1003: All attrs rejected when the default rule is deleted.
|
||||
+ def test_all_attrs_rejected_without_default_rule(self):
|
||||
+ """All attrs are rejected when the default rule is deleted."""
|
||||
+ attrs = {
|
||||
+ 'givenname': 'Bad',
|
||||
+ 'sn': 'LUser',
|
||||
+ 'cn': 'badluser',
|
||||
+ 'displayname': 'badluser',
|
||||
+ 'initials': 'BL',
|
||||
+ 'gecos': 'badluser@bad.example.com',
|
||||
+ 'loginshell': '/bin/tcsh',
|
||||
+ 'street': 'Bad_Street_Av',
|
||||
+ 'l': 'Bad_City',
|
||||
+ 'st': 'Badstate',
|
||||
+ 'postalcode': '99999',
|
||||
+ 'telephonenumber': '999-999-9999',
|
||||
+ 'mobile': '999-999-9999',
|
||||
+ 'pager': '999-999-9999',
|
||||
+ 'facsimiletelephonenumber': '999-999-9999',
|
||||
+ 'ou': 'bad-org',
|
||||
+ 'title': 'bad_admin',
|
||||
+ 'manager': 'admin',
|
||||
+ 'carlicense': 'bad-9999',
|
||||
+ }
|
||||
+
|
||||
+ api.Command['selfservice_del'](SS_DEFAULT_SELFSERVICE)
|
||||
+ try:
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ for attr, value in attrs.items():
|
||||
+ with pytest.raises(errors.ACIError):
|
||||
+ api.Command['user_mod'](SS_USER1, **{attr: value})
|
||||
+ finally:
|
||||
+ api.Command['selfservice_add'](
|
||||
+ SS_DEFAULT_SELFSERVICE,
|
||||
+ attrs=SS_DEFAULT_SELFSERVICE_ATTRS,
|
||||
+ )
|
||||
+
|
||||
+ # usertest_1004: Custom rule grants write access to its specified attrs.
|
||||
+ def test_custom_rule_grants_write_access(
|
||||
+ self, custom_selfservice_rule):
|
||||
+ """Custom rule grants write access to its specified attrs."""
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ api.Command['user_mod'](
|
||||
+ SS_USER1, telephonenumber='777-777-7777')
|
||||
+ api.Command['user_mod'](SS_USER1, mobile='777-777-7777')
|
||||
+ api.Command['user_mod'](SS_USER1, pager='777-777-7777')
|
||||
+ api.Command['user_mod'](
|
||||
+ SS_USER1,
|
||||
+ facsimiletelephonenumber='777-777-7777')
|
||||
+
|
||||
+ # usertest_1005: Persisted attrs and user-find by phone, fax, manager.
|
||||
+ def test_verify_persisted_attrs(self):
|
||||
+ """Verify attrs set by previous tests are persisted."""
|
||||
+ expected = {
|
||||
+ 'givenname': 'Good',
|
||||
+ 'sn': 'User',
|
||||
+ 'cn': 'gooduser',
|
||||
+ 'displayname': 'gooduser',
|
||||
+ 'initials': 'GU',
|
||||
+ 'gecos': 'gooduser@good.example.com',
|
||||
+ 'loginshell': '/bin/bash',
|
||||
+ 'street': 'Good_Street_Rd',
|
||||
+ 'l': 'Good_City',
|
||||
+ 'st': 'Goodstate',
|
||||
+ 'postalcode': '33333',
|
||||
+ 'telephonenumber': '777-777-7777',
|
||||
+ 'mobile': '777-777-7777',
|
||||
+ 'pager': '777-777-7777',
|
||||
+ 'facsimiletelephonenumber': '777-777-7777',
|
||||
+ 'ou': 'good-org',
|
||||
+ 'title': 'good_admin',
|
||||
+ 'carlicense': 'good-3333',
|
||||
+ }
|
||||
+
|
||||
+ entry = api.Command['user_show'](SS_USER1, all=True)['result']
|
||||
+ for attr, value in expected.items():
|
||||
+ assert_attr_equal(entry, attr, value)
|
||||
+ assert_attr_equal(entry, 'manager', SS_GOOD_MANAGER)
|
||||
+
|
||||
+ def test_user_find_by_phone(self):
|
||||
+ """BZ 1188195: user-find by phone number returns results."""
|
||||
+ result = api.Command['user_find'](
|
||||
+ telephonenumber='777-777-7777')
|
||||
+ assert result['count'] >= 1
|
||||
+ uids = [e['uid'][0] for e in result['result']]
|
||||
+ assert SS_USER1 in uids
|
||||
+
|
||||
+ def test_user_find_by_fax(self):
|
||||
+ """BZ 1188195: user-find by fax number returns results."""
|
||||
+ result = api.Command['user_find'](
|
||||
+ facsimiletelephonenumber='777-777-7777')
|
||||
+ assert result['count'] >= 1
|
||||
+ uids = [e['uid'][0] for e in result['result']]
|
||||
+ assert SS_USER1 in uids
|
||||
+
|
||||
+ def test_user_find_by_manager(self):
|
||||
+ """BZ 781208: user-find by manager returns matches."""
|
||||
+ result = api.Command['user_find'](
|
||||
+ SS_USER1, manager=SS_GOOD_MANAGER)
|
||||
+ assert result['count'] >= 1, (
|
||||
+ 'BZ 781208: user-find --manager did not find matches'
|
||||
+ )
|
||||
+ uids = [e['uid'][0] for e in result['result']]
|
||||
+ assert SS_USER1 in uids
|
||||
+
|
||||
+ # usertest_1006: BZ 985016, 967509: user can modify an allowed attr.
|
||||
+ def test_user_can_modify_allowed_attr(self):
|
||||
+ """BZ 985016, 967509: user can modify an allowed attr."""
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ api.Command['user_mod'](SS_USER1, mobile='888-888-8888')
|
||||
+ entry = api.Command['user_show'](SS_USER1, all=True)['result']
|
||||
+ assert_attr_equal(entry, 'mobile', '888-888-8888')
|
||||
+
|
||||
+ # usertest_1007: BZ 985016, 967509: disallowed attribute is rejected.
|
||||
+ def test_disallowed_attr_rejected_with_custom_rule(
|
||||
+ self, custom_selfservice_rule):
|
||||
+ """BZ 985016, 967509: disallowed attribute is rejected."""
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ with pytest.raises(errors.ACIError):
|
||||
+ api.Command['user_mod'](SS_USER1, title='Dr')
|
||||
+
|
||||
+ # usertest_1008: user-mod fails atomically on mixed attr permissions.
|
||||
+ def test_user_mod_atomic_failure_mixed_perms(
|
||||
+ self, custom_selfservice_rule):
|
||||
+ """user-mod fails atomically when one attr is disallowed."""
|
||||
+ original_title = api.Command['user_show'](
|
||||
+ SS_USER1)['result'].get('title')
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ with pytest.raises(errors.ACIError):
|
||||
+ api.Command['user_mod'](
|
||||
+ SS_USER1,
|
||||
+ title='notgonnawork',
|
||||
+ telephonenumber='999-999-9990',
|
||||
+ )
|
||||
+ result = api.Command['user_find'](
|
||||
+ SS_USER1, telephonenumber='999-999-9990')
|
||||
+ assert result['count'] == 0, (
|
||||
+ 'Phone was changed despite disallowed title in same call'
|
||||
+ )
|
||||
+ after = api.Command['user_show'](SS_USER1)['result']
|
||||
+ assert after.get('title') == original_title, (
|
||||
+ 'Title was modified despite being disallowed'
|
||||
+ )
|
||||
+
|
||||
+ # usertest_1009: BZ 985013: user can change their own password.
|
||||
+ def test_self_password_change_via_passwd(self):
|
||||
+ """BZ 985013: user can change their own password via passwd."""
|
||||
+ policy = api.Command['pwpolicy_show']()['result']
|
||||
+ orig_minlife = policy.get('krbminpwdlife', ('1',))[0]
|
||||
+
|
||||
+ api.Command['pwpolicy_mod'](krbminpwdlife=0)
|
||||
+ try:
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ api.Command['passwd'](
|
||||
+ SS_USER1,
|
||||
+ password='MyN3wP@55',
|
||||
+ current_password=SS_USER1_PASSWORD,
|
||||
+ )
|
||||
+ # Reset password so the next test can authenticate
|
||||
+ unlock_principal_password(
|
||||
+ SS_USER1, 'MyN3wP@55', SS_USER1_PASSWORD,
|
||||
+ )
|
||||
+ finally:
|
||||
+ api.Command['pwpolicy_mod'](krbminpwdlife=int(orig_minlife))
|
||||
+
|
||||
+ def test_self_password_change_via_user_mod(self):
|
||||
+ """BZ 985013: user can change their own password via user_mod."""
|
||||
+ policy = api.Command['pwpolicy_show']()['result']
|
||||
+ orig_minlife = policy.get('krbminpwdlife', ('1',))[0]
|
||||
+
|
||||
+ api.Command['pwpolicy_mod'](krbminpwdlife=0)
|
||||
+ try:
|
||||
+ with change_principal(SS_USER1, SS_USER1_PASSWORD):
|
||||
+ api.Command['user_mod'](
|
||||
+ SS_USER1,
|
||||
+ userpassword='MyN3wP@55',
|
||||
+ )
|
||||
+ finally:
|
||||
+ api.Command['pwpolicy_mod'](krbminpwdlife=int(orig_minlife))
|
||||
+
|
||||
+ # usertest_1010: User cannot modify another user's attributes.
|
||||
+ def test_cross_user_modification_rejected(self):
|
||||
+ """User cannot modify another user's attributes."""
|
||||
+ with change_principal(SS_USER2, SS_USER2_PASSWORD):
|
||||
+ with pytest.raises(errors.ACIError):
|
||||
+ api.Command['user_mod'](SS_USER1, mobile='867-5309')
|
||||
+
|
||||
+ def test_verify_cross_user_modification_rejected(self):
|
||||
+ """Verify attrs did not change after cross-user modification."""
|
||||
+ result = api.Command['user_find'](SS_USER1, mobile='867-5309')
|
||||
+ assert result['count'] == 0, (
|
||||
+ 'Mobile was changed by a different user'
|
||||
+ )
|
||||
--
|
||||
2.52.0
|
||||
|
||||
138
0030-ipatests-Fix-test_allow_query_transfer_ipv6-when-IPv.patch
Normal file
138
0030-ipatests-Fix-test_allow_query_transfer_ipv6-when-IPv.patch
Normal file
@ -0,0 +1,138 @@
|
||||
From 313bd8ff118a79dca5aad0b19ec8f69519258f89 Mon Sep 17 00:00:00 2001
|
||||
From: PRANAV THUBE <pthube@redhat.com>
|
||||
Date: Wed, 11 Mar 2026 20:13:31 +0530
|
||||
Subject: [PATCH] ipatests: Fix test_allow_query_transfer_ipv6 when
|
||||
IPv6 is disabled
|
||||
|
||||
The test was failing in environments where IPv6 is disabled at the
|
||||
kernel level because it attempted to add a temporary IPv6 address
|
||||
without first checking if IPv6 is enabled on the interface.
|
||||
|
||||
This fix restructures the test to:
|
||||
- Check if IPv6 is disabled via sysctl before attempting IPv6 setup
|
||||
- Always run IPv4 allow-query and allow-transfer tests
|
||||
- Only run IPv6-related tests when IPv6 is available
|
||||
|
||||
This ensures the test passes in IPv4-only environments while still
|
||||
providing full coverage when IPv6 is enabled.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9944
|
||||
Signed-off-by: Pranav Thube pthube@redhat.com
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_dns.py | 62 ++++++---------------------
|
||||
1 file changed, 14 insertions(+), 48 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_dns.py b/ipatests/test_integration/test_dns.py
|
||||
index 4b9ab1fe8..947cff5c0 100644
|
||||
--- a/ipatests/test_integration/test_dns.py
|
||||
+++ b/ipatests/test_integration/test_dns.py
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
+import pytest
|
||||
import time
|
||||
import dns.exception
|
||||
import dns.resolver
|
||||
@@ -1714,13 +1715,12 @@ class TestDNSMisc(IntegrationTest):
|
||||
tasks.del_dns_zone(self.master, zone, raiseonerr=False)
|
||||
|
||||
def test_allow_query_transfer_ipv6(self):
|
||||
- """Test allow-query and allow-transfer with IPv4 and IPv6.
|
||||
+ """Test allow-query and allow-transfer with IPv6.
|
||||
|
||||
Bugzilla: https://bugzilla.redhat.com/show_bug.cgi?id=701677
|
||||
"""
|
||||
tasks.kinit_admin(self.master)
|
||||
- zone = "example.com"
|
||||
- ipv4 = self.master.ip
|
||||
+ zone = "example6.com"
|
||||
ipv6_added = False
|
||||
temp_ipv6 = '2001:0db8:0:f101::1/64'
|
||||
|
||||
@@ -1732,6 +1732,13 @@ class TestDNSMisc(IntegrationTest):
|
||||
])
|
||||
eth = result.stdout_text.strip()
|
||||
|
||||
+ # Check if IPv6 is disabled on the interface
|
||||
+ result = self.master.run_command([
|
||||
+ 'sysctl', '-n', f'net.ipv6.conf.{eth}.disable_ipv6'
|
||||
+ ])
|
||||
+ if result.stdout_text.strip() == '1':
|
||||
+ pytest.skip(f"IPv6 is disabled on interface {eth}")
|
||||
+
|
||||
# Add temporary IPv6 if none exists
|
||||
result = self.master.run_command(
|
||||
['ip', 'addr', 'show', 'scope', 'global'], raiseonerr=False
|
||||
@@ -1754,62 +1761,21 @@ class TestDNSMisc(IntegrationTest):
|
||||
tasks.add_dns_zone(self.master, zone, skip_overlap_check=True,
|
||||
admin_email=self.EMAIL)
|
||||
|
||||
- # Test allow-query: IPv4 allowed, IPv6 denied
|
||||
- tasks.mod_dns_zone(
|
||||
- self.master, zone,
|
||||
- f"--allow-query={ipv4};!{ipv6};"
|
||||
- )
|
||||
- result = self.master.run_command(
|
||||
- ['dig', f'@{ipv4}', '-t', 'soa', zone], raiseonerr=False
|
||||
- )
|
||||
- assert 'ANSWER SECTION' in result.stdout_text
|
||||
- result = self.master.run_command(
|
||||
- ['dig', f'@{ipv6}', '-t', 'soa', zone], raiseonerr=False
|
||||
- )
|
||||
- assert 'ANSWER SECTION' not in result.stdout_text
|
||||
-
|
||||
- # Test allow-query: IPv6 allowed, IPv4 denied
|
||||
+ # Test allow-query: IPv6 allowed
|
||||
tasks.mod_dns_zone(
|
||||
self.master, zone,
|
||||
- f"--allow-query={ipv6};!{ipv4};"
|
||||
- )
|
||||
- result = self.master.run_command(
|
||||
- ['dig', f'@{ipv4}', '-t', 'soa', zone], raiseonerr=False
|
||||
+ f"--allow-query={ipv6};"
|
||||
)
|
||||
- assert 'ANSWER SECTION' not in result.stdout_text
|
||||
result = self.master.run_command(
|
||||
['dig', f'@{ipv6}', '-t', 'soa', zone], raiseonerr=False
|
||||
)
|
||||
assert 'ANSWER SECTION' in result.stdout_text
|
||||
|
||||
- # Reset allow-query to any
|
||||
- tasks.mod_dns_zone(
|
||||
- self.master, zone, "--allow-query=any;"
|
||||
- )
|
||||
-
|
||||
- # Test allow-transfer: IPv4 allowed, IPv6 denied
|
||||
- tasks.mod_dns_zone(
|
||||
- self.master, zone,
|
||||
- f"--allow-transfer={ipv4};!{ipv6};"
|
||||
- )
|
||||
- result = self.master.run_command(
|
||||
- ['dig', f'@{ipv4}', zone, 'axfr'], raiseonerr=False
|
||||
- )
|
||||
- assert 'Transfer failed' not in result.stdout_text
|
||||
- result = self.master.run_command(
|
||||
- ['dig', f'@{ipv6}', zone, 'axfr'], raiseonerr=False
|
||||
- )
|
||||
- assert 'Transfer failed' in result.stdout_text
|
||||
-
|
||||
- # Test allow-transfer: IPv6 allowed, IPv4 denied
|
||||
+ # Test allow-transfer: IPv6 allowed
|
||||
tasks.mod_dns_zone(
|
||||
self.master, zone,
|
||||
- f"--allow-transfer={ipv6};!{ipv4};"
|
||||
- )
|
||||
- result = self.master.run_command(
|
||||
- ['dig', f'@{ipv4}', zone, 'axfr'], raiseonerr=False
|
||||
+ f"--allow-transfer={ipv6};"
|
||||
)
|
||||
- assert 'Transfer failed' in result.stdout_text
|
||||
result = self.master.run_command(
|
||||
['dig', f'@{ipv6}', zone, 'axfr'], raiseonerr=False
|
||||
)
|
||||
--
|
||||
2.52.0
|
||||
|
||||
217
0031-ipatests-Add-XML-RPC-tests-for-i18n-user-attributes.patch
Normal file
217
0031-ipatests-Add-XML-RPC-tests-for-i18n-user-attributes.patch
Normal file
@ -0,0 +1,217 @@
|
||||
From c8a832e19699a7bb6ff486055015f033a3137e5f Mon Sep 17 00:00:00 2001
|
||||
From: PRANAV THUBE <pthube@redhat.com>
|
||||
Date: Mon, 16 Mar 2026 14:29:12 +0530
|
||||
Subject: [PATCH] ipatests: Add XML-RPC tests for i18n user attributes
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Add tests for internationalization support in user plugin:
|
||||
- User creation/deletion with i18n givenname and sn
|
||||
- Lastname modification with Swedish/European names (13 values)
|
||||
- Firstname modification with European accented names (4 values)
|
||||
- Firstname modification with single i18n characters (67 values)
|
||||
|
||||
Test data includes characters like Çándide, Örjan, Éric, ß, ü, etc.
|
||||
|
||||
Related: https://pagure.io/freeipa/issue/9959
|
||||
Signed-off-by: Pranav Thube pthube@redhat.com
|
||||
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
Reviewed-By: Carla Martinez <carlmart@redhat.com>
|
||||
---
|
||||
ipatests/test_xmlrpc/test_i18n_user_plugin.py | 182 ++++++++++++++++++
|
||||
1 file changed, 182 insertions(+)
|
||||
create mode 100644 ipatests/test_xmlrpc/test_i18n_user_plugin.py
|
||||
|
||||
diff --git a/ipatests/test_xmlrpc/test_i18n_user_plugin.py b/ipatests/test_xmlrpc/test_i18n_user_plugin.py
|
||||
new file mode 100644
|
||||
index 000000000..146ffdc51
|
||||
--- /dev/null
|
||||
+++ b/ipatests/test_xmlrpc/test_i18n_user_plugin.py
|
||||
@@ -0,0 +1,182 @@
|
||||
+# Authors:
|
||||
+# Pranav Thube <pthube@redhat.com>
|
||||
+#
|
||||
+# Copyright (C) 2026 Red Hat
|
||||
+# see file 'COPYING' for use and warranty information
|
||||
+#
|
||||
+# 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 <http://www.gnu.org/licenses/>.
|
||||
+
|
||||
+"""
|
||||
+Test the i18n (internationalization) support for user plugin.
|
||||
+
|
||||
+This module tests that IPA correctly handles international characters
|
||||
+in user attributes such as first name (givenname) and last name (sn).
|
||||
+"""
|
||||
+
|
||||
+import pytest
|
||||
+
|
||||
+from ipatests.test_xmlrpc.xmlrpc_test import XMLRPC_test
|
||||
+from ipatests.test_xmlrpc.tracker.user_plugin import UserTracker
|
||||
+
|
||||
+
|
||||
+# Test data Users with i18n names
|
||||
+I18N_USERS = {
|
||||
+ 'user1': {
|
||||
+ 'name': 'i18nuser1',
|
||||
+ 'givenname': 'Çándide',
|
||||
+ 'sn': 'Rùiz',
|
||||
+ },
|
||||
+ 'user2': {
|
||||
+ 'name': 'i18nuser2',
|
||||
+ 'givenname': 'Rôséñe',
|
||||
+ 'sn': 'zackr',
|
||||
+ },
|
||||
+ 'user3': {
|
||||
+ 'name': 'i18nuser3',
|
||||
+ 'givenname': 'Älka',
|
||||
+ 'sn': 'Màrzella',
|
||||
+ },
|
||||
+ 'user4': {
|
||||
+ 'name': 'i18nuser4',
|
||||
+ 'givenname': 'Feâtlëss',
|
||||
+ 'sn': 'Watérmân',
|
||||
+ },
|
||||
+}
|
||||
+
|
||||
+# CNS test data - Swedish/European last names
|
||||
+CNS_LASTNAMES = [
|
||||
+ 'Oskar',
|
||||
+ 'Anders',
|
||||
+ 'Örjan',
|
||||
+ 'Jonas',
|
||||
+ 'Ulf',
|
||||
+ 'Äke',
|
||||
+ 'Bertold',
|
||||
+ 'Bruno',
|
||||
+ 'Didier',
|
||||
+ 'Éric',
|
||||
+ 'Jean-Luc',
|
||||
+ 'Laurent',
|
||||
+ 'Têko',
|
||||
+]
|
||||
+
|
||||
+# European names with mixed accents for firstname tests
|
||||
+EUROPEAN_FIRSTNAMES = [
|
||||
+ 'Rôséñel',
|
||||
+ 'Tàrqùinio',
|
||||
+ 'PASSWÖRD',
|
||||
+ 'Nomeuropéen',
|
||||
+ # Names with special characters (apostrophe, space)
|
||||
+ "O'Brian",
|
||||
+ 'Maria José',
|
||||
+]
|
||||
+
|
||||
+# Firstname test data - Single characters including accented
|
||||
+# 73 characters total: 26 ASCII A-Z + 47 accented/special characters
|
||||
+FIRSTNAME_SINGLE_CHARS = [
|
||||
+ # ASCII uppercase letters A-Z (26 characters)
|
||||
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J',
|
||||
+ 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T',
|
||||
+ 'U', 'V', 'W', 'X', 'Y', 'Z',
|
||||
+ # Extended Latin uppercase characters (20 characters)
|
||||
+ 'À', 'Á', 'Â', 'Ä', 'Ç', 'È', 'É', 'Ê', 'Ë',
|
||||
+ 'Í', 'Î', 'Ï', 'Ñ', 'Ó', 'Ô', 'Ö', 'Ù', 'Ú', 'Û', 'Ü',
|
||||
+ # German eszett (1 character)
|
||||
+ 'ß',
|
||||
+ # Extended Latin lowercase characters (20 characters)
|
||||
+ 'à', 'á', 'â', 'ä', 'ç', 'è', 'é', 'ê', 'ë',
|
||||
+ 'í', 'î', 'ï', 'ñ', 'ó', 'ô', 'ö', 'ù', 'ú', 'û', 'ü',
|
||||
+ # Nordic characters (4 characters)
|
||||
+ 'Ø', 'ø', 'Å', 'å',
|
||||
+ # Polish character (2 characters)
|
||||
+ 'Ł', 'ł',
|
||||
+]
|
||||
+
|
||||
+
|
||||
+@pytest.fixture(scope='class')
|
||||
+def i18n_users(request, xmlrpc_setup):
|
||||
+ """Single fixture providing all i18n test users as a dictionary"""
|
||||
+ users = {}
|
||||
+ for user_key, user_data in I18N_USERS.items():
|
||||
+ tracker = UserTracker(
|
||||
+ name=user_data['name'],
|
||||
+ givenname=user_data['givenname'],
|
||||
+ sn=user_data['sn']
|
||||
+ )
|
||||
+ users[user_key] = tracker.make_fixture(request)
|
||||
+ return users
|
||||
+
|
||||
+
|
||||
+@pytest.mark.tier1
|
||||
+class TestI18nUser(XMLRPC_test):
|
||||
+ """
|
||||
+ Test i18n (internationalization) support for user plugin.
|
||||
+
|
||||
+ Tests that IPA correctly handles international characters in user
|
||||
+ attributes such as first name (givenname) and last name (sn).
|
||||
+ """
|
||||
+
|
||||
+ ##########################################################################
|
||||
+ # User Creation Tests
|
||||
+ ##########################################################################
|
||||
+
|
||||
+ @pytest.mark.parametrize('user_key', I18N_USERS.keys())
|
||||
+ def test_add_i18n_user(self, i18n_users, user_key):
|
||||
+ """Adding i18n user"""
|
||||
+ i18n_users[user_key].create()
|
||||
+
|
||||
+ @pytest.mark.parametrize('user_key', I18N_USERS.keys())
|
||||
+ def test_verify_i18n_user(self, i18n_users, user_key):
|
||||
+ """Verify i18n user has correct full name"""
|
||||
+ user = i18n_users[user_key]
|
||||
+ user.ensure_exists()
|
||||
+ command = user.make_find_command(uid=user.uid, all=True)
|
||||
+ result = command()
|
||||
+ assert result['count'] == 1
|
||||
+ entry = result['result'][0]
|
||||
+ assert I18N_USERS[user_key]['givenname'] in entry['givenname']
|
||||
+ assert I18N_USERS[user_key]['sn'] in entry['sn']
|
||||
+
|
||||
+ ##########################################################################
|
||||
+ # CNS Tests - Lastname modification with Swedish/European names
|
||||
+ ##########################################################################
|
||||
+
|
||||
+ @pytest.mark.parametrize('lastname', CNS_LASTNAMES)
|
||||
+ def test_cns_modify_lastname(self, i18n_users, lastname):
|
||||
+ """Modify lastname to Swedish/European name"""
|
||||
+ user = i18n_users['user1']
|
||||
+ user.ensure_exists()
|
||||
+ user.update(dict(sn=lastname))
|
||||
+
|
||||
+ ##########################################################################
|
||||
+ # European accented firstname tests
|
||||
+ ##########################################################################
|
||||
+
|
||||
+ @pytest.mark.parametrize('firstname', EUROPEAN_FIRSTNAMES)
|
||||
+ def test_european_modify_firstname(self, i18n_users, firstname):
|
||||
+ """Modify firstname to European accented name"""
|
||||
+ user = i18n_users['user2']
|
||||
+ user.ensure_exists()
|
||||
+ user.update(dict(givenname=firstname))
|
||||
+
|
||||
+ ##########################################################################
|
||||
+ # Firstname Tests - Single character modification
|
||||
+ ##########################################################################
|
||||
+
|
||||
+ @pytest.mark.parametrize('char', FIRSTNAME_SINGLE_CHARS)
|
||||
+ def test_firstname_modify_single_char(self, i18n_users, char):
|
||||
+ """Modify firstname to single character"""
|
||||
+ user = i18n_users['user3']
|
||||
+ user.ensure_exists()
|
||||
+ user.update(dict(givenname=char))
|
||||
--
|
||||
2.52.0
|
||||
|
||||
229
0032-ipatests-Add-selfservice-add-and-selfservice-del-cli.patch
Normal file
229
0032-ipatests-Add-selfservice-add-and-selfservice-del-cli.patch
Normal file
@ -0,0 +1,229 @@
|
||||
From 3056bf8fb27732213591f4c86044ce6980054ec9 Mon Sep 17 00:00:00 2001
|
||||
From: Jay Gondaliya <jgondali@redhat.com>
|
||||
Date: Tue, 10 Mar 2026 19:14:48 +0530
|
||||
Subject: [PATCH] ipatests: Add selfservice-add and selfservice-del cli
|
||||
tests
|
||||
|
||||
Add a new Declarative test class `test_selfservice_cli_add_del` covering CLI-level behaviour of the selfservice-add and selfservice-del commands:
|
||||
|
||||
- add_1002: bad attrs with valid permissions rejects with InvalidSyntax
|
||||
- add_1003: valid attrs with invalid permissions rejects with ValidationError
|
||||
- add_1004: valid attrs and permissions with --all --raw succeeds and
|
||||
returns the raw ACI string (BZ 772106)
|
||||
- add_1005: bad attrs only rejects with InvalidSyntax
|
||||
- add_1006: valid attrs only succeeds with default write permission
|
||||
- del_1001: deleting an existing selfservice rule succeeds
|
||||
- del_1002: deleting a non-existent rule raises NotFound
|
||||
|
||||
Signed-off-by: Jay Gondaliya <jgondali@redhat.com>
|
||||
Fixes: https://pagure.io/freeipa/issue/9945
|
||||
Assisted-by: Claude noreply@anthropic.com
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
.../test_xmlrpc/test_selfservice_plugin.py | 192 ++++++++++++++++++
|
||||
1 file changed, 192 insertions(+)
|
||||
|
||||
diff --git a/ipatests/test_xmlrpc/test_selfservice_plugin.py b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
index e55502a2d..8f2307a20 100644
|
||||
--- a/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
+++ b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
@@ -750,3 +750,195 @@ class test_selfservice_users(XMLRPC_test):
|
||||
assert result['count'] == 0, (
|
||||
'Mobile was changed by a different user'
|
||||
)
|
||||
+
|
||||
+
|
||||
+# Module-level constants for CLI test classes
|
||||
+# selfservice-add / selfservice-del CLI tests
|
||||
+SS_CLI_ADD_1004 = 'selfservice_add_1004'
|
||||
+SS_CLI_ADD_1006 = 'selfservice_add_1006'
|
||||
+SS_CLI_DEL_1001 = 'selfservice_del_1001'
|
||||
+
|
||||
+
|
||||
+@pytest.mark.tier1
|
||||
+class test_selfservice_cli_add_del(Declarative):
|
||||
+ """CLI tests for selfservice-add and selfservice-del commands."""
|
||||
+
|
||||
+ cleanup_commands = [
|
||||
+ ('selfservice_del', [SS_CLI_ADD_1004], {}),
|
||||
+ ('selfservice_del', [SS_CLI_ADD_1006], {}),
|
||||
+ ]
|
||||
+
|
||||
+ tests = [
|
||||
+
|
||||
+ # add_1002: bad attrs + valid permissions + --all --raw
|
||||
+ dict(
|
||||
+ desc='add_1002: selfservice-add with bad attrs, valid permissions,'
|
||||
+ ' --all --raw',
|
||||
+ command=(
|
||||
+ 'selfservice_add',
|
||||
+ ['selfservice_add_1002'],
|
||||
+ dict(
|
||||
+ attrs=['badattr'],
|
||||
+ permissions='write',
|
||||
+ all=True,
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=errors.InvalidSyntax(
|
||||
+ attr=r'targetattr "badattr" does not exist in schema. '
|
||||
+ r'Please add attributeTypes "badattr" to '
|
||||
+ r'schema if necessary. '
|
||||
+ r'ACL Syntax Error(-5):'
|
||||
+ r'(targetattr = \22badattr\22)'
|
||||
+ r'(version 3.0;acl '
|
||||
+ r'\22selfservice:selfservice_add_1002\22;'
|
||||
+ r'allow (write) userdn = \22ldap:///self\22;)',
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # add_1003: valid attrs + bad permissions + --all --raw
|
||||
+ dict(
|
||||
+ desc='add_1003: selfservice-add with valid attrs, bad permissions,'
|
||||
+ ' --all --raw',
|
||||
+ command=(
|
||||
+ 'selfservice_add',
|
||||
+ ['selfservice_add_1003'],
|
||||
+ dict(
|
||||
+ attrs=[
|
||||
+ 'telephonenumber', 'mobile',
|
||||
+ 'pager', 'facsimiletelephonenumber',
|
||||
+ ],
|
||||
+ permissions='badperm',
|
||||
+ all=True,
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=errors.ValidationError(
|
||||
+ name='permissions',
|
||||
+ error='"badperm" is not a valid permission',
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # add_1004: valid attrs + valid permissions + --all --raw (BZ 772106)
|
||||
+ # selfservice-add with --raw must not return "internal error" message.
|
||||
+ dict(
|
||||
+ desc='add_1004: selfservice-add with valid attrs and permissions,'
|
||||
+ ' --all --raw (BZ 772106)',
|
||||
+ command=(
|
||||
+ 'selfservice_add',
|
||||
+ [SS_CLI_ADD_1004],
|
||||
+ dict(
|
||||
+ attrs=[
|
||||
+ 'telephonenumber', 'mobile',
|
||||
+ 'pager', 'facsimiletelephonenumber',
|
||||
+ ],
|
||||
+ permissions='write',
|
||||
+ all=True,
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_ADD_1004,
|
||||
+ summary='Added selfservice "%s"' % SS_CLI_ADD_1004,
|
||||
+ result={
|
||||
+ 'aci': (
|
||||
+ '(targetattr = "telephonenumber || mobile || pager'
|
||||
+ ' || facsimiletelephonenumber")'
|
||||
+ '(version 3.0;acl "selfservice:%s";'
|
||||
+ 'allow (write) userdn = "ldap:///self";)'
|
||||
+ % SS_CLI_ADD_1004
|
||||
+ ),
|
||||
+ },
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # add_1005: bad attrs only
|
||||
+ dict(
|
||||
+ desc='add_1005: selfservice-add with bad attrs only',
|
||||
+ command=(
|
||||
+ 'selfservice_add',
|
||||
+ ['selfservice_add_1005'],
|
||||
+ dict(attrs=['badattrs']),
|
||||
+ ),
|
||||
+ expected=errors.InvalidSyntax(
|
||||
+ attr=r'targetattr "badattrs" does not exist in schema. '
|
||||
+ r'Please add attributeTypes "badattrs" to '
|
||||
+ r'schema if necessary. '
|
||||
+ r'ACL Syntax Error(-5):'
|
||||
+ r'(targetattr = \22badattrs\22)'
|
||||
+ r'(version 3.0;acl '
|
||||
+ r'\22selfservice:selfservice_add_1005\22;'
|
||||
+ r'allow (write) userdn = \22ldap:///self\22;)',
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # add_1006: valid attrs only
|
||||
+ dict(
|
||||
+ desc='add_1006: selfservice-add with valid attrs only',
|
||||
+ command=(
|
||||
+ 'selfservice_add',
|
||||
+ [SS_CLI_ADD_1006],
|
||||
+ dict(attrs=[
|
||||
+ 'telephonenumber', 'mobile',
|
||||
+ 'pager', 'facsimiletelephonenumber',
|
||||
+ ]),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_ADD_1006,
|
||||
+ summary='Added selfservice "%s"' % SS_CLI_ADD_1006,
|
||||
+ result=dict(
|
||||
+ attrs=[
|
||||
+ 'telephonenumber', 'mobile',
|
||||
+ 'pager', 'facsimiletelephonenumber',
|
||||
+ ],
|
||||
+ permissions=['write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_ADD_1006,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Setup for del tests: create the rule that del_1001 will delete.
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Setup: create %r for selfservice-del tests'
|
||||
+ % SS_CLI_DEL_1001
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_add',
|
||||
+ [SS_CLI_DEL_1001],
|
||||
+ dict(attrs=['l'], permissions='write'),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_DEL_1001,
|
||||
+ summary='Added selfservice "%s"' % SS_CLI_DEL_1001,
|
||||
+ result=dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions=['write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_DEL_1001,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # del_1001: delete an existing rule
|
||||
+ dict(
|
||||
+ desc='del_1001: selfservice-del of an existing rule',
|
||||
+ command=('selfservice_del', [SS_CLI_DEL_1001], {}),
|
||||
+ expected=dict(
|
||||
+ result=True,
|
||||
+ value=SS_CLI_DEL_1001,
|
||||
+ summary='Deleted selfservice "%s"' % SS_CLI_DEL_1001,
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # del_1002: delete a non-existent rule
|
||||
+ dict(
|
||||
+ desc='del_1002: selfservice-del of a non-existent rule',
|
||||
+ command=('selfservice_del', ['badname'], {}),
|
||||
+ expected=errors.NotFound(
|
||||
+ reason='ACI with name "badname" not found',
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ ]
|
||||
--
|
||||
2.52.0
|
||||
|
||||
421
0033-ipatests-Additional-tests-for-32BitIdranges.patch
Normal file
421
0033-ipatests-Additional-tests-for-32BitIdranges.patch
Normal file
@ -0,0 +1,421 @@
|
||||
From b05586c2a6a81c7121dd40f8d627cd8a2c5908d8 Mon Sep 17 00:00:00 2001
|
||||
From: Sudhir Menon <sumenon@redhat.com>
|
||||
Date: Wed, 18 Mar 2026 16:36:06 +0530
|
||||
Subject: [PATCH] ipatests: Additional tests for 32BitIdranges
|
||||
|
||||
Below tests are added
|
||||
|
||||
1. Create ipauser with 32bit id.
|
||||
2. Create ipagroup with 32Bit id.
|
||||
3. Create ipauser with 32Bit groupid range.
|
||||
4. Test ssh login with 32Bit id user.
|
||||
5. Test that ipauser with 32Bit is replicated.
|
||||
6. Test that 32Bit idrange is created in IPA-AD trust enviornment.
|
||||
|
||||
Signed-off-by: Sudhir Menon <sumenon@redhat.com>
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
.../test_integration/test_32bit_idranges.py | 333 +++++++++++++++---
|
||||
1 file changed, 284 insertions(+), 49 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_32bit_idranges.py b/ipatests/test_integration/test_32bit_idranges.py
|
||||
index a928628d3..9b91fc618 100644
|
||||
--- a/ipatests/test_integration/test_32bit_idranges.py
|
||||
+++ b/ipatests/test_integration/test_32bit_idranges.py
|
||||
@@ -4,17 +4,85 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
+import re
|
||||
+
|
||||
from ipatests.pytest_ipa.integration import tasks
|
||||
from ipatests.test_integration.base import IntegrationTest
|
||||
from ipatests.test_integration.test_trust import BaseTestTrust
|
||||
|
||||
+# The tests focus on 32-bit UID/GID creation and replication,
|
||||
+# SID behavior is not covered in the tests.
|
||||
+
|
||||
+# Range with First Posix ID >= 2^31 is considered a 32-bit range.
|
||||
+IDRANGE_32BIT_NAME = "{realm}_upper_32bit_range"
|
||||
+IDRANGE_32BIT_BASE_ID = 1 << 31 # 2147483648
|
||||
+
|
||||
+
|
||||
+def _32bit_idrange_exists(master):
|
||||
+ """
|
||||
+ Return True if an ipa-local range with base ID >= 2^31 already exists.
|
||||
+ """
|
||||
+ result = master.run_command(
|
||||
+ ["ipa", "idrange-find", "--type", "ipa-local"]
|
||||
+ )
|
||||
+ # Parse all "First Posix ID of the range: in the output'
|
||||
+ for match in re.finditer(
|
||||
+ r"First Posix ID of the range:\s*(\d+)",
|
||||
+ result.stdout_text
|
||||
+ ):
|
||||
+ if int(match.group(1)) >= IDRANGE_32BIT_BASE_ID:
|
||||
+ return True
|
||||
+ return False
|
||||
+
|
||||
+
|
||||
+def _add_32bit_idrange_if_missing(master):
|
||||
+ """
|
||||
+ Create the 32-bit ID range only if it does not already exist.
|
||||
+ Returns True if the range was added, False if it already existed.
|
||||
+ """
|
||||
+ if _32bit_idrange_exists(master):
|
||||
+ return False
|
||||
+ idrange = IDRANGE_32BIT_NAME.format(realm=master.domain.realm)
|
||||
+ id_length = 10000
|
||||
+ rid_base = 300_000_000
|
||||
+ secondary_rid_base = 500_000_000
|
||||
+ master.run_command(
|
||||
+ [
|
||||
+ "ipa",
|
||||
+ "idrange-add",
|
||||
+ idrange,
|
||||
+ "--base-id", str(IDRANGE_32BIT_BASE_ID),
|
||||
+ "--range-size", str(id_length),
|
||||
+ "--rid-base", str(rid_base),
|
||||
+ "--secondary-rid-base", str(secondary_rid_base),
|
||||
+ "--type=ipa-local"
|
||||
+ ]
|
||||
+ )
|
||||
+ # Restart dirsrv instance after the new idrange is added.
|
||||
+ tasks.restart_ipa_server(master)
|
||||
+ # Clear SSSD cache
|
||||
+ tasks.clear_sssd_cache(master)
|
||||
+ return True
|
||||
+
|
||||
|
||||
class Test32BitIdRanges(IntegrationTest):
|
||||
topology = "line"
|
||||
+ num_replicas = 1
|
||||
+ num_clients = 1
|
||||
+ # Counter for 32-bit UID/GID allocation; reset in install() so each
|
||||
+ # test run starts from 0 (install/uninstall gives a fresh environment).
|
||||
+ id_counter = 0
|
||||
+
|
||||
+ def get_next_32bit_id(self):
|
||||
+ """
|
||||
+ Generate unique 32-bit IDs for testing
|
||||
+ """
|
||||
+ self.id_counter += 1
|
||||
+ return IDRANGE_32BIT_BASE_ID + self.__class__.id_counter
|
||||
|
||||
def test_remove_subid_range(self):
|
||||
"""
|
||||
- Test that allocating subid will fail after disabling global option
|
||||
+ Test that allocating subids will fail after disabling the attribute
|
||||
"""
|
||||
master = self.master
|
||||
tasks.kinit_admin(master)
|
||||
@@ -23,19 +91,28 @@ class Test32BitIdRanges(IntegrationTest):
|
||||
master.run_command(
|
||||
["ipa", "config-mod", "--addattr", "ipaconfigstring=SubID:Disable"]
|
||||
)
|
||||
- master.run_command(["ipa", "idrange-del", idrange])
|
||||
+ master.run_command(
|
||||
+ ["ipa", "idrange-del", idrange]
|
||||
+ )
|
||||
+ master.run_command(["systemctl", "restart", "sssd"])
|
||||
|
||||
tasks.user_add(master, 'subiduser')
|
||||
- result = master.run_command(
|
||||
- ["ipa", "subid-generate", "--owner", "subiduser"], raiseonerr=False
|
||||
- )
|
||||
- assert result.returncode > 0
|
||||
- assert "Support for subordinate IDs is disabled" in result.stderr_text
|
||||
- tasks.user_del(master, 'subiduser')
|
||||
+ try:
|
||||
+ result = master.run_command(
|
||||
+ ["ipa", "subid-generate", "--owner", "subiduser"],
|
||||
+ raiseonerr=False
|
||||
+ )
|
||||
+ assert result.returncode > 0
|
||||
+ assert "Support for subordinate IDs is disabled" in \
|
||||
+ result.stderr_text
|
||||
+ finally:
|
||||
+ # Cleanup: Remove test user
|
||||
+ tasks.user_del(master, 'subiduser')
|
||||
|
||||
def test_invoke_upgrader(self):
|
||||
- """Test that ipa-server-upgrade does not add subid ranges back"""
|
||||
-
|
||||
+ """
|
||||
+ Test that ipa-server-upgrade does not add subid ranges back.
|
||||
+ """
|
||||
master = self.master
|
||||
master.run_command(['ipa-server-upgrade'], raiseonerr=True)
|
||||
idrange = f"{master.domain.realm}_subid_range"
|
||||
@@ -58,69 +135,227 @@ class Test32BitIdRanges(IntegrationTest):
|
||||
assert "dnatype: " not in output
|
||||
|
||||
def test_create_user_with_32bit_id(self):
|
||||
- """Test that ID range above 2^31 can be used to assign IDs
|
||||
- to users and groups. Also check that SIDs generated properly.
|
||||
"""
|
||||
+ Test checks that 32Bit idrange is assigned to the user
|
||||
+ and getent passwd <username> returns the output.
|
||||
+ """
|
||||
+ master = self.master
|
||||
+ _add_32bit_idrange_if_missing(master)
|
||||
+
|
||||
+ uid = self.get_next_32bit_id()
|
||||
+ gid = self.get_next_32bit_id()
|
||||
+
|
||||
+ tasks.clear_sssd_cache(master)
|
||||
+ username = "user"
|
||||
+ tasks.create_active_user(
|
||||
+ master, username, "Secret123",
|
||||
+ extra_args=["--uid", str(uid), "--gid", str(gid)]
|
||||
+ )
|
||||
+ tasks.kinit_admin(master)
|
||||
+ try:
|
||||
+ result = master.run_command(
|
||||
+ ["ipa", "user-show", username, "--all", "--raw"]
|
||||
+ )
|
||||
+ assert result.returncode == 0, (
|
||||
+ f"User not found: {result.stderr_text}"
|
||||
+ )
|
||||
+ assert "ipantsecurityidentifier" in \
|
||||
+ result.stdout_text.lower(), (
|
||||
+ "SID not found in user entry"
|
||||
+ )
|
||||
+ if hasattr(self, 'clients') and self.clients:
|
||||
+ client = self.clients[0]
|
||||
+ tasks.clear_sssd_cache(client)
|
||||
+ result = client.run_command(
|
||||
+ ["getent", "passwd", username], raiseonerr=False
|
||||
+ )
|
||||
+ assert result.returncode == 0, (
|
||||
+ f"getent passwd failed: {result.stderr_text}"
|
||||
+ )
|
||||
+ assert str(uid) in result.stdout_text
|
||||
+ assert str(gid) in result.stdout_text
|
||||
+ finally:
|
||||
+ tasks.user_del(master, username)
|
||||
+
|
||||
+ def test_create_group_with_32bit_gid(self):
|
||||
+ """
|
||||
+ Test that a group can be created with a GID from the 32-bit range.
|
||||
+ """
|
||||
+ master = self.master
|
||||
+ groupname = 'grp32bit'
|
||||
+ gid = self.get_next_32bit_id()
|
||||
+ tasks.group_add(master, groupname, extra_args=["--gid", str(gid)])
|
||||
+ try:
|
||||
+ result = master.run_command(
|
||||
+ ["ipa", "group-show", groupname, "--all", "--raw"]
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ assert str(gid) in result.stdout_text, (
|
||||
+ f"GID {gid} not in group entry"
|
||||
+ )
|
||||
+ finally:
|
||||
+ tasks.group_del(master, groupname)
|
||||
|
||||
+ def test_user_in_group_with_32bit_ids(self):
|
||||
+ """
|
||||
+ Test user with 32-bit UID in a group with 32-bit GID.
|
||||
+ """
|
||||
master = self.master
|
||||
- idrange = f"{master.domain.realm}_upper_32bit_range"
|
||||
- id_base = 1 << 31
|
||||
- id_length = (1 << 31) - 2
|
||||
- uid = id_base + 1
|
||||
- gid = id_base + 1
|
||||
- master.run_command(
|
||||
- [
|
||||
- "ipa",
|
||||
- "idrange-add",
|
||||
- idrange,
|
||||
- "--base-id", str(id_base),
|
||||
- "--range-size", str(id_length),
|
||||
- "--rid-base", str(int(id_base >> 3)),
|
||||
- "--secondary-rid-base", str(int(id_base >> 3) + id_length),
|
||||
- "--type=ipa-local"
|
||||
- ]
|
||||
+ groupname = 'grp32bit2'
|
||||
+ username = 'user32bit'
|
||||
+ uid = self.get_next_32bit_id()
|
||||
+ gid = self.get_next_32bit_id()
|
||||
+ tasks.group_add(master, groupname, extra_args=["--gid", str(gid)])
|
||||
+ tasks.create_active_user(
|
||||
+ master, username, "Secret123",
|
||||
+ extra_args=["--uid", str(uid), "--gid", str(gid)]
|
||||
)
|
||||
+ tasks.kinit_admin(master)
|
||||
+ try:
|
||||
+ tasks.group_add_member(master, groupname, users=username)
|
||||
+ result = master.run_command(
|
||||
+ ["ipa", "group-show", groupname, "--all", "--raw"]
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ assert username in result.stdout_text
|
||||
+ assert f"gidnumber: {gid}" in result.stdout_text, (
|
||||
+ f"GID {gid} not found in group entry"
|
||||
+ )
|
||||
+ assert "ipaNTSecurityIdentifier:" in result.stdout_text, (
|
||||
+ "Group does not contain a SID"
|
||||
+ )
|
||||
+ result = master.run_command(
|
||||
+ ["ipa", "user-show", username, "--all", "--raw"]
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ finally:
|
||||
+ master.run_command(
|
||||
+ ["ipa", "group-remove-member", groupname,
|
||||
+ "--users", username],
|
||||
+ raiseonerr=False
|
||||
+ )
|
||||
+ tasks.user_del(master, username)
|
||||
+ tasks.group_del(master, groupname)
|
||||
|
||||
- # We added new ID range, SIDGEN will only take it after
|
||||
- # restarting a directory server instance.
|
||||
- tasks.restart_ipa_server(master)
|
||||
+ def test_ssh_login_with_32bit_id(self):
|
||||
+ """
|
||||
+ Test that a user with 32-bit UID/GID can kinit and log in via SSH
|
||||
+ from the client to the master using GSSAPI (Kerberos).
|
||||
+ """
|
||||
+ client = self.clients[0]
|
||||
+ master = self.master
|
||||
+ testuser = 'sshuser32bit'
|
||||
+ password = 'Secret123'
|
||||
+ uid = self.get_next_32bit_id()
|
||||
+ gid = self.get_next_32bit_id()
|
||||
|
||||
- # Clear SSSD cache to pick up new ID range
|
||||
tasks.clear_sssd_cache(master)
|
||||
+ tasks.create_active_user(
|
||||
+ master, testuser, password,
|
||||
+ extra_args=["--uid", str(uid), "--gid", str(gid)]
|
||||
+ )
|
||||
+ tasks.kinit_admin(master)
|
||||
+ hbac_rule = "allow_ssh_32bit_test"
|
||||
+ tasks.hbacrule_add(master, hbac_rule, extra_args=["--hostcat=all"])
|
||||
+ tasks.hbacrule_add_user(master, hbac_rule, users=testuser)
|
||||
+ tasks.hbacrule_add_service(master, hbac_rule, services="sshd")
|
||||
+ try:
|
||||
+ result = master.run_command(
|
||||
+ ["ipa", "user-show", testuser, "--all", "--raw"]
|
||||
+ )
|
||||
+ assert result.returncode == 0, (
|
||||
+ f"User {testuser} not found: {result.stderr_text}"
|
||||
+ )
|
||||
|
||||
- tasks.user_add(master, "user", extra_args=[
|
||||
- "--uid", str(uid), "--gid", str(gid)
|
||||
- ])
|
||||
+ tasks.clear_sssd_cache(client)
|
||||
+ tasks.clear_sssd_cache(master)
|
||||
+ tasks.kdestroy_all(client)
|
||||
+ tasks.kinit_as_user(client, testuser, password)
|
||||
+ result = client.run_command([
|
||||
+ 'ssh', '-o', 'StrictHostKeyChecking=no', '-K',
|
||||
+ '-l', testuser, master.hostname, 'echo login successful'
|
||||
+ ], raiseonerr=False)
|
||||
+ assert result.returncode == 0, (
|
||||
+ "SSH (GSSAPI) from client to master failed: "
|
||||
+ f"{result.stderr_text}"
|
||||
+ )
|
||||
+ assert 'login successful' in result.stdout_text, (
|
||||
+ "SSH succeeded but expected output missing: "
|
||||
+ f"{result.stdout_text}"
|
||||
+ )
|
||||
+ finally:
|
||||
+ tasks.kdestroy_all(client)
|
||||
+ master.run_command(
|
||||
+ ["ipa", "hbacrule-del", hbac_rule], raiseonerr=False
|
||||
+ )
|
||||
+ tasks.kinit_admin(master)
|
||||
+ tasks.user_del(master, testuser)
|
||||
|
||||
- result = master.run_command(
|
||||
- ["ipa", "user-show", "user", "--all", "--raw"], raiseonerr=False
|
||||
- )
|
||||
- assert result.returncode == 0
|
||||
- assert "ipaNTSecurityIdentifier:" in result.stdout_text
|
||||
+ def test_32bit_id_replication(self):
|
||||
+ """
|
||||
+ Test that users with 32-bit IDs replicate correctly
|
||||
+ """
|
||||
+ master = self.master
|
||||
+ replica = self.replicas[0]
|
||||
+ tasks.kinit_admin(master)
|
||||
+ testuser = 'repluser32bit'
|
||||
+ uid = self.get_next_32bit_id()
|
||||
+ gid = self.get_next_32bit_id()
|
||||
|
||||
- result = master.run_command(
|
||||
- ["id", "user"], raiseonerr=False
|
||||
+ tasks.clear_sssd_cache(master)
|
||||
+
|
||||
+ # Create user on master
|
||||
+ tasks.create_active_user(
|
||||
+ master, testuser, "Secret123",
|
||||
+ extra_args=["--uid", str(uid), "--gid", str(gid)]
|
||||
)
|
||||
- assert result.returncode == 0
|
||||
- assert str(uid) in result.stdout_text
|
||||
+ tasks.kinit_admin(master)
|
||||
+ try:
|
||||
+ tasks.wait_for_replication(master.ldap_connect())
|
||||
+
|
||||
+ result = master.run_command(
|
||||
+ ["ipa", "user-show", testuser, "--all", "--raw"],
|
||||
+ raiseonerr=False
|
||||
+ )
|
||||
+ assert result.returncode == 0, (
|
||||
+ f"User {testuser} not found on master"
|
||||
+ )
|
||||
+ assert str(uid) in result.stdout_text, (
|
||||
+ f"UID {uid} not on master"
|
||||
+ )
|
||||
+
|
||||
+ tasks.kinit_admin(replica)
|
||||
+ result = replica.run_command(
|
||||
+ ["ipa", "user-show", testuser, "--all", "--raw"],
|
||||
+ raiseonerr=False
|
||||
+ )
|
||||
+ assert result.returncode == 0, (
|
||||
+ f"User {testuser} not replicated to replica"
|
||||
+ )
|
||||
+ assert str(uid) in result.stdout_text, (
|
||||
+ f"UID {uid} not on replica"
|
||||
+ )
|
||||
+ finally:
|
||||
+ # Cleanup: Remove test user from master
|
||||
+ tasks.kinit_admin(master)
|
||||
+ tasks.user_del(master, testuser)
|
||||
|
||||
|
||||
class Test32BitIdrangeInTrustEnv(Test32BitIdRanges, BaseTestTrust):
|
||||
"""
|
||||
Tests to check 32BitIdrange functionality
|
||||
- in IPA-AD trust enviornment
|
||||
+ in IPA-AD trust environment
|
||||
"""
|
||||
topology = 'line'
|
||||
+ num_replicas = 1
|
||||
num_ad_domains = 1
|
||||
num_ad_subdomains = 0
|
||||
num_ad_treedomains = 0
|
||||
- num_clients = 0
|
||||
+ num_clients = 1
|
||||
|
||||
@classmethod
|
||||
def install(cls, mh):
|
||||
- super(BaseTestTrust, cls).install(mh)
|
||||
+ super(Test32BitIdrangeInTrustEnv, cls).install(mh)
|
||||
cls.ad = cls.ads[0]
|
||||
- cls.ad_domain = cls.ad.domain.name
|
||||
tasks.configure_dns_for_trust(cls.master, cls.ad)
|
||||
- tasks.install_adtrust(cls.master)
|
||||
tasks.establish_trust_with_ad(cls.master, cls.ad.domain.name)
|
||||
--
|
||||
2.52.0
|
||||
|
||||
260
0034-ipatests-add-HTTP-GSSAPI-Kerberos-authentication-tes.patch
Normal file
260
0034-ipatests-add-HTTP-GSSAPI-Kerberos-authentication-tes.patch
Normal file
@ -0,0 +1,260 @@
|
||||
From 3acf55ed7fcf9a3b38deb0efb98d222d57bafbbc Mon Sep 17 00:00:00 2001
|
||||
From: Anuja More <amore@redhat.com>
|
||||
Date: Thu, 26 Feb 2026 12:04:50 +0530
|
||||
Subject: [PATCH] ipatests: add HTTP GSSAPI Kerberos authentication
|
||||
tests with AD trust
|
||||
|
||||
Add TestTrustFunctionalHttp integration test class covering GSSAPI-protected
|
||||
HTTP access in an AD trust environment:
|
||||
|
||||
- test_ipa_trust_func_http_krb_ipauser: IPA user with a valid Kerberos ticket
|
||||
can access the GSSAPI-protected webapp; AD root and subdomain users are
|
||||
denied when only the IPA user is in the Allow list
|
||||
- test_ipa_trust_func_http_krb_aduser: AD root domain and subdomain users with
|
||||
valid Kerberos tickets can access the webapp; IPA users are denied when the
|
||||
Allow list is configured for an AD user
|
||||
- test_ipa_trust_func_http_krb_nouser: a user without a Kerberos ticket
|
||||
receives a 401 Unauthorized response
|
||||
|
||||
The class sets up an Apache httpd instance with mod_auth_gssapi on the IPA
|
||||
client, obtains a service keytab via ipa-getkeytab, and uses curl with
|
||||
GSSAPI negotiate to drive each scenario.
|
||||
|
||||
Related: https://pagure.io/freeipa/issue/9845
|
||||
Assisted-by: Claude noreply@anthropic.com
|
||||
Signed-off-by: Anuja More <amore@redhat.com>
|
||||
Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
|
||||
---
|
||||
.../test_integration/test_trust_functional.py | 209 +++++++++++++++++-
|
||||
1 file changed, 208 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_trust_functional.py b/ipatests/test_integration/test_trust_functional.py
|
||||
index a85f21e96463757b9a446df666d5361e65ba686c..5a9eae8cebfdae23bf37d3298e455bab8e3304ef 100644
|
||||
--- a/ipatests/test_integration/test_trust_functional.py
|
||||
+++ b/ipatests/test_integration/test_trust_functional.py
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
+import re
|
||||
import time
|
||||
-
|
||||
+import textwrap
|
||||
from ipaplatform.paths import paths
|
||||
from ipatests.pytest_ipa.integration import tasks
|
||||
from ipatests.test_integration.test_trust import BaseTestTrust
|
||||
@@ -658,3 +659,209 @@ class TestTrustFunctionalSudo(BaseTestTrust):
|
||||
raiseonerr=False)
|
||||
finally:
|
||||
self._cleanup_srule(srule)
|
||||
+
|
||||
+
|
||||
+class TestTrustFunctionalHttp(BaseTestTrust):
|
||||
+ topology = 'line'
|
||||
+ num_ad_treedomains = 0
|
||||
+
|
||||
+ ad_user_password = 'Secret123'
|
||||
+
|
||||
+ # Apache configuration for GSSAPI-protected webapp. The /mywebapp
|
||||
+ # location requires Kerberos authentication and restricts access by
|
||||
+ # domain: IPA users (@IPA_REALM) or AD users (@AD_DOMAIN).
|
||||
+ apache_conf = textwrap.dedent('''
|
||||
+ Alias /mywebapp "/var/www/html/mywebapp"
|
||||
+ <Directory "/var/www/html/mywebapp">
|
||||
+ Allow from all
|
||||
+ </Directory>
|
||||
+ <Location "/mywebapp">
|
||||
+ LogLevel debug
|
||||
+ AuthType GSSAPI
|
||||
+ AuthName "IPA Kerberos authentication"
|
||||
+ GssapiNegotiateOnce on
|
||||
+ GssapiBasicAuthMech krb5
|
||||
+ GssapiCredStore keytab:{keytab_path}
|
||||
+ <RequireAll>
|
||||
+ Require valid-user
|
||||
+ # Require expr: restrict access by domain. REMOTE_USER is set by
|
||||
+ # mod_auth_gssapi after GSSAPI authentication. Allow users whose
|
||||
+ # principal ends with the domain (IPA realm or AD domain).
|
||||
+ Require expr %{{REMOTE_USER}} =~ /{allowed_domain_regex}$/
|
||||
+ </RequireAll>
|
||||
+ </Location>
|
||||
+ ''')
|
||||
+
|
||||
+ def _configure_webapp(self, allowed_domain):
|
||||
+ """Write the GSSAPI vhost config and restart httpd on the client.
|
||||
+
|
||||
+ allowed_domain: realm/domain for access control (e.g. IPA.TEST for
|
||||
+ IPA users, AD.DOMAIN for AD users). Users whose principal ends with
|
||||
+ @allowed_domain are granted access.
|
||||
+ """
|
||||
+ # Escape dots for regex (e.g. IPA.TEST -> IPA\\.TEST)
|
||||
+ escaped = re.escape(allowed_domain)
|
||||
+ allowed_domain_regex = '.*@' + escaped
|
||||
+ keytab_path = f"/etc/httpd/conf/{self.clients[0].hostname}.keytab"
|
||||
+ self.clients[0].put_file_contents(
|
||||
+ '/etc/httpd/conf.d/mywebapp.conf',
|
||||
+ self.apache_conf.format(
|
||||
+ keytab_path=keytab_path,
|
||||
+ allowed_domain_regex=allowed_domain_regex,
|
||||
+ )
|
||||
+ )
|
||||
+ self.clients[0].run_command(['systemctl', 'restart', 'httpd'])
|
||||
+
|
||||
+ def _assert_curl_ok(self, msg=None):
|
||||
+ """Run curl with GSSAPI negotiate and assert the webapp responds."""
|
||||
+ url = f"http://{self.clients[0].hostname}/mywebapp/index.html"
|
||||
+ result = self.clients[0].run_command([
|
||||
+ paths.BIN_CURL, '-v', '--negotiate', '-u:', url
|
||||
+ ])
|
||||
+ assert "TEST_MY_WEB_APP" in result.stdout_text, (
|
||||
+ msg or f"Expected webapp content at {url}"
|
||||
+ )
|
||||
+
|
||||
+ def _assert_curl_GSSAPI_access_denied(self, msg=None):
|
||||
+ """Run curl with GSSAPI negotiate and assert a 401 is returned."""
|
||||
+ url = f"http://{self.clients[0].hostname}/mywebapp/index.html"
|
||||
+ result = self.clients[0].run_command([
|
||||
+ paths.BIN_CURL, '-v', '--negotiate', '-u:', url
|
||||
+ ], raiseonerr=False)
|
||||
+ output = f"{result.stdout_text}{result.stderr_text}"
|
||||
+ assert ("401" in output
|
||||
+ or "Unauthorized" in output
|
||||
+ or "Authorization Required" in output), (
|
||||
+ msg or f"Expected 401/Unauthorized at {url}, got: {output[:200]}"
|
||||
+ )
|
||||
+
|
||||
+ @classmethod
|
||||
+ def install(cls, mh):
|
||||
+ """Extend base install to configure Apache/GSSAPI for HTTP tests.
|
||||
+
|
||||
+ Runs once before any test in this class. Sets up the AD trust,
|
||||
+ creates the HTTP service principal and IPA test user, installs
|
||||
+ mod_auth_gssapi, retrieves the service keytab, and provisions the
|
||||
+ static webapp content used by all HTTP tests.
|
||||
+ """
|
||||
+ super().install(mh)
|
||||
+ tasks.configure_dns_for_trust(cls.master, cls.ad)
|
||||
+ tasks.establish_trust_with_ad(
|
||||
+ cls.master, cls.ad_domain,
|
||||
+ extra_args=['--range-type', 'ipa-ad-trust'])
|
||||
+
|
||||
+ # Create HTTP service principal on master
|
||||
+ service_principal = f"HTTP/{cls.clients[0].hostname}"
|
||||
+ cls.master.run_command(
|
||||
+ ["ipa", "service-add", service_principal]
|
||||
+ )
|
||||
+
|
||||
+ # Create IPA user for HTTP tests
|
||||
+ tasks.create_active_user(
|
||||
+ cls.master, "ipahttpuser1", password="Passw0rd1",
|
||||
+ first="f", last="l"
|
||||
+ )
|
||||
+
|
||||
+ # Clear SSSD cache on master
|
||||
+ tasks.clear_sssd_cache(cls.master)
|
||||
+ tasks.wait_for_sssd_domain_status_online(cls.master)
|
||||
+
|
||||
+ # Install Apache and the GSSAPI module on the IPA client
|
||||
+ tasks.install_packages(
|
||||
+ cls.clients[0], ['mod_auth_gssapi', 'httpd']
|
||||
+ )
|
||||
+
|
||||
+ # Retrieve and protect the HTTP service keytab
|
||||
+ keytab_path = f"/etc/httpd/conf/{cls.clients[0].hostname}.keytab"
|
||||
+ cls.clients[0].run_command([
|
||||
+ 'ipa-getkeytab', '-s', cls.master.hostname,
|
||||
+ '-k', keytab_path,
|
||||
+ '-p', service_principal
|
||||
+ ])
|
||||
+ cls.clients[0].run_command(
|
||||
+ ['chown', 'apache:apache', keytab_path]
|
||||
+ )
|
||||
+
|
||||
+ # Create webapp directory and static content
|
||||
+ cls.clients[0].run_command(
|
||||
+ ['mkdir', '-p', '/var/www/html/mywebapp']
|
||||
+ )
|
||||
+ cls.clients[0].put_file_contents(
|
||||
+ '/var/www/html/mywebapp/index.html',
|
||||
+ 'TEST_MY_WEB_APP\n'
|
||||
+ )
|
||||
+
|
||||
+ def test_ipa_trust_func_http_krb_ipauser(self):
|
||||
+ """
|
||||
+ Test IPA User access http with kerberos ticket via valid user.
|
||||
+
|
||||
+ This test verifies that an IPA user with a valid Kerberos ticket
|
||||
+ can successfully access an HTTP resource protected by GSSAPI
|
||||
+ authentication and restricted to IPA users.
|
||||
+ """
|
||||
+ ipa_realm = self.clients[0].domain.realm
|
||||
+ self._configure_webapp(ipa_realm)
|
||||
+
|
||||
+ tasks.kdestroy_all(self.clients[0])
|
||||
+ tasks.kinit_as_user(
|
||||
+ self.clients[0], f'ipahttpuser1@{ipa_realm}', "Passw0rd1"
|
||||
+ )
|
||||
+
|
||||
+ self._assert_curl_ok()
|
||||
+
|
||||
+ users = [
|
||||
+ (self.aduser, self.ad_domain),
|
||||
+ (self.subaduser, self.ad_subdomain),
|
||||
+ ]
|
||||
+ for aduser, domain in users:
|
||||
+ tasks.kdestroy_all(self.clients[0])
|
||||
+ # pylint: disable=use-maxsplit-arg
|
||||
+ principal = f"{aduser.split('@')[0]}@{domain.upper()}"
|
||||
+ tasks.kinit_as_user(
|
||||
+ self.clients[0], principal, self.ad_user_password
|
||||
+ )
|
||||
+ self._assert_curl_GSSAPI_access_denied(
|
||||
+ msg=f"Expected 401 for AD user {aduser}"
|
||||
+ )
|
||||
+
|
||||
+ def test_ipa_trust_func_http_krb_aduser(self):
|
||||
+ """
|
||||
+ Test AD root and subdomain users access http with kerberos ticket.
|
||||
+
|
||||
+ This test verifies that both a root AD domain user and a child
|
||||
+ subdomain user with valid Kerberos tickets can successfully access
|
||||
+ an HTTP resource protected by GSSAPI authentication and restricted
|
||||
+ to AD domain / AD subdomain users.
|
||||
+ """
|
||||
+ users = [
|
||||
+ (self.aduser, self.ad_domain),
|
||||
+ (self.subaduser, self.ad_subdomain),
|
||||
+ ]
|
||||
+ for aduser, domain in users:
|
||||
+ tasks.kdestroy_all(self.clients[0])
|
||||
+ # pylint: disable=use-maxsplit-arg
|
||||
+ principal = f"{aduser.split('@')[0]}@{domain.upper()}"
|
||||
+ self._configure_webapp(domain.upper())
|
||||
+ tasks.kinit_as_user(
|
||||
+ self.clients[0], principal, self.ad_user_password
|
||||
+ )
|
||||
+ self._assert_curl_ok(
|
||||
+ msg=f"Expected webapp content for AD user {aduser}"
|
||||
+ )
|
||||
+ tasks.kdestroy_all(self.clients[0])
|
||||
+ tasks.kinit_as_user(self.clients[0], "ipahttpuser1", "Passw0rd1")
|
||||
+ self._assert_curl_GSSAPI_access_denied(
|
||||
+ msg=f"Expected 401 for IPA user after AD user {aduser}"
|
||||
+ )
|
||||
+
|
||||
+ def test_ipa_trust_func_http_krb_nouser(self):
|
||||
+ """
|
||||
+ Test User cannot access http without kerberos ticket via valid user.
|
||||
+
|
||||
+ This test verifies that an user without a valid Kerberos ticket
|
||||
+ is denied access to an HTTP resource protected by GSSAPI
|
||||
+ authentication, receiving a 401 Unauthorized error.
|
||||
+ """
|
||||
+ tasks.kdestroy_all(self.clients[0])
|
||||
+
|
||||
+ self._assert_curl_GSSAPI_access_denied()
|
||||
--
|
||||
2.52.0
|
||||
|
||||
2595
0035-ipatests-Extend-netgroup-test-coverage.patch
Normal file
2595
0035-ipatests-Extend-netgroup-test-coverage.patch
Normal file
File diff suppressed because it is too large
Load Diff
543
0036-ipatests-Add-user-principal-ipa-getkeytab-and-ipa-rm.patch
Normal file
543
0036-ipatests-Add-user-principal-ipa-getkeytab-and-ipa-rm.patch
Normal file
@ -0,0 +1,543 @@
|
||||
From 9a8553fc297987b006b28ade2098d613f9b5360f Mon Sep 17 00:00:00 2001
|
||||
From: Jay Gondaliya <jgondali@redhat.com>
|
||||
Date: Thu, 12 Mar 2026 19:03:24 +0530
|
||||
Subject: [PATCH] ipatests: Add user-principal ipa-getkeytab and
|
||||
ipa-rmkeytab cmdline tests
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
Port bash acceptance tests (getkeytab_001–getkeytab_007, rmkeytab_001-rmkeytab_003) from downstream to cmdline tests in test_ipagetkeytab.py:
|
||||
|
||||
test_getkeytab_users:
|
||||
getkeytab_001: quiet mode, insufficient access, empty ccache
|
||||
getkeytab_002: --server / -s with valid and invalid hostnames, DNS-based server discovery when -s is omitted
|
||||
getkeytab_003: --principal / -p with unknown, invalid-realm,
|
||||
bare, and realm-qualified principals
|
||||
getkeytab_004: --keytab / -k creation, enctype content, invalid path
|
||||
getkeytab_005: -e single enctype filtering and kinit validation
|
||||
getkeytab_006: --password / -P key rotation
|
||||
getkeytab_007: -D / -w bind DN error cases (empty DN, wrong
|
||||
password, missing password)
|
||||
|
||||
test_rmkeytab_cmd:
|
||||
rmkeytab_001: removal by principal (valid and invalid)
|
||||
rmkeytab_002: removal by realm
|
||||
rmkeytab_003: invalid keytab path
|
||||
|
||||
Tests that duplicate existing coverage are skipped with comments referencing the original tests. Parametrized variants are used where flags accept both short and long forms.
|
||||
|
||||
Signed-off-by: Jay Gondaliya <jgondali@redhat.com>
|
||||
Fixes: https://pagure.io/freeipa/issue/9957
|
||||
Assisted-by: Claude <noreply@anthropic.com>
|
||||
Reviewed-By: Rob Crittenden <rcritten@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
---
|
||||
ipatests/test_cmdline/test_ipagetkeytab.py | 459 +++++++++++++++++++++
|
||||
1 file changed, 459 insertions(+)
|
||||
|
||||
diff --git a/ipatests/test_cmdline/test_ipagetkeytab.py b/ipatests/test_cmdline/test_ipagetkeytab.py
|
||||
index c4678a76d..9ec9efae6 100755
|
||||
--- a/ipatests/test_cmdline/test_ipagetkeytab.py
|
||||
+++ b/ipatests/test_cmdline/test_ipagetkeytab.py
|
||||
@@ -22,6 +22,7 @@ Test `ipa-getkeytab`
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
+import configparser
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
@@ -40,6 +41,28 @@ from ipatests.test_xmlrpc.tracker import host_plugin, service_plugin
|
||||
from ipatests.test_xmlrpc.xmlrpc_test import fuzzy_digits, add_oc
|
||||
from contextlib import contextmanager
|
||||
|
||||
+from ipatests.test_xmlrpc.tracker.user_plugin import UserTracker
|
||||
+from ipatests.util import unlock_principal_password
|
||||
+
|
||||
+GK_USER1 = 'gkuser1'
|
||||
+GK_USER2 = 'gkuser2'
|
||||
+GK_INIT_PW = 'TempSecret123!'
|
||||
+GK_USER_PW = 'Secret123'
|
||||
+
|
||||
+CRYPTO_POLICIES_KRB5 = '/etc/krb5.conf.d/crypto-policies'
|
||||
+
|
||||
+
|
||||
+def get_permitted_enctypes():
|
||||
+ """Read permitted encryption types from the system crypto-policies.
|
||||
+
|
||||
+ Returns a list of enctype name strings, e.g.
|
||||
+ ['aes256-cts-hmac-sha1-96', 'aes128-cts-hmac-sha1-96', ...].
|
||||
+ """
|
||||
+ cfg = configparser.ConfigParser()
|
||||
+ cfg.read(CRYPTO_POLICIES_KRB5)
|
||||
+ raw = cfg.get('libdefaults', 'permitted_enctypes', fallback='')
|
||||
+ return [e for e in raw.split() if e]
|
||||
+
|
||||
|
||||
@contextmanager
|
||||
def use_keytab(principal, keytab):
|
||||
@@ -63,6 +86,57 @@ def use_keytab(principal, keytab):
|
||||
setattr(context, 'principal', old_principal)
|
||||
|
||||
|
||||
+@contextmanager
|
||||
+def kinit_as_user(principal, password):
|
||||
+ """Kinit as *principal* into a private ccache; yield its path.
|
||||
+
|
||||
+ private_ccache() already sets KRB5CCNAME in os.environ, so callers
|
||||
+ (and ipautil.run) inherit it automatically.
|
||||
+ """
|
||||
+ with private_ccache() as ccache_path:
|
||||
+ ipautil.run(
|
||||
+ ['kinit', principal],
|
||||
+ stdin=password + '\n',
|
||||
+ raiseonerr=True,
|
||||
+ capture_output=True,
|
||||
+ capture_error=True,
|
||||
+ )
|
||||
+ yield ccache_path
|
||||
+
|
||||
+
|
||||
+def run_getkeytab(args, env=None, stdin=None):
|
||||
+ """Run ipa-getkeytab with arbitrary arguments."""
|
||||
+ return ipautil.run(
|
||||
+ [paths.IPA_GETKEYTAB] + list(args),
|
||||
+ raiseonerr=False,
|
||||
+ capture_output=True,
|
||||
+ capture_error=True,
|
||||
+ stdin=stdin,
|
||||
+ env=env,
|
||||
+ )
|
||||
+
|
||||
+
|
||||
+def run_rmkeytab(args, env=None):
|
||||
+ """Run ipa-rmkeytab with arbitrary arguments."""
|
||||
+ return ipautil.run(
|
||||
+ [paths.IPA_RMKEYTAB] + list(args),
|
||||
+ raiseonerr=False,
|
||||
+ capture_output=True,
|
||||
+ capture_error=True,
|
||||
+ env=env,
|
||||
+ )
|
||||
+
|
||||
+
|
||||
+def klist_keytab(keytab_path):
|
||||
+ """Run ``klist -ekt`` and return the result object."""
|
||||
+ return ipautil.run(
|
||||
+ ['klist', '-ekt', keytab_path],
|
||||
+ raiseonerr=False,
|
||||
+ capture_output=True,
|
||||
+ capture_error=True,
|
||||
+ )
|
||||
+
|
||||
+
|
||||
@pytest.fixture(scope='class')
|
||||
def test_host(request):
|
||||
host_tracker = host_plugin.HostTracker(u'test-host')
|
||||
@@ -76,6 +150,20 @@ def test_service(request, test_host, keytab_retrieval_setup):
|
||||
return service_tracker.make_fixture(request)
|
||||
|
||||
|
||||
+@pytest.fixture(scope='class')
|
||||
+def gk_users(request, keytab_retrieval_setup):
|
||||
+ """Create GK_USER1 and GK_USER2; delete them after the class."""
|
||||
+ for uid in (GK_USER1, GK_USER2):
|
||||
+ tracker = UserTracker(
|
||||
+ name=uid, givenname='Test', sn='GKUser',
|
||||
+ userpassword=GK_INIT_PW,
|
||||
+ )
|
||||
+ tracker.make_fixture(request)
|
||||
+ tracker.make_create_command()()
|
||||
+ tracker.exists = True
|
||||
+ unlock_principal_password(uid, GK_INIT_PW, GK_USER_PW)
|
||||
+
|
||||
+
|
||||
@pytest.mark.needs_ipaapi
|
||||
class KeytabRetrievalTest(cmdline_test):
|
||||
"""
|
||||
@@ -134,6 +222,22 @@ class KeytabRetrievalTest(cmdline_test):
|
||||
rc = result.returncode
|
||||
assert rc == retcode
|
||||
|
||||
+ def _get_user1_keytab(self, keytab_path=None):
|
||||
+ """Populate *keytab_path* (default self.keytabname) with GK_USER1's
|
||||
+ keytab."""
|
||||
+ keytab = keytab_path or self.keytabname
|
||||
+ run_getkeytab(
|
||||
+ ['-s', api.env.host, '-p', GK_USER1, '-k', keytab]
|
||||
+ )
|
||||
+
|
||||
+ def _assert_getkeytab_succeeds(self, result):
|
||||
+ """Assert ipa-getkeytab returned success with the expected message."""
|
||||
+ assert result.returncode == 0
|
||||
+ assert (
|
||||
+ 'Keytab successfully retrieved and stored in:'
|
||||
+ in result.error_output
|
||||
+ )
|
||||
+
|
||||
|
||||
@pytest.mark.tier0
|
||||
class test_ipagetkeytab(KeytabRetrievalTest):
|
||||
@@ -495,3 +599,358 @@ class test_smb_service(KeytabRetrievalTest):
|
||||
ipanthash = entry.single_value.get('ipanthash')
|
||||
conn.disconnect()
|
||||
assert ipanthash != b'MagicRegen', 'LDBM backend entry corruption'
|
||||
+
|
||||
+
|
||||
+# -----------------------------------------------------------------------
|
||||
+# User-principal tests ported from bash acceptance tests
|
||||
+# (t.ipa-get-rm-keytabs.sh: getkeytab_001 – getkeytab_006)
|
||||
+# -----------------------------------------------------------------------
|
||||
+
|
||||
+
|
||||
+@pytest.mark.tier1
|
||||
+@pytest.mark.usefixtures('gk_users')
|
||||
+class test_getkeytab_users(KeytabRetrievalTest):
|
||||
+ """
|
||||
+ User-principal ipa-getkeytab tests (bash getkeytab_001 – getkeytab_006).
|
||||
+ """
|
||||
+ command = paths.IPA_GETKEYTAB
|
||||
+
|
||||
+ def _ensure_keytab_absent(self, keytab_path=None):
|
||||
+ try:
|
||||
+ os.unlink(keytab_path or self.keytabname)
|
||||
+ except OSError:
|
||||
+ pass
|
||||
+
|
||||
+ # --- getkeytab_001: quiet mode, access rights, missing ccache ---
|
||||
+
|
||||
+ def test_insufficient_access_as_other_user(self):
|
||||
+ """Retrieving another user's keytab without admin rights fails."""
|
||||
+ principal1 = '{}@{}'.format(GK_USER1, api.env.realm)
|
||||
+ principal2 = '{}@{}'.format(GK_USER2, api.env.realm)
|
||||
+ with kinit_as_user(principal1, GK_USER_PW):
|
||||
+ result = run_getkeytab(
|
||||
+ ['-s', api.env.host,
|
||||
+ '-p', principal2,
|
||||
+ '-k', self.keytabname],
|
||||
+ )
|
||||
+ assert result.returncode == 9
|
||||
+ assert (
|
||||
+ 'Failed to parse result: Insufficient access rights'
|
||||
+ in result.error_output
|
||||
+ )
|
||||
+
|
||||
+ def test_empty_ccache_returns_exit6(self):
|
||||
+ """An empty ccache (simulating kdestroy) returns exit 6."""
|
||||
+ principal1 = '{}@{}'.format(GK_USER1, api.env.realm)
|
||||
+ with private_ccache():
|
||||
+ result = run_getkeytab(
|
||||
+ ['-s', api.env.host, '-p', principal1,
|
||||
+ '-k', self.keytabname],
|
||||
+ )
|
||||
+ assert result.returncode == 6
|
||||
+ assert (
|
||||
+ 'Kerberos User Principal not found. '
|
||||
+ 'Do you have a valid Credential Cache?'
|
||||
+ in result.error_output
|
||||
+ )
|
||||
+
|
||||
+ def test_quiet_suppresses_success_message(self):
|
||||
+ """-q must suppress the keytab-stored success message.
|
||||
+
|
||||
+ Note: test_6_quiet_mode checks -q returncode with a service
|
||||
+ principal but does not verify the message is absent.
|
||||
+ """
|
||||
+ result = run_getkeytab(
|
||||
+ ['-q', '-s', api.env.host, '-p', GK_USER1,
|
||||
+ '-k', self.keytabname],
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ assert result.error_output == '', (
|
||||
+ f"Expected no output with -q, got: {result.error_output!r}"
|
||||
+ )
|
||||
+
|
||||
+ # Skipped: test_normal_mode_shows_success_message
|
||||
+ # — covered by test_6_quiet_mode which verifies the success message
|
||||
+ # appears without -q.
|
||||
+
|
||||
+ # --- getkeytab_002: --server / -s ---
|
||||
+
|
||||
+ @pytest.mark.parametrize('flag', ['--server', '-s'])
|
||||
+ def test_invalid_server_fails(self, flag):
|
||||
+ """An invalid server hostname returns exit 9 with bind error."""
|
||||
+ result = run_getkeytab(
|
||||
+ [flag, 'invalid.ipaserver.com',
|
||||
+ '-p', GK_USER1,
|
||||
+ '-k', self.keytabname],
|
||||
+ )
|
||||
+ assert result.returncode == 9
|
||||
+ assert 'Failed to bind to server' in result.error_output
|
||||
+
|
||||
+ @pytest.mark.parametrize('flag', ['--server', '-s'])
|
||||
+ def test_valid_server_succeeds(self, flag):
|
||||
+ """A valid server hostname retrieves the keytab successfully.
|
||||
+
|
||||
+ Note: -s case is also covered by test_7_server_name_check
|
||||
+ with a service principal.
|
||||
+ """
|
||||
+ result = run_getkeytab(
|
||||
+ [flag, api.env.host, '-p', GK_USER1,
|
||||
+ '-k', self.keytabname],
|
||||
+ )
|
||||
+ self._assert_getkeytab_succeeds(result)
|
||||
+
|
||||
+ def test_dns_discovery_succeeds(self):
|
||||
+ """ipa-getkeytab discovers the server via DNS when -s is omitted."""
|
||||
+ result = run_getkeytab(
|
||||
+ ['-p', GK_USER1, '-k', self.keytabname],
|
||||
+ )
|
||||
+ self._assert_getkeytab_succeeds(result)
|
||||
+
|
||||
+ # --- getkeytab_003: --principal / -p ---
|
||||
+
|
||||
+ @pytest.mark.parametrize('flag', ['--principal', '-p'])
|
||||
+ @pytest.mark.parametrize('principal', [
|
||||
+ pytest.param('unknownuser', id='unknown'),
|
||||
+ pytest.param(
|
||||
+ '{}@INVALID.IPASERVER.REALM.COM'.format(GK_USER1),
|
||||
+ id='invalid-realm',
|
||||
+ ),
|
||||
+ ])
|
||||
+ def test_principal_not_found(self, flag, principal):
|
||||
+ """Unresolvable principals return exit 9 with not-found error.
|
||||
+
|
||||
+ Note: test_1_run tests a similar scenario with a non-existent
|
||||
+ service principal.
|
||||
+ """
|
||||
+ result = run_getkeytab(
|
||||
+ ['-s', api.env.host, flag, principal,
|
||||
+ '-k', self.keytabname],
|
||||
+ )
|
||||
+ assert result.returncode == 9
|
||||
+ assert (
|
||||
+ 'Failed to parse result: PrincipalName not found.'
|
||||
+ in result.error_output
|
||||
+ )
|
||||
+
|
||||
+ @pytest.mark.parametrize('flag', ['--principal', '-p'])
|
||||
+ @pytest.mark.parametrize('with_realm', [False, True],
|
||||
+ ids=['bare', 'with-realm'])
|
||||
+ def test_valid_principal_succeeds(self, flag, with_realm):
|
||||
+ """Both bare and realm-qualified principals retrieve the keytab."""
|
||||
+ principal = ('{}@{}'.format(GK_USER1, api.env.realm)
|
||||
+ if with_realm else GK_USER1)
|
||||
+ result = run_getkeytab(
|
||||
+ ['-s', api.env.host, flag, principal,
|
||||
+ '-k', self.keytabname],
|
||||
+ )
|
||||
+ self._assert_getkeytab_succeeds(result)
|
||||
+
|
||||
+ # --- getkeytab_004: --keytab / -k ---
|
||||
+
|
||||
+ @pytest.mark.parametrize('flag', ['--keytab', '-k'])
|
||||
+ def test_creates_keytab_when_absent(self, flag):
|
||||
+ """The keytab file is created when it does not previously exist."""
|
||||
+ self._ensure_keytab_absent()
|
||||
+ result = run_getkeytab(
|
||||
+ ['-s', api.env.host, '-p', GK_USER1, flag, self.keytabname],
|
||||
+ )
|
||||
+ self._assert_getkeytab_succeeds(result)
|
||||
+ assert os.path.isfile(self.keytabname)
|
||||
+
|
||||
+ @pytest.mark.parametrize('flag', ['--keytab', '-k'])
|
||||
+ def test_keytab_contains_aes_enctypes(self, flag):
|
||||
+ """The created keytab contains both aes256 and aes128 entries."""
|
||||
+ run_getkeytab(
|
||||
+ ['-s', api.env.host, '-p', GK_USER1, flag, self.keytabname]
|
||||
+ )
|
||||
+ klist_result = klist_keytab(self.keytabname)
|
||||
+ assert api.env.realm in klist_result.output
|
||||
+ assert 'aes128' in klist_result.output
|
||||
+ assert 'aes256' in klist_result.output
|
||||
+
|
||||
+ @pytest.mark.parametrize('flag', ['--keytab', '-k'])
|
||||
+ def test_text_file_path_returns_exit11(self, flag):
|
||||
+ """Writing to a pre-existing plain-text file returns exit 11."""
|
||||
+ txtfd, txt_path = tempfile.mkstemp(suffix='.txt')
|
||||
+ os.close(txtfd)
|
||||
+ try:
|
||||
+ result = run_getkeytab(
|
||||
+ ['-s', api.env.host, '-p', GK_USER1, flag, txt_path],
|
||||
+ )
|
||||
+ assert result.returncode == 11
|
||||
+ assert 'Failed to add key to the keytab' in result.error_output
|
||||
+ finally:
|
||||
+ try:
|
||||
+ os.unlink(txt_path)
|
||||
+ except OSError:
|
||||
+ pass
|
||||
+
|
||||
+ # --- getkeytab_005: -e encryption types ---
|
||||
+
|
||||
+ def test_system_keytab_has_default_enctypes(self):
|
||||
+ """The system keytab must contain both aes256 and aes128."""
|
||||
+ klist_result = klist_keytab('/etc/krb5.keytab')
|
||||
+ assert '(aes256-cts-hmac-sha1-96)' in klist_result.output
|
||||
+ assert '(aes128-cts-hmac-sha1-96)' in klist_result.output
|
||||
+
|
||||
+ @pytest.mark.parametrize('enctype', get_permitted_enctypes())
|
||||
+ def test_single_enctype(self, enctype):
|
||||
+ """With -e <enctype> only that enctype appears and kinit works.
|
||||
+
|
||||
+ Note: test_8_keytab_encryption_check tests -e with multiple
|
||||
+ enctypes but only asserts success, not klist content.
|
||||
+ """
|
||||
+ expected = f"({enctype})"
|
||||
+ self._ensure_keytab_absent()
|
||||
+ run_getkeytab(
|
||||
+ ['-s', api.env.host, '-p', GK_USER1,
|
||||
+ '-k', self.keytabname, '-e', enctype],
|
||||
+ )
|
||||
+ klist_result = klist_keytab(self.keytabname)
|
||||
+ assert expected in klist_result.output
|
||||
+ for enc in get_permitted_enctypes():
|
||||
+ if enc != enctype:
|
||||
+ assert f"({enc})" not in klist_result.output
|
||||
+ assert '(des3-cbc-sha1)' not in klist_result.output
|
||||
+ assert '(arcfour-hmac)' not in klist_result.output
|
||||
+ with private_ccache():
|
||||
+ ipautil.run(
|
||||
+ ['kinit', '-k', '-t', self.keytabname,
|
||||
+ '{}@{}'.format(GK_USER1, api.env.realm)],
|
||||
+ raiseonerr=True,
|
||||
+ capture_output=True,
|
||||
+ capture_error=True,
|
||||
+ )
|
||||
+
|
||||
+ def test_invalid_enctype_returns_exit8(self):
|
||||
+ """An invalid -e value returns exit 8 and creates no keytab."""
|
||||
+ self._ensure_keytab_absent()
|
||||
+ result = run_getkeytab(
|
||||
+ ['-s', api.env.host, '-p', GK_USER1,
|
||||
+ '-k', self.keytabname, '-e', 'invalid'],
|
||||
+ )
|
||||
+ assert result.returncode == 8
|
||||
+ assert 'Warning unrecognized encryption type' in result.error_output
|
||||
+ assert not os.path.isfile(self.keytabname)
|
||||
+
|
||||
+ # --- getkeytab_006: --password / -P ---
|
||||
+
|
||||
+ @pytest.mark.parametrize('flag', ['--password', '-P'])
|
||||
+ def test_password_flag_rotates_keys(self, flag):
|
||||
+ """After --password and a random-key reset, the original password
|
||||
+ will no longer authenticate.
|
||||
+
|
||||
+ Sequence: admin sets password-derived keys -> user kinits -> user
|
||||
+ regenerates random keys -> kinit with the original password must fail.
|
||||
+ """
|
||||
+ self._ensure_keytab_absent()
|
||||
+ principal1 = '{}@{}'.format(GK_USER1, api.env.realm)
|
||||
+ stdin = '{pw}\n{pw}\n'.format(pw=GK_USER_PW)
|
||||
+ result = run_getkeytab(
|
||||
+ ['-s', api.env.host, '-p', GK_USER1,
|
||||
+ '-k', self.keytabname, flag],
|
||||
+ stdin=stdin,
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ with kinit_as_user(principal1, GK_USER_PW):
|
||||
+ regen = run_getkeytab(
|
||||
+ ['-s', api.env.host, '-p', GK_USER1,
|
||||
+ '-k', self.keytabname],
|
||||
+ )
|
||||
+ assert regen.returncode == 0
|
||||
+ with private_ccache():
|
||||
+ kinit_result = ipautil.run(
|
||||
+ ['kinit', principal1],
|
||||
+ stdin=GK_USER_PW + '\n',
|
||||
+ raiseonerr=False,
|
||||
+ capture_output=True,
|
||||
+ capture_error=True,
|
||||
+ )
|
||||
+ assert kinit_result.returncode != 0
|
||||
+ assert (
|
||||
+ 'Password incorrect' in kinit_result.error_output
|
||||
+ or 'Preauthentication failed' in kinit_result.error_output
|
||||
+ )
|
||||
+
|
||||
+ # --- getkeytab_007: -D / -w bind DN error cases ---
|
||||
+
|
||||
+ # Skipped: test_no_ccache_returns_exit6
|
||||
+ # — identical to test_empty_ccache_returns_exit6 above.
|
||||
+ # Skipped: test_valid_dm_credentials_succeed
|
||||
+ # — covered by TestBindMethods.test_retrieval_with_dm_creds.
|
||||
+
|
||||
+ @pytest.mark.parametrize('extra_args,exitcode,message', [
|
||||
+ (['-D', ' ', '-w', GK_USER_PW],
|
||||
+ 9, 'Anonymous Binds are not allowed'),
|
||||
+ (['-D', 'cn=Directory Manager', '-w', ' '],
|
||||
+ 9, 'Simple bind failed'),
|
||||
+ (['-D', 'cn=Directory Manager'],
|
||||
+ 10, 'Bind password required when using a bind DN'),
|
||||
+ ], ids=['empty-dn', 'wrong-password', 'missing-password'])
|
||||
+ def test_binddn_error_cases(self, extra_args, exitcode, message):
|
||||
+ """Bind DN error cases return appropriate exit codes."""
|
||||
+ result = run_getkeytab(
|
||||
+ ['--server', 'localhost',
|
||||
+ '-p', GK_USER1,
|
||||
+ '-k', self.keytabname] + extra_args,
|
||||
+ )
|
||||
+ assert result.returncode == exitcode
|
||||
+ assert message in result.error_output
|
||||
+
|
||||
+
|
||||
+# -----------------------------------------------------------------------
|
||||
+# ipa-rmkeytab tests (bash rmkeytab_001 – rmkeytab_003)
|
||||
+# -----------------------------------------------------------------------
|
||||
+
|
||||
+
|
||||
+@pytest.mark.tier1
|
||||
+@pytest.mark.usefixtures('gk_users')
|
||||
+class test_rmkeytab_cmd(KeytabRetrievalTest):
|
||||
+ """
|
||||
+ ipa-rmkeytab tests (bash rmkeytab_001 – rmkeytab_003).
|
||||
+ """
|
||||
+ command = paths.IPA_RMKEYTAB
|
||||
+
|
||||
+ # --- rmkeytab_001: -p removes a named principal ---
|
||||
+
|
||||
+ def test_invalid_principal_returns_exit5(self):
|
||||
+ """A non-existent principal name returns exit 5."""
|
||||
+ self._get_user1_keytab()
|
||||
+ result = run_rmkeytab(['-p', 'invalidprinc', '-k', self.keytabname])
|
||||
+ assert result.returncode == 5
|
||||
+ assert 'principal not found' in result.error_output
|
||||
+
|
||||
+ def test_valid_principal_removed(self):
|
||||
+ """A present principal is removed and absent from klist."""
|
||||
+ self._get_user1_keytab()
|
||||
+ result = run_rmkeytab(['-p', GK_USER1, '-k', self.keytabname])
|
||||
+ assert result.returncode == 0
|
||||
+ assert (
|
||||
+ 'Removing principal {}'.format(GK_USER1)
|
||||
+ in result.error_output
|
||||
+ )
|
||||
+ assert GK_USER1 not in klist_keytab(self.keytabname).output
|
||||
+
|
||||
+ # --- rmkeytab_002: -r removes all principals of the given realm ---
|
||||
+
|
||||
+ def test_realm_removes_all_principals(self):
|
||||
+ """All principals of the realm are removed and absent from klist."""
|
||||
+ self._get_user1_keytab()
|
||||
+ result = run_rmkeytab(['-r', api.env.realm, '-k', self.keytabname])
|
||||
+ assert result.returncode == 0
|
||||
+ assert (
|
||||
+ 'Removing principal {}@{}'.format(GK_USER1, api.env.realm)
|
||||
+ in result.error_output
|
||||
+ )
|
||||
+ assert api.env.realm not in klist_keytab(self.keytabname).output
|
||||
+
|
||||
+ # --- rmkeytab_003: -k with a non-existent path ---
|
||||
+
|
||||
+ def test_invalid_keytab_path_returns_exit7(self):
|
||||
+ """A non-existent keytab path returns exit 7."""
|
||||
+ self._get_user1_keytab()
|
||||
+ result = run_rmkeytab(
|
||||
+ ['-p', GK_USER1, '-k', '/opt/invalid.keytab']
|
||||
+ )
|
||||
+ assert result.returncode == 7
|
||||
+ assert 'Failed to set cursor' in result.error_output
|
||||
--
|
||||
2.52.0
|
||||
|
||||
285
0037-ipatests-Additional-tests-for-ipa-ipa-migration-test.patch
Normal file
285
0037-ipatests-Additional-tests-for-ipa-ipa-migration-test.patch
Normal file
@ -0,0 +1,285 @@
|
||||
From b6d50197882dd62b5416948cb232c77c0349c0d3 Mon Sep 17 00:00:00 2001
|
||||
From: Sudhir Menon <sumenon@redhat.com>
|
||||
Date: Thu, 5 Mar 2026 19:29:26 +0530
|
||||
Subject: [PATCH] ipatests: Additional tests for ipa-ipa migration
|
||||
testsuite
|
||||
|
||||
Covers tests scenarios for sshpubkey.
|
||||
|
||||
1. Test to check sshpubkey is migrated for regular ipa user
|
||||
2. Test to check sshpubkey is migrated for staged user
|
||||
3. Test to check sshpubkey is migrated for preserved user
|
||||
4. Test to check idoverides is migrated.
|
||||
|
||||
Covers test scenarios for TestIPAMigrationDNSRecords
|
||||
|
||||
1. Test to check dns zone is migrated
|
||||
2. Test to check dynamic update value is preserved when migrated
|
||||
3. Test to check dnsforwardzone is migrated
|
||||
4. Test to check A record is migrated
|
||||
|
||||
Covers test scenarios for IPA Privieleges and Permissions
|
||||
|
||||
1. Test to check that IPA role and its privilege are migrated in Prod mode
|
||||
2. Test to check that PBAC Permissions are migrated in Prod mode
|
||||
3. Test to check that sysaccounts are migrated.
|
||||
4. Test to check selinuxusermap is migrated
|
||||
|
||||
Assisted-by: Claude <noreply@anthropic.com>
|
||||
Signed-off-by: Sudhir Menon <sumenon@redhat.com>
|
||||
Fixes: https://pagure.io/freeipa/issue/9964
|
||||
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
Reviewed-By: Sudhir Menon <sumenon@redhat.com>
|
||||
---
|
||||
.../test_ipa_ipa_migration.py | 164 +++++++++++++++++-
|
||||
1 file changed, 155 insertions(+), 9 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_ipa_ipa_migration.py b/ipatests/test_integration/test_ipa_ipa_migration.py
|
||||
index 18f335de196a9d6c7d30e4adf35385122376b7d9..105c79b610804b68522e568b4028be5a2dc72714 100644
|
||||
--- a/ipatests/test_integration/test_ipa_ipa_migration.py
|
||||
+++ b/ipatests/test_integration/test_ipa_ipa_migration.py
|
||||
@@ -201,6 +201,12 @@ def prepare_ipa_server(master):
|
||||
master.run_command(
|
||||
["ipa", "dnszone-mod", "example.test", "--dynamic-update=TRUE"]
|
||||
)
|
||||
+ master.run_command(
|
||||
+ [
|
||||
+ "ipa", "dnsrecord-add", "example.test", "migratetest",
|
||||
+ "--a-rec", "192.0.2.100",
|
||||
+ ]
|
||||
+ )
|
||||
|
||||
# Add hbac rule
|
||||
master.run_command(["ipa", "hbacrule-add", "--usercat=all", "test1"])
|
||||
@@ -221,7 +227,7 @@ def prepare_ipa_server(master):
|
||||
"dnsforwardzone-add",
|
||||
"forwardzone.test",
|
||||
"--forwarder",
|
||||
- "10.11.12.13",
|
||||
+ "192.168.124.10",
|
||||
]
|
||||
)
|
||||
|
||||
@@ -293,6 +299,11 @@ def prepare_ipa_server(master):
|
||||
]
|
||||
)
|
||||
|
||||
+ # Add sysaccount
|
||||
+ master.run_command(
|
||||
+ ["ipa", "sysaccount-add", "migrate-test-sysaccount", "--random"]
|
||||
+ )
|
||||
+
|
||||
|
||||
def run_migrate(
|
||||
host, mode, remote_host, bind_dn=None, bind_pwd=None, extra_args=None
|
||||
@@ -664,15 +675,15 @@ class TestIPAMigrateCLIOptions(MigrationTest):
|
||||
realm_name = self.master.domain.realm
|
||||
base_dn = str(self.master.domain.basedn)
|
||||
dse_ldif = textwrap.dedent(
|
||||
- f"""
|
||||
+ """
|
||||
dn: cn={realm_name},cn=kerberos,{base_dn}
|
||||
cn: {realm_name}
|
||||
objectClass: top
|
||||
objectClass: krbrealmcontainer
|
||||
"""
|
||||
).format(
|
||||
- realm_name=self.master.domain.realm,
|
||||
- base_dn=str(self.master.domain.basedn),
|
||||
+ realm_name=realm_name,
|
||||
+ base_dn=base_dn,
|
||||
)
|
||||
self.replicas[0].put_file_contents(ldif_file_path, dse_ldif)
|
||||
result = run_migrate(
|
||||
@@ -731,7 +742,6 @@ class TestIPAMigrateCLIOptions(MigrationTest):
|
||||
base_dn = str(self.master.domain.basedn)
|
||||
subtree = 'cn=security,{}'.format(base_dn)
|
||||
params = ['-s', subtree, '-n', '-x']
|
||||
- base_dn = str(self.master.domain.basedn)
|
||||
CUSTOM_SUBTREE_LOG = (
|
||||
"Add db entry 'cn=security,{} - custom'"
|
||||
).format(base_dn)
|
||||
@@ -853,7 +863,7 @@ class TestIPAMigrateCLIOptions(MigrationTest):
|
||||
def test_ipa_migrate_stage_mode_with_cert(self):
|
||||
"""
|
||||
This testcase checks that ipa-migrate command
|
||||
- works without the 'ValuerError'
|
||||
+ works without the 'ValueError'
|
||||
when -Z <cert> option is used with valid cert
|
||||
"""
|
||||
cert_file = '/tmp/ipa.crt'
|
||||
@@ -878,7 +888,7 @@ class TestIPAMigrateCLIOptions(MigrationTest):
|
||||
error when invalid cert is specified with
|
||||
-Z option
|
||||
"""
|
||||
- cert_file = '/tmp/invaid_cert.crt'
|
||||
+ cert_file = '/tmp/invalid_cert.crt'
|
||||
invalid_cert = (
|
||||
b'-----BEGIN CERTIFICATE-----\n'
|
||||
b'MIIFazCCDQYJKoZIhvcNAQELBQAw\n'
|
||||
@@ -1026,6 +1036,59 @@ class TestIPAMigrationProdMode(MigrationTest):
|
||||
assert 'Rule name: readfiles\n' in cmd1.stdout_text
|
||||
assert 'Sudo Command: /usr/bin/less\n' in cmd2.stdout_text
|
||||
|
||||
+ def test_ipa_migrate_prod_mode_roles_privileges(self):
|
||||
+ """
|
||||
+ Test that IPA roles are migrated from remote to local server
|
||||
+ """
|
||||
+ role_name = "junioradmin"
|
||||
+ privilege_name = "User Administrators"
|
||||
+ tasks.kinit_admin(self.replicas[0])
|
||||
+ result = self.replicas[0].run_command(
|
||||
+ ["ipa", "role-show", role_name]
|
||||
+ )
|
||||
+ assert "Role name: {}".format(role_name) in result.stdout_text
|
||||
+ assert "Privileges: {}".format(privilege_name) in result.stdout_text
|
||||
+
|
||||
+ def test_ipa_migrate_prod_mode_permission(self):
|
||||
+ """
|
||||
+ Test that PBAC permission is migrated
|
||||
+ """
|
||||
+ permission_name = "Add Users"
|
||||
+ tasks.kinit_admin(self.replicas[0])
|
||||
+ result = self.replicas[0].run_command(
|
||||
+ ["ipa", "permission-show", permission_name]
|
||||
+ )
|
||||
+ assert (f"Permission name: {permission_name}" in
|
||||
+ result.stdout_text)
|
||||
+
|
||||
+ def test_ipa_migrate_prod_mode_sysaccounts(self):
|
||||
+ """
|
||||
+ Test that system accounts (sysaccounts) are migrated
|
||||
+ from remote server to local server in prod mode.
|
||||
+ """
|
||||
+ sysaccount_name = "migrate-test-sysaccount"
|
||||
+ tasks.kinit_admin(self.replicas[0])
|
||||
+ result = self.replicas[0].run_command(
|
||||
+ ["ipa", "sysaccount-show", sysaccount_name]
|
||||
+ )
|
||||
+ assert sysaccount_name in result.stdout_text
|
||||
+ assert (f"System account ID: {sysaccount_name}" in
|
||||
+ result.stdout_text)
|
||||
+
|
||||
+ def test_ipa_migrate_prod_mode_selinuxusermap(self):
|
||||
+ """
|
||||
+ Test that SELinux usermap is migrated from remote
|
||||
+ to local server.
|
||||
+ """
|
||||
+ usermap_name = "test1"
|
||||
+ tasks.kinit_admin(self.replicas[0])
|
||||
+ result = self.replicas[0].run_command(
|
||||
+ ["ipa", "selinuxusermap-show", usermap_name]
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ assert f"Rule name: {usermap_name}" in result.stdout_text
|
||||
+ assert "xguest_u:s0" in result.stdout_text
|
||||
+
|
||||
def test_ipa_migrate_prod_mode_new_user_sid(self):
|
||||
"""
|
||||
This testcase checks that in prod-mode uid/gid of the
|
||||
@@ -1188,6 +1251,89 @@ class TestIPAMigrationProdMode(MigrationTest):
|
||||
assert DEBUG_LOG in install_msg
|
||||
|
||||
|
||||
+class TestIPAMigrationDNSRecords(MigrationTest):
|
||||
+ """
|
||||
+ Tests to verify all DNS zones, forward zones, and DNS records
|
||||
+ are migrated when ipa-migrate is run with --migrate-dns (-B).
|
||||
+ "By default all DNS entries are migrated" / -B to migrate DNS.
|
||||
+ """
|
||||
+ num_replicas = 1
|
||||
+ num_clients = 1
|
||||
+ topology = "line"
|
||||
+
|
||||
+ @pytest.fixture(autouse=True)
|
||||
+ def run_migration_with_dns(self):
|
||||
+ """
|
||||
+ Run full prod-mode migration with -B so that DNS is migrated
|
||||
+ to the local server. All tests in this class assume DNS has
|
||||
+ been migrated.
|
||||
+ """
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ tasks.kinit_admin(self.replicas[0])
|
||||
+ run_migrate(
|
||||
+ self.replicas[0],
|
||||
+ "prod-mode",
|
||||
+ self.master.hostname,
|
||||
+ "cn=Directory Manager",
|
||||
+ self.master.config.admin_password,
|
||||
+ extra_args=["-B", "-n"],
|
||||
+ )
|
||||
+
|
||||
+ def test_dns_zone_example_test_migrated(self):
|
||||
+ """
|
||||
+ Check that DNS zone example.test (from prepare_ipa_server)
|
||||
+ is migrated to the local server.
|
||||
+ """
|
||||
+ zone_name = "example.test"
|
||||
+ result = self.replicas[0].run_command(
|
||||
+ ["ipa", "dnszone-show", zone_name]
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ assert "Zone name: {}".format(zone_name) in result.stdout_text
|
||||
+
|
||||
+ def test_dns_zone_dynamic_update_preserved(self):
|
||||
+ """
|
||||
+ Check that zone attribute dynamic update is preserved
|
||||
+ (prepare_ipa_server sets dynamic-update=TRUE for example.test).
|
||||
+ """
|
||||
+ zone_name = "example.test"
|
||||
+ result = self.replicas[0].run_command(
|
||||
+ ["ipa", "dnszone-show", zone_name]
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ assert "Dynamic update: True" in result.stdout_text
|
||||
+
|
||||
+ def test_dns_zone_has_system_records(self):
|
||||
+ """
|
||||
+ Check that migrated zone has system records (NS/SOA).
|
||||
+ """
|
||||
+ zone_name = "example.test"
|
||||
+ result = self.replicas[0].run_command(
|
||||
+ ["ipa", "dnsrecord-find", zone_name]
|
||||
+ )
|
||||
+ assert result.returncode == 0
|
||||
+ # Zone should have records (e.g. NS, SOA, or record list)
|
||||
+ assert (
|
||||
+ "NS record" in result.stdout_text
|
||||
+ or "SOA record" in result.stdout_text
|
||||
+ or "Record name" in result.stdout_text
|
||||
+ )
|
||||
+
|
||||
+ def test_dns_record_a_migrated(self):
|
||||
+ """
|
||||
+ Verify that the A record added in prepare_ipa_server is
|
||||
+ migrated to the local server.
|
||||
+ """
|
||||
+ zone_name = "example.test"
|
||||
+ record_name = "migratetest"
|
||||
+ record_value = "192.0.2.100"
|
||||
+ result = self.replicas[0].run_command(
|
||||
+ ["ipa", "dnsrecord-show", zone_name, record_name]
|
||||
+ )
|
||||
+ assert record_name in result.stdout_text
|
||||
+ assert record_value in result.stdout_text
|
||||
+
|
||||
+
|
||||
class TestIPAMigrationWithADtrust(IntegrationTest):
|
||||
"""
|
||||
Test for ipa-migrate tool with IPA Master having trust setup
|
||||
@@ -1319,9 +1465,9 @@ class TestIPAMigratewithBackupRestore(IntegrationTest):
|
||||
DB_LDIF_FILE = '{}-userRoot.ldif'.format(
|
||||
dashed_domain_name
|
||||
)
|
||||
- SCHEMA_LDIF_FILE = '{}''/config_files/schema/99user.ldif'.format(
|
||||
+ SCHEMA_LDIF_FILE = "{}/config_files/schema/99user.ldif".format(
|
||||
dashed_domain_name)
|
||||
- CONFIG_LDIF_FILE = '{}''/config_files/dse.ldif'.format(
|
||||
+ CONFIG_LDIF_FILE = "{}/config_files/dse.ldif".format(
|
||||
dashed_domain_name)
|
||||
param = [
|
||||
'-n', '-g', CONFIG_LDIF_FILE, '-m', SCHEMA_LDIF_FILE,
|
||||
--
|
||||
2.52.0
|
||||
|
||||
288
0038-ipatests-ipa-migrate-ds-test-scenarios.patch
Normal file
288
0038-ipatests-ipa-migrate-ds-test-scenarios.patch
Normal file
@ -0,0 +1,288 @@
|
||||
From 8f2bbb02ae64f322fb34f073d7a7cdb00a69aea0 Mon Sep 17 00:00:00 2001
|
||||
From: Sudhir Menon <sumenon@redhat.com>
|
||||
Date: Thu, 19 Mar 2026 17:12:12 +0530
|
||||
Subject: [PATCH] ipatests: ipa-migrate-ds test scenarios
|
||||
|
||||
This patch tests ipa-migrated-ds related sceanrios
|
||||
|
||||
1. 389-ds instance is setup on client system.
|
||||
2. Attached sample instance1.ldif file in data/ds_migration folder.
|
||||
3. Attempt ipa-migrate-ds with configuration disabled.
|
||||
3. Attempt ipa-migrate-ds over ldaps.
|
||||
|
||||
Assisted by: claude-4.6-opus-high <noreply@anthropic.com>
|
||||
Signed-off-by: Sudhir Menon <sumenon@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <flo@redhat.com>
|
||||
---
|
||||
ipatests/setup.py | 2 +-
|
||||
.../data/ds_migration/instance1.ldif | 48 +++++
|
||||
.../test_integration/test_ds_migration.py | 189 ++++++++++++++++++
|
||||
3 files changed, 238 insertions(+), 1 deletion(-)
|
||||
create mode 100644 ipatests/test_integration/data/ds_migration/instance1.ldif
|
||||
create mode 100644 ipatests/test_integration/test_ds_migration.py
|
||||
|
||||
diff --git a/ipatests/setup.py b/ipatests/setup.py
|
||||
index 0aec4a70d..07595a501 100644
|
||||
--- a/ipatests/setup.py
|
||||
+++ b/ipatests/setup.py
|
||||
@@ -56,7 +56,7 @@ if __name__ == '__main__':
|
||||
'ipatests': ['prci_definitions/*'],
|
||||
'ipatests.test_custodia': ['*.conf', 'empty.conf.d/*.conf'],
|
||||
'ipatests.test_install': ['*.update'],
|
||||
- 'ipatests.test_integration': ['scripts/*'],
|
||||
+ 'ipatests.test_integration': ['data/*/*.ldif'],
|
||||
'ipatests.test_ipaclient': ['data/*/*/*'],
|
||||
'ipatests.test_ipalib': ['data/*'],
|
||||
'ipatests.test_ipaplatform': ['data/*'],
|
||||
diff --git a/ipatests/test_integration/data/ds_migration/instance1.ldif b/ipatests/test_integration/data/ds_migration/instance1.ldif
|
||||
new file mode 100644
|
||||
index 000000000..a148b52c8
|
||||
--- /dev/null
|
||||
+++ b/ipatests/test_integration/data/ds_migration/instance1.ldif
|
||||
@@ -0,0 +1,48 @@
|
||||
+# Minimal DS migration test data: base OUs and sample user/group
|
||||
+# Used when setting up 389-ds on the client for migrate-ds tests.
|
||||
+# Base suffix dc=testrealm,dc=test is created by dscreate.
|
||||
+
|
||||
+dn: dc=testrealm,dc=test
|
||||
+objectClass: top
|
||||
+objectClass: domain
|
||||
+objectClass: dcObject
|
||||
+dc: testrealm
|
||||
+
|
||||
+dn: ou=People,dc=testrealm,dc=test
|
||||
+objectClass: top
|
||||
+objectClass: organizationalUnit
|
||||
+ou: People
|
||||
+
|
||||
+dn: ou=Groups,dc=testrealm,dc=test
|
||||
+objectClass: top
|
||||
+objectClass: organizationalUnit
|
||||
+ou: Groups
|
||||
+
|
||||
+dn: cn=Directory Administrators, ou=Groups, dc=testrealm,dc=test
|
||||
+cn: Directory Administrators
|
||||
+objectclass: top
|
||||
+objectclass: groupofuniquenames
|
||||
+ou: Groups
|
||||
+uniquemember: uid=ldapuser_0001, ou=People, dc=testrealm,dc=test
|
||||
+
|
||||
+dn: uid=ldapuser_0001,ou=People,dc=testrealm,dc=test
|
||||
+objectClass: top
|
||||
+objectClass: person
|
||||
+objectClass: posixAccount
|
||||
+uid: ldapuser_0001
|
||||
+cn: LDAP User 1
|
||||
+sn: User 1
|
||||
+uidNumber: 1001
|
||||
+gidNumber: 1001
|
||||
+homeDirectory: /home/ldapuser_0001
|
||||
+userPassword: fo0m4nchU
|
||||
+telephonenumber: +1 123 444 555
|
||||
+
|
||||
+dn: cn=ldapgroup_0001,ou=Groups,dc=testrealm,dc=test
|
||||
+objectClass: top
|
||||
+objectClass: groupOfNames
|
||||
+objectClass: posixGroup
|
||||
+cn: ldapgroup_0001
|
||||
+gidNumber: 1001
|
||||
+member: uid=ldapuser_0001,ou=People,dc=testrealm,dc=test
|
||||
+
|
||||
diff --git a/ipatests/test_integration/test_ds_migration.py b/ipatests/test_integration/test_ds_migration.py
|
||||
new file mode 100644
|
||||
index 000000000..f16759b37
|
||||
--- /dev/null
|
||||
+++ b/ipatests/test_integration/test_ds_migration.py
|
||||
@@ -0,0 +1,189 @@
|
||||
+#
|
||||
+# Copyright (C) 2026 FreeIPA Contributors see COPYING for license
|
||||
+#
|
||||
+
|
||||
+"""
|
||||
+ipa-migrate-ds migration acceptance tests.
|
||||
+"""
|
||||
+
|
||||
+from __future__ import absolute_import
|
||||
+
|
||||
+import os
|
||||
+import textwrap
|
||||
+
|
||||
+from ipatests.test_integration.base import IntegrationTest
|
||||
+from ipatests.pytest_ipa.integration import tasks
|
||||
+
|
||||
+# 389-ds instance name and base DN on the client
|
||||
+DS_INSTANCE_NAME = "dsinstance_01"
|
||||
+DS_BASEDN = "dc=testrealm,dc=test"
|
||||
+DS_PORT = 389
|
||||
+DS_SECURE_PORT = 636
|
||||
+
|
||||
+
|
||||
+def _setup_389ds_on_client(client, admin_password):
|
||||
+ """
|
||||
+ Install 389 Directory Server on the client and load migration
|
||||
+ test data from instance1.ldif i.e (ou=People, ou=groups,
|
||||
+ sample user/group).
|
||||
+ """
|
||||
+ tasks.install_packages(client, ["389-ds-base"])
|
||||
+
|
||||
+ # Create instance via dscreate
|
||||
+ inf_content = textwrap.dedent("""\
|
||||
+ [general]
|
||||
+ full_machine_name = {hostname}
|
||||
+ [slapd]
|
||||
+ instance_name = {instance}
|
||||
+ port = {port}
|
||||
+ secure_port = {secure_port}
|
||||
+ root_dn = cn=Directory Manager
|
||||
+ root_password = {password}
|
||||
+ [backend-userroot]
|
||||
+ sample_entries = no
|
||||
+ suffix = {basedn}
|
||||
+ """).format(
|
||||
+ hostname=client.hostname,
|
||||
+ instance=DS_INSTANCE_NAME,
|
||||
+ port=DS_PORT,
|
||||
+ secure_port=DS_SECURE_PORT,
|
||||
+ password=admin_password,
|
||||
+ basedn=DS_BASEDN,
|
||||
+ )
|
||||
+ client.put_file_contents("/tmp/ds-instance.inf", inf_content)
|
||||
+ client.run_command(
|
||||
+ ["dscreate", "from-file", "/tmp/ds-instance.inf"]
|
||||
+ )
|
||||
+
|
||||
+ # Load migration test data from instance1.ldif
|
||||
+ test_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
+ ldif_path = os.path.join(
|
||||
+ test_dir, "data", "ds_migration", "instance1.ldif"
|
||||
+ )
|
||||
+ with open(ldif_path) as f:
|
||||
+ ldif_content = f.read()
|
||||
+ client.put_file_contents("/tmp/instance1.ldif", ldif_content)
|
||||
+ client.run_command(
|
||||
+ [
|
||||
+ "/usr/bin/ldapmodify",
|
||||
+ "-a", "-x", "-H", "ldap://localhost:{}".format(DS_PORT),
|
||||
+ "-D", "cn=Directory Manager", "-w", admin_password,
|
||||
+ "-f", "/tmp/instance1.ldif",
|
||||
+ ]
|
||||
+ )
|
||||
+
|
||||
+
|
||||
+class TestDSMigrationConfig(IntegrationTest):
|
||||
+ """
|
||||
+ Test ipa migrate-ds related scenarios.
|
||||
+
|
||||
+ Uses a client host with 389-ds populated from instance1.ldif
|
||||
+ (ou=People, ou=groups, dc=testrealm,dc=test) for migration tests.
|
||||
+ """
|
||||
+
|
||||
+ topology = "line"
|
||||
+ num_replicas = 0
|
||||
+ num_clients = 1
|
||||
+
|
||||
+ @classmethod
|
||||
+ def install(cls, mh):
|
||||
+ # Install master and IPA client (full topology)
|
||||
+ super(TestDSMigrationConfig, cls).install(mh)
|
||||
+ # On the client host, set up 389-ds with migration test data
|
||||
+ _setup_389ds_on_client(
|
||||
+ cls.clients[0],
|
||||
+ cls.master.config.admin_password,
|
||||
+ )
|
||||
+
|
||||
+ def test_attempt_migration_with_configuration_false(self):
|
||||
+ """
|
||||
+ Test attempts ipa-migrate-ds with migration disabled.
|
||||
+ """
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ # Ensure migration is disabled
|
||||
+ cmd = ["ipa", "config-mod", "--enable-migration", "FALSE"]
|
||||
+ error_msg = "ipa: ERROR: no modifications to be performed"
|
||||
+ result = self.master.run_command(cmd, raiseonerr=False)
|
||||
+ assert result.returncode != 0
|
||||
+ tasks.assert_error(result, error_msg)
|
||||
+ client_host = self.clients[0].hostname
|
||||
+ ldap_uri = "ldap://{}:{}".format(client_host, DS_PORT)
|
||||
+ result = self.master.run_command(
|
||||
+ [
|
||||
+ "ipa",
|
||||
+ "migrate-ds",
|
||||
+ "--user-container=ou=People",
|
||||
+ "--group-container=ou=groups",
|
||||
+ ldap_uri,
|
||||
+ ],
|
||||
+ stdin_text=self.master.config.admin_password,
|
||||
+ raiseonerr=False,
|
||||
+ )
|
||||
+ assert (
|
||||
+ result.returncode != 0
|
||||
+ ), "migrate-ds should fail when migration is disabled"
|
||||
+ assert "migration mode is disabled" in (
|
||||
+ result.stdout_text + result.stderr_text
|
||||
+ ).lower()
|
||||
+
|
||||
+ def test_migration_over_ldaps(self):
|
||||
+ """
|
||||
+ Migrate from the client's 389-ds over LDAPS (port 636).
|
||||
+ """
|
||||
+ ca_cert_file = "/etc/ipa/remoteds.crt"
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "config-mod", "--enable-migration", "TRUE"],
|
||||
+ )
|
||||
+
|
||||
+ # Copy 389-ds CA cert from client to master for LDAPS verification
|
||||
+ client = self.clients[0]
|
||||
+ ds_cert_dir = "/etc/dirsrv/slapd-{}".format(DS_INSTANCE_NAME)
|
||||
+ cert_result = client.run_command(
|
||||
+ [
|
||||
+ "certutil", "-d", ds_cert_dir, "-L", "-n",
|
||||
+ "Self-Signed-CA", "-a",
|
||||
+ ],
|
||||
+ )
|
||||
+ self.master.put_file_contents(
|
||||
+ ca_cert_file, cert_result.stdout_text
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ ["restorecon", ca_cert_file], raiseonerr=False
|
||||
+ )
|
||||
+
|
||||
+ client_host = client.hostname
|
||||
+ ldaps_uri = "ldaps://{}:{}".format(client_host, DS_SECURE_PORT)
|
||||
+ user_container = "ou=People,{}".format(DS_BASEDN)
|
||||
+ group_container = "ou=groups,{}".format(DS_BASEDN)
|
||||
+
|
||||
+ self.master.run_command(
|
||||
+ [
|
||||
+ "ipa",
|
||||
+ "migrate-ds",
|
||||
+ "--with-compat",
|
||||
+ "--user-container",
|
||||
+ user_container,
|
||||
+ "--group-container",
|
||||
+ group_container,
|
||||
+ ldaps_uri,
|
||||
+ "--ca-cert-file",
|
||||
+ ca_cert_file,
|
||||
+ ],
|
||||
+ stdin_text=self.master.config.admin_password,
|
||||
+ )
|
||||
+ # Verify migrated user and group from instance1.ldif
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "user-show", "ldapuser_0001"]
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "group-show", "ldapgroup_0001"]
|
||||
+ )
|
||||
+
|
||||
+ # Clean up migrated user and group
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "user-del", "ldapuser_0001"], raiseonerr=False
|
||||
+ )
|
||||
+ self.master.run_command(
|
||||
+ ["ipa", "group-del", "ldapgroup_0001"], raiseonerr=False
|
||||
+ )
|
||||
--
|
||||
2.52.0
|
||||
|
||||
506
0039-ipatests-Add-ipa-selfservice-show-and-selfservice-mo.patch
Normal file
506
0039-ipatests-Add-ipa-selfservice-show-and-selfservice-mo.patch
Normal file
@ -0,0 +1,506 @@
|
||||
From 51e669d46f912d39841bc40560682f9f414f68fe Mon Sep 17 00:00:00 2001
|
||||
From: Jay Gondaliya <jgondali@redhat.com>
|
||||
Date: Mon, 16 Mar 2026 15:03:17 +0530
|
||||
Subject: [PATCH] ipatests: Add ipa selfservice-show and
|
||||
selfservice-mod CLI tests to xmlrpc
|
||||
|
||||
Add two new Declarative test classes to test_selfservice_plugin.py covering the selfservice-show and selfservice-mod CLI command options, converted from the legacy bash test suite (selfservice_show_1001-1003 and selfservice_mod_1002-1008).
|
||||
|
||||
test_selfservice_show_cli covers:
|
||||
- selfservice-show --all
|
||||
- selfservice-show --all --raw
|
||||
- selfservice-show --raw
|
||||
|
||||
test_selfservice_mod_cli covers:
|
||||
- selfservice-mod with invalid attrs (negative)
|
||||
- selfservice-mod with invalid permissions (negative)
|
||||
- selfservice-mod with no changes / EmptyModlist (negative)
|
||||
- selfservice-mod with bad attrs must not delete the entry (BZ 747741)
|
||||
- selfservice-mod changing and expanding attrs (positive)
|
||||
- selfservice-mod with invalid/same permissions (negative)
|
||||
- selfservice-mod changing and expanding permissions (positive)
|
||||
|
||||
Tests for selfservice_mod_1001 and selfservice_mod_1009, which require interactive input and are not valid in a non-interactive context, are covered as integration tests in test_commands.py by test_selfservice_mod_no_attrs_or_permissions_all and test_selfservice_mod_no_attrs_or_permissions_raw. Tests for BZ 772675 (mod --raw) and BZ 747741 (bad attrs) cross-reference existing coverage in test_selfservice_misc.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9945
|
||||
Assisted-by: Claude <noreply@anthropic.com>
|
||||
Signed-off-by: Jay Gondaliya <jgondali@redhat.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_commands.py | 54 +++
|
||||
.../test_xmlrpc/test_selfservice_plugin.py | 396 ++++++++++++++++++
|
||||
2 files changed, 450 insertions(+)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_commands.py b/ipatests/test_integration/test_commands.py
|
||||
index 6d7ee8f2d..5021747b6 100644
|
||||
--- a/ipatests/test_integration/test_commands.py
|
||||
+++ b/ipatests/test_integration/test_commands.py
|
||||
@@ -391,6 +391,60 @@ class TestIPACommand(IntegrationTest):
|
||||
assert result.returncode == 1
|
||||
assert "Number of permissions added 0" in result.stdout_text
|
||||
|
||||
+ def test_selfservice_mod_no_attrs_or_permissions_all(self):
|
||||
+ """selfservice-mod --all with no --attrs or --permissions fails.
|
||||
+
|
||||
+ When selfservice-mod is invoked with only --all and neither --attrs
|
||||
+ nor --permissions is supplied, the CLI has no modifications to apply
|
||||
+ and must return a non-zero exit code with an appropriate error message.
|
||||
+ """
|
||||
+ entry = 'test_selfservice_mod_all'
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ self.master.run_command(
|
||||
+ ['ipa', 'selfservice-add', entry,
|
||||
+ '--attrs=l', '--permissions=write']
|
||||
+ )
|
||||
+ try:
|
||||
+ result = self.master.run_command(
|
||||
+ ['ipa', 'selfservice-mod', entry, '--all'],
|
||||
+ stdin_text='\n',
|
||||
+ raiseonerr=False,
|
||||
+ )
|
||||
+ assert result.returncode != 0
|
||||
+ assert 'no modifications to be performed' in result.stderr_text
|
||||
+ finally:
|
||||
+ self.master.run_command(
|
||||
+ ['ipa', 'selfservice-del', entry],
|
||||
+ raiseonerr=False,
|
||||
+ )
|
||||
+
|
||||
+ def test_selfservice_mod_no_attrs_or_permissions_raw(self):
|
||||
+ """selfservice-mod --raw with no --attrs or --permissions fails.
|
||||
+
|
||||
+ When selfservice-mod is invoked with only --raw and neither --attrs
|
||||
+ nor --permissions is supplied, the CLI has no modifications to apply
|
||||
+ and must return a non-zero exit code with an appropriate error message.
|
||||
+ """
|
||||
+ entry = 'test_selfservice_mod_raw'
|
||||
+ tasks.kinit_admin(self.master)
|
||||
+ self.master.run_command(
|
||||
+ ['ipa', 'selfservice-add', entry,
|
||||
+ '--attrs=l', '--permissions=write']
|
||||
+ )
|
||||
+ try:
|
||||
+ result = self.master.run_command(
|
||||
+ ['ipa', 'selfservice-mod', entry, '--raw'],
|
||||
+ stdin_text='\n',
|
||||
+ raiseonerr=False,
|
||||
+ )
|
||||
+ assert result.returncode != 0
|
||||
+ assert 'no modifications to be performed' in result.stderr_text
|
||||
+ finally:
|
||||
+ self.master.run_command(
|
||||
+ ['ipa', 'selfservice-del', entry],
|
||||
+ raiseonerr=False,
|
||||
+ )
|
||||
+
|
||||
def test_change_sysaccount_password_issue7561(self):
|
||||
sysuser = 'system'
|
||||
original_passwd = 'Secret123'
|
||||
diff --git a/ipatests/test_xmlrpc/test_selfservice_plugin.py b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
index 8f2307a20..48dfd7cc3 100644
|
||||
--- a/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
+++ b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
@@ -942,3 +942,399 @@ class test_selfservice_cli_add_del(Declarative):
|
||||
),
|
||||
|
||||
]
|
||||
+
|
||||
+
|
||||
+# selfservice-show & selfservice-mod CLI test rule names
|
||||
+
|
||||
+SS_CLI_SHOW = 'SELFSERVICE_SHOW_TEST'
|
||||
+
|
||||
+
|
||||
+@pytest.mark.tier1
|
||||
+class test_selfservice_show_cli(Declarative):
|
||||
+ """Test selfservice-show CLI options."""
|
||||
+
|
||||
+ cleanup_commands = [
|
||||
+ ('selfservice_del', [SS_CLI_SHOW], {}),
|
||||
+ ]
|
||||
+
|
||||
+ tests = [
|
||||
+ dict(
|
||||
+ desc='Create %r for show tests' % SS_CLI_SHOW,
|
||||
+ command=(
|
||||
+ 'selfservice_add',
|
||||
+ [SS_CLI_SHOW],
|
||||
+ dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions='write',
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_SHOW,
|
||||
+ summary='Added selfservice "%s"' % SS_CLI_SHOW,
|
||||
+ result=dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions=['write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_SHOW,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Show with --all (positive test)
|
||||
+ dict(
|
||||
+ desc='Show %r with --all' % SS_CLI_SHOW,
|
||||
+ command=(
|
||||
+ 'selfservice_show',
|
||||
+ [SS_CLI_SHOW],
|
||||
+ {'all': True},
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_SHOW,
|
||||
+ summary=None,
|
||||
+ result=dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions=['write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_SHOW,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Show with --all and --raw (positive test)
|
||||
+ dict(
|
||||
+ desc='Show %r with --all and --raw' % SS_CLI_SHOW,
|
||||
+ command=(
|
||||
+ 'selfservice_show',
|
||||
+ [SS_CLI_SHOW],
|
||||
+ {'all': True, 'raw': True},
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_SHOW,
|
||||
+ summary=None,
|
||||
+ result={
|
||||
+ 'aci': '(targetattr = "l")'
|
||||
+ '(version 3.0;acl '
|
||||
+ '"selfservice:%s";'
|
||||
+ 'allow (write) '
|
||||
+ 'userdn = "ldap:///self";)'
|
||||
+ % SS_CLI_SHOW,
|
||||
+ },
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Show with --raw (positive test)
|
||||
+ dict(
|
||||
+ desc='Show %r with --raw' % SS_CLI_SHOW,
|
||||
+ command=(
|
||||
+ 'selfservice_show',
|
||||
+ [SS_CLI_SHOW],
|
||||
+ {'raw': True},
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_SHOW,
|
||||
+ summary=None,
|
||||
+ result={
|
||||
+ 'aci': '(targetattr = "l")'
|
||||
+ '(version 3.0;acl '
|
||||
+ '"selfservice:%s";'
|
||||
+ 'allow (write) '
|
||||
+ 'userdn = "ldap:///self";)'
|
||||
+ % SS_CLI_SHOW,
|
||||
+ },
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ dict(
|
||||
+ desc='Delete %r' % SS_CLI_SHOW,
|
||||
+ command=('selfservice_del', [SS_CLI_SHOW], {}),
|
||||
+ expected=dict(
|
||||
+ result=True,
|
||||
+ value=SS_CLI_SHOW,
|
||||
+ summary='Deleted selfservice "%s"' % SS_CLI_SHOW,
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ ]
|
||||
+
|
||||
+
|
||||
+SS_CLI_MOD = 'SELFSERVICE_MOD_TEST'
|
||||
+
|
||||
+
|
||||
+@pytest.mark.tier1
|
||||
+class test_selfservice_mod_cli(Declarative):
|
||||
+ """Test selfservice-mod CLI options."""
|
||||
+
|
||||
+ cleanup_commands = [
|
||||
+ ('selfservice_del', [SS_CLI_MOD], {}),
|
||||
+ ]
|
||||
+
|
||||
+ tests = [
|
||||
+ dict(
|
||||
+ desc='Create %r for mod tests' % SS_CLI_MOD,
|
||||
+ command=(
|
||||
+ 'selfservice_add',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions='write',
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_MOD,
|
||||
+ summary='Added selfservice "%s"' % SS_CLI_MOD,
|
||||
+ result=dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions=['write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_MOD,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # test_selfservice_mod_no_attrs_or_permissions_all and
|
||||
+ # test_selfservice_mod_no_attrs_or_permissions_raw are covered in
|
||||
+ # integration tests in test_commands.py since they are
|
||||
+ # interactive tests.
|
||||
+
|
||||
+ # Modify with --all --attrs=badattr --permissions=write --raw
|
||||
+ # (negative test - invalid attr value)
|
||||
+ dict(
|
||||
+ desc='Try to modify %r with invalid attrs' % SS_CLI_MOD,
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(
|
||||
+ attrs=['badattr'],
|
||||
+ permissions='write',
|
||||
+ all=True,
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=errors.InvalidSyntax(
|
||||
+ attr=(
|
||||
+ r'targetattr "badattr" does not exist in schema. '
|
||||
+ r'Please add attributeTypes "badattr" to '
|
||||
+ r'schema if necessary. '
|
||||
+ r'ACL Syntax Error(-5):'
|
||||
+ r'(targetattr = \22badattr\22)'
|
||||
+ r'(version 3.0;acl '
|
||||
+ r'\22selfservice:%s\22;'
|
||||
+ r'allow (write) userdn = \22ldap:///self\22;)'
|
||||
+ ) % SS_CLI_MOD,
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Modify with --all --attrs=l --permissions=badperm --raw
|
||||
+ # (negative test - invalid permission value)
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Try to modify %r with invalid permissions'
|
||||
+ % SS_CLI_MOD
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions='badperm',
|
||||
+ all=True,
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=errors.ValidationError(
|
||||
+ name='permissions',
|
||||
+ error='"badperm" is not a valid permission',
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Modify with same attrs and perms already set
|
||||
+ # (negative test - no modifications error)
|
||||
+ dict(
|
||||
+ desc='Try to modify %r with no changes' % SS_CLI_MOD,
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions='write',
|
||||
+ all=True,
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=errors.EmptyModlist(),
|
||||
+ ),
|
||||
+
|
||||
+ # Modify with --attrs=badattrs (negative test,
|
||||
+ # BZ 747741 - a bad attrs mod must not delete the entry).
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Try to modify %r with bad attrs (BZ 747741)'
|
||||
+ % SS_CLI_MOD
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(attrs=['badattrs']),
|
||||
+ ),
|
||||
+ expected=errors.InvalidSyntax(
|
||||
+ attr=(
|
||||
+ r'targetattr "badattrs" does not exist in schema. '
|
||||
+ r'Please add attributeTypes "badattrs" to '
|
||||
+ r'schema if necessary. '
|
||||
+ r'ACL Syntax Error(-5):'
|
||||
+ r'(targetattr = \22badattrs\22)'
|
||||
+ r'(version 3.0;acl '
|
||||
+ r'\22selfservice:%s\22;'
|
||||
+ r'allow (write) userdn = \22ldap:///self\22;)'
|
||||
+ ) % SS_CLI_MOD,
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Verify entry still exists after failed mod (BZ 747741)
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Verify %r still exists after failed mod'
|
||||
+ % SS_CLI_MOD
|
||||
+ ),
|
||||
+ command=('selfservice_show', [SS_CLI_MOD], {}),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_MOD,
|
||||
+ summary=None,
|
||||
+ result=dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions=['write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_MOD,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Modify with --attrs=mobile (positive test - change attr)
|
||||
+ dict(
|
||||
+ desc='Modify %r attrs to mobile' % SS_CLI_MOD,
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(attrs=['mobile']),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_MOD,
|
||||
+ summary='Modified selfservice "%s"' % SS_CLI_MOD,
|
||||
+ result=dict(
|
||||
+ attrs=['mobile'],
|
||||
+ permissions=['write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_MOD,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Modify with --attrs={mobile,l} (positive test - add attr)
|
||||
+ dict(
|
||||
+ desc='Modify %r attrs to mobile and l' % SS_CLI_MOD,
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(attrs=['mobile', 'l']),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_MOD,
|
||||
+ summary='Modified selfservice "%s"' % SS_CLI_MOD,
|
||||
+ result=dict(
|
||||
+ attrs=['mobile', 'l'],
|
||||
+ permissions=['write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_MOD,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Modify with --permissions=badperm
|
||||
+ # (negative test - invalid permission string)
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Try to modify %r with invalid permissions'
|
||||
+ % SS_CLI_MOD
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(permissions='badperm'),
|
||||
+ ),
|
||||
+ expected=errors.ValidationError(
|
||||
+ name='permissions',
|
||||
+ error='"badperm" is not a valid permission',
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Modify with --permissions=write
|
||||
+ # (negative test - same perm already set, no modification)
|
||||
+ dict(
|
||||
+ desc='Try to modify %r with same permissions' % SS_CLI_MOD,
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(permissions='write'),
|
||||
+ ),
|
||||
+ expected=errors.EmptyModlist(),
|
||||
+ ),
|
||||
+
|
||||
+ # Modify with --permissions=read (positive test - change perm)
|
||||
+ dict(
|
||||
+ desc='Modify %r permissions to read' % SS_CLI_MOD,
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(permissions='read'),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_MOD,
|
||||
+ summary='Modified selfservice "%s"' % SS_CLI_MOD,
|
||||
+ result=dict(
|
||||
+ attrs=['mobile', 'l'],
|
||||
+ permissions=['read'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_MOD,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Modify with --permissions={read,write} (positive test - add perm)
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Modify %r permissions to read and write'
|
||||
+ % SS_CLI_MOD
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_mod',
|
||||
+ [SS_CLI_MOD],
|
||||
+ dict(permissions=['read', 'write']),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_MOD,
|
||||
+ summary='Modified selfservice "%s"' % SS_CLI_MOD,
|
||||
+ result=dict(
|
||||
+ attrs=['mobile', 'l'],
|
||||
+ permissions=['read', 'write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_MOD,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # test_selfservice_mod_no_attrs_or_permissions_all and
|
||||
+ # test_selfservice_mod_no_attrs_or_permissions_raw are covered in
|
||||
+ # integration tests in test_commands.py since they are
|
||||
+ # interactive tests.
|
||||
+
|
||||
+ dict(
|
||||
+ desc='Delete %r' % SS_CLI_MOD,
|
||||
+ command=('selfservice_del', [SS_CLI_MOD], {}),
|
||||
+ expected=dict(
|
||||
+ result=True,
|
||||
+ value=SS_CLI_MOD,
|
||||
+ summary='Deleted selfservice "%s"' % SS_CLI_MOD,
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ ]
|
||||
--
|
||||
2.52.0
|
||||
|
||||
506
0040-ipatests-Add-selfservice-find-cli-tests-to-xmlrpc-Ad.patch
Normal file
506
0040-ipatests-Add-selfservice-find-cli-tests-to-xmlrpc-Ad.patch
Normal file
@ -0,0 +1,506 @@
|
||||
From c294159878de52fa5762025ee4be893f280c5320 Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Tue, 31 Mar 2026 15:38:36 +0200
|
||||
Subject: [PATCH] ipatests: Add selfservice-find cli tests to xmlrpc
|
||||
Add a new Declarative test class test_selfservice_cli_find covering CLI-level
|
||||
behaviour of the selfservice-find command:
|
||||
|
||||
- find with --all succeeds and returns the matching rule
|
||||
- non-existent or wrong attrs filter returns zero results
|
||||
- bad --name filter returns zero results with valid and invalid
|
||||
positional arg
|
||||
- bad permissions filter with --all --raw returns zero results,
|
||||
no internal error (BZ 747693)
|
||||
- all valid params with --all --raw succeeds and returns the raw
|
||||
ACI string (BZ 747693)
|
||||
- wrong or non-existent attrs filter returns zero results with and
|
||||
without name arg
|
||||
- valid --attrs filter succeeds with positional name arg and with
|
||||
--name option
|
||||
- valid --name filter succeeds and returns the matching rule
|
||||
- bad permissions filter returns zero results
|
||||
- valid --permissions filter succeeds with positional name arg and
|
||||
with --name option
|
||||
- --raw only succeeds and returns the raw ACI string, no internal
|
||||
error (BZ 747693)
|
||||
|
||||
Signed-off-by: Jay Gondaliya <jgondali@redhat.com>
|
||||
Fixes: https://pagure.io/freeipa/issue/9945
|
||||
Assisted-by: Claude <noreply@anthropic.com>
|
||||
Reviewed-By: Florence Blanc-Renaud <frenaud@redhat.com>
|
||||
Reviewed-By: Rafael Guterres Jeffman <rjeffman@redhat.com>
|
||||
Reviewed-By: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
.../test_xmlrpc/test_selfservice_plugin.py | 456 ++++++++++++++++++
|
||||
1 file changed, 456 insertions(+)
|
||||
|
||||
diff --git a/ipatests/test_xmlrpc/test_selfservice_plugin.py b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
index 48dfd7cc3..9e921156a 100644
|
||||
--- a/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
+++ b/ipatests/test_xmlrpc/test_selfservice_plugin.py
|
||||
@@ -944,6 +944,462 @@ class test_selfservice_cli_add_del(Declarative):
|
||||
]
|
||||
|
||||
|
||||
+# selfservice-find CLI test rule name
|
||||
+SS_CLI_FIND = 'SELFSERVICE_FIND_TEST'
|
||||
+
|
||||
+
|
||||
+@pytest.mark.tier1
|
||||
+class test_selfservice_cli_find(Declarative):
|
||||
+ """Tests for the selfservice-find CLI command."""
|
||||
+
|
||||
+ cleanup_commands = [
|
||||
+ ('selfservice_del', [SS_CLI_FIND], {}),
|
||||
+ ]
|
||||
+
|
||||
+ tests = [
|
||||
+
|
||||
+ # Setup: create the rule used by all find tests
|
||||
+ dict(
|
||||
+ desc='Setup: create %r' % SS_CLI_FIND,
|
||||
+ command=(
|
||||
+ 'selfservice_add',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(attrs=['l'], permissions='write'),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ value=SS_CLI_FIND,
|
||||
+ summary='Added selfservice "%s"' % SS_CLI_FIND,
|
||||
+ result=dict(
|
||||
+ attrs=['l'],
|
||||
+ permissions=['write'],
|
||||
+ selfaci=True,
|
||||
+ aciname=SS_CLI_FIND,
|
||||
+ ),
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Find with --all returns the parsed result
|
||||
+ dict(
|
||||
+ desc='Search for %r with --all' % SS_CLI_FIND,
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(all=True),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=1,
|
||||
+ truncated=False,
|
||||
+ summary='1 selfservice matched',
|
||||
+ result=[{
|
||||
+ 'attrs': ['l'],
|
||||
+ 'permissions': ['write'],
|
||||
+ 'selfaci': True,
|
||||
+ 'aciname': SS_CLI_FIND,
|
||||
+ }],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Bad attrs filter -- aci_find does pure string
|
||||
+ # comparison; no schema validation in find.
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Non-existent attr with all filters'
|
||||
+ ' returns no match (--all --raw)'
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(
|
||||
+ all=True,
|
||||
+ attrs=['badattrs'],
|
||||
+ aciname=SS_CLI_FIND,
|
||||
+ permissions='write',
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Wrong attr for this rule (has 'l', not 'mobile')
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Wrong attr for rule with all filters'
|
||||
+ ' returns no match (--all --raw)'
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(
|
||||
+ all=True,
|
||||
+ attrs=['mobile'],
|
||||
+ aciname=SS_CLI_FIND,
|
||||
+ permissions='write',
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Bad --name filter with --all --raw
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Valid name arg with bad --name filter'
|
||||
+ ' returns no match (--all --raw)'
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(
|
||||
+ all=True,
|
||||
+ attrs=['l'],
|
||||
+ aciname='badname',
|
||||
+ permissions='write',
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Bad name arg also set to 'badname'
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Bad name arg with bad --name filter'
|
||||
+ ' returns no match (--all --raw)'
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ ['badname'],
|
||||
+ dict(
|
||||
+ all=True,
|
||||
+ attrs=['l'],
|
||||
+ aciname='badname',
|
||||
+ permissions='write',
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Bad permissions with --all --raw (BZ 747693)
|
||||
+ # selfservice-find --raw must not return "internal error".
|
||||
+ # aci_find treats permissions as a plain string filter (no
|
||||
+ # validation), so 'badperm' simply matches nothing.
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Bad permissions with --all --raw'
|
||||
+ ' returns no match (BZ 747693)'
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(
|
||||
+ all=True,
|
||||
+ attrs=['l'],
|
||||
+ aciname=SS_CLI_FIND,
|
||||
+ permissions='badperm',
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # All valid params with --all --raw (BZ 747693)
|
||||
+ # selfservice-find --raw must not return "internal error".
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'All valid params with --all --raw'
|
||||
+ ' returns raw ACI (BZ 747693)'
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(
|
||||
+ all=True,
|
||||
+ attrs=['l'],
|
||||
+ aciname=SS_CLI_FIND,
|
||||
+ permissions='write',
|
||||
+ raw=True,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=1,
|
||||
+ truncated=False,
|
||||
+ summary='1 selfservice matched',
|
||||
+ result=[{
|
||||
+ 'aci': (
|
||||
+ '(targetattr = "l")'
|
||||
+ '(version 3.0;acl "selfservice:%s";'
|
||||
+ 'allow (write) '
|
||||
+ 'userdn = "ldap:///self";)'
|
||||
+ % SS_CLI_FIND
|
||||
+ ),
|
||||
+ }],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Bad attrs filter without --all --raw
|
||||
+ dict(
|
||||
+ desc='Wrong attr filter returns no match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(attrs=['mobile']),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Non-existent attr in filter
|
||||
+ dict(
|
||||
+ desc='Non-existent attr filter returns no match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(attrs=['badattrs']),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Non-existent attr without name arg
|
||||
+ dict(
|
||||
+ desc='Non-existent attr without name arg returns no match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [],
|
||||
+ dict(attrs=['badattrs']),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Valid attrs filter
|
||||
+ dict(
|
||||
+ desc='Valid attrs filter with name arg returns match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(attrs=['l']),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=1,
|
||||
+ truncated=False,
|
||||
+ summary='1 selfservice matched',
|
||||
+ result=[{
|
||||
+ 'attrs': ['l'],
|
||||
+ 'permissions': ['write'],
|
||||
+ 'selfaci': True,
|
||||
+ 'aciname': SS_CLI_FIND,
|
||||
+ }],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Without positional name arg but with --name
|
||||
+ # filter to get a deterministic result.
|
||||
+ dict(
|
||||
+ desc='Valid attrs filter with --name option returns match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [],
|
||||
+ dict(attrs=['l'], aciname=SS_CLI_FIND),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=1,
|
||||
+ truncated=False,
|
||||
+ summary='1 selfservice matched',
|
||||
+ result=[{
|
||||
+ 'attrs': ['l'],
|
||||
+ 'permissions': ['write'],
|
||||
+ 'selfaci': True,
|
||||
+ 'aciname': SS_CLI_FIND,
|
||||
+ }],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Bad --name filter
|
||||
+ dict(
|
||||
+ desc='Valid name arg with bad --name filter returns no match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(aciname='badname'),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Bad name arg also set to 'badname'
|
||||
+ dict(
|
||||
+ desc='Bad name arg with bad --name filter returns no match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ ['badname'],
|
||||
+ dict(aciname='badname'),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Valid --name filter
|
||||
+ dict(
|
||||
+ desc='Valid --name filter returns match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(aciname=SS_CLI_FIND),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=1,
|
||||
+ truncated=False,
|
||||
+ summary='1 selfservice matched',
|
||||
+ result=[{
|
||||
+ 'attrs': ['l'],
|
||||
+ 'permissions': ['write'],
|
||||
+ 'selfaci': True,
|
||||
+ 'aciname': SS_CLI_FIND,
|
||||
+ }],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Bad permissions filter -- aci_find treats permissions
|
||||
+ # as a plain string filter; 'badperm' matches nothing.
|
||||
+ dict(
|
||||
+ desc='Bad permissions filter returns no match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(permissions='badperm'),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=0,
|
||||
+ truncated=False,
|
||||
+ summary='0 selfservices matched',
|
||||
+ result=[],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Valid permissions filter
|
||||
+ dict(
|
||||
+ desc='Valid permissions filter with name arg returns match',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(permissions='write'),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=1,
|
||||
+ truncated=False,
|
||||
+ summary='1 selfservice matched',
|
||||
+ result=[{
|
||||
+ 'attrs': ['l'],
|
||||
+ 'permissions': ['write'],
|
||||
+ 'selfaci': True,
|
||||
+ 'aciname': SS_CLI_FIND,
|
||||
+ }],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Without positional name arg but with --name
|
||||
+ # filter to get a deterministic result.
|
||||
+ dict(
|
||||
+ desc=(
|
||||
+ 'Valid permissions filter with --name'
|
||||
+ ' option returns match'
|
||||
+ ),
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [],
|
||||
+ dict(
|
||||
+ permissions='write',
|
||||
+ aciname=SS_CLI_FIND,
|
||||
+ ),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=1,
|
||||
+ truncated=False,
|
||||
+ summary='1 selfservice matched',
|
||||
+ result=[{
|
||||
+ 'attrs': ['l'],
|
||||
+ 'permissions': ['write'],
|
||||
+ 'selfaci': True,
|
||||
+ 'aciname': SS_CLI_FIND,
|
||||
+ }],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ # Raw output only (BZ 747693)
|
||||
+ # selfservice-find --raw must not return "internal error".
|
||||
+ dict(
|
||||
+ desc='Raw output returns ACI string without error (BZ 747693)',
|
||||
+ command=(
|
||||
+ 'selfservice_find',
|
||||
+ [SS_CLI_FIND],
|
||||
+ dict(raw=True),
|
||||
+ ),
|
||||
+ expected=dict(
|
||||
+ count=1,
|
||||
+ truncated=False,
|
||||
+ summary='1 selfservice matched',
|
||||
+ result=[{
|
||||
+ 'aci': (
|
||||
+ '(targetattr = "l")'
|
||||
+ '(version 3.0;acl "selfservice:%s";'
|
||||
+ 'allow (write) '
|
||||
+ 'userdn = "ldap:///self";)'
|
||||
+ % SS_CLI_FIND
|
||||
+ ),
|
||||
+ }],
|
||||
+ ),
|
||||
+ ),
|
||||
+
|
||||
+ ]
|
||||
+
|
||||
+
|
||||
# selfservice-show & selfservice-mod CLI test rule names
|
||||
|
||||
SS_CLI_SHOW = 'SELFSERVICE_SHOW_TEST'
|
||||
--
|
||||
2.52.0
|
||||
|
||||
36
0041-ipatests-fix-the-method-add_a_record.patch
Normal file
36
0041-ipatests-fix-the-method-add_a_record.patch
Normal file
@ -0,0 +1,36 @@
|
||||
From 0306d2c5ebb1732fb33f3da4199f29ec887e1db8 Mon Sep 17 00:00:00 2001
|
||||
From: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Date: Thu, 9 Apr 2026 12:49:18 +0200
|
||||
Subject: [PATCH] ipatests: fix the method add_a_record
|
||||
|
||||
The method add_a_record first checks if a DNS record exists.
|
||||
If it finds one, it assumes there is already a DNS A record for
|
||||
the host but this assumption is wrong. The dnsrecord-show command
|
||||
may return another type of DNS record (for instance an SSHFP record).
|
||||
|
||||
Create the A record if dnsrecord-show fails or if it doesn't return
|
||||
any A record.
|
||||
|
||||
Fixes: https://pagure.io/freeipa/issue/9972
|
||||
Signed-off-by: Florence Blanc-Renaud <flo@redhat.com>
|
||||
Reviewed-By: Anuja More <amore@redhat.com>
|
||||
---
|
||||
ipatests/pytest_ipa/integration/tasks.py | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ipatests/pytest_ipa/integration/tasks.py b/ipatests/pytest_ipa/integration/tasks.py
|
||||
index ee2befd6998da3b8035e4e015d7528b5a9676a7b..5ce18483d6c1049104afc1cef6524a7a19fc9047 100755
|
||||
--- a/ipatests/pytest_ipa/integration/tasks.py
|
||||
+++ b/ipatests/pytest_ipa/integration/tasks.py
|
||||
@@ -1620,7 +1620,7 @@ def add_a_record(master, host):
|
||||
raiseonerr=False)
|
||||
|
||||
# If not, add it
|
||||
- if cmd.returncode != 0:
|
||||
+ if cmd.returncode != 0 or 'A record' not in cmd.stdout_text:
|
||||
master.run_command(['ipa',
|
||||
'dnsrecord-add',
|
||||
master.domain.name,
|
||||
--
|
||||
2.52.0
|
||||
|
||||
48
0042-ipatests-Remove-xfail-for-sssd-issues-7169.patch
Normal file
48
0042-ipatests-Remove-xfail-for-sssd-issues-7169.patch
Normal file
@ -0,0 +1,48 @@
|
||||
From a1b1a45c45f521659ee25709141b6470599030d0 Mon Sep 17 00:00:00 2001
|
||||
From: David Hanina <dhanina@redhat.com>
|
||||
Date: Mon, 13 Apr 2026 16:23:24 +0200
|
||||
Subject: [PATCH] ipatests-Remove xfail for sssd/issues/7169
|
||||
|
||||
4 of the failing tests are now succeeding, removing the xfail.
|
||||
|
||||
Signed-off-by: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
ipatests/test_integration/test_trust.py | 18 +++---------------
|
||||
1 file changed, 3 insertions(+), 15 deletions(-)
|
||||
|
||||
diff --git a/ipatests/test_integration/test_trust.py b/ipatests/test_integration/test_trust.py
|
||||
index 0cab277c910a6d35f35b57e3068ee6f38706af59..d7f25d658b24f3b261260e735d3679e81e35ed15 100644
|
||||
--- a/ipatests/test_integration/test_trust.py
|
||||
+++ b/ipatests/test_integration/test_trust.py
|
||||
@@ -1219,14 +1219,7 @@ class TestNonPosixAutoPrivateGroup(BaseTestTrust):
|
||||
assert (uid == self.uid_override and gid == self.gid_override)
|
||||
test_group = self.clients[0].run_command(
|
||||
["id", nonposixuser]).stdout_text
|
||||
- cond2 = (((type == 'false'
|
||||
- and sssd_version >= tasks.parse_version("2.9.4"))
|
||||
- or type == 'hybrid')
|
||||
- and sssd_version < tasks.parse_version("2.12.0"))
|
||||
- with xfail_context(cond2,
|
||||
- 'https://github.com/SSSD/sssd/issues/5989 '
|
||||
- 'and 7169'):
|
||||
- assert "domain users@{0}".format(self.ad_domain) in test_group
|
||||
+ assert "domain users@{0}".format(self.ad_domain) in test_group
|
||||
|
||||
@pytest.mark.parametrize('type', ['hybrid', 'true', "false"])
|
||||
def test_nonposixuser_nondefault_primary_group(self, type):
|
||||
@@ -1347,10 +1340,5 @@ class TestPosixAutoPrivateGroup(BaseTestTrust):
|
||||
assert (uid == self.uid_override
|
||||
and gid == self.gid_override)
|
||||
result = self.clients[0].run_command(['id', posixuser])
|
||||
- sssd_version = tasks.get_sssd_version(self.clients[0])
|
||||
- bad_version = (tasks.parse_version("2.9.4") <= sssd_version
|
||||
- < tasks.parse_version("2.12.0"))
|
||||
- with xfail_context(bad_version and type in ('false', 'hybrid'),
|
||||
- "https://github.com/SSSD/sssd/issues/7169"):
|
||||
- assert "10047(testgroup@{0})".format(
|
||||
- self.ad_domain) in result.stdout_text
|
||||
+ assert "10047(testgroup@{0})".format(
|
||||
+ self.ad_domain) in result.stdout_text
|
||||
--
|
||||
2.52.0
|
||||
|
||||
28
0043-Fix-ipa-ca-show-ipa-all-not-listing-RSN-version.patch
Normal file
28
0043-Fix-ipa-ca-show-ipa-all-not-listing-RSN-version.patch
Normal file
@ -0,0 +1,28 @@
|
||||
From 86a6e9cef5a3fdea3cb84c39575c90481bfe1623 Mon Sep 17 00:00:00 2001
|
||||
From: David Hanina <dhanina@redhat.com>
|
||||
Date: Tue, 14 Apr 2026 14:24:13 +0200
|
||||
Subject: [PATCH] Fix ipa ca-show ipa --all not listing RSN version
|
||||
|
||||
Resolves: RHEL-168047
|
||||
|
||||
Signed-off-by: David Hanina <dhanina@redhat.com>
|
||||
---
|
||||
ipaserver/install/cainstance.py | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/ipaserver/install/cainstance.py b/ipaserver/install/cainstance.py
|
||||
index 4738517..8e7512f 100644
|
||||
--- a/ipaserver/install/cainstance.py
|
||||
+++ b/ipaserver/install/cainstance.py
|
||||
@@ -1681,7 +1681,7 @@ class CAInstance(DogtagInstance):
|
||||
api.env.basedn)
|
||||
entry_attrs = api.Backend.ldap2.get_entry(dn)
|
||||
version = entry_attrs.single_value.get(
|
||||
- "ipaCaRandomSerialNumberVersion", "0"
|
||||
+ "ipaCaRandomSerialNumberVersion", "-"
|
||||
)
|
||||
if str(version) == str(value):
|
||||
return
|
||||
--
|
||||
2.52.0
|
||||
|
||||
31
freeipa.spec
31
freeipa.spec
@ -229,7 +229,7 @@
|
||||
|
||||
Name: %{package_name}
|
||||
Version: %{IPA_VERSION}
|
||||
Release: 3%{?rc_version:.%rc_version}%{?dist}
|
||||
Release: 3%{?rc_version:.%rc_version}%{?dist}.1
|
||||
Summary: The Identity, Policy and Audit system
|
||||
|
||||
License: GPL-3.0-or-later
|
||||
@ -267,6 +267,29 @@ Patch0017: 0017-Replace-None-with-when-uninstalling-CA.patch
|
||||
Patch0018: 0018-ipatests-Add-xmlrpc-tests-for-ipa-delegation-cli.patch
|
||||
Patch0019: 0019-ipa-join-initialize-pointer.patch
|
||||
Patch0020: 0020-ipatests-pruning-is-enabled-when-RSN-is-enabled.patch
|
||||
Patch0021: 0021-ipatests-Add-DNS-bugzilla-integration-tests.patch
|
||||
Patch0022: 0022-Avoid-int-overflow-with-pwpolicy-minlife.patch
|
||||
Patch0023: 0023-ipatests-fix-install-method-for-BasePWpolicy.patch
|
||||
Patch0024: 0024-webui-tests-update-expected-max-value-for-krbminpwdl.patch
|
||||
Patch0025: 0025-ipatests-Add-DNS-integration-tests.patch
|
||||
Patch0026: 0026-ipatest-make-tests-compatible-with-Pytest-9.patch
|
||||
Patch0027: 0027-ipatests-Add-ipa-selfservice-BZ-tests-to-xmlrpc.patch
|
||||
Patch0028: 0028-Allow-32bit-gid.patch
|
||||
Patch0029: 0029-ipatests-Add-ipa-selfservice-users-tests-to-xmlrpc.patch
|
||||
Patch0030: 0030-ipatests-Fix-test_allow_query_transfer_ipv6-when-IPv.patch
|
||||
Patch0031: 0031-ipatests-Add-XML-RPC-tests-for-i18n-user-attributes.patch
|
||||
Patch0032: 0032-ipatests-Add-selfservice-add-and-selfservice-del-cli.patch
|
||||
Patch0033: 0033-ipatests-Additional-tests-for-32BitIdranges.patch
|
||||
Patch0034: 0034-ipatests-add-HTTP-GSSAPI-Kerberos-authentication-tes.patch
|
||||
Patch0035: 0035-ipatests-Extend-netgroup-test-coverage.patch
|
||||
Patch0036: 0036-ipatests-Add-user-principal-ipa-getkeytab-and-ipa-rm.patch
|
||||
Patch0037: 0037-ipatests-Additional-tests-for-ipa-ipa-migration-test.patch
|
||||
Patch0038: 0038-ipatests-ipa-migrate-ds-test-scenarios.patch
|
||||
Patch0039: 0039-ipatests-Add-ipa-selfservice-show-and-selfservice-mo.patch
|
||||
Patch0040: 0040-ipatests-Add-selfservice-find-cli-tests-to-xmlrpc-Ad.patch
|
||||
Patch0041: 0041-ipatests-fix-the-method-add_a_record.patch
|
||||
Patch0042: 0042-ipatests-Remove-xfail-for-sssd-issues-7169.patch
|
||||
Patch0043: 0043-Fix-ipa-ca-show-ipa-all-not-listing-RSN-version.patch
|
||||
Patch1001: 1001-Change-branding-to-IPA-and-Identity-Management.patch
|
||||
%endif
|
||||
%endif
|
||||
@ -1979,6 +2002,12 @@ fi
|
||||
%endif
|
||||
|
||||
%changelog
|
||||
* Mon Apr 13 2026 David Hanina <dhanina@redhat.com> - 4.13.1-3.1
|
||||
- Resolves: RHEL-166865 Include latest fixes in python3-ipatests package [rhel-9.8.z]
|
||||
- Resolves: RHEL-155037 Pagure #9953: Adding a group with 32Bit Idrange fails. [rhel-9.8.z]
|
||||
- Resolves: RHEL-153146 IdM password policy Min lifetime is not enforced when high minlife is set [rhel-9.8.z]
|
||||
- Resolves: RHEL-168047 ipa ca-show ipa --all failing to list RSN version
|
||||
|
||||
* Tue Feb 10 2026 Florence Blanc-Renaud <flo@redhat.com> - 4.13.1-3
|
||||
- RHEL-148282 ipa-replica-conncheck fails with "an internal error has occured"
|
||||
- RHEL-148481 Pruning is enabled by default with RSN on RHEL 9.8
|
||||
|
||||
Loading…
Reference in New Issue
Block a user