Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions src/db/sysdb.h
Original file line number Diff line number Diff line change
Expand Up @@ -872,12 +872,6 @@ int sysdb_getpwuid(TALLOC_CTX *mem_ctx,
uid_t uid,
struct ldb_result **res);

int sysdb_getpwupn(TALLOC_CTX *mem_ctx,
struct sss_domain_info *domain,
bool domain_scope,
const char *upn,
struct ldb_result **res);

int sysdb_enumpwent(TALLOC_CTX *mem_ctx,
struct sss_domain_info *domain,
struct ldb_result **res);
Expand Down Expand Up @@ -1067,6 +1061,13 @@ int sysdb_search_user_by_upn_res(TALLOC_CTX *mem_ctx,
const char **attrs,
struct ldb_result **out_res);

int sysdb_search_user_by_upn_with_view_res(TALLOC_CTX *mem_ctx,
struct sss_domain_info *domain,
bool domain_scope,
const char *upn,
const char **attrs,
struct ldb_result **out_res);

int sysdb_search_user_by_upn(TALLOC_CTX *mem_ctx,
struct sss_domain_info *domain,
bool domain_scope,
Expand Down
37 changes: 37 additions & 0 deletions src/db/sysdb_ops.c
Original file line number Diff line number Diff line change
Expand Up @@ -712,6 +712,43 @@ int sysdb_search_user_by_upn_res(TALLOC_CTX *mem_ctx,
return ret;
}

int sysdb_search_user_by_upn_with_view_res(TALLOC_CTX *mem_ctx,
struct sss_domain_info *domain,
bool domain_scope,
const char *upn,
const char **attrs,
struct ldb_result **out_res)
{
int ret;
struct ldb_result *orig_obj = NULL;

/* The UPN or the email address cannot be overwritten and we can search
* directly the original object. */
ret = sysdb_search_user_by_upn_res(mem_ctx, domain, domain_scope, upn,
attrs, &orig_obj);
if (ret != EOK) {
DEBUG(ret == ENOENT ? SSSDBG_MINOR_FAILURE : SSSDBG_OP_FAILURE,
"Failed to find UPN [%s] in cache [%d][%s].\n",
upn, ret, sss_strerror(ret));
return ret;
}

/* If there are views we have to check if override values must be added to
* the original object. */
Comment on lines +736 to +737

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

There is a potential logic error in this function. If sysdb_search_user_by_upn_res returns ENOENT, ret is set to ENOENT. The code then proceeds to line 759, where ret is unconditionally overwritten to EOK. This causes the function to return EOK with an empty result set, instead of ENOENT, which was the original error code indicating the entry was not found. This changes the semantics of the function's return value.

To preserve the ENOENT error code, you should handle this case before ret is overwritten. I suggest adding a check after the initial search and before attempting to process overrides.

    if (ret == ENOENT) {
        goto done;
    }

    /* If there are views we have to check if override values must be added to
     * the original object. */

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is valid. The original code returns the empty object but keeps ENOENT return code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'empty object' == NULL?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, what's the point, why not simply if (ret != EOK) return ret;?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,

I fixed this as Pavel suggested, the function will return ENOENT with an empty (not NULL) struct ldb_result as sysdb_search_user_by_upn_res(). The caller in the cache_req code is quite robust and could handle the original version as well, but being consistent with sysdb_search_user_by_upn_res() makes sense imo.

bye,
Sumit

Copy link
Member

@alexey-tikhonov alexey-tikhonov Dec 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, perhaps empty object is expected by the caller of 'cache_req_plugin::lookup_fn' (I didn't check).

But current patch returns NULL in case sysdb_search_user_by_upn_res() == ENOENT, not empty struct...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, perhaps empty object is expected by the caller of 'cache_req_plugin::lookup_fn' (I didn't check).

No, it isn't expected if ENOENT is returned.

But current patch returns NULL in case sysdb_search_user_by_upn_res() == ENOENT, not empty struct...

I think unfortunately both cases are possible. But in either case sysdb_search_user_by_upn_with_view_res() would return the same combination as sysdb_search_user_by_upn_res().

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, perhaps empty object is expected by the caller of 'cache_req_plugin::lookup_fn' (I didn't check).

No, it isn't expected if ENOENT is returned.

I'm confused: what is the point then to orig_obj = talloc_zero(mem_ctx, struct ldb_result); if sysdb_add_overrides_to_object() == ENOENT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,

in the latest version I simplified sysdb_search_user_by_upn_with_view_res() wit hthe result that it might behaver differently than other lookup_fn methods of cache_req plugins but not cause issues in the single caller.

bye,
Sumit

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you.

@pbrezina, are you fine with the latest version?

if (DOM_HAS_VIEWS(domain)) {
ret = sysdb_add_overrides_to_object(domain, orig_obj->msgs[0], NULL,
attrs);
if (ret != EOK && ret != ENOENT) {
talloc_free(orig_obj);
DEBUG(SSSDBG_OP_FAILURE, "sysdb_add_overrides_to_object failed.\n");
return ret;
}
}

*out_res = orig_obj;
return ret;
}

int sysdb_search_user_by_upn(TALLOC_CTX *mem_ctx,
struct sss_domain_info *domain,
bool domain_scope,
Expand Down
30 changes: 0 additions & 30 deletions src/db/sysdb_search.c
Original file line number Diff line number Diff line change
Expand Up @@ -598,36 +598,6 @@ static char *enum_filter(TALLOC_CTX *mem_ctx,
return filter;
}

int sysdb_getpwupn(TALLOC_CTX *mem_ctx,
struct sss_domain_info *domain,
bool domain_scope,
const char *upn,
struct ldb_result **_res)
{
TALLOC_CTX *tmp_ctx;
struct ldb_result *res;
static const char *attrs[] = SYSDB_PW_ATTRS;
errno_t ret;

tmp_ctx = talloc_new(NULL);
if (tmp_ctx == NULL) {
DEBUG(SSSDBG_CRIT_FAILURE, "talloc_new() failed\n");
return ENOMEM;
}

ret = sysdb_search_user_by_upn_res(tmp_ctx, domain, domain_scope, upn, attrs, &res);
if (ret != EOK && ret != ENOENT) {
DEBUG(SSSDBG_OP_FAILURE, "sysdb_search_user_by_upn_res() failed.\n");
goto done;
}

*_res = talloc_steal(mem_ctx, res);

done:
talloc_free(tmp_ctx);
return ret;
}

errno_t sysdb_search_ts_matches(TALLOC_CTX *mem_ctx,
struct sysdb_ctx *sysdb,
const char *attrs[],
Expand Down
15 changes: 8 additions & 7 deletions src/responder/common/cache_req/plugins/cache_req_user_by_upn.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,14 @@ cache_req_user_by_upn_lookup(TALLOC_CTX *mem_ctx,
struct sss_domain_info *domain,
struct ldb_result **_result)
{
if (data->attrs == NULL) {
return sysdb_getpwupn(mem_ctx, domain, true, data->name.lookup, _result);
}

return sysdb_search_user_by_upn_res(mem_ctx, domain, true,
data->name.lookup, data->attrs,
_result);
static const char *def_attrs[] = SYSDB_PW_ATTRS;

return sysdb_search_user_by_upn_with_view_res(mem_ctx, domain, true,
data->name.lookup,
data->attrs == NULL
? def_attrs
: data->attrs,
_result);
}

static struct tevent_req *
Expand Down
6 changes: 0 additions & 6 deletions src/tests/cmocka/test_sysdb_ts_cache.c
Original file line number Diff line number Diff line change
Expand Up @@ -1303,12 +1303,6 @@ static void test_user_byupn(void **state)
TEST_NOW_2);
assert_int_equal(ret, EOK);

ret = sysdb_getpwupn(test_ctx, test_ctx->tctx->dom, false, TEST_USER_UPN, &res);
assert_int_equal(ret, EOK);
assert_int_equal(res->count, 1);
assert_ts_attrs_res(res, TEST_NOW_2 + TEST_CACHE_TIMEOUT, TEST_NOW_2);
talloc_free(res);

ret = sysdb_search_user_by_upn_res(test_ctx, test_ctx->tctx->dom,
false, TEST_USER_UPN, pw_fetch_attrs,
&res);
Expand Down
49 changes: 49 additions & 0 deletions src/tests/system/tests/test_ipa.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,55 @@ def test_ipa__idview_fails_to_apply_on_ipa_master(ipa: IPA):
), "Did not get an error message when trying to apply ID view on server!"


@pytest.mark.importance("high")
@pytest.mark.topology(KnownTopology.IPA)
def test_ipa__idview_lookup_user_by_email_with_overrides(client: Client, ipa: IPA):
"""
:title: IPA ID view overrides are applied when looking up user by email
:setup:
1. Create an ID view and apply the view to the client
2. Create a user with an email address
3. Add user override with modified login, uid, gid, and home
4. Restart SSSD
:steps:
1. Look up the user by original name
2. Look up the user by overridden name
3. Look up the user by email address
:expectedresults:
1. User found with overridden uid, gid, and home
2. User found with overridden uid, gid, and home
3. User found with overridden uid, gid, and home
:customerscenario: True
"""
ipa.idview("testview1").add(description="View for email lookup test")

ipa.idview("testview1").apply(hosts=[f"{client.host.hostname}"])

user = ipa.user("user-1").add(email="user1@ipa.test")

user.iduseroverride().add_override("testview1", login="o-user1", uid=999999, gid=888888, home="/home/o-user1")

client.sssd.restart()

result = client.tools.getent.passwd("user-1")
assert result is not None, "User not found by original name!"
assert result.uid == 999999, f"Override uid not applied for original name, got {result.uid}!"
assert result.gid == 888888, f"Override gid not applied for original name, got {result.gid}!"
assert result.home == "/home/o-user1", f"Override home not applied for original name, got {result.home}!"

result = client.tools.getent.passwd("o-user1")
assert result is not None, "User not found by overridden name!"
assert result.uid == 999999, f"Override uid not applied for overridden name, got {result.uid}!"
assert result.gid == 888888, f"Override gid not applied for overridden name, got {result.gid}!"
assert result.home == "/home/o-user1", f"Override home not applied for overridden name, got {result.home}!"

result = client.tools.getent.passwd("user1@ipa.test")
assert result is not None, "User not found by email!"
assert result.uid == 999999, f"Override uid not applied for email lookup, got {result.uid}!"
assert result.gid == 888888, f"Override gid not applied for email lookup, got {result.gid}!"
assert result.home == "/home/o-user1", f"Override home not applied for email lookup, got {result.home}!"


@pytest.mark.importance("high")
@pytest.mark.topology(KnownTopology.IPA)
@pytest.mark.builtwith(client="virtualsmartcard")
Expand Down
57 changes: 57 additions & 0 deletions src/tests/system/tests/test_sss_override.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,63 @@ def test_sss_override__user_attributes(client: Client, provider: GenericProvider
assert result.home == "/home/o-user1", "User's override name homedir does not match override value!"


@pytest.mark.importance("high")
@pytest.mark.topology([KnownTopology.LDAP, KnownTopology.AD, KnownTopology.Samba])
def test_sss_overrides__overriding_username_and_attributes_lookup_by_email(client: Client, provider: GenericProvider):
"""
:title: Locally overriding the name and POSIX attributes of a user and lookup with the email address
:setup:
1. Create POSIX user "user1" with email "email@example.com", email is
used because the UPN is not supported in all roles of the framework
2. Configure SSSD with "ldap_id_mapping = false" and start SSSD
3. Create local override for "user1"
4. Restart SSSD, this is necessary to enable local overrides
:steps:
1. Lookup user by the original name, check the uid and gid
2. Lookup user by the overridden name, check the uid and gid
3. Lookup user by email, check the uid and gid
:expectedresults:
1. User is found and uid and gid match new values
2. User is found and uid and gid match new values
3. User is found and uid and gid match new values
:customerscenario: True
"""
provider.user("user1").add(
uid=999011,
gid=999011,
home="/home/user1",
gecos="user",
shell="/bin/bash",
password="Secret123",
email="email@example.com",
)

client.sssd.domain["ldap_id_mapping"] = "False"
client.sssd.start()

client.sss_override.user("user1").add(name="o-user1", uid=999999, gid=888888, home="/home/o-user1")

client.sssd.restart()

result = client.tools.getent.passwd("user1")
assert result is not None, "User not found!"
assert result.uid == 999999, "User's uid does not match override value!"
assert result.gid == 888888, "User's gid does not match override value!"
assert result.home == "/home/o-user1", "User's homedir does not match override value!"

result = client.tools.getent.passwd("o-user1")
assert result is not None, "User not found by override name!"
assert result.uid == 999999, "Local override uid does not match override value!"
assert result.gid == 888888, "Local override gid does not match override value!"
assert result.home == "/home/o-user1", "User's override name homedir does not match override value!"

result = client.tools.getent.passwd("email@example.com")
assert result is not None, "User not found by email!"
assert result.uid == 999999, "Local override uid does not match override value!"
assert result.gid == 888888, "Local override gid does not match override value!"
assert result.home == "/home/o-user1", "User's override name homedir does not match override value!"


@pytest.mark.importance("high")
@pytest.mark.topology([KnownTopology.LDAP, KnownTopology.AD, KnownTopology.Samba])
@pytest.mark.preferred_topology(KnownTopology.LDAP)
Expand Down