forked from 389ds/389-ds-base
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Issue 152 - RFE - Add support for LDAP alias entries
Description: Per RFC rfc4512#section-2.6 add support for Alias Entries. Currently this is only designed to work with "base" searches. Thanks for @anilech for the code contribution!!! relates: 389ds#152 Reviewed by: spichugi, tbordaz, and progier(Thanks!!!)
- Loading branch information
1 parent
3dd9bd3
commit f8458fc
Showing
9 changed files
with
346 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
# --- BEGIN COPYRIGHT BLOCK --- | ||
# Copyright (C) 2023 Red Hat, Inc. | ||
# All rights reserved. | ||
# | ||
# License: GPL (version 3 or any later version). | ||
# See LICENSE for details. | ||
# --- END COPYRIGHT BLOCK --- | ||
# | ||
import logging | ||
import pytest | ||
import os | ||
import ldap | ||
import time | ||
from lib389.utils import ensure_str | ||
from lib389.plugins import AliasEntriesPlugin | ||
from lib389.idm.user import UserAccount | ||
from lib389._constants import DEFAULT_SUFFIX | ||
from lib389.topologies import topology_st as topo | ||
|
||
log = logging.getLogger(__name__) | ||
|
||
TEST_ENTRY_NAME = "entry" | ||
TEST_ENTRY_DN = "cn=entry," + DEFAULT_SUFFIX | ||
TEST_ALIAS_NAME = "alias entry" | ||
TEST_ALIAS_DN = "cn=alias_entry," + DEFAULT_SUFFIX | ||
TEST_ALIAS_DN_WRONG = "cn=alias_entry_not_there," + DEFAULT_SUFFIX | ||
EXPECTED_UIDNUM = "1000" | ||
|
||
|
||
def test_entry_alias(topo): | ||
"""Test that deref search for alias entry works | ||
:id: 454e85af-0e20-4a36-9b3a-02562b1db53d | ||
:setup: Standalone Instance | ||
:steps: | ||
1. Enable alias entry plugin | ||
2. Create entry and alias entry | ||
3. Set deref option and do a base search | ||
4. Test non-base scope ssearch returns error | ||
5. Test invalid alias DN returns error | ||
:expectedresults: | ||
1. Success | ||
2. Success | ||
3. Success | ||
4. Success | ||
5. Success | ||
""" | ||
inst = topo.standalone | ||
|
||
# Enable Alias Entries plugin | ||
alias_plugin = AliasEntriesPlugin(inst) | ||
alias_plugin.enable() | ||
inst.restart() | ||
|
||
# Add entry | ||
test_user = UserAccount(inst, TEST_ENTRY_DN) | ||
test_user.create(properties={ | ||
'uid': TEST_ENTRY_NAME, | ||
'cn': TEST_ENTRY_NAME, | ||
'sn': TEST_ENTRY_NAME, | ||
'userPassword': TEST_ENTRY_NAME, | ||
'uidNumber': '1000', | ||
'gidNumber': '2000', | ||
'homeDirectory': '/home/alias_test', | ||
}) | ||
|
||
# Add entry that has an alias set to the first entry | ||
test_alias = UserAccount(inst, TEST_ALIAS_DN) | ||
test_alias.create(properties={ | ||
'uid': TEST_ALIAS_NAME, | ||
'cn': TEST_ALIAS_NAME, | ||
'sn': TEST_ALIAS_NAME, | ||
'userPassword': TEST_ALIAS_NAME, | ||
'uidNumber': '1001', | ||
'gidNumber': '2001', | ||
'homeDirectory': '/home/alias_test', | ||
'objectclass': ['alias', 'extensibleObject'], | ||
'aliasedObjectName': TEST_ENTRY_DN, | ||
}) | ||
|
||
# Set the deref "finding" option | ||
inst.set_option(ldap.OPT_DEREF, ldap.DEREF_FINDING) | ||
|
||
# Do base search which could map entry to the aliased one | ||
log.info("Test alias") | ||
deref_user = UserAccount(inst, TEST_ALIAS_DN) | ||
result = deref_user.search(scope="base") | ||
assert result[0].dn == TEST_ENTRY_DN | ||
assert ensure_str(result[0].getValue('uidNumber')) == EXPECTED_UIDNUM | ||
|
||
# Do non-base search which could raise an error | ||
log.info("Test unsupported search scope") | ||
with pytest.raises(ldap.UNWILLING_TO_PERFORM): | ||
deref_user.search(scope="subtree") | ||
|
||
# Reset the aliasObjectname to a DN that does not exist, and try again | ||
log.info("Test invalid alias") | ||
test_alias.replace('aliasedObjectName', TEST_ALIAS_DN_WRONG) | ||
try: | ||
deref_user.search(scope="base") | ||
assert False | ||
except ldap.LDAPError as e: | ||
msg = e.args[0]['info'] | ||
assert msg.startswith("Failed to dereference alias object") | ||
|
||
|
||
if __name__ == '__main__': | ||
# Run isolated | ||
# -s for DEBUG mode | ||
CURRENT_FILE = os.path.realpath(__file__) | ||
pytest.main(["-s", CURRENT_FILE]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
/** BEGIN COPYRIGHT BLOCK | ||
* Copyright (C) 2023 anilech | ||
* Copyright (C) 2023 Red Hat, Inc. | ||
* All rights reserved. | ||
* | ||
* License: GPL (version 3 or any later version). | ||
* See LICENSE for details. | ||
* END COPYRIGHT BLOCK **/ | ||
|
||
#include "alias-entries.h" | ||
|
||
static Slapi_ComponentId *plugin_id = NULL; | ||
Slapi_PluginDesc srchpdesc = { PLUGINNAME, PLUGINVNDR, PLUGINVERS, PLUGINDESC }; | ||
|
||
static Slapi_DN* | ||
alias_get_next_dn(Slapi_DN *sdn, int *rc) | ||
{ | ||
Slapi_PBlock *pb = NULL; | ||
Slapi_Entry **e; | ||
Slapi_DN *alias_dn = NULL; | ||
int derefNever = LDAP_DEREF_NEVER; | ||
char *attrs[] = { "aliasedObjectName", NULL }; | ||
|
||
/* search dn to check if it is an alias */ | ||
pb = slapi_pblock_new(); | ||
slapi_search_internal_set_pb_ext(pb, sdn, LDAP_SCOPE_BASE, ALIASFILTER, attrs, 0, NULL, NULL, plugin_id, 0); | ||
slapi_pblock_set(pb, SLAPI_SEARCH_DEREF, (void *)&derefNever); | ||
|
||
if (slapi_search_internal_pb(pb) == 0) { | ||
slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_RESULT, rc); | ||
if (*rc == LDAP_SUCCESS) { | ||
slapi_pblock_get(pb, SLAPI_PLUGIN_INTOP_SEARCH_ENTRIES, &e); | ||
if (e && e[0]) { | ||
alias_dn = slapi_sdn_new_dn_byval(slapi_entry_attr_get_ref(e[0], attrs[0])); | ||
} | ||
} | ||
} | ||
slapi_free_search_results_internal(pb); | ||
slapi_pblock_destroy(pb); | ||
|
||
return alias_dn; | ||
} | ||
|
||
int | ||
alias_entry_init(Slapi_PBlock *pb) | ||
{ | ||
if (slapi_pblock_set(pb, SLAPI_PLUGIN_VERSION, SLAPI_PLUGIN_VERSION_03) != 0 || | ||
slapi_pblock_set(pb, SLAPI_PLUGIN_DESCRIPTION, (void *)&srchpdesc) != 0 || | ||
slapi_pblock_set(pb, SLAPI_PLUGIN_PRE_SEARCH_FN, (void *)alias_entry_srch) != 0 || | ||
slapi_pblock_get(pb, SLAPI_PLUGIN_IDENTITY, &plugin_id) != 0) | ||
{ | ||
slapi_log_error(SLAPI_LOG_ERR, PLUGINNAME, | ||
"alias_entry_init: Error registering the plug-in.\n"); | ||
return -1; | ||
} | ||
|
||
return 0; | ||
} | ||
|
||
int | ||
alias_entry_srch(Slapi_PBlock *pb) | ||
{ | ||
Slapi_DN *search_target = NULL; | ||
Slapi_DN *dn1 = NULL; | ||
Slapi_DN *dn2 = NULL; | ||
char *str_filter = NULL; | ||
int scope = 0; | ||
int deref_enabled = 0; | ||
size_t i = 0; | ||
int rc = 0; | ||
|
||
/* Skip reserved operations */ | ||
if (slapi_op_reserved(pb)) { | ||
return 0; | ||
} | ||
|
||
/* Base dn should be provided */ | ||
if (slapi_pblock_get(pb, SLAPI_SEARCH_TARGET_SDN, &search_target) != 0 || search_target == NULL) { | ||
return 0; | ||
} | ||
|
||
/* deref must be enabled */ | ||
if (slapi_pblock_get(pb, SLAPI_SEARCH_DEREF, &deref_enabled) != 0) { | ||
return 0; | ||
} | ||
if (deref_enabled != LDAP_DEREF_FINDING && deref_enabled != LDAP_DEREF_ALWAYS) { | ||
return 0; | ||
} | ||
|
||
/* Only base search is supported */ | ||
if (slapi_pblock_get(pb, SLAPI_SEARCH_SCOPE, &scope) != 0 || scope != LDAP_SCOPE_BASE) { | ||
char errorbuf[SLAPI_DSE_RETURNTEXT_SIZE] = {0}; | ||
slapi_create_errormsg(errorbuf, sizeof(errorbuf), | ||
"Only base level scoped searches are allowed to dereference alias entries"); | ||
rc = LDAP_UNWILLING_TO_PERFORM; | ||
slapi_send_ldap_result(pb, rc, NULL, errorbuf, 0, NULL); | ||
slapi_pblock_set(pb, SLAPI_PLUGIN_OPRETURN, &rc); | ||
return SLAPI_PLUGIN_FAILURE; | ||
} | ||
|
||
/* Skip our own requests */ | ||
if (slapi_pblock_get(pb, SLAPI_SEARCH_STRFILTER, &str_filter) != 0 || | ||
str_filter == NULL || strcmp(str_filter, ALIASFILTER) == 0) | ||
{ | ||
return 0; | ||
} | ||
|
||
/* Follow the alias chain */ | ||
dn2 = search_target; | ||
do { | ||
dn1 = dn2; | ||
dn2 = alias_get_next_dn(dn1, &rc); | ||
if (i > 0 && dn2 != NULL) { | ||
slapi_sdn_free(&dn1); | ||
} | ||
if (rc != LDAP_SUCCESS) { | ||
char errorbuf[SLAPI_DSE_RETURNTEXT_SIZE] = {0}; | ||
slapi_create_errormsg(errorbuf, sizeof(errorbuf), | ||
"Failed to dereference alias object name (%s) error %d", | ||
slapi_sdn_get_dn(dn1), rc); | ||
slapi_log_error(SLAPI_LOG_PLUGIN, PLUGINNAME, | ||
"alias_entry_srch - %s\n", errorbuf); | ||
|
||
slapi_send_ldap_result(pb, rc, NULL, errorbuf, 0, NULL); | ||
slapi_pblock_set(pb, SLAPI_PLUGIN_OPRETURN, &rc); | ||
return SLAPI_PLUGIN_FAILURE; | ||
} | ||
} while (dn2 != NULL && i++ < MAXALIASCHAIN); | ||
|
||
if (dn1 == search_target) { | ||
/* Source dn is not an alias */ | ||
return 0; | ||
} | ||
|
||
if (dn2 == NULL) { | ||
/* alias resolved, set new base */ | ||
slapi_sdn_free(&search_target); | ||
slapi_pblock_set(pb, SLAPI_SEARCH_TARGET_SDN, dn1); | ||
} else { | ||
/* Here we hit an alias chain longer than MAXALIASCHAIN */ | ||
slapi_sdn_free(&dn2); | ||
} | ||
|
||
return 0; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/** BEGIN COPYRIGHT BLOCK | ||
* Copyright (c) 2023 anilech | ||
* Copyright (C) 2023 Red Hat, Inc. | ||
* All rights reserved. | ||
* | ||
* License: GPL (version 3 or any later version). | ||
* See LICENSE for details. | ||
* END COPYRIGHT BLOCK **/ | ||
|
||
#ifdef HAVE_CONFIG_H | ||
#include <config.h> | ||
#endif | ||
|
||
#include <stdio.h> | ||
#include <string.h> | ||
#include "slapi-plugin.h" | ||
#include "slapi-private.h" | ||
|
||
#define PLUGINNAME "Alias Entries" | ||
#define PLUGINVNDR "anilech" | ||
#define PLUGINVERS "0.1" | ||
#define PLUGINDESC "alias entries plugin [base search only]" | ||
|
||
#define MAXALIASCHAIN 8 | ||
#define ALIASFILTER "(&(objectClass=alias)(aliasedObjectName=*))" | ||
|
||
// function prototypes | ||
int alias_entry_init(Slapi_PBlock *pb); | ||
int alias_entry_srch(Slapi_PBlock *pb); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.